This commit is contained in:
commit
2ec3765616
|
@ -0,0 +1,111 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# no build on github
|
||||
builds
|
||||
|
||||
# must build typescript itself
|
||||
dist
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"extends": ["eslint:recommended", "plugin:react/recommended"],
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"jquery": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 8,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"semi": 2,
|
||||
"space-infix-ops": ["error", {"int32Hint": false}],
|
||||
"quotes": ["error", "double", {"allowTemplateLiterals": true}],
|
||||
"no-console": 0,
|
||||
"brace-style": ["error", "stroustrup", {"allowSingleLine": true}],
|
||||
"keyword-spacing": 2,
|
||||
"no-else-return": 2,
|
||||
"curly": ["error", "multi-line", "consistent"],
|
||||
"dot-notation": 2,
|
||||
"yoda": 2,
|
||||
"linebreak-style": ["error", "windows"],
|
||||
"quote-props": ["error", "consistent-as-needed", {"keywords": true}],
|
||||
"object-curly-spacing": ["error", "never", { "objectsInObjects": false }],
|
||||
"no-var": "error",
|
||||
"prefer-const": "error",
|
||||
"react/jsx-uses-react": "error",
|
||||
"react/jsx-uses-vars": "error",
|
||||
"react/prop-types": "off",
|
||||
"react/jsx-no-target-blank": "error",
|
||||
"react/jsx-key": "off"
|
||||
},
|
||||
"globals": {
|
||||
"webpackJsonp": false,
|
||||
"Proxy": false,
|
||||
"Set": false,
|
||||
"WeakMap": false,
|
||||
"Promise": false,
|
||||
"ace": false,
|
||||
"Reflect": false,
|
||||
"Array": false,
|
||||
"DiscordNative": false,
|
||||
"self": "off",
|
||||
"name": "off",
|
||||
"__non_webpack_require__": false
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
node_modules
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2015-present Jiiks | 2017-present Zack Rauen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,159 @@
|
|||
# BandagedBD [![Patreon][patreon-badge]][patreon-link] [![Paypal][paypal-badge]][paypal-link]
|
||||
[patreon-badge]: https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.herokuapp.com%2FZerebos&style=flat-square
|
||||
[patreon-link]: https://patreon.com/Zerebos
|
||||
|
||||
[paypal-badge]: https://img.shields.io/badge/Paypal-Donate!-%2300457C.svg?logo=paypal&style=flat-square
|
||||
[paypal-link]: https://paypal.me/ZackRauen
|
||||
|
||||
BandagedBD (Bandaged BetterDiscord) is a fork of the original [BetterDiscord](https://github.com/Jiiks/BetterDiscordApp) by Jiiks. This has a number of improvements over the original. The original version has been unmaintained hence this fork existing. There have been attempts to rewrite the original that I have been and will continue to be involved in, but in the meantime I will continue to maintain and improve BBD.
|
||||
|
||||
# Installation
|
||||
|
||||
## Auto Installers
|
||||
|
||||
### Windows
|
||||
Grab the `exe` file from [here](https://github.com/rauenzi/BetterDiscordApp/releases/latest/download/BandagedBD_Windows.exe).
|
||||
|
||||
### macOS/OS X
|
||||
Grab the `zip` file from [here](https://github.com/rauenzi/BetterDiscordApp/releases/latest/download/BandagedBD_Mac.zip).
|
||||
|
||||
### Linux
|
||||
See this [gist](https://gist.github.com/ObserverOfTime/d7e60eb9aa7fe837545c8cb77cf31172).
|
||||
|
||||
## Manual Installation
|
||||
|
||||
### Windows
|
||||
1. Download and extract this: https://github.com/rauenzi/BetterDiscordApp/archive/injector.zip
|
||||
2. Rename `BetterDiscordApp-injector` to `app`.
|
||||
3. Go to `%localappdata%\Discord\`, and locate the directory with the largest version number (e.g. `app-0.0.306`).
|
||||
4. Within `app-0.0.306` navigate to `resources`.
|
||||
5. If an `app` folder already exists inside `resources`, delete it.
|
||||
6. Move the `app` folder (the one you downloaded and renamed) inside of `resources`.
|
||||
7. Fully quit Discord and restart it.
|
||||
|
||||
### macOS/OS X
|
||||
1. Download and extract this: https://github.com/rauenzi/BetterDiscordApp/archive/injector.zip
|
||||
2. Rename `BetterDiscordApp-injector` to `app`.
|
||||
3. Go to `/Applications/`, right click `Discord.app` and select `Show Package Contents`.
|
||||
4. Within `Discord.app` navigate to `Contents` -> `Resources`.
|
||||
5. If an `app` folder already exists inside `Resources`, delete it.
|
||||
6. Move the `app` folder (the one you downloaded and renamed) inside of `Resources`.
|
||||
7. Fully quit Discord and restart it.
|
||||
|
||||
# FAQ
|
||||
|
||||
### What is this?
|
||||
This is a client modification for Discord. It allows you to add plugins and themes to your client. Plugins can add functionality and useful features. Themes can completely change the look and feel of Discord.
|
||||
|
||||
BBD has some other built-in features such as Emotes from Twitch, FFZ, and BBTV, as well as an in-client server browser.
|
||||
|
||||
### Where can I get plugins and themes?
|
||||
In our support servers we have channels with lists of official plugins and themes. Please note we do not have an official listing on a website and are **not affiliated with any of those websites**.
|
||||
|
||||
### Support Servers?
|
||||
There are two: [The main server](https://discord.gg/0Tmfo5ZbORCRqbAd), and [the backup](https://discord.gg/2HScm8j).
|
||||
|
||||
|
||||
|
||||
# Supporters
|
||||
These people have all subscribed to the `True Supporter` tier on Patreon to support BandagedBD.
|
||||
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<img src="https://cdn.discordapp.com/avatars/196098063092154368/90f1a7202955dac7a6c685cca3181ab1.webp" width="100px;" alt="Kraken"/><br />
|
||||
<strong>Kraken</strong><br />
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="https://cdn.discordapp.com/attachments/585514483699417089/585552300354043915/34959069_500_500.jpg" width="100px;" alt="SPHHAX"/><br />
|
||||
<a href="http://sphh.ax/" target="_blank" rel="noreferrer noopener"><strong>SPHHAX</strong></a><br />
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="https://cdn.discordapp.com/attachments/622954403262889995/622957122765848587/5364774.jpg" width="100px;" alt="DefCon42"/><br />
|
||||
<a href="https://twitter.com/def_con42" target="_blank" rel="noreferrer noopener"><strong>DefCon42</strong></a><br />
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="https://cdn.discordapp.com/avatars/629231564261425163/a_36cc7d2940b4ffb8a660b1076ab2087f.webp" width="100px;" alt="Justxn"/><br />
|
||||
<strong>Justxn</strong><br />
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="https://cdn.discordapp.com/attachments/682750073448169513/682763113296429087/definitely_not_the_dick_police.png" width="100px;" alt="monkey"/><br />
|
||||
<a href="https://heartunderbla.de" target="_blank" rel="noreferrer noopener"><strong>monkey</strong></a><br />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
# Bandagers
|
||||
These people have all subscribed to the `Bandager` tier on Patreon to support BandagedBD.
|
||||
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<img src="https://cdn.discordapp.com/avatars/332199319169925120/4709f8f0c9cb7ababd85459bf71848b9.png" width="50px;" alt="William JCM"/><br />
|
||||
<a href="https://github.com/williamjcm" target="_blank" rel="noreferrer noopener"><strong>William JCM</strong></a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="https://avatars0.githubusercontent.com/u/24623601" width="50px;" alt="NFLD99"/><br />
|
||||
<a href="https://github.com/NFLD99" target="_blank" rel="noreferrer noopener"><strong>NFLD99</strong></a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="https://avatars3.githubusercontent.com/u/20338746?s=460&u=d9ebab4f6f0f5221390bca1eaf8f191acd275afe&v=4" width="50px;" alt="Gibbu"/><br />
|
||||
<a href="https://github.com/Gibbu" target="_blank" rel="noreferrer noopener"><strong>Gibbu</strong></a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="https://i.postimg.cc/5NVxqMnb/Cute-Squid-Circle.png" width="50px;" alt="Tenuit"/><br />
|
||||
<strong>Tenuit</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
# Donors
|
||||
These people have either donated or subscribed to the most basic patron tier to support BandagedBD.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<img src="https://cdn.discordapp.com/avatars/284122164582416385/ebaa1b63191ce70e48ae24f32f452773.webp" width="25px;" /><br />
|
||||
<strong>aetheryx</strong>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="https://cdn.discordapp.com/avatars/216782345779281921/d4b651b606f108cd2f96a19af68f942f.png" width="25px;" /><br />
|
||||
<strong>JBeauDee</strong>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="https://cdn.discordapp.com/avatars/261673576216789004/31d590fb92329e270a6225a13d500c1d.png" width="25px;" /><br />
|
||||
<strong>vantiss</strong>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="https://cdn.discordapp.com/avatars/122204411962327043/7f44a9b036b9e2691f4e81d9e34a78b4.webp" width="25px;" /><br />
|
||||
<strong>xstefen</strong>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="https://cdn.discordapp.com/avatars/219400174869413888/7c88015869990ba97b614b1ac784f8e8.png" width="25px;" /><br />
|
||||
<strong>『Sorey』</strong>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="https://cdn.discordapp.com/avatars/95263213842608128/5024b83e1bff3096d7fc93e8de09d582.gif" width="25px;" /><br />
|
||||
<strong>LiVeR</strong>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="https://cdn.discordapp.com/avatars/144458450192171008/13a3e66d73d216974504b8aad257b7b4.png" width="25px;" /><br />
|
||||
<strong>SweetLilyCake</strong>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="https://cdn.discordapp.com/avatars/398951709336010793/eb6f63eb2f3a5102fb900e60d1a26cdc.png" width="25px;" /><br />
|
||||
<strong>GameKuchen</strong>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="https://i.imgur.com/qrWcKfH.png" width="25px;" /><br />
|
||||
<strong>Lozo</strong>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="https://media.discordapp.net/attachments/575576868166828032/692136786893340752/pfp.gif" width="25px;" /><br />
|
||||
<strong>Akira</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,33 @@
|
|||
const gulp = require("gulp");
|
||||
const rename = require("gulp-rename");
|
||||
const minify = require("gulp-babel-minify");
|
||||
const csso = require("gulp-csso");
|
||||
|
||||
gulp.task("minify-js", minifyJS);
|
||||
gulp.task("minify-css", minifyCSS);
|
||||
|
||||
gulp.task("watch-js", function() {
|
||||
return gulp.watch(["./js/main.js"], minifyJS);
|
||||
});
|
||||
|
||||
gulp.task("watch-css", function() {
|
||||
return gulp.watch(["./css/main.css"], minifyCSS);
|
||||
});
|
||||
|
||||
gulp.task("watch", function() {
|
||||
return gulp.watch(["./js/main.js", "./css/main.css"], gulp.series(minifyJS, minifyCSS));
|
||||
});
|
||||
|
||||
function minifyJS() {
|
||||
return gulp.src("./js/main.js")
|
||||
.pipe(minify({mangle: {keepClassName: true}}))
|
||||
.pipe(rename("main.min.js"))
|
||||
.pipe(gulp.dest("./js"));
|
||||
}
|
||||
|
||||
function minifyCSS() {
|
||||
return gulp.src("./css/main.css")
|
||||
.pipe(csso({restructure: false}))
|
||||
.pipe(rename("main.min.css"))
|
||||
.pipe(gulp.dest("./css"));
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"name": "bandagedbd",
|
||||
"version": "0.2.3",
|
||||
"description": "Enhances Discord adding functionality and themes.",
|
||||
"main": "js/main.js",
|
||||
"scripts": {
|
||||
"build": "webpack --progress --colors",
|
||||
"watch": "webpack --progress --colors --watch",
|
||||
"build-prod": "webpack --progress --colors --mode production -o js/main.min.js --devtool none",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"minify": "gulp minify-js && gulp minify-css",
|
||||
"minify-js": "gulp minify-js",
|
||||
"minify-css": "gulp minify-css",
|
||||
"watch-js": "gulp watch-js",
|
||||
"watch-css": "gulp watch-css"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/rauenzi/BetterDiscordApp.git"
|
||||
},
|
||||
"author": "rauenzi",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/rauenzi/BetterDiscordApp/issues"
|
||||
},
|
||||
"homepage": "https://github.com/rauenzi/BetterDiscordApp#readme",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.3.4",
|
||||
"@babel/preset-env": "^7.3.4",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/register": "^7.0.0",
|
||||
"babel-loader": "^8.0.6",
|
||||
"circular-dependency-plugin": "^5.0.2",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-babel-minify": "^0.5.1",
|
||||
"gulp-csso": "^3.0.1",
|
||||
"gulp-rename": "^1.4.0",
|
||||
"webpack": "^4.29.6",
|
||||
"webpack-cli": "^3.2.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"mkdirp": "^1.0.4",
|
||||
"react": "^16.13.1",
|
||||
"request": "^2.88.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"yauzl": "^2.10.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
// var settingsPanel, voiceMode, pluginModule, themeModule, dMode, publicServersModule, mainCore, BDV2;
|
||||
export const minimumDiscordVersion = "0.0.306";
|
||||
export const currentDiscordVersion = (window.DiscordNative && window.DiscordNative.remoteApp && window.DiscordNative.remoteApp.getVersion && window.DiscordNative.remoteApp.getVersion()) || "0.0.306";
|
||||
export const minSupportedVersion = "0.3.0";
|
||||
export const bbdVersion = "0.3.4";
|
||||
export const bbdChangelog = {
|
||||
description: "LightCord Edition.",
|
||||
changes: [
|
||||
{
|
||||
title: "What's New?",
|
||||
items: [
|
||||
"**LightCord** is now using BandagedBD. That means all plugins you were using can be used too !",
|
||||
"**Window Transparency** changes were made to more compatible with external window managers and addons like Glasscord.",
|
||||
"Initialization sequence has once again been changed slightly to hopefully improve loading times.",
|
||||
"We removed emotes. That's sad for people who were actually using it, but it was leading to problems like this https://github.com/rauenzi/BetterDiscordApp/issues/348."
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Bug Fixes",
|
||||
type: "fixed",
|
||||
items: [
|
||||
"Some fixes related to showing modals in the `BdApi`."
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const settings = {
|
||||
"Custom css live update": {id: "bda-css-0", info: "", implemented: true, hidden: true, cat: "core"},
|
||||
"Custom css auto udpate": {id: "bda-css-1", info: "", implemented: true, hidden: true, cat: "core"},
|
||||
"BetterDiscord Blue": {id: "bda-gs-b", info: "Replace Discord blue with BD Blue", implemented: false, hidden: false, cat: "core"},
|
||||
|
||||
/* Core */
|
||||
/* ====== */
|
||||
"Public Servers": {id: "bda-gs-1", info: "Display public servers button", implemented: true, hidden: false, cat: "core", category: "modules"},
|
||||
"Minimal Mode": {id: "bda-gs-2", info: "Hide elements and reduce the size of elements.", implemented: true, hidden: false, cat: "core", category: "modules"},
|
||||
"Voice Mode": {id: "bda-gs-4", info: "Only show voice chat", implemented: true, hidden: false, cat: "core", category: "modules"},
|
||||
"Hide Channels": {id: "bda-gs-3", info: "Hide channels in minimal mode", implemented: true, hidden: false, cat: "core", category: "modules"},
|
||||
"Dark Mode": {id: "bda-gs-5", info: "Make certain elements dark by default(wip)", implemented: true, hidden: false, cat: "core", category: "modules"},
|
||||
"Voice Disconnect": {id: "bda-dc-0", info: "Disconnect from voice server when closing Discord", implemented: true, hidden: false, cat: "core", category: "modules"},
|
||||
"24 Hour Timestamps": {id: "bda-gs-6", info: "Replace 12hr timestamps with proper ones", implemented: true, hidden: false, cat: "core", category: "modules"},
|
||||
"Colored Text": {id: "bda-gs-7", info: "Make text color the same as role color", implemented: true, hidden: false, cat: "core", category: "modules"},
|
||||
"Normalize Classes": {id: "fork-ps-4", info: "Adds stable classes to elements to help themes. (e.g. adds .da-channels to .channels-Ie2l6A)", implemented: true, hidden: false, cat: "core", category: "modules"},
|
||||
|
||||
/* Content */
|
||||
"Content Error Modal": {id: "fork-ps-1", info: "Shows a modal with plugin/theme errors", implemented: true, hidden: false, cat: "core", category: "content manager"},
|
||||
"Show Toasts": {id: "fork-ps-2", info: "Shows a small notification for important information", implemented: true, hidden: false, cat: "core", category: "content manager"},
|
||||
"Scroll To Settings": {id: "fork-ps-3", info: "Auto-scrolls to a plugin's settings when the button is clicked (only if out of view)", implemented: true, hidden: false, cat: "core", category: "content manager"},
|
||||
"Automatic Loading": {id: "fork-ps-5", info: "Automatically loads, reloads, and unloads plugins and themes", implemented: true, hidden: false, cat: "core", category: "content manager"},
|
||||
|
||||
/* Developer */
|
||||
"Developer Mode": {id: "bda-gs-8", info: "Developer Mode", implemented: true, hidden: false, cat: "core", category: "developer settings"},
|
||||
"Copy Selector": {id: "fork-dm-1", info: "Adds a \"Copy Selector\" option to context menus when developer mode is active", implemented: true, hidden: false, cat: "core", category: "developer settings"},
|
||||
"React DevTools": {id: "reactDevTools", info: "Adds react developer tools to the devtools. Must be installed in Google Chrome on your pc.", implemented: true, hidden: true, cat: "core", category: "developer settings"},
|
||||
|
||||
/** LightCord */
|
||||
"Disable BetterDiscord": {id: "bd-disable", info: "Disable Betterdiscord (plugins, themes, etc).", implemented: true, hidden: false, cat: "lightcord", category: "Lightcord"},
|
||||
"Calling Ring Beat": {id: "lightcord-2", info: "Enable Discord's special calling beat.", implemented: true, hidden: false, cat: "lightcord", category: "Lightcord"},
|
||||
"Developer Mode": {id: "lightcord-1", info: "Enable Discord's Internal Developer Options. This allow the \"Experiments\" tab and the \"Developer Options\" tab. (must close and reopen settings)", implemented: true, hidden: false, cat: "lightcord", category: "Lightcord"},
|
||||
|
||||
/** RichPresence */
|
||||
"Enable": {id: "lightcord-presence-1", info: "Enable RichPresence below.", implemented: true, hidden: false, cat: "status"}
|
||||
};
|
||||
|
||||
export const defaultCookie = {
|
||||
"bda-gs-1": true,
|
||||
"bda-gs-2": false,
|
||||
"bda-gs-3": false,
|
||||
"bda-gs-4": false,
|
||||
"bda-gs-5": true,
|
||||
"bda-gs-6": false,
|
||||
"bda-gs-7": false,
|
||||
"bda-gs-8": false,
|
||||
"bda-es-0": true,
|
||||
"bda-es-1": true,
|
||||
"bda-es-2": true,
|
||||
"bda-es-4": false,
|
||||
"bda-es-6": true,
|
||||
"bda-es-7": true,
|
||||
"bda-gs-b": false,
|
||||
"bda-es-8": true,
|
||||
"bda-dc-0": false,
|
||||
"bda-css-0": false,
|
||||
"bda-css-1": false,
|
||||
"bda-es-9": true,
|
||||
"fork-dm-1": false,
|
||||
"fork-ps-1": true,
|
||||
"fork-ps-2": true,
|
||||
"fork-ps-3": true,
|
||||
"fork-ps-4": true,
|
||||
"fork-ps-5": true,
|
||||
"fork-es-2": false,
|
||||
"fork-es-3": true,
|
||||
"fork-wp-1": false,
|
||||
"fork-wp-2": false,
|
||||
"fork-beta": true,
|
||||
"reactDevTools": false,
|
||||
"lightcord-1": false,
|
||||
"lightcord-2": true,
|
||||
"lightcord-presence-1": false
|
||||
};
|
||||
|
||||
|
||||
export const settingsCookie = {};
|
||||
|
||||
export const bdpluginErrors = [];
|
||||
export const bdthemeErrors = []; // define for backwards compatibility
|
||||
|
||||
export const bdConfig = Object.create(BetterDiscordConfig);
|
||||
|
||||
export const bdthemes = {};
|
||||
export const bdplugins = {};
|
||||
|
||||
export const pluginCookie = {};
|
||||
export const themeCookie = {};
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
import localStorageFix from "./localStorageFix";
|
||||
import loadingIcon from "./loadingIcon";
|
||||
localStorageFix();
|
||||
loadingIcon();
|
||||
|
||||
const deprecateGlobal = (key, value) => {
|
||||
// value = typeof(value) !== "object" ? value : new Proxy(value, {
|
||||
// get: function(obj, mod) {
|
||||
// if (!obj.hasOwnProperty(mod)) return undefined;
|
||||
// return obj[mod];
|
||||
// },
|
||||
// set: function(obj, mod) {
|
||||
// if (obj.hasOwnProperty(mod)) return Utils.err("Deprecated Global", "Trying to overwrite deprecated BD globals");
|
||||
// }
|
||||
// });
|
||||
Object.defineProperty(window, key, {
|
||||
get() {
|
||||
Utils.warn("Deprecated Global", `"${key}" will be removed in future versions. Please only use BdApi.`);
|
||||
return value;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
import * as Globals from "./0globals";
|
||||
|
||||
const globalKeys = Object.keys(Globals);
|
||||
for (const key of globalKeys) deprecateGlobal(key, Globals[key]);
|
||||
|
||||
|
||||
import BdApi from "./modules/bdApi";
|
||||
import BDV2 from "./modules/v2";
|
||||
import pluginModule from "./modules/pluginModule";
|
||||
import themeModule from "./modules/themeModule";
|
||||
import Utils from "./modules/utils";
|
||||
import BDEvents from "./modules/bdEvents";
|
||||
import settingsPanel from "./modules/settingsPanel";
|
||||
import DataStore from "./modules/dataStore";
|
||||
import ContentManager from "./modules/contentManager";
|
||||
import ClassNormalizer from "./modules/classNormalizer";
|
||||
|
||||
deprecateGlobal("BDV2", BDV2);
|
||||
deprecateGlobal("pluginModule", pluginModule);
|
||||
deprecateGlobal("themeModule", themeModule);
|
||||
deprecateGlobal("Utils", Utils);
|
||||
deprecateGlobal("BDEvents", BDEvents);
|
||||
deprecateGlobal("settingsPanel", settingsPanel);
|
||||
deprecateGlobal("DataStore", DataStore);
|
||||
deprecateGlobal("ContentManager", ContentManager);
|
||||
deprecateGlobal("ClassNormalizer", ClassNormalizer);
|
||||
|
||||
window.BdApi = BdApi;
|
||||
|
||||
import Core from "./modules/core";
|
||||
deprecateGlobal("mainCore", Core);
|
||||
export default class CoreWrapper {
|
||||
constructor(bdConfig) {
|
||||
Core.setConfig(bdConfig);
|
||||
}
|
||||
|
||||
init() {
|
||||
// deprecateGlobal("mainCore", this.mainCore);
|
||||
Core.init();
|
||||
}
|
||||
}
|
||||
|
||||
// function patchModuleLoad() {
|
||||
// const namespace = "betterdiscord";
|
||||
// const prefix = `${namespace}/`;
|
||||
// const Module = require("module");
|
||||
// const load = Module._load;
|
||||
// // const resolveFilename = Module._resolveFilename;
|
||||
|
||||
// Module._load = function(request) {
|
||||
// if (request === namespace || request.startsWith(prefix)) {
|
||||
// const requested = request.substr(prefix.length);
|
||||
// if (requested == "api") return BdApi;
|
||||
// }
|
||||
|
||||
// return load.apply(this, arguments);
|
||||
// };
|
||||
|
||||
// // Module._resolveFilename = function (request, parent, isMain) {
|
||||
// // if (request === "betterdiscord" || request.startsWith("betterdiscord/")) {
|
||||
// // const contentPath = PluginManager.getPluginPathByModule(parent);
|
||||
// // if (contentPath) return request;
|
||||
// // }
|
||||
|
||||
// // return resolveFilename.apply(this, arguments);
|
||||
// // };
|
||||
|
||||
// return function() {
|
||||
// Module._load = load;
|
||||
// };
|
||||
// }
|
||||
|
||||
// patchModuleLoad();
|
||||
|
||||
// var settingsPanel, voiceMode,, dMode, publicServersModule;
|
||||
// var bdConfig = null;
|
||||
|
||||
require("request")// just in cache so plugin can require it too
|
|
@ -0,0 +1,6 @@
|
|||
export default () => {
|
||||
const v2Loader = document.createElement("div");
|
||||
v2Loader.className = "bd-loaderv2";
|
||||
v2Loader.title = "BandagedBD is loading...";
|
||||
document.body.appendChild(v2Loader);
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
export default function() {
|
||||
const contentWindowGetter = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, "contentWindow").get;
|
||||
Object.defineProperty(HTMLIFrameElement.prototype, "contentWindow", {
|
||||
get: function () {
|
||||
const contentWindow = Reflect.apply(contentWindowGetter, this, arguments);
|
||||
return new Proxy(contentWindow, {
|
||||
getOwnPropertyDescriptor: function(obj, prop) {
|
||||
if (prop === "localStorage") return undefined;
|
||||
return Object.getOwnPropertyDescriptor(obj, prop);
|
||||
},
|
||||
get: function(obj, prop) {
|
||||
if (prop === "localStorage") return null;
|
||||
const val = obj[prop];
|
||||
if (typeof val === "function") return val.bind(obj);
|
||||
return val;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Prevent interception by patching Reflect.apply and Function.prototype.bind
|
||||
Object.defineProperty(Reflect, "apply", {value: Reflect.apply, writable: false, configurable: false});
|
||||
Object.defineProperty(Function.prototype, "bind", {value: Function.prototype.bind, writable: false, configurable: false});
|
||||
|
||||
const oOpen = XMLHttpRequest.prototype.open;
|
||||
XMLHttpRequest.prototype.open = function() {
|
||||
const url = arguments[1];
|
||||
if (url.toLowerCase().includes("api/webhooks")) return null;
|
||||
return Reflect.apply(oOpen, this, arguments);
|
||||
};
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import {settingsCookie} from "../0globals";
|
||||
import BDV2 from "./v2";
|
||||
import Utils from "./utils";
|
||||
|
||||
export default new class TFHour {
|
||||
inject24Hour() {
|
||||
if (this.cancel24Hour) return;
|
||||
|
||||
const twelveHour = new RegExp(`([0-9]{1,2}):([0-9]{1,2})\\s(AM|PM)`);
|
||||
const convert = (data) => {
|
||||
if (!settingsCookie["bda-gs-6"]) return;
|
||||
const matched = data.returnValue.match(twelveHour);
|
||||
if (!matched || matched.length !== 4) return;
|
||||
if (matched[3] === "AM") return data.returnValue = data.returnValue.replace(matched[0], `${matched[1] === "12" ? "00" : matched[1].padStart(2, "0")}:${matched[2]}`);
|
||||
return data.returnValue = data.returnValue.replace(matched[0], `${matched[1] === "12" ? "12" : parseInt(matched[1]) + 12}:${matched[2]}`);
|
||||
};
|
||||
|
||||
const cancelCozy = Utils.monkeyPatch(BDV2.TimeFormatter, "calendarFormat", {after: convert}); // Called in Cozy mode
|
||||
const cancelCompact = Utils.monkeyPatch(BDV2.TimeFormatter, "dateFormat", {after: convert}); // Called in Compact mode
|
||||
this.cancel24Hour = () => {cancelCozy(); cancelCompact();}; // Cancel both
|
||||
}
|
||||
|
||||
remove24Hour() {
|
||||
if (this.cancel24Hour) this.cancel24Hour();
|
||||
}
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
import {settingsCookie} from "../0globals";
|
||||
import BDV2 from "./v2";
|
||||
import Utils from "./utils";
|
||||
|
||||
export default new class CustomRichPresence {
|
||||
constructor(){
|
||||
this.enabled = false
|
||||
}
|
||||
enable() {
|
||||
if(this.enabled)return
|
||||
this.enabled = true
|
||||
console.log("Enabling custom RichPresence")
|
||||
}
|
||||
|
||||
disable() {
|
||||
if(!this.enabled)return
|
||||
this.enabled = false
|
||||
console.log("Disabling custom RichPresence")
|
||||
}
|
||||
};
|
|
@ -0,0 +1,252 @@
|
|||
import {pluginCookie, themeCookie, bdplugins, bdthemes, settingsCookie, settings} from "../0globals";
|
||||
import mainCore from "./core";
|
||||
import Utils from "./utils";
|
||||
import BDV2 from "./v2";
|
||||
import DataStore from "./dataStore";
|
||||
import pluginModule from "./pluginModule";
|
||||
import themeModule from "./themeModule";
|
||||
import settingsPanel from "./settingsPanel";
|
||||
import DOM from "./domtools";
|
||||
|
||||
const BdApi = {
|
||||
get React() { return BDV2.React; },
|
||||
get ReactDOM() { return BDV2.ReactDom; },
|
||||
get ReactComponent() {return BDV2.ReactComponent;},
|
||||
get WindowConfigFile() {return Utils.WindowConfigFile;},
|
||||
get settings() {return settings;},
|
||||
get emotes() {return {}}, // deprecated, deleted all emotes from betterdiscord.
|
||||
get screenWidth() { return Math.max(document.documentElement.clientWidth, window.innerWidth || 0); },
|
||||
get screenHeight() { return Math.max(document.documentElement.clientHeight, window.innerHeight || 0); }
|
||||
};
|
||||
|
||||
BdApi.getAllWindowPreferences = function() {
|
||||
return Utils.getAllWindowPreferences();
|
||||
};
|
||||
|
||||
BdApi.getWindowPreference = function(key) {
|
||||
return Utils.getWindowPreference(key);
|
||||
};
|
||||
|
||||
BdApi.setWindowPreference = function(key, value) {
|
||||
return Utils.setWindowPreference(key, value);
|
||||
};
|
||||
|
||||
//Inject CSS to document head
|
||||
//id = id of element
|
||||
//css = custom css
|
||||
BdApi.injectCSS = function (id, css) {
|
||||
DOM.addStyle(DOM.escapeID(id), css);
|
||||
};
|
||||
|
||||
//Clear css/remove any element
|
||||
//id = id of element
|
||||
BdApi.clearCSS = function (id) {
|
||||
DOM.removeStyle(DOM.escapeID(id));
|
||||
};
|
||||
|
||||
//Inject CSS to document head
|
||||
//id = id of element
|
||||
//css = custom css
|
||||
BdApi.linkJS = function (id, url) {
|
||||
DOM.addScript(DOM.escapeID(id), url);
|
||||
};
|
||||
|
||||
//Clear css/remove any element
|
||||
//id = id of element
|
||||
BdApi.unlinkJS = function (id) {
|
||||
DOM.removeScript(DOM.escapeID(id));
|
||||
};
|
||||
|
||||
//Get another plugin
|
||||
//name = name of plugin
|
||||
BdApi.getPlugin = function (name) {
|
||||
if (bdplugins.hasOwnProperty(name)) {
|
||||
return bdplugins[name].plugin;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
//Get BetterDiscord Core
|
||||
BdApi.getCore = function () {
|
||||
Utils.warn("Deprecation Notice", `BdApi.getCore() will be removed in future versions.`);
|
||||
return mainCore;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows a generic but very customizable modal.
|
||||
* @param {string} title - title of the modal
|
||||
* @param {string} content - a string of text to display in the modal
|
||||
*/
|
||||
BdApi.alert = function (title, content) {
|
||||
return Utils.showConfirmationModal(title, content, {cancelText: null});
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows a generic but very customizable confirmation modal with optional confirm and cancel callbacks.
|
||||
* @param {string} title - title of the modal
|
||||
* @param {(string|ReactElement|Array<string|ReactElement>)} children - a single or mixed array of react elements and strings. Every string is wrapped in Discord's `Markdown` component so strings will show and render properly.
|
||||
* @param {object} [options] - options to modify the modal
|
||||
* @param {boolean} [options.danger=false] - whether the main button should be red or not
|
||||
* @param {string} [options.confirmText=Okay] - text for the confirmation/submit button
|
||||
* @param {string} [options.cancelText=Cancel] - text for the cancel button
|
||||
* @param {callable} [options.onConfirm=NOOP] - callback to occur when clicking the submit button
|
||||
* @param {callable} [options.onCancel=NOOP] - callback to occur when clicking the cancel button
|
||||
* @param {string} [options.key] - key used to identify the modal. If not provided, one is generated and returned
|
||||
* @returns {string} - the key used for this modal
|
||||
*/
|
||||
BdApi.showConfirmationModal = function (title, content, options = {}) {
|
||||
return Utils.showConfirmationModal(title, content, options);
|
||||
};
|
||||
|
||||
//Show toast alert
|
||||
BdApi.showToast = function(content, options = {}) {
|
||||
Utils.showToast(content, options);
|
||||
};
|
||||
|
||||
// Finds module
|
||||
BdApi.findModule = function(filter) {
|
||||
return BDV2.WebpackModules.find(filter);
|
||||
};
|
||||
|
||||
// Finds module
|
||||
BdApi.findAllModules = function(filter) {
|
||||
return BDV2.WebpackModules.findAll(filter);
|
||||
};
|
||||
|
||||
// Finds module
|
||||
BdApi.findModuleByProps = function(...props) {
|
||||
return BDV2.WebpackModules.findByUniqueProperties(props);
|
||||
};
|
||||
|
||||
BdApi.findModuleByPrototypes = function(...protos) {
|
||||
return BDV2.WebpackModules.findByPrototypes(protos);
|
||||
};
|
||||
|
||||
BdApi.findModuleByDisplayName = function(name) {
|
||||
return BDV2.WebpackModules.findByDisplayName(name);
|
||||
};
|
||||
|
||||
// Gets react instance
|
||||
BdApi.getInternalInstance = function(node) {
|
||||
if (!(node instanceof window.jQuery) && !(node instanceof Element)) return undefined;
|
||||
if (node instanceof jQuery) node = node[0];
|
||||
return BDV2.getInternalInstance(node);
|
||||
};
|
||||
|
||||
// Gets data
|
||||
BdApi.loadData = function(pluginName, key) {
|
||||
return DataStore.getPluginData(pluginName, key);
|
||||
};
|
||||
|
||||
BdApi.getData = BdApi.loadData;
|
||||
|
||||
// Sets data
|
||||
BdApi.saveData = function(pluginName, key, data) {
|
||||
return DataStore.setPluginData(pluginName, key, data);
|
||||
};
|
||||
|
||||
BdApi.setData = BdApi.saveData;
|
||||
|
||||
// Deletes data
|
||||
BdApi.deleteData = function(pluginName, key) {
|
||||
return DataStore.deletePluginData(pluginName, key);
|
||||
};
|
||||
|
||||
// Patches other functions
|
||||
BdApi.monkeyPatch = function(what, methodName, options) {
|
||||
return Utils.monkeyPatch(what, methodName, options);
|
||||
};
|
||||
|
||||
// Event when element is removed
|
||||
BdApi.onRemoved = function(node, callback) {
|
||||
return Utils.onRemoved(node, callback);
|
||||
};
|
||||
|
||||
// Wraps function in try..catch
|
||||
BdApi.suppressErrors = function(method, message) {
|
||||
return Utils.suppressErrors(method, message);
|
||||
};
|
||||
|
||||
// Tests for valid JSON
|
||||
BdApi.testJSON = function(data) {
|
||||
return Utils.testJSON(data);
|
||||
};
|
||||
|
||||
BdApi.isPluginEnabled = function(name) {
|
||||
return !!pluginCookie[name];
|
||||
};
|
||||
|
||||
BdApi.isThemeEnabled = function(name) {
|
||||
return !!themeCookie[name];
|
||||
};
|
||||
|
||||
BdApi.isSettingEnabled = function(id) {
|
||||
return !!settingsCookie[id];
|
||||
};
|
||||
|
||||
BdApi.enableSetting = function(id) {
|
||||
return settingsPanel.onChange(id, true);
|
||||
};
|
||||
|
||||
BdApi.disableSetting = function(id) {
|
||||
return settingsPanel.onChange(id, false);
|
||||
};
|
||||
|
||||
BdApi.toggleSetting = function(id) {
|
||||
return settingsPanel.onChange(id, !settingsCookie[id]);
|
||||
};
|
||||
|
||||
// Gets data
|
||||
BdApi.getBDData = function(key) {
|
||||
return DataStore.getBDData(key);
|
||||
};
|
||||
|
||||
// Sets data
|
||||
BdApi.setBDData = function(key, data) {
|
||||
return DataStore.setBDData(key, data);
|
||||
};
|
||||
|
||||
|
||||
|
||||
const makeAddonAPI = (cookie, list, manager) => new class AddonAPI {
|
||||
|
||||
get folder() {return manager.folder;}
|
||||
|
||||
isEnabled(name) {
|
||||
return !!cookie[name];
|
||||
}
|
||||
|
||||
enable(name) {
|
||||
return manager.enable(name);
|
||||
}
|
||||
|
||||
disable(name) {
|
||||
return manager.disable(name);
|
||||
}
|
||||
|
||||
toggle(name) {
|
||||
if (cookie[name]) this.disable(name);
|
||||
else this.enable(name);
|
||||
}
|
||||
|
||||
reload(name) {
|
||||
return manager.reload(name);
|
||||
}
|
||||
|
||||
get(name) {
|
||||
if (list.hasOwnProperty(name)) {
|
||||
if (list[name].plugin) return list[name].plugin;
|
||||
return list[name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getAll() {
|
||||
return Object.keys(list).map(k => this.get(k)).filter(a => a);
|
||||
}
|
||||
};
|
||||
|
||||
BdApi.Plugins = makeAddonAPI(pluginCookie, bdplugins, pluginModule);
|
||||
BdApi.Themes = makeAddonAPI(themeCookie, bdthemes, themeModule);
|
||||
|
||||
export default BdApi;
|
|
@ -0,0 +1,6 @@
|
|||
/* BDEvents */
|
||||
const EventEmitter = require("events");
|
||||
export default new class BDEvents extends EventEmitter {
|
||||
dispatch(eventName, ...args) {this.emit(eventName, ...args);}
|
||||
off(eventName, eventAction) {this.removeListener(eventName, eventAction);}
|
||||
};
|
|
@ -0,0 +1,136 @@
|
|||
import WebpackModules from "./webpackModules";
|
||||
|
||||
const normalizedPrefix = "da";
|
||||
const randClass = new RegExp(`^(?!${normalizedPrefix}-)((?:[A-Za-z]|[0-9]|-)+)-(?:[A-Za-z]|[0-9]|-|_){6}$`);
|
||||
|
||||
export default new class ClassNormalizer {
|
||||
|
||||
stop() {
|
||||
if (!this.hasPatched) return;
|
||||
this.unpatchClassModules(WebpackModules.findAll(this.moduleFilter.bind(this)));
|
||||
this.revertElement(document.querySelector("#app-mount"));
|
||||
this.hasPatched = false;
|
||||
}
|
||||
|
||||
start() {
|
||||
if (this.hasPatched) return;
|
||||
this.patchClassModules(WebpackModules.findAll(this.moduleFilter.bind(this)));
|
||||
this.normalizeElement(document.querySelector("#app-mount"));
|
||||
this.hasPatched = true;
|
||||
this.patchDOMMethods();
|
||||
}
|
||||
|
||||
patchClassModules(modules) {
|
||||
for (const module of modules) {
|
||||
this.patchClassModule(normalizedPrefix, module);
|
||||
}
|
||||
}
|
||||
|
||||
unpatchClassModules(modules) {
|
||||
for (const module of modules) {
|
||||
this.unpatchClassModule(normalizedPrefix, 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("layerContainer-")) return true;
|
||||
if (value.startsWith("#") && (value.length == 7 || value.length == 4)) return true;
|
||||
if (value.includes("calc(") || value.includes("rgba")) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
moduleFilter(module) {
|
||||
if (typeof module !== "object" || Array.isArray(module)) return false;
|
||||
if (module.__esModule) return false;
|
||||
if (!Object.keys(module).length) return false;
|
||||
for (const baseClassName in module) {
|
||||
const value = module[baseClassName];
|
||||
if (typeof value !== "string") return false;
|
||||
if (this.shouldIgnore(value)) continue;
|
||||
if (value.split("-").length === 1) return false;
|
||||
if (!randClass.test(value.split(" ")[0])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
patchClassModule(componentName, classNames) {
|
||||
for (const baseClassName in classNames) {
|
||||
const value = classNames[baseClassName];
|
||||
if (this.shouldIgnore(value)) continue;
|
||||
const classList = value.split(" ");
|
||||
for (const normalClass of classList) {
|
||||
const match = normalClass.match(randClass);
|
||||
if (!match || !match.length || match.length < 2) continue; // Shouldn't ever happen since they passed the moduleFilter, but you never know
|
||||
const camelCase = match[1].split("-").map((s, i) => i ? s[0].toUpperCase() + s.slice(1) : s).join("");
|
||||
classNames[baseClassName] += ` ${componentName}-${camelCase}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unpatchClassModule(componentName, classNames) {
|
||||
for (const baseClassName in classNames) {
|
||||
const value = classNames[baseClassName];
|
||||
if (this.shouldIgnore(value)) continue;
|
||||
let newString = "";
|
||||
const classList = value.split(" ");
|
||||
for (const normalClass of classList) {
|
||||
if (normalClass.startsWith(`${componentName}-`)) continue;
|
||||
newString += ` ${normalClass}`;
|
||||
}
|
||||
classNames[baseClassName] = newString.trim();
|
||||
}
|
||||
}
|
||||
|
||||
normalizeElement(element) {
|
||||
if (!(element instanceof Element)) return;
|
||||
const classes = element.classList;
|
||||
for (let c = 0, clen = classes.length; c < clen; c++) {
|
||||
if (!randClass.test(classes[c])) continue;
|
||||
const match = classes[c].match(randClass)[1];
|
||||
const newClass = match.split("-").map((s, i) => i ? s[0].toUpperCase() + s.slice(1) : s).join("");
|
||||
element.classList.add(`${normalizedPrefix}-${newClass}`);
|
||||
}
|
||||
for (const child of element.children) this.normalizeElement(child);
|
||||
}
|
||||
|
||||
revertElement(element) {
|
||||
if (!(element instanceof Element)) return;
|
||||
if (element.children && element.children.length) this.revertElement(element.children[0]);
|
||||
if (element.nextElementSibling) this.revertElement(element.nextElementSibling);
|
||||
const classes = element.classList;
|
||||
const toRemove = [];
|
||||
for (let c = 0; c < classes.length; c++) {
|
||||
if (classes[c].startsWith(`${normalizedPrefix}-`)) toRemove.push(classes[c]);
|
||||
}
|
||||
element.classList.remove(...toRemove);
|
||||
}
|
||||
|
||||
patchDOMMethods() {
|
||||
const contains = DOMTokenList.prototype.contains;
|
||||
DOMTokenList.prototype.contains = function(token) {
|
||||
// const tokens = token.split(" ");
|
||||
return Reflect.apply(contains, this, [token.split(" ")[0]]);
|
||||
// return tokens.every(t => contains.call(this, t));
|
||||
};
|
||||
|
||||
const add = DOMTokenList.prototype.add;
|
||||
DOMTokenList.prototype.add = function(...tokens) {
|
||||
for (let t = 0; t < tokens.length; t++) {
|
||||
tokens[t] = tokens[t].split(" ")[0];
|
||||
}
|
||||
return Reflect.apply(add, this, tokens);
|
||||
};
|
||||
|
||||
const remove = DOMTokenList.prototype.remove;
|
||||
DOMTokenList.prototype.remove = function(...tokens) {
|
||||
for (let t = 0; t < tokens.length; t++) {
|
||||
tokens[t] = tokens[t].split(" ")[0];
|
||||
}
|
||||
return Reflect.apply(remove, this, tokens);
|
||||
};
|
||||
}
|
||||
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
import {settingsCookie} from "../0globals";
|
||||
import BDV2 from "./v2";
|
||||
import Utils from "./utils";
|
||||
|
||||
export default new class ColoredText {
|
||||
injectColoredText() {
|
||||
if (this.cancelColoredText) return;
|
||||
if (!BDV2.MessageComponent) return;
|
||||
|
||||
this.cancelColoredText = Utils.monkeyPatch(BDV2.MessageComponent, "default", {before: (data) => {
|
||||
const props = data.methodArguments[0];
|
||||
if (!props || !props.childrenMessageContent) return;
|
||||
const messageContent = props.childrenMessageContent;
|
||||
|
||||
if (!messageContent.type || !messageContent.type.type || messageContent.type.type.displayName != "MessageContent") return;
|
||||
const originalType = messageContent.type.type;
|
||||
if (originalType.__originalMethod) return; // Don't patch again
|
||||
messageContent.type.type = function(props) {
|
||||
const returnValue = originalType(props);
|
||||
const roleColor = settingsCookie["bda-gs-7"] ? props.message.colorString || "" : "";
|
||||
returnValue.props.style = {color: roleColor};
|
||||
return returnValue;
|
||||
};
|
||||
|
||||
messageContent.type.type.__originalMethod = originalType;
|
||||
Object.assign(messageContent.type.type, originalType);
|
||||
}});
|
||||
}
|
||||
|
||||
removeColoredText() {
|
||||
let classNameMarkup = BDModules.get(e => e.markup)[0].markup
|
||||
document.querySelectorAll("."+classNameMarkup.split(" ")[0]).forEach(elem => {
|
||||
elem.style.setProperty("color", "");
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,246 @@
|
|||
const __non_webpack_require__ = window.require
|
||||
import {bdConfig, bdplugins, bdthemes} from "../0globals";
|
||||
import pluginModule from "./pluginModule";
|
||||
import themeModule from "./themeModule";
|
||||
import Utils from "./utils";
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const Module = require("module").Module;
|
||||
Module.globalPaths.push(path.resolve(require("electron").remote.app.getAppPath(), "node_modules"));
|
||||
class MetaError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "MetaError";
|
||||
}
|
||||
}
|
||||
const originalJSRequire = Module._extensions[".js"];
|
||||
const originalCSSRequire = Module._extensions[".css"] ? Module._extensions[".css"] : () => {return null;};
|
||||
const splitRegex = /[^\S\r\n]*?(?:\r\n|\n)[^\S\r\n]*?\*[^\S\r\n]?/;
|
||||
const escapedAtRegex = /^\\@/;
|
||||
|
||||
|
||||
export default new class ContentManager {
|
||||
|
||||
constructor() {
|
||||
this.timeCache = {};
|
||||
this.watchers = {};
|
||||
Module._extensions[".js"] = this.getContentRequire("plugin");
|
||||
Module._extensions[".css"] = this.getContentRequire("theme");
|
||||
}
|
||||
|
||||
get pluginsFolder() {return this._pluginsFolder || (this._pluginsFolder = fs.realpathSync(path.resolve(bdConfig.dataPath + "plugins/")));}
|
||||
get themesFolder() {return this._themesFolder || (this._themesFolder = fs.realpathSync(path.resolve(bdConfig.dataPath + "themes/")));}
|
||||
|
||||
watchContent(contentType) {
|
||||
if (this.watchers[contentType]) return;
|
||||
const isPlugin = contentType === "plugin";
|
||||
const baseFolder = isPlugin ? this.pluginsFolder : this.themesFolder;
|
||||
const fileEnding = isPlugin ? ".plugin.js" : ".theme.css";
|
||||
this.watchers[contentType] = fs.watch(baseFolder, {persistent: false}, async (eventType, filename) => {
|
||||
if (!eventType || !filename || !filename.endsWith(fileEnding)) return;
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
try {fs.statSync(path.resolve(baseFolder, filename));}
|
||||
catch (err) {
|
||||
if (err.code !== "ENOENT") return;
|
||||
delete this.timeCache[filename];
|
||||
if (isPlugin) return pluginModule.unloadPlugin(filename);
|
||||
return themeModule.unloadTheme(filename);
|
||||
}
|
||||
if (!fs.statSync(path.resolve(baseFolder, filename)).isFile()) return;
|
||||
const stats = fs.statSync(path.resolve(baseFolder, filename));
|
||||
if (!stats || !stats.mtime || !stats.mtime.getTime()) return;
|
||||
if (typeof(stats.mtime.getTime()) !== "number") return;
|
||||
if (this.timeCache[filename] == stats.mtime.getTime()) return;
|
||||
this.timeCache[filename] = stats.mtime.getTime();
|
||||
if (eventType == "rename") {
|
||||
if (isPlugin) pluginModule.loadPlugin(filename);
|
||||
else themeModule.loadTheme(filename);
|
||||
}
|
||||
if (eventType == "change") {
|
||||
if (isPlugin) pluginModule.reloadPlugin(filename);
|
||||
else themeModule.reloadTheme(filename);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
unwatchContent(contentType) {
|
||||
if (!this.watchers[contentType]) return;
|
||||
this.watchers[contentType].close();
|
||||
delete this.watchers[contentType];
|
||||
}
|
||||
|
||||
extractMeta(content) {
|
||||
const firstLine = content.split("\n")[0];
|
||||
const hasOldMeta = firstLine.includes("//META");
|
||||
if (hasOldMeta) return this.parseOldMeta(content);
|
||||
const hasNewMeta = firstLine.includes("/**");
|
||||
if (hasNewMeta) return this.parseNewMeta(content);
|
||||
throw new MetaError("META was not found.");
|
||||
}
|
||||
|
||||
parseOldMeta(content) {
|
||||
const meta = content.split("\n")[0];
|
||||
const rawMeta = meta.substring(meta.lastIndexOf("//META") + 6, meta.lastIndexOf("*//"));
|
||||
if (meta.indexOf("META") < 0) throw new MetaError("META was not found.");
|
||||
const parsed = Utils.testJSON(rawMeta);
|
||||
if (!parsed) throw new MetaError("META could not be parsed.");
|
||||
if (!parsed.name) throw new MetaError("META missing name data.");
|
||||
parsed.format = "json";
|
||||
return parsed;
|
||||
}
|
||||
|
||||
parseNewMeta(content) {
|
||||
const block = content.split("/**", 2)[1].split("*/", 1)[0];
|
||||
const out = {};
|
||||
let field = "";
|
||||
let accum = "";
|
||||
for (const line of block.split(splitRegex)) {
|
||||
if (line.length === 0) continue;
|
||||
if (line.charAt(0) === "@" && line.charAt(1) !== " ") {
|
||||
out[field] = accum;
|
||||
const l = line.indexOf(" ");
|
||||
field = line.substr(1, l - 1);
|
||||
accum = line.substr(l + 1);
|
||||
}
|
||||
else {
|
||||
accum += " " + line.replace("\\n", "\n").replace(escapedAtRegex, "@");
|
||||
}
|
||||
}
|
||||
out[field] = accum.trim();
|
||||
delete out[""];
|
||||
out.format = "jsdoc";
|
||||
return out;
|
||||
}
|
||||
|
||||
getContentRequire(type) {
|
||||
const isPlugin = type === "plugin";
|
||||
const self = this;
|
||||
const originalRequire = isPlugin ? originalJSRequire : originalCSSRequire;
|
||||
return function(module, filename) {
|
||||
const baseFolder = isPlugin ? self.pluginsFolder : self.themesFolder;
|
||||
const possiblePath = path.resolve(baseFolder, path.basename(filename));
|
||||
if (!fs.existsSync(possiblePath) || filename !== fs.realpathSync(possiblePath)) return Reflect.apply(originalRequire, this, arguments);
|
||||
let content = fs.readFileSync(filename, "utf8");
|
||||
content = Utils.stripBOM(content);
|
||||
|
||||
const stats = fs.statSync(filename);
|
||||
const meta = self.extractMeta(content);
|
||||
meta.filename = path.basename(filename);
|
||||
meta.added = stats.atimeMs;
|
||||
meta.modified = stats.mtimeMs;
|
||||
meta.size = stats.size;
|
||||
if (!isPlugin) {
|
||||
meta.css = content;
|
||||
if (meta.format == "json") meta.css = meta.css.split("\n").slice(1).join("\n");
|
||||
content = `module.exports = ${JSON.stringify(meta)};`;
|
||||
}
|
||||
if (isPlugin) {
|
||||
module._compile(content, module.filename);
|
||||
const didExport = !Utils.isEmpty(module.exports);
|
||||
if (didExport) {
|
||||
meta.type = module.exports;
|
||||
module.exports = meta;
|
||||
content = "";
|
||||
}
|
||||
else {
|
||||
content += `\nmodule.exports = ${JSON.stringify(meta)};\nmodule.exports.type = ${meta.exports || meta.name};`;
|
||||
}
|
||||
}
|
||||
module._compile(content, filename);
|
||||
};
|
||||
}
|
||||
|
||||
makePlaceholderPlugin(data) {
|
||||
return {plugin: {
|
||||
start: () => {},
|
||||
getName: () => {return data.name || data.filename;},
|
||||
getAuthor: () => {return "???";},
|
||||
getDescription: () => {return data.message ? data.message : "This plugin was unable to be loaded. Check the author's page for updates.";},
|
||||
getVersion: () => {return "???";}
|
||||
},
|
||||
name: data.name || data.filename,
|
||||
filename: data.filename,
|
||||
source: data.source ? data.source : "",
|
||||
website: data.website ? data.website : ""
|
||||
};
|
||||
}
|
||||
|
||||
loadContent(filename, type) {
|
||||
if (typeof(filename) === "undefined" || typeof(type) === "undefined") return;
|
||||
const isPlugin = type === "plugin";
|
||||
const baseFolder = isPlugin ? this.pluginsFolder : this.themesFolder;
|
||||
try {__non_webpack_require__(path.resolve(baseFolder, filename));}
|
||||
catch (error) {return {name: filename, file: filename, message: "Could not be compiled.", error: {message: error.message, stack: error.stack}};}
|
||||
const content = __non_webpack_require__(path.resolve(baseFolder, filename));
|
||||
if(!content.name)return {name: filename, file: filename, message: "Cannot escape the ID.", error: {message: "Cannot read property 'replace' of undefined", stack: "Cannot read property 'replace' of undefined"}}
|
||||
content.id = Utils.escapeID(content.name);
|
||||
if (isPlugin) {
|
||||
if (!content.type) return;
|
||||
try {
|
||||
content.plugin = new content.type();
|
||||
delete bdplugins[content.plugin.getName()];
|
||||
bdplugins[content.plugin.getName()] = content;
|
||||
}
|
||||
catch (error) {return {name: filename, file: filename, message: "Could not be constructed.", error: {message: error.message, stack: error.stack}};}
|
||||
}
|
||||
else {
|
||||
delete bdthemes[content.name];
|
||||
bdthemes[content.name] = content;
|
||||
}
|
||||
}
|
||||
|
||||
unloadContent(filename, type) {
|
||||
if (typeof(filename) === "undefined" || typeof(type) === "undefined") return;
|
||||
const isPlugin = type === "plugin";
|
||||
const baseFolder = isPlugin ? this.pluginsFolder : this.themesFolder;
|
||||
try {
|
||||
delete __non_webpack_require__.cache[__non_webpack_require__.resolve(path.resolve(baseFolder, filename))];
|
||||
}
|
||||
catch (err) {return {name: filename, file: filename, message: "Could not be unloaded.", error: {message: err.message, stack: err.stack}};}
|
||||
}
|
||||
|
||||
isLoaded(filename, type) {
|
||||
const isPlugin = type === "plugin";
|
||||
const baseFolder = isPlugin ? this.pluginsFolder : this.themesFolder;
|
||||
try {__non_webpack_require__.cache[__non_webpack_require__.resolve(path.resolve(baseFolder, filename))];}
|
||||
catch (err) {return false;}
|
||||
return true;
|
||||
}
|
||||
|
||||
reloadContent(filename, type) {
|
||||
const cantUnload = this.unloadContent(filename, type);
|
||||
if (cantUnload) return cantUnload;
|
||||
return this.loadContent(filename, type);
|
||||
}
|
||||
|
||||
loadNewContent(type) {
|
||||
const isPlugin = type === "plugin";
|
||||
const fileEnding = isPlugin ? ".plugin.js" : ".theme.css";
|
||||
const basedir = isPlugin ? this.pluginsFolder : this.themesFolder;
|
||||
const files = fs.readdirSync(basedir);
|
||||
const contentList = Object.values(isPlugin ? bdplugins : bdthemes);
|
||||
const removed = contentList.filter(t => !files.includes(t.filename)).map(c => isPlugin ? c.plugin.getName() : c.name);
|
||||
const added = files.filter(f => !contentList.find(t => t.filename == f) && f.endsWith(fileEnding) && fs.statSync(path.resolve(basedir, f)).isFile());
|
||||
return {added, removed};
|
||||
}
|
||||
|
||||
loadAllContent(type) {
|
||||
const isPlugin = type === "plugin";
|
||||
const fileEnding = isPlugin ? ".plugin.js" : ".theme.css";
|
||||
const basedir = isPlugin ? this.pluginsFolder : this.themesFolder;
|
||||
const errors = [];
|
||||
const files = fs.readdirSync(basedir);
|
||||
|
||||
for (const filename of files) {
|
||||
if (!fs.statSync(path.resolve(basedir, filename)).isFile() || !filename.endsWith(fileEnding)) continue;
|
||||
const error = this.loadContent(filename, type);
|
||||
if (error) errors.push(error);
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
loadPlugins() {return this.loadAllContent("plugin");}
|
||||
loadThemes() {return this.loadAllContent("theme");}
|
||||
};
|
|
@ -0,0 +1,463 @@
|
|||
import {bdConfig, minSupportedVersion, bbdVersion, settingsCookie, bdpluginErrors, bdthemeErrors, bbdChangelog, defaultCookie, currentDiscordVersion} from "../0globals";
|
||||
import Utils from "./utils";
|
||||
|
||||
import BDV2 from "./v2";
|
||||
import settingsPanel from "./settingsPanel";
|
||||
import pluginModule from "./pluginModule";
|
||||
import themeModule from "./themeModule";
|
||||
import DataStore from "./dataStore";
|
||||
import WebpackModules from "./webpackModules";
|
||||
import DOM from "./domtools";
|
||||
|
||||
import BDLogo from "../ui/bdLogo";
|
||||
import TooltipWrap from "../ui/tooltipWrap";
|
||||
import LightcordLogo from "../ui/lightcordLogo";
|
||||
|
||||
function Core() {
|
||||
// Object.assign(bdConfig, __non_webpack_require__(DataStore.configFile));
|
||||
// this.init();
|
||||
}
|
||||
|
||||
Core.prototype.setConfig = function(config) {
|
||||
Object.assign(bdConfig, config);
|
||||
};
|
||||
|
||||
Core.prototype.init = async function() {
|
||||
if (!Array.prototype.flat) {
|
||||
Utils.alert("Not Supported", "BetterDiscord v" + bbdVersion + " does not support this old version (" + currentDiscordVersion + ") of Discord. Please update your Discord installation before proceeding.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (bdConfig.version < minSupportedVersion) {
|
||||
Utils.alert("Not Supported", "BetterDiscord v" + bdConfig.version + " (your version)" + " is not supported by the latest js (" + bbdVersion + ").<br><br> Please download the latest version from <a href='https://github.com/rauenzi/BetterDiscordApp/releases/latest' target='_blank'>GitHub</a>");
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.ED) {
|
||||
Utils.alert("Not Supported", "BandagedBD does not work with EnhancedDiscord. Please uninstall one of them.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.WebSocket && window.WebSocket.name && window.WebSocket.name.includes("Patched")) {
|
||||
Utils.alert("Not Supported", "BandagedBD does not work with Powercord. Please uninstall one of them.");
|
||||
return;
|
||||
}
|
||||
|
||||
const latestLocalVersion = bdConfig.updater ? bdConfig.updater.LatestVersion : bdConfig.latestVersion;
|
||||
if (latestLocalVersion > bdConfig.version) {
|
||||
Utils.showConfirmationModal("Update Available", [`There is an update available for BandagedBD's Injector (${latestLocalVersion}).`, "You can either update and restart now, or later."], {
|
||||
confirmText: "Update Now",
|
||||
cancelText: "Maybe Later",
|
||||
onConfirm: async () => {
|
||||
const onUpdateFailed = () => {Utils.alert("Could Not Update", `Unable to update automatically, please download the installer and reinstall normally.<br /><br /><a href='https://github.com/rauenzi/BetterDiscordApp/releases/latest' target='_blank'>Download Installer</a>`);};
|
||||
try {
|
||||
const didUpdate = await this.updateInjector();
|
||||
if (!didUpdate) return onUpdateFailed();
|
||||
const app = require("electron").remote.app;
|
||||
app.relaunch();
|
||||
app.exit();
|
||||
}
|
||||
catch (err) {
|
||||
onUpdateFailed();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Utils.log("Startup", "Initializing Settings");
|
||||
this.initSettings();
|
||||
|
||||
await this.checkForGuilds();
|
||||
BDV2.initialize();
|
||||
Utils.log("Startup", "Updating Settings");
|
||||
settingsPanel.initializeSettings();
|
||||
|
||||
Utils.log("Startup", "Loading Plugins");
|
||||
pluginModule.loadPlugins();
|
||||
|
||||
Utils.log("Startup", "Loading Themes");
|
||||
themeModule.loadThemes();
|
||||
|
||||
DOM.addStyle("customcss", atob(DataStore.getBDData("bdcustomcss")));
|
||||
|
||||
window.addEventListener("beforeunload", function() {
|
||||
if (settingsCookie["bda-dc-0"]) document.querySelector(".btn.btn-disconnect").click();
|
||||
});
|
||||
|
||||
Utils.log("Startup", "Removing Loading Icon");
|
||||
if (document.getElementsByClassName("bd-loaderv2").length) document.getElementsByClassName("bd-loaderv2")[0].remove();
|
||||
Utils.log("Startup", "Initializing Main Observer");
|
||||
this.initObserver();
|
||||
|
||||
// Show loading errors
|
||||
if (settingsCookie["fork-ps-1"]) {
|
||||
Utils.log("Startup", "Collecting Startup Errors");
|
||||
Utils.showContentErrors({plugins: bdpluginErrors, themes: bdthemeErrors});
|
||||
}
|
||||
|
||||
const previousVersion = DataStore.getBDData("version");
|
||||
if (bbdVersion > previousVersion) {
|
||||
if (bbdChangelog) this.showChangelogModal(bbdChangelog);
|
||||
DataStore.setBDData("version", bbdVersion);
|
||||
}
|
||||
|
||||
Utils.suppressErrors(this.patchSocial.bind(this), "BD Social Patch")();
|
||||
Utils.suppressErrors(this.patchGuildPills.bind(this), "BD Guild Pills Patch")();
|
||||
Utils.suppressErrors(this.patchGuildListItems.bind(this), "BD Guild List Items Patch")();
|
||||
Utils.suppressErrors(this.patchGuildSeparator.bind(this), "BD Guild Separator Patch")();
|
||||
Utils.suppressErrors(this.patchMessageHeader.bind(this), "BD Badge Chat Patch")();
|
||||
Utils.suppressErrors(this.patchMemberList.bind(this), "BD Badge Member List Patch")();
|
||||
};
|
||||
|
||||
Core.prototype.checkForGuilds = function() {
|
||||
let timesChecked = 0;
|
||||
return new Promise(resolve => {
|
||||
const checkForGuilds = function() {
|
||||
const wrapper = BDV2.guildClasses.wrapper.split(" ")[0];
|
||||
if (document.querySelectorAll(`.${wrapper}`).length > 0) timesChecked++;
|
||||
const guild = BDV2.guildClasses.listItem.split(" ")[0];
|
||||
const blob = BDV2.guildClasses.blobContainer.split(" ")[0];
|
||||
if (document.querySelectorAll(`.${wrapper} .${guild} .${blob}`).length > 0) return resolve(bdConfig.deferLoaded = true);
|
||||
else if (timesChecked >= 50) return resolve(bdConfig.deferLoaded = true);
|
||||
setTimeout(checkForGuilds, 100);
|
||||
};
|
||||
if (document.readyState != "loading") setTimeout(checkForGuilds, 100);
|
||||
document.addEventListener("DOMContentLoaded", () => {setTimeout(checkForGuilds, 100);});
|
||||
});
|
||||
};
|
||||
|
||||
Core.prototype.injectExternals = async function() {
|
||||
await DOM.addScript("ace-script", "https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.9/ace.js");
|
||||
if (window.require.original) window.require = window.require.original;
|
||||
};
|
||||
|
||||
Core.prototype.initSettings = function () {
|
||||
DataStore.initialize();
|
||||
if (!DataStore.getSettingGroup("settings")) {
|
||||
Object.assign(settingsCookie, defaultCookie);
|
||||
settingsPanel.saveSettings();
|
||||
}
|
||||
else {
|
||||
settingsPanel.loadSettings();
|
||||
for (const setting in defaultCookie) {
|
||||
if (settingsCookie[setting] == undefined) {
|
||||
settingsCookie[setting] = defaultCookie[setting];
|
||||
settingsPanel.saveSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Core.prototype.initObserver = function () {
|
||||
const mainObserver = new MutationObserver((mutations) => {
|
||||
|
||||
for (let i = 0, mlen = mutations.length; i < mlen; i++) {
|
||||
const mutation = mutations[i];
|
||||
if (typeof pluginModule !== "undefined") pluginModule.rawObserver(mutation);
|
||||
|
||||
// if there was nothing added, skip
|
||||
if (!mutation.addedNodes.length || !(mutation.addedNodes[0] instanceof Element)) continue;
|
||||
|
||||
const node = mutation.addedNodes[0];
|
||||
|
||||
let [
|
||||
classNameLayer,
|
||||
classNameSocialLinks
|
||||
] = [
|
||||
BDModules.get((e) => e.layer && typeof e.layer === "string" && e.animating)[0].layer,
|
||||
BDModules.get((e) => e.socialLinks && typeof e.socialLinks === "string")[0].socialLinks
|
||||
]
|
||||
|
||||
if (node.classList.contains(classNameLayer)) {
|
||||
if (node.getElementsByClassName("guild-settings-base-section").length) node.setAttribute("layer-id", "server-settings");
|
||||
|
||||
if (node.getElementsByClassName(classNameSocialLinks).length) {
|
||||
node.setAttribute("layer-id", "user-settings");
|
||||
node.setAttribute("id", "user-settings");
|
||||
if (!document.getElementById("bd-settings-sidebar")) settingsPanel.renderSidebar();
|
||||
}
|
||||
}
|
||||
|
||||
if (node.parentElement == document.body && node.querySelector("#ace_settingsmenu")) node.id = "ace_settingsmenu_container";
|
||||
|
||||
// Emoji Picker
|
||||
//node.getElementsByClassName("emojiPicker-3m1S-j").length && !node.querySelector(".emojiPicker-3m1S-j").parentElement.classList.contains("animatorLeft-1EQxU0")
|
||||
//if (node.classList.contains(classNameLayer2) && node.getElementsByClassName(classNameEmojiPicker).length && !node.querySelector("."+classNameEmojiPicker).parentElement.classList.contains(classNameAnimatorLeft)) quickEmoteMenu.obsCallback(node);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
mainObserver.observe(document, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
};
|
||||
|
||||
Core.prototype.showChangelogModal = function(options = {}) {
|
||||
return Utils.showChangelogModal(options);
|
||||
};
|
||||
|
||||
Core.prototype.alert = function(title, content) {
|
||||
return Utils.alert(title, content);
|
||||
};
|
||||
|
||||
Core.prototype.patchSocial = function() {
|
||||
if (this.socialPatch) return;
|
||||
const TabBar = WebpackModules.find(m => m.displayName == "TabBar");
|
||||
const Anchor = WebpackModules.find(m => m.displayName == "Anchor");
|
||||
if (!TabBar) return;
|
||||
this.socialPatch = Utils.monkeyPatch(TabBar.prototype, "render", {after: (data) => {
|
||||
const children = data.returnValue.props.children;
|
||||
if (!children || !children.length || children.length < 3) return;
|
||||
if (children[children.length - 3].type.displayName !== "Separator") return;
|
||||
if (!children[children.length - 2].type.toString().includes("socialLinks")) return;
|
||||
if (Anchor) {
|
||||
const original = children[children.length - 2].type;
|
||||
const newOne = function() {
|
||||
const returnVal = original(...arguments);
|
||||
returnVal.props.children.push(
|
||||
BDV2.React.createElement(TooltipWrap, {color: "black", side: "top", text: "BandagedBD"},
|
||||
BDV2.React.createElement(Anchor, {className: "bd-social-link", href: "https://github.com/rauenzi/BetterDiscordApp", title: "BandagedBD", target: "_blank"},
|
||||
BDV2.React.createElement(BDLogo, {size: "16px", className: "bd-social-logo"})
|
||||
)
|
||||
)
|
||||
);
|
||||
return returnVal;
|
||||
};
|
||||
children[children.length - 2].type = newOne;
|
||||
}
|
||||
|
||||
let [
|
||||
classNameColorMuted,
|
||||
sizes,
|
||||
classNameVersionHash
|
||||
] = [
|
||||
BDModules.get(e => e.colorMuted)[0].colorMuted,
|
||||
BDModules.get(e => e.size32)[0],
|
||||
BDModules.get(e => e.versionHash)[0].versionHash
|
||||
]
|
||||
|
||||
const injector = BDV2.react.createElement("div", {className: `${classNameColorMuted} ${sizes.size12}`}, `Injector ${bdConfig.version}`);
|
||||
const versionHash = `(${bdConfig.hash ? bdConfig.hash.substring(0, 7) : bdConfig.branch})`;
|
||||
const additional = BDV2.react.createElement("div", {className: `${classNameColorMuted} ${sizes.size12}`}, `BBD ${bbdVersion} `, BDV2.react.createElement("span", {className: classNameVersionHash+" da-versionHash"}, versionHash));
|
||||
|
||||
|
||||
const originalVersions = children[children.length - 1].type;
|
||||
children[children.length - 1].type = function() {
|
||||
const returnVal = originalVersions(...arguments);
|
||||
returnVal.props.children.splice(returnVal.props.children.length - 1, 0, injector);
|
||||
returnVal.props.children.splice(1, 0, additional);
|
||||
return returnVal;
|
||||
};
|
||||
}});
|
||||
};
|
||||
|
||||
const getGuildClasses = function() {
|
||||
const guildsWrapper = WebpackModules.findByProps("wrapper", "unreadMentionsBar");
|
||||
const guilds = WebpackModules.findByProps("guildsError", "selected");
|
||||
const pill = WebpackModules.findByProps("blobContainer");
|
||||
return Object.assign({}, guildsWrapper, guilds, pill);
|
||||
};
|
||||
|
||||
Core.prototype.patchGuildListItems = function() {
|
||||
if (this.guildListItemsPatch) return;
|
||||
const GuildClasses = getGuildClasses();
|
||||
const listItemClass = GuildClasses.listItem.split(" ")[0];
|
||||
const blobClass = GuildClasses.blobContainer.split(" ")[0];
|
||||
const reactInstance = BDV2.getInternalInstance(document.querySelector(`.${listItemClass} .${blobClass}`).parentElement);
|
||||
const GuildComponent = reactInstance.return.type;
|
||||
if (!GuildComponent) return;
|
||||
this.guildListItemsPatch = Utils.monkeyPatch(GuildComponent.prototype, "render", {after: (data) => {
|
||||
if (data.returnValue && data.thisObject) {
|
||||
const returnValue = data.returnValue;
|
||||
const guildData = data.thisObject.props;
|
||||
returnValue.props.className += " bd-guild";
|
||||
if (guildData.unread) returnValue.props.className += " bd-unread";
|
||||
if (guildData.selected) returnValue.props.className += " bd-selected";
|
||||
if (guildData.audio) returnValue.props.className += " bd-audio";
|
||||
if (guildData.video) returnValue.props.className += " bd-video";
|
||||
if (guildData.badge) returnValue.props.className += " bd-badge";
|
||||
if (guildData.animatable) returnValue.props.className += " bd-animatable";
|
||||
return returnValue;
|
||||
}
|
||||
}});
|
||||
};
|
||||
|
||||
Core.prototype.patchGuildPills = function() {
|
||||
if (this.guildPillPatch) return;
|
||||
const guildPill = WebpackModules.find(m => m.default && !m.default.displayName && m.default.toString && m.default.toString().includes("translate3d"));
|
||||
if (!guildPill) return;
|
||||
this.guildPillPatch = Utils.monkeyPatch(guildPill, "default", {after: (data) => {
|
||||
const props = data.methodArguments[0];
|
||||
if (props.unread) data.returnValue.props.className += " bd-unread";
|
||||
if (props.selected) data.returnValue.props.className += " bd-selected";
|
||||
if (props.hovered) data.returnValue.props.className += " bd-hovered";
|
||||
return data.returnValue;
|
||||
}});
|
||||
};
|
||||
|
||||
Core.prototype.patchGuildSeparator = function() {
|
||||
if (this.guildSeparatorPatch) return;
|
||||
const Guilds = WebpackModules.findByDisplayName("Guilds");
|
||||
const guildComponents = WebpackModules.findByProps("renderListItem");
|
||||
if (!guildComponents || !Guilds) return;
|
||||
const GuildSeparator = function() {
|
||||
const returnValue = guildComponents.Separator(...arguments);
|
||||
returnValue.props.className += " bd-guild-separator";
|
||||
return returnValue;
|
||||
};
|
||||
this.guildSeparatorPatch = Utils.monkeyPatch(Guilds.prototype, "render", {after: (data) => {
|
||||
data.returnValue.props.children[1].props.children[3].type = GuildSeparator;
|
||||
}});
|
||||
};
|
||||
|
||||
Core.prototype.patchMessageHeader = function() {
|
||||
if (this.messageHeaderPatch) return;
|
||||
const MessageHeader = WebpackModules.findByProps("MessageTimestamp");
|
||||
const Anchor = WebpackModules.find(m => m.displayName == "Anchor");
|
||||
if (!Anchor || !MessageHeader || !MessageHeader.default) return;
|
||||
this.messageHeaderPatch = Utils.monkeyPatch(MessageHeader, "default", {after: (data) => {
|
||||
const author = Utils.getNestedProp(data.methodArguments[0], "message.author");
|
||||
// const header = Utils.getNestedProp(data.returnValue, "props.children.1.props");
|
||||
const children = Utils.getNestedProp(data.returnValue, "props.children.1.props.children.1.props.children");
|
||||
if (!children || !author || !author.id)return
|
||||
// if (header && header.className) header.className += " "
|
||||
if (!Array.isArray(children)) return;
|
||||
if (author.id === "249746236008169473") {
|
||||
children.push(
|
||||
BDV2.React.createElement(TooltipWrap, {color: "black", side: "top", text: "BandagedBD Developer"},
|
||||
BDV2.React.createElement(Anchor, {className: "bd-chat-badge", href: "https://github.com/rauenzi/BetterDiscordApp", title: "BandagedBD", target: "_blank"},
|
||||
BDV2.React.createElement(BDLogo, {size: "16px", className: "bd-logo"})
|
||||
)
|
||||
)
|
||||
);
|
||||
} else if (author.id === "696481194443014174"){
|
||||
children.push(
|
||||
BDV2.React.createElement(TooltipWrap, {color: "black", side: "top", text: "Lightcord Developer"},
|
||||
BDV2.React.createElement(Anchor, {className: "bd-chat-badge", href: "https://github.com/jeanouina/Lightcord", title: "Lightcord", target: "_blank"},
|
||||
BDV2.React.createElement(LightcordLogo, {size: "32px", className: "bd-logo"})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}});
|
||||
};
|
||||
|
||||
Core.prototype.patchMemberList = function() {
|
||||
if (this.memberListPatch) return;
|
||||
const MemberListItem = WebpackModules.findByDisplayName("MemberListItem");
|
||||
const Anchor = WebpackModules.find(m => m.displayName == "Anchor");
|
||||
if (!Anchor || !MemberListItem || !MemberListItem.prototype || !MemberListItem.prototype.renderDecorators) return;
|
||||
this.memberListPatch = Utils.monkeyPatch(MemberListItem.prototype, "renderDecorators", {after: (data) => {
|
||||
const user = Utils.getNestedProp(data.thisObject, "props.user");
|
||||
const children = Utils.getNestedProp(data.returnValue, "props.children");
|
||||
if (!children || !user || !user.id)return
|
||||
// if (header && header.className) header.className += " "
|
||||
if (!Array.isArray(children)) return;
|
||||
if (user.id === "249746236008169473") {
|
||||
children.push(
|
||||
BDV2.React.createElement(TooltipWrap, {color: "black", side: "top", text: "BandagedBD Developer"},
|
||||
BDV2.React.createElement(Anchor, {className: "bd-member-badge", href: "https://github.com/rauenzi/BetterDiscordApp", title: "BandagedBD", target: "_blank"},
|
||||
BDV2.React.createElement(BDLogo, {size: "16px", className: "bd-logo"})
|
||||
)
|
||||
)
|
||||
);
|
||||
} else if (user.id === "696481194443014174"){
|
||||
children.push(
|
||||
BDV2.React.createElement(TooltipWrap, {color: "black", side: "top", text: "Lightcord Developer"},
|
||||
BDV2.React.createElement(Anchor, {className: "bd-member-badge", href: "https://github.com/jeanouina/Lightcord", title: "Lightcord", target: "_blank"},
|
||||
BDV2.React.createElement(LightcordLogo, {size: "32px", className: "bd-logo"})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}});
|
||||
};
|
||||
|
||||
Core.prototype.updateInjector = async function() {
|
||||
const injectionPath = DataStore.injectionPath;
|
||||
if (!injectionPath) return false;
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const rmrf = require("rimraf");
|
||||
const yauzl = require("yauzl");
|
||||
const mkdirp = require("mkdirp");
|
||||
const request = /*require("request");*/ null
|
||||
|
||||
const parentPath = path.resolve(injectionPath, "..");
|
||||
const folderName = path.basename(injectionPath);
|
||||
const zipLink = "https://github.com/rauenzi/BetterDiscordApp/archive/injector.zip";
|
||||
const savedZip = path.resolve(parentPath, "injector.zip");
|
||||
const extractedFolder = path.resolve(parentPath, "BetterDiscordApp-injector");
|
||||
|
||||
// Download the injector zip file
|
||||
Utils.log("InjectorUpdate", "Downloading " + zipLink);
|
||||
let success = await new Promise(resolve => {
|
||||
request.get({url: zipLink, encoding: null}, async (error, response, body) => {
|
||||
if (error || response.statusCode !== 200) return resolve(false);
|
||||
// Save a backup in case someone has their own copy
|
||||
const alreadyExists = await new Promise(res => fs.exists(savedZip, res));
|
||||
if (alreadyExists) await new Promise(res => fs.rename(savedZip, `${savedZip}.bak${Math.round(performance.now())}`, res));
|
||||
|
||||
Utils.log("InjectorUpdate", "Writing " + savedZip);
|
||||
fs.writeFile(savedZip, body, err => resolve(!err));
|
||||
});
|
||||
});
|
||||
if (!success) return success;
|
||||
|
||||
// Check and delete rename extraction
|
||||
const alreadyExists = await new Promise(res => fs.exists(extractedFolder, res));
|
||||
if (alreadyExists) await new Promise(res => fs.rename(extractedFolder, `${extractedFolder}.bak${Math.round(performance.now())}`, res));
|
||||
|
||||
// Unzip the downloaded zip file
|
||||
const zipfile = await new Promise(r => yauzl.open(savedZip, {lazyEntries: true}, (err, zip) => r(zip)));
|
||||
zipfile.on("entry", function(entry) {
|
||||
// Skip directories, they are handled with mkdirp
|
||||
if (entry.fileName.endsWith("/")) return zipfile.readEntry();
|
||||
|
||||
Utils.log("InjectorUpdate", "Extracting " + entry.fileName);
|
||||
// Make any needed parent directories
|
||||
const fullPath = path.resolve(parentPath, entry.fileName);
|
||||
mkdirp.sync(path.dirname(fullPath));
|
||||
zipfile.openReadStream(entry, function(err, readStream) {
|
||||
if (err) return success = false;
|
||||
readStream.on("end", function() {zipfile.readEntry();}); // Go to next file after this
|
||||
readStream.pipe(fs.createWriteStream(fullPath));
|
||||
});
|
||||
});
|
||||
zipfile.readEntry(); // Start reading
|
||||
|
||||
// Wait for the final file to finish
|
||||
await new Promise(resolve => zipfile.once("end", resolve));
|
||||
|
||||
// Save a backup in case something goes wrong during final step
|
||||
const backupFolder = path.resolve(parentPath, `${folderName}.bak${Math.round(performance.now())}`);
|
||||
await new Promise(resolve => fs.rename(injectionPath, backupFolder, resolve));
|
||||
|
||||
// Rename the extracted folder to what it should be
|
||||
Utils.log("InjectorUpdate", `Renaming ${path.basename(extractedFolder)} to ${folderName}`);
|
||||
success = await new Promise(resolve => fs.rename(extractedFolder, injectionPath, err => resolve(!err)));
|
||||
if (!success) {
|
||||
Utils.err("InjectorUpdate", "Failed to rename the final directory");
|
||||
return success;
|
||||
}
|
||||
|
||||
// If rename had issues, delete what we tried to rename and restore backup
|
||||
if (!success) {
|
||||
Utils.err("InjectorUpdate", "Something went wrong... restoring backups.");
|
||||
await new Promise(resolve => rmrf(extractedFolder, resolve));
|
||||
await new Promise(resolve => fs.rename(backupFolder, injectionPath, resolve));
|
||||
return success;
|
||||
}
|
||||
|
||||
// If we've gotten to this point, everything should have gone smoothly.
|
||||
// Cleanup the backup folder then remove the zip
|
||||
await new Promise(resolve => rmrf(backupFolder, resolve));
|
||||
await new Promise(resolve => fs.unlink(savedZip, resolve));
|
||||
|
||||
Utils.log("InjectorUpdate", "Injector Updated!");
|
||||
return success;
|
||||
};
|
||||
|
||||
export default new Core();
|
|
@ -0,0 +1,89 @@
|
|||
const __non_webpack_require__ = window.require
|
||||
|
||||
import {bdConfig} from "../0globals";
|
||||
import Utils from "./utils";
|
||||
import ContentManager from "./contentManager";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const releaseChannel = DiscordNative.globals.releaseChannel;
|
||||
|
||||
export default new class DataStore {
|
||||
constructor() {
|
||||
this.data = {settings: {stable: {}, canary: {}, ptb: {}}};
|
||||
this.pluginData = {};
|
||||
}
|
||||
|
||||
initialize() {
|
||||
try {
|
||||
if (!fs.existsSync(this.BDFile)) fs.writeFileSync(this.BDFile, JSON.stringify(this.data, null, 4), "binary");
|
||||
const data = JSON.parse(fs.readFileSync(this.BDFile, "binary"))
|
||||
if (data.hasOwnProperty("settings")) this.data = data;
|
||||
if (!fs.existsSync(this.settingsFile)) return;
|
||||
let settings = __non_webpack_require__(this.settingsFile);
|
||||
fs.unlinkSync(this.settingsFile);
|
||||
if (settings.hasOwnProperty("settings")) settings = Object.assign({stable: {}, canary: {}, ptb: {}}, {[releaseChannel]: settings});
|
||||
else settings = Object.assign({stable: {}, canary: {}, ptb: {}}, settings);
|
||||
this.setBDData("settings", settings);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
Utils.alert("Corrupt Storage", "The bd storage has somehow become corrupt. You may either try to salvage the file or delete it then reload.");
|
||||
}
|
||||
}
|
||||
|
||||
get injectionPath() {
|
||||
if (this._injectionPath) return this._injectionPath;
|
||||
const electron = require("electron").remote.app;
|
||||
const base = electron.getAppPath();
|
||||
const roamingBase = electron.getPath("userData");
|
||||
const roamingLocation = path.resolve(roamingBase, electron.getVersion(), "modules", "discord_desktop_core", "injector");
|
||||
const location = path.resolve(base, "..", "app");
|
||||
const realLocation = fs.existsSync(location) ? location : fs.existsSync(roamingLocation) ? roamingLocation : null;
|
||||
if (!realLocation) return this._injectionPath = null;
|
||||
return this._injectionPath = realLocation;
|
||||
}
|
||||
|
||||
get configFile() {return this._configFile || (this._configFile = path.resolve(this.injectionPath, "betterdiscord", "config.json"));}
|
||||
get BDFile() {return this._BDFile || (this._BDFile = path.resolve(bdConfig.dataPath, "bdstorage.json"));}
|
||||
get settingsFile() {return this._settingsFile || (this._settingsFile = path.resolve(bdConfig.dataPath, "bdsettings.json"));}
|
||||
getPluginFile(pluginName) {return path.resolve(ContentManager.pluginsFolder, pluginName + ".config.json");}
|
||||
|
||||
getSettingGroup(key) {
|
||||
return this.data.settings[releaseChannel][key] || null;
|
||||
}
|
||||
|
||||
setSettingGroup(key, data) {
|
||||
this.data.settings[releaseChannel][key] = data;
|
||||
fs.writeFileSync(this.BDFile, JSON.stringify(this.data, null, 4), "binary");
|
||||
}
|
||||
|
||||
getBDData(key) {
|
||||
return this.data[key] || "";
|
||||
}
|
||||
|
||||
setBDData(key, value) {
|
||||
this.data[key] = value;
|
||||
fs.writeFileSync(this.BDFile, JSON.stringify(this.data, null, 4), "binary");
|
||||
}
|
||||
|
||||
getPluginData(pluginName, key) {
|
||||
if (this.pluginData[pluginName] !== undefined) return this.pluginData[pluginName][key] || undefined;
|
||||
if (!fs.existsSync(this.getPluginFile(pluginName))) return undefined;
|
||||
this.pluginData[pluginName] = JSON.parse(fs.readFileSync(this.getPluginFile(pluginName)));
|
||||
return this.pluginData[pluginName][key] || undefined;
|
||||
}
|
||||
|
||||
setPluginData(pluginName, key, value) {
|
||||
if (value === undefined) return;
|
||||
if (this.pluginData[pluginName] === undefined) this.pluginData[pluginName] = {};
|
||||
this.pluginData[pluginName][key] = value;
|
||||
fs.writeFileSync(this.getPluginFile(pluginName), JSON.stringify(this.pluginData[pluginName], null, 4), "binary");
|
||||
}
|
||||
|
||||
deletePluginData(pluginName, key) {
|
||||
if (this.pluginData[pluginName] === undefined) this.pluginData[pluginName] = {};
|
||||
delete this.pluginData[pluginName][key];
|
||||
fs.writeFileSync(this.getPluginFile(pluginName), JSON.stringify(this.pluginData[pluginName], null, 4), "binary");
|
||||
}
|
||||
};
|
|
@ -0,0 +1,146 @@
|
|||
import {settingsCookie} from "../0globals";
|
||||
import BDV2 from "./v2";
|
||||
import DOM from "./domtools";
|
||||
|
||||
export default new class DevMode {
|
||||
constructor() {
|
||||
this.debugListener = this.debugListener.bind(this);
|
||||
this.copySelectorListener = this.copySelectorListener.bind(this);
|
||||
}
|
||||
|
||||
start() {
|
||||
this.startDebugListener();
|
||||
if (settingsCookie["fork-dm-1"]) this.startCopySelector();
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.stopDebugListener();
|
||||
this.stopCopySelector();
|
||||
}
|
||||
|
||||
startDebugListener() {
|
||||
this.stopDebugListener();
|
||||
document.addEventListener("keydown", this.debugListener);
|
||||
}
|
||||
|
||||
stopDebugListener() {
|
||||
document.removeEventListener("keydown", this.debugListener);
|
||||
}
|
||||
|
||||
startCopySelector() {
|
||||
this.stopCopySelector();
|
||||
document.addEventListener("contextmenu", this.copySelectorListener);
|
||||
}
|
||||
|
||||
stopCopySelector() {
|
||||
document.removeEventListener("contextmenu", this.copySelectorListener);
|
||||
}
|
||||
|
||||
debugListener(e) {
|
||||
if (e.which === 119 || e.which == 118) {//F8
|
||||
console.log("%c[%cDevMode%c] %cBreak/Resume", "color: red;", "color: #303030; font-weight:700;", "color:red;", "");
|
||||
debugger; // eslint-disable-line no-debugger
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
|
||||
copySelectorListener(e) {
|
||||
try{
|
||||
e.stopPropagation();
|
||||
const selector = this.getSelector(e.target);
|
||||
|
||||
let [
|
||||
classNameLayer,
|
||||
classItems
|
||||
] = [
|
||||
BDModules.get((e) => e.layer && typeof e.layer === "string" && e.disabledPointerEvents)[0].layer,
|
||||
BDModules.get((e) => e.contextMenu)[0],
|
||||
]
|
||||
|
||||
function attach() {
|
||||
let cm = DOM.query("."+classItems.contextMenu.split(" ")[0]);
|
||||
if (!cm) {
|
||||
const container = DOM.query("#app-mount");
|
||||
const cmWrap = DOM.createElement(`<div class="${classNameLayer}">`);
|
||||
cm = DOM.createElement(`<div class="${classItems.contextMenu} bd-context-menu"></div>`);
|
||||
cmWrap.append(cm);
|
||||
container.append(cmWrap);
|
||||
cmWrap.style.top = e.clientY + "px";
|
||||
cmWrap.style.left = e.clientX + "px";
|
||||
cmWrap.style.zIndex = "1002";
|
||||
const removeCM = function(e) {
|
||||
if (e.keyCode && e.keyCode !== 27) return;
|
||||
cmWrap.remove();
|
||||
document.removeEventListener("click", removeCM);
|
||||
document.removeEventListener("contextmenu", removeCM);
|
||||
document.removeEventListener("keyup", removeCM);
|
||||
};
|
||||
document.addEventListener("click", removeCM);
|
||||
document.addEventListener("contextmenu", removeCM);
|
||||
document.addEventListener("keyup", removeCM);
|
||||
}
|
||||
const cmWrap = DOM.query("."+classNameLayer.split(" ")[0])
|
||||
|
||||
const cmg = DOM.createElement(`<div class="${classItems.itemGroup}">`);
|
||||
const cmi = DOM.createElement(`<div class="${classItems.item} ${classItems.clickable}">`);
|
||||
cmi.append(DOM.createElement(`<div class="${classItems.label}">Copy Selector</div>`));
|
||||
cmi.addEventListener("click", () => {
|
||||
BDV2.NativeModule.copy(selector);
|
||||
cm.style.display = "none";
|
||||
});
|
||||
cmg.append(cmi);
|
||||
cm.append(cmg);
|
||||
if(cmWrap.clientHeight < cmWrap.scrollHeight){
|
||||
console.log("overflowing "+cmWrap.style.top)
|
||||
cmWrap.style.top = (cmWrap.style.top - cmg.clientHeight) + "px";
|
||||
console.log("overflowing"+cmWrap.style.top)
|
||||
}
|
||||
}
|
||||
|
||||
process.nextTick(attach);
|
||||
}catch(e){
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
getSelector(element) {
|
||||
if (element.id) return `#${element.id}`;
|
||||
/**
|
||||
*
|
||||
* @param {HTMLElement} el
|
||||
*/
|
||||
function fullPath(el){
|
||||
var names = [];
|
||||
while (el.parentNode){
|
||||
if (el.id){
|
||||
names.unshift('#'+el.id);
|
||||
break;
|
||||
}else{
|
||||
if (el==el.ownerDocument.documentElement) names.unshift(el.tagName.toLowerCase()+Array.from(el.classList.entries()).map(e => "."+e).join(""));
|
||||
else{
|
||||
for (var c=1,e=el;e.previousElementSibling;e=e.previousElementSibling,c++);
|
||||
names.unshift(el.tagName.toLowerCase()+(el.className || "").split(" ").map(e => "."+e).join("")+":nth-child("+c+")");
|
||||
}
|
||||
el=el.parentNode;
|
||||
}
|
||||
}
|
||||
return names.join(" > ");
|
||||
}
|
||||
return fullPath(element)
|
||||
/*
|
||||
const rules = this.getRules(element);
|
||||
const latestRule = rules[rules.length - 1];
|
||||
if (latestRule) return latestRule.selectorText;
|
||||
else if (element.classList.length) return `.${Array.from(element.classList).join(".")}`;
|
||||
return `.${Array.from(element.parentElement.classList).join(".")}`;*/
|
||||
}
|
||||
|
||||
getRules(element, css = element.ownerDocument.styleSheets) {
|
||||
//if (window.getMatchedCSSRules) return window.getMatchedCSSRules(element);
|
||||
const sheets = [...css].filter(s => !s.href || !s.href.includes("BetterDiscordApp"));
|
||||
const rules = sheets.map(s => [...(s.cssRules || [])]).flat();
|
||||
const elementRules = rules.filter(r => r && r.selectorText && element.matches(r.selectorText) && r.style.length && r.selectorText.split(", ").length < 8 && !r.selectorText.split(", ").includes("*"));
|
||||
return elementRules;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,753 @@
|
|||
/**
|
||||
* Copyright 2018 Zachary Rauen
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is furnished
|
||||
* to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* From: https://github.com/rauenzi/BDPluginLibrary
|
||||
*/
|
||||
|
||||
/**
|
||||
* @interface
|
||||
* @name Offset
|
||||
* @property {number} top - Top offset of the target element.
|
||||
* @property {number} right - Right offset of the target element.
|
||||
* @property {number} bottom - Bottom offset of the target element.
|
||||
* @property {number} left - Left offset of the target element.
|
||||
* @property {number} height - Outer height of the target element.
|
||||
* @property {number} width - Outer width of the target element.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Function that automatically removes added listener.
|
||||
* @callback module:DOMTools~CancelListener
|
||||
*/
|
||||
|
||||
export default class DOMTools {
|
||||
|
||||
static escapeID(id) {
|
||||
return id.replace(/^[^a-z]+|[^\w-]+/gi, "-");
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a style to the document.
|
||||
* @param {string} id - identifier to use as the element id
|
||||
* @param {string} css - css to add to the document
|
||||
*/
|
||||
static addStyle(id, css) {
|
||||
document.head.append(DOMTools.createElement(`<style id="${id}">${css}</style>`));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a style from the document.
|
||||
* @param {string} id - original identifier used
|
||||
*/
|
||||
static removeStyle(id) {
|
||||
const element = document.getElementById(id);
|
||||
if (element) element.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds/requires a remote script to be loaded
|
||||
* @param {string} id - identifier to use for this script
|
||||
* @param {string} url - url from which to load the script
|
||||
* @returns {Promise} promise that resolves when the script is loaded
|
||||
*/
|
||||
static addScript(id, url) {
|
||||
return new Promise(resolve => {
|
||||
const script = document.createElement("script");
|
||||
script.id = id;
|
||||
script.src = url;
|
||||
script.type = "text/javascript";
|
||||
script.onload = resolve;
|
||||
document.head.append(script);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a remote script from the document.
|
||||
* @param {string} id - original identifier used
|
||||
*/
|
||||
static removeScript(id) {
|
||||
id = this.escapeID(id);
|
||||
const element = document.getElementById(id);
|
||||
if (element) element.remove();
|
||||
}
|
||||
|
||||
// https://javascript.info/js-animation
|
||||
static animate({timing = _ => _, update, duration}) {
|
||||
const start = performance.now();
|
||||
|
||||
requestAnimationFrame(function animate(time) {
|
||||
// timeFraction goes from 0 to 1
|
||||
let timeFraction = (time - start) / duration;
|
||||
if (timeFraction > 1) timeFraction = 1;
|
||||
|
||||
// calculate the current animation state
|
||||
const progress = timing(timeFraction);
|
||||
|
||||
update(progress); // draw it
|
||||
|
||||
if (timeFraction < 1) {
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This is my shit version of not having to use `$` from jQuery. Meaning
|
||||
* that you can pass a selector and it will automatically run {@link module:DOMTools.query}.
|
||||
* It also means that you can pass a string of html and it will perform and return `parseHTML`.
|
||||
* @see module:DOMTools.parseHTML
|
||||
* @see module:DOMTools.query
|
||||
* @param {string} selector - Selector to query or HTML to parse
|
||||
* @returns {(DocumentFragment|NodeList|HTMLElement)} - Either the result of `parseHTML` or `query`
|
||||
*/
|
||||
static Q(selector) {
|
||||
const element = this.parseHTML(selector);
|
||||
const isHTML = element instanceof NodeList ? Array.from(element).some(n => n.nodeType === 1) : element.nodeType === 1;
|
||||
if (isHTML) return element;
|
||||
return this.query(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Essentially a shorthand for `document.querySelector`. If the `baseElement` is not provided
|
||||
* `document` is used by default.
|
||||
* @param {string} selector - Selector to query
|
||||
* @param {Element} [baseElement] - Element to base the query from
|
||||
* @returns {(Element|null)} - The found element or null if not found
|
||||
*/
|
||||
static query(selector, baseElement) {
|
||||
if (!baseElement) baseElement = document;
|
||||
return baseElement.querySelector(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Essentially a shorthand for `document.querySelectorAll`. If the `baseElement` is not provided
|
||||
* `document` is used by default.
|
||||
* @param {string} selector - Selector to query
|
||||
* @param {Element} [baseElement] - Element to base the query from
|
||||
* @returns {Array<Element>} - Array of all found elements
|
||||
*/
|
||||
static queryAll(selector, baseElement) {
|
||||
if (!baseElement) baseElement = document;
|
||||
return baseElement.querySelectorAll(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string of HTML and returns the results. If the second parameter is true,
|
||||
* the parsed HTML will be returned as a document fragment {@see https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment}.
|
||||
* This is extremely useful if you have a list of elements at the top level, they can then be appended all at once to another node.
|
||||
*
|
||||
* If the second parameter is false, then the return value will be the list of parsed
|
||||
* nodes and there were multiple top level nodes, otherwise the single node is returned.
|
||||
* @param {string} html - HTML to be parsed
|
||||
* @param {boolean} [fragment=false] - Whether or not the return should be the raw `DocumentFragment`
|
||||
* @returns {(DocumentFragment|NodeList|HTMLElement)} - The result of HTML parsing
|
||||
*/
|
||||
static parseHTML(html, fragment = false) {
|
||||
const template = document.createElement("template");
|
||||
template.innerHTML = html;
|
||||
const node = template.content.cloneNode(true);
|
||||
if (fragment) return node;
|
||||
return node.childNodes.length > 1 ? node.childNodes : node.childNodes[0];
|
||||
}
|
||||
|
||||
/** Alternate name for {@link module:DOMTools.parseHTML} */
|
||||
static createElement(html, fragment = false) {return this.parseHTML(html, fragment);}
|
||||
|
||||
/**
|
||||
* Takes a string of html and escapes it using the brower's own escaping mechanism.
|
||||
* @param {String} html - html to be escaped
|
||||
*/
|
||||
static escapeHTML(html) {
|
||||
const textNode = document.createTextNode("");
|
||||
const spanElement = document.createElement("span");
|
||||
spanElement.append(textNode);
|
||||
textNode.nodeValue = html;
|
||||
return spanElement.innerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a list of classes from the target element.
|
||||
* @param {Element} element - Element to edit classes of
|
||||
* @param {...string} classes - Names of classes to add
|
||||
* @returns {Element} - `element` to allow for chaining
|
||||
*/
|
||||
static addClass(element, ...classes) {
|
||||
classes = classes.flat().filter(c => c);
|
||||
for (let c = 0; c < classes.length; c++) classes[c] = classes[c].toString().split(" ");
|
||||
classes = classes.flat().filter(c => c);
|
||||
element.classList.add(...classes);
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a list of classes from the target element.
|
||||
* @param {Element} element - Element to edit classes of
|
||||
* @param {...string} classes - Names of classes to remove
|
||||
* @returns {Element} - `element` to allow for chaining
|
||||
*/
|
||||
static removeClass(element, ...classes) {
|
||||
for (let c = 0; c < classes.length; c++) classes[c] = classes[c].toString().split(" ");
|
||||
classes = classes.flat().filter(c => c);
|
||||
element.classList.remove(...classes);
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* When only one argument is present: Toggle class value;
|
||||
* i.e., if class exists then remove it and return false, if not, then add it and return true.
|
||||
* When a second argument is present:
|
||||
* If the second argument evaluates to true, add specified class value, and if it evaluates to false, remove it.
|
||||
* @param {Element} element - Element to edit classes of
|
||||
* @param {string} classname - Name of class to toggle
|
||||
* @param {boolean} [indicator] - Optional indicator for if the class should be toggled
|
||||
* @returns {Element} - `element` to allow for chaining
|
||||
*/
|
||||
static toggleClass(element, classname, indicator) {
|
||||
classname = classname.toString().split(" ").filter(c => c);
|
||||
if (typeof(indicator) !== "undefined") classname.forEach(c => element.classList.toggle(c, indicator));
|
||||
else classname.forEach(c => element.classList.toggle(c));
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an element has a specific class
|
||||
* @param {Element} element - Element to edit classes of
|
||||
* @param {string} classname - Name of class to check
|
||||
* @returns {boolean} - `true` if the element has the class, `false` otherwise.
|
||||
*/
|
||||
static hasClass(element, classname) {
|
||||
return classname.toString().split(" ").filter(c => c).every(c => element.classList.contains(c));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces one class with another
|
||||
* @param {Element} element - Element to edit classes of
|
||||
* @param {string} oldName - Name of class to replace
|
||||
* @param {string} newName - New name for the class
|
||||
* @returns {Element} - `element` to allow for chaining
|
||||
*/
|
||||
static replaceClass(element, oldName, newName) {
|
||||
element.classList.replace(oldName, newName);
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends `thisNode` to `thatNode`
|
||||
* @param {Node} thisNode - Node to be appended to another node
|
||||
* @param {Node} thatNode - Node for `thisNode` to be appended to
|
||||
* @returns {Node} - `thisNode` to allow for chaining
|
||||
*/
|
||||
static appendTo(thisNode, thatNode) {
|
||||
if (typeof(thatNode) == "string") thatNode = this.query(thatNode);
|
||||
if (!thatNode) return null;
|
||||
thatNode.append(thisNode);
|
||||
return thisNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends `thisNode` to `thatNode`
|
||||
* @param {Node} thisNode - Node to be prepended to another node
|
||||
* @param {Node} thatNode - Node for `thisNode` to be prepended to
|
||||
* @returns {Node} - `thisNode` to allow for chaining
|
||||
*/
|
||||
static prependTo(thisNode, thatNode) {
|
||||
if (typeof(thatNode) == "string") thatNode = this.query(thatNode);
|
||||
if (!thatNode) return null;
|
||||
thatNode.prepend(thisNode);
|
||||
return thisNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert after a specific element, similar to jQuery's `thisElement.insertAfter(otherElement)`.
|
||||
* @param {Node} thisNode - The node to insert
|
||||
* @param {Node} targetNode - Node to insert after in the tree
|
||||
* @returns {Node} - `thisNode` to allow for chaining
|
||||
*/
|
||||
static insertAfter(thisNode, targetNode) {
|
||||
targetNode.parentNode.insertBefore(thisNode, targetNode.nextSibling);
|
||||
return thisNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert after a specific element, similar to jQuery's `thisElement.after(newElement)`.
|
||||
* @param {Node} thisNode - The node to insert
|
||||
* @param {Node} newNode - Node to insert after in the tree
|
||||
* @returns {Node} - `thisNode` to allow for chaining
|
||||
*/
|
||||
static after(thisNode, newNode) {
|
||||
thisNode.parentNode.insertBefore(newNode, thisNode.nextSibling);
|
||||
return thisNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next sibling element that matches the selector.
|
||||
* @param {Element} element - Element to get the next sibling of
|
||||
* @param {string} [selector=""] - Optional selector
|
||||
* @returns {Element} - The sibling element
|
||||
*/
|
||||
static next(element, selector = "") {
|
||||
return selector ? element.querySelector("+ " + selector) : element.nextElementSibling;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all subsequent siblings.
|
||||
* @param {Element} element - Element to get next siblings of
|
||||
* @returns {NodeList} - The list of siblings
|
||||
*/
|
||||
static nextAll(element) {
|
||||
return element.querySelectorAll("~ *");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the subsequent siblings until an element matches the selector.
|
||||
* @param {Element} element - Element to get the following siblings of
|
||||
* @param {string} selector - Selector to stop at
|
||||
* @returns {Array<Element>} - The list of siblings
|
||||
*/
|
||||
static nextUntil(element, selector) {
|
||||
const next = [];
|
||||
while (element.nextElementSibling && !element.nextElementSibling.matches(selector)) next.push(element = element.nextElementSibling);
|
||||
return next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the previous sibling element that matches the selector.
|
||||
* @param {Element} element - Element to get the previous sibling of
|
||||
* @param {string} [selector=""] - Optional selector
|
||||
* @returns {Element} - The sibling element
|
||||
*/
|
||||
static previous(element, selector = "") {
|
||||
const previous = element.previousElementSibling;
|
||||
if (selector) return previous && previous.matches(selector) ? previous : null;
|
||||
return previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all preceeding siblings.
|
||||
* @param {Element} element - Element to get preceeding siblings of
|
||||
* @returns {NodeList} - The list of siblings
|
||||
*/
|
||||
static previousAll(element) {
|
||||
const previous = [];
|
||||
while (element.previousElementSibling) previous.push(element = element.previousElementSibling);
|
||||
return previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the preceeding siblings until an element matches the selector.
|
||||
* @param {Element} element - Element to get the preceeding siblings of
|
||||
* @param {string} selector - Selector to stop at
|
||||
* @returns {Array<Element>} - The list of siblings
|
||||
*/
|
||||
static previousUntil(element, selector) {
|
||||
const previous = [];
|
||||
while (element.previousElementSibling && !element.previousElementSibling.matches(selector)) previous.push(element = element.previousElementSibling);
|
||||
return previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find which index in children a certain node is. Similar to jQuery's `$.index()`
|
||||
* @param {HTMLElement} node - The node to find its index in parent
|
||||
* @returns {number} Index of the node
|
||||
*/
|
||||
static indexInParent(node) {
|
||||
const children = node.parentNode.childNodes;
|
||||
let num = 0;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
if (children[i] == node) return num;
|
||||
if (children[i].nodeType == 1) num++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Shorthand for {@link module:DOMTools.indexInParent} */
|
||||
static index(node) {return this.indexInParent(node);}
|
||||
|
||||
/**
|
||||
* Gets the parent of the element if it matches the selector,
|
||||
* otherwise returns null.
|
||||
* @param {Element} element - Element to get parent of
|
||||
* @param {string} [selector=""] - Selector to match parent
|
||||
* @returns {(Element|null)} - The sibling element or null
|
||||
*/
|
||||
static parent(element, selector = "") {
|
||||
return !selector || element.parentElement.matches(selector) ? element.parentElement : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all children of Element that match the selector if provided.
|
||||
* @param {Element} element - Element to get all children of
|
||||
* @param {string} selector - Selector to match the children to
|
||||
* @returns {Array<Element>} - The list of children
|
||||
*/
|
||||
static findChild(element, selector) {
|
||||
return element.querySelector(":scope > " + selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all children of Element that match the selector if provided.
|
||||
* @param {Element} element - Element to get all children of
|
||||
* @param {string} selector - Selector to match the children to
|
||||
* @returns {Array<Element>} - The list of children
|
||||
*/
|
||||
static findChildren(element, selector) {
|
||||
return element.querySelectorAll(":scope > " + selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all ancestors of Element that match the selector if provided.
|
||||
* @param {Element} element - Element to get all parents of
|
||||
* @param {string} [selector=""] - Selector to match the parents to
|
||||
* @returns {Array<Element>} - The list of parents
|
||||
*/
|
||||
static parents(element, selector = "") {
|
||||
const parents = [];
|
||||
if (selector) while (element.parentElement && element.parentElement.closest(selector)) parents.push(element = element.parentElement.closest(selector));
|
||||
else while (element.parentElement) parents.push(element = element.parentElement);
|
||||
return parents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ancestors until an element matches the selector.
|
||||
* @param {Element} element - Element to get the ancestors of
|
||||
* @param {string} selector - Selector to stop at
|
||||
* @returns {Array<Element>} - The list of parents
|
||||
*/
|
||||
static parentsUntil(element, selector) {
|
||||
const parents = [];
|
||||
while (element.parentElement && !element.parentElement.matches(selector)) parents.push(element = element.parentElement);
|
||||
return parents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all siblings of the element that match the selector.
|
||||
* @param {Element} element - Element to get all siblings of
|
||||
* @param {string} [selector="*"] - Selector to match the siblings to
|
||||
* @returns {Array<Element>} - The list of siblings
|
||||
*/
|
||||
static siblings(element, selector = "*") {
|
||||
return Array.from(element.parentElement.children).filter(e => e != element && e.matches(selector));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or gets css styles for a specific element. If `value` is provided
|
||||
* then it sets the style and returns the element to allow for chaining,
|
||||
* otherwise returns the style.
|
||||
* @param {Element} element - Element to set the CSS of
|
||||
* @param {string} attribute - Attribute to get or set
|
||||
* @param {string} [value] - Value to set for attribute
|
||||
* @returns {Element|string} - When setting a value, element is returned for chaining, otherwise the value is returned.
|
||||
*/
|
||||
static css(element, attribute, value) {
|
||||
if (typeof(value) == "undefined") return global.getComputedStyle(element)[attribute];
|
||||
element.style[attribute] = value;
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or gets the width for a specific element. If `value` is provided
|
||||
* then it sets the width and returns the element to allow for chaining,
|
||||
* otherwise returns the width.
|
||||
* @param {Element} element - Element to set the CSS of
|
||||
* @param {string} [value] - Width to set
|
||||
* @returns {Element|string} - When setting a value, element is returned for chaining, otherwise the value is returned.
|
||||
*/
|
||||
static width(element, value) {
|
||||
if (typeof(value) == "undefined") return parseInt(getComputedStyle(element).width);
|
||||
element.style.width = value;
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or gets the height for a specific element. If `value` is provided
|
||||
* then it sets the height and returns the element to allow for chaining,
|
||||
* otherwise returns the height.
|
||||
* @param {Element} element - Element to set the CSS of
|
||||
* @param {string} [value] - Height to set
|
||||
* @returns {Element|string} - When setting a value, element is returned for chaining, otherwise the value is returned.
|
||||
*/
|
||||
static height(element, value) {
|
||||
if (typeof(value) == "undefined") return parseInt(getComputedStyle(element).height);
|
||||
element.style.height = value;
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the inner text of an element if given a value, otherwise returns it.
|
||||
* @param {Element} element - Element to set the text of
|
||||
* @param {string} [text] - Content to set
|
||||
* @returns {string} - Either the string set by this call or the current text content of the node.
|
||||
*/
|
||||
static text(element, text) {
|
||||
if (typeof(text) == "undefined") return element.textContent;
|
||||
return element.textContent = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the innerWidth of the element.
|
||||
* @param {Element} element - Element to retrieve inner width of
|
||||
* @return {number} - The inner width of the element.
|
||||
*/
|
||||
static innerWidth(element) {
|
||||
return element.clientWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the innerHeight of the element.
|
||||
* @param {Element} element - Element to retrieve inner height of
|
||||
* @return {number} - The inner height of the element.
|
||||
*/
|
||||
static innerHeight(element) {
|
||||
return element.clientHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the outerWidth of the element.
|
||||
* @param {Element} element - Element to retrieve outer width of
|
||||
* @return {number} - The outer width of the element.
|
||||
*/
|
||||
static outerWidth(element) {
|
||||
return element.offsetWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the outerHeight of the element.
|
||||
* @param {Element} element - Element to retrieve outer height of
|
||||
* @return {number} - The outer height of the element.
|
||||
*/
|
||||
static outerHeight(element) {
|
||||
return element.offsetHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the offset of the element in the page.
|
||||
* @param {Element} element - Element to get offset of
|
||||
* @return {Offset} - The offset of the element
|
||||
*/
|
||||
static offset(element) {
|
||||
return element.getBoundingClientRect();
|
||||
}
|
||||
|
||||
static get listeners() { return this._listeners || (this._listeners = {}); }
|
||||
|
||||
/**
|
||||
* This is similar to jQuery's `on` function and can *hopefully* be used in the same way.
|
||||
*
|
||||
* Rather than attempt to explain, I'll show some example usages.
|
||||
*
|
||||
* The following will add a click listener (in the `myPlugin` namespace) to `element`.
|
||||
* `DOMTools.on(element, "click.myPlugin", () => {console.log("clicked!");});`
|
||||
*
|
||||
* The following will add a click listener (in the `myPlugin` namespace) to `element` that only fires when the target is a `.block` element.
|
||||
* `DOMTools.on(element, "click.myPlugin", ".block", () => {console.log("clicked!");});`
|
||||
*
|
||||
* The following will add a click listener (without namespace) to `element`.
|
||||
* `DOMTools.on(element, "click", () => {console.log("clicked!");});`
|
||||
*
|
||||
* The following will add a click listener (without namespace) to `element` that only fires once.
|
||||
* `const cancel = DOMTools.on(element, "click", () => {console.log("fired!"); cancel();});`
|
||||
*
|
||||
* @param {Element} element - Element to add listener to
|
||||
* @param {string} event - Event to listen to with option namespace (e.g. "event.namespace")
|
||||
* @param {(string|callable)} delegate - Selector to run on element to listen to
|
||||
* @param {callable} [callback] - Function to fire on event
|
||||
* @returns {module:DOMTools~CancelListener} - A function that will undo the listener
|
||||
*/
|
||||
static on(element, event, delegate, callback) {
|
||||
const [type, namespace] = event.split(".");
|
||||
const hasDelegate = delegate && callback;
|
||||
if (!callback) callback = delegate;
|
||||
const eventFunc = !hasDelegate ? callback : function(event) {
|
||||
if (event.target.matches(delegate)) {
|
||||
callback(event);
|
||||
}
|
||||
};
|
||||
|
||||
element.addEventListener(type, eventFunc);
|
||||
const cancel = () => {
|
||||
element.removeEventListener(type, eventFunc);
|
||||
};
|
||||
if (namespace) {
|
||||
if (!this.listeners[namespace]) this.listeners[namespace] = [];
|
||||
const newCancel = () => {
|
||||
cancel();
|
||||
this.listeners[namespace].splice(this.listeners[namespace].findIndex(l => l.event == type && l.element == element), 1);
|
||||
};
|
||||
this.listeners[namespace].push({
|
||||
event: type,
|
||||
element: element,
|
||||
cancel: newCancel
|
||||
});
|
||||
return newCancel;
|
||||
}
|
||||
return cancel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Functionality for this method matches {@link module:DOMTools.on} but automatically cancels itself
|
||||
* and removes the listener upon the first firing of the desired event.
|
||||
*
|
||||
* @param {Element} element - Element to add listener to
|
||||
* @param {string} event - Event to listen to with option namespace (e.g. "event.namespace")
|
||||
* @param {(string|callable)} delegate - Selector to run on element to listen to
|
||||
* @param {callable} [callback] - Function to fire on event
|
||||
* @returns {module:DOMTools~CancelListener} - A function that will undo the listener
|
||||
*/
|
||||
static once(element, event, delegate, callback) {
|
||||
const [type, namespace] = event.split(".");
|
||||
const hasDelegate = delegate && callback;
|
||||
if (!callback) callback = delegate;
|
||||
const eventFunc = !hasDelegate ? function(event) {
|
||||
callback(event);
|
||||
element.removeEventListener(type, eventFunc);
|
||||
} : function(event) {
|
||||
if (!event.target.matches(delegate)) return;
|
||||
callback(event);
|
||||
element.removeEventListener(type, eventFunc);
|
||||
};
|
||||
|
||||
element.addEventListener(type, eventFunc);
|
||||
const cancel = () => {
|
||||
element.removeEventListener(type, eventFunc);
|
||||
};
|
||||
if (namespace) {
|
||||
if (!this.listeners[namespace]) this.listeners[namespace] = [];
|
||||
const newCancel = () => {
|
||||
cancel();
|
||||
this.listeners[namespace].splice(this.listeners[namespace].findIndex(l => l.event == type && l.element == element), 1);
|
||||
};
|
||||
this.listeners[namespace].push({
|
||||
event: type,
|
||||
element: element,
|
||||
cancel: newCancel
|
||||
});
|
||||
return newCancel;
|
||||
}
|
||||
return cancel;
|
||||
}
|
||||
|
||||
static __offAll(event, element) {
|
||||
const [type, namespace] = event.split(".");
|
||||
let matchFilter = listener => listener.event == type, defaultFilter = _ => _;
|
||||
if (element) matchFilter = l => l.event == type && l.element == element, defaultFilter = l => l.element == element;
|
||||
const listeners = this.listeners[namespace] || [];
|
||||
const list = type ? listeners.filter(matchFilter) : listeners.filter(defaultFilter);
|
||||
for (let c = 0; c < list.length; c++) list[c].cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is similar to jQuery's `off` function and can *hopefully* be used in the same way.
|
||||
*
|
||||
* Rather than attempt to explain, I'll show some example usages.
|
||||
*
|
||||
* The following will remove a click listener called `onClick` (in the `myPlugin` namespace) from `element`.
|
||||
* `DOMTools.off(element, "click.myPlugin", onClick);`
|
||||
*
|
||||
* The following will remove a click listener called `onClick` (in the `myPlugin` namespace) from `element` that only fired when the target is a `.block` element.
|
||||
* `DOMTools.off(element, "click.myPlugin", ".block", onClick);`
|
||||
*
|
||||
* The following will remove a click listener (without namespace) from `element`.
|
||||
* `DOMTools.off(element, "click", onClick);`
|
||||
*
|
||||
* The following will remove all listeners in namespace `myPlugin` from `element`.
|
||||
* `DOMTools.off(element, ".myPlugin");`
|
||||
*
|
||||
* The following will remove all click listeners in namespace `myPlugin` from *all elements*.
|
||||
* `DOMTools.off("click.myPlugin");`
|
||||
*
|
||||
* The following will remove all listeners in namespace `myPlugin` from *all elements*.
|
||||
* `DOMTools.off(".myPlugin");`
|
||||
*
|
||||
* @param {(Element|string)} element - Element to remove listener from
|
||||
* @param {string} [event] - Event to listen to with option namespace (e.g. "event.namespace")
|
||||
* @param {(string|callable)} [delegate] - Selector to run on element to listen to
|
||||
* @param {callable} [callback] - Function to fire on event
|
||||
* @returns {Element} - The original element to allow for chaining
|
||||
*/
|
||||
static off(element, event, delegate, callback) {
|
||||
if (typeof(element) == "string") return this.__offAll(element);
|
||||
const [type, namespace] = event.split(".");
|
||||
if (namespace) return this.__offAll(event, element);
|
||||
|
||||
const hasDelegate = delegate && callback;
|
||||
if (!callback) callback = delegate;
|
||||
const eventFunc = !hasDelegate ? callback : function(event) {
|
||||
if (event.target.matches(delegate)) {
|
||||
callback(event);
|
||||
}
|
||||
};
|
||||
|
||||
element.removeEventListener(type, eventFunc);
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener for when the node is added/removed from the document body.
|
||||
* The listener is automatically removed upon firing.
|
||||
* @param {HTMLElement} node - node to wait for
|
||||
* @param {callable} callback - function to be performed on event
|
||||
* @param {boolean} onMount - determines if it should fire on Mount or on Unmount
|
||||
*/
|
||||
static onMountChange(node, callback, onMount = true) {
|
||||
const wrappedCallback = () => {
|
||||
this.observer.unsubscribe(wrappedCallback);
|
||||
callback();
|
||||
};
|
||||
this.observer.subscribe(wrappedCallback, mutation => {
|
||||
const nodes = Array.from(onMount ? mutation.addedNodes : mutation.removedNodes);
|
||||
const directMatch = nodes.indexOf(node) > -1;
|
||||
const parentMatch = nodes.some(parent => parent.contains(node));
|
||||
return directMatch || parentMatch;
|
||||
});
|
||||
return node;
|
||||
}
|
||||
|
||||
/** Shorthand for {@link module:DOMTools.onMountChange} with third parameter `true` */
|
||||
static onMount(node, callback) { return this.onMountChange(node, callback); }
|
||||
|
||||
/** Shorthand for {@link module:DOMTools.onMountChange} with third parameter `false` */
|
||||
static onUnmount(node, callback) { return this.onMountChange(node, callback, false); }
|
||||
|
||||
/** Alias for {@link module:DOMTools.onMount} */
|
||||
static onAdded(node, callback) { return this.onMount(node, callback); }
|
||||
|
||||
/** Alias for {@link module:DOMTools.onUnmount} */
|
||||
static onRemoved(node, callback) { return this.onUnmount(node, callback, false); }
|
||||
|
||||
/**
|
||||
* Helper function which combines multiple elements into one parent element
|
||||
* @param {Array<HTMLElement>} elements - array of elements to put into a single parent
|
||||
*/
|
||||
static wrap(elements) {
|
||||
const domWrapper = this.parseHTML(`<div class="dom-wrapper"></div>`);
|
||||
for (let e = 0; e < elements.length; e++) domWrapper.appendChild(elements[e]);
|
||||
return domWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the node to an HTMLElement. This is mainly used by library modules.
|
||||
* @param {(jQuery|Element)} node - node to resolve
|
||||
*/
|
||||
static resolveElement(node) {
|
||||
if (!(node instanceof jQuery) && !(node instanceof Element)) return undefined;
|
||||
return node instanceof jQuery ? node[0] : node;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
import {bdpluginErrors, pluginCookie, settingsCookie, bdplugins} from "../0globals";
|
||||
import ContentManager from "./contentManager";
|
||||
import DataStore from "./dataStore";
|
||||
import BDEvents from "./bdEvents";
|
||||
import Utils from "./utils";
|
||||
|
||||
class PluginModule {
|
||||
get folder() {return ContentManager.pluginsFolder;}
|
||||
}
|
||||
|
||||
PluginModule.prototype.loadPlugins = function () {
|
||||
this.loadPluginData();
|
||||
bdpluginErrors.splice(0, 0, ...ContentManager.loadPlugins());
|
||||
const plugins = Object.keys(bdplugins);
|
||||
for (let i = 0; i < plugins.length; i++) {
|
||||
let plugin, name;
|
||||
|
||||
try {
|
||||
plugin = bdplugins[plugins[i]].plugin;
|
||||
name = plugin.getName();
|
||||
if (plugin.load && typeof(plugin.load) == "function") plugin.load();
|
||||
}
|
||||
catch (err) {
|
||||
pluginCookie[name] = false;
|
||||
Utils.err("Plugins", name + " could not be loaded.", err);
|
||||
bdpluginErrors.push({name: name, file: bdplugins[plugins[i]].filename, message: "load() could not be fired.", error: {message: err.message, stack: err.stack}});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!pluginCookie[name]) pluginCookie[name] = false;
|
||||
|
||||
if (pluginCookie[name]) {
|
||||
try {
|
||||
plugin.start();
|
||||
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${plugin.getName()} v${plugin.getVersion()} has started.`);
|
||||
}
|
||||
catch (err) {
|
||||
pluginCookie[name] = false;
|
||||
Utils.err("Plugins", name + " could not be started.", err);
|
||||
bdpluginErrors.push({name: name, file: bdplugins[plugins[i]].filename, message: "start() could not be fired.", error: {message: err.message, stack: err.stack}});
|
||||
}
|
||||
}
|
||||
}
|
||||
this.savePluginData();
|
||||
|
||||
require("electron").remote.getCurrentWebContents().on("did-navigate-in-page", this.channelSwitch.bind(this));
|
||||
// if (settingsCookie["fork-ps-5"]) ContentManager.watchContent("plugin");
|
||||
};
|
||||
|
||||
PluginModule.prototype.startPlugin = function(plugin, reload = false) {
|
||||
try {
|
||||
bdplugins[plugin].plugin.start();
|
||||
if (settingsCookie["fork-ps-2"] && !reload) Utils.showToast(`${bdplugins[plugin].plugin.getName()} v${bdplugins[plugin].plugin.getVersion()} has started.`);
|
||||
}
|
||||
catch (err) {
|
||||
if (settingsCookie["fork-ps-2"] && !reload) Utils.showToast(`${bdplugins[plugin].plugin.getName()} v${bdplugins[plugin].plugin.getVersion()} could not be started.`, {type: "error"});
|
||||
pluginCookie[plugin] = false;
|
||||
this.savePluginData();
|
||||
Utils.err("Plugins", plugin + " could not be started.", err);
|
||||
}
|
||||
};
|
||||
|
||||
PluginModule.prototype.stopPlugin = function(plugin, reload = false) {
|
||||
try {
|
||||
bdplugins[plugin].plugin.stop();
|
||||
if (settingsCookie["fork-ps-2"] && !reload) Utils.showToast(`${bdplugins[plugin].plugin.getName()} v${bdplugins[plugin].plugin.getVersion()} has stopped.`);
|
||||
}
|
||||
catch (err) {
|
||||
if (settingsCookie["fork-ps-2"] && !reload) Utils.showToast(`${bdplugins[plugin].plugin.getName()} v${bdplugins[plugin].plugin.getVersion()} could not be stopped.`, {type: "error"});
|
||||
Utils.err("Plugins", bdplugins[plugin].plugin.getName() + " could not be stopped.", err);
|
||||
}
|
||||
};
|
||||
|
||||
PluginModule.prototype.enablePlugin = function (plugin, reload = false) {
|
||||
if (pluginCookie[plugin]) return;
|
||||
pluginCookie[plugin] = true;
|
||||
this.savePluginData();
|
||||
this.startPlugin(plugin, reload);
|
||||
};
|
||||
|
||||
PluginModule.prototype.enable = function (plugin, reload = false) {
|
||||
return this.enablePlugin(plugin, reload);
|
||||
};
|
||||
|
||||
PluginModule.prototype.disablePlugin = function (plugin, reload = false) {
|
||||
if (!pluginCookie[plugin]) return;
|
||||
pluginCookie[plugin] = false;
|
||||
this.savePluginData();
|
||||
this.stopPlugin(plugin, reload);
|
||||
};
|
||||
|
||||
PluginModule.prototype.disable = function (plugin, reload = false) {
|
||||
return this.disablePlugin(plugin, reload);
|
||||
};
|
||||
|
||||
PluginModule.prototype.togglePlugin = function (plugin) {
|
||||
if (pluginCookie[plugin]) this.disablePlugin(plugin);
|
||||
else this.enablePlugin(plugin);
|
||||
};
|
||||
|
||||
PluginModule.prototype.toggle = function (plugin, reload = false) {
|
||||
return this.togglePlugin(plugin, reload);
|
||||
};
|
||||
|
||||
PluginModule.prototype.loadPlugin = function(filename) {
|
||||
const error = ContentManager.loadContent(filename, "plugin");
|
||||
if (error) {
|
||||
if (settingsCookie["fork-ps-1"]) Utils.showContentErrors({plugins: [error]});
|
||||
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${filename} could not be loaded.`, {type: "error"});
|
||||
return Utils.err("ContentManager", `${filename} could not be loaded.`, error);
|
||||
}
|
||||
const plugin = Object.values(bdplugins).find(p => p.filename == filename).plugin;
|
||||
try { if (plugin.load && typeof(plugin.load) == "function") plugin.load();}
|
||||
catch (err) {if (settingsCookie["fork-ps-1"]) Utils.showContentErrors({plugins: [err]});}
|
||||
Utils.log("ContentManager", `${plugin.getName()} v${plugin.getVersion()} was loaded.`);
|
||||
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${plugin.getName()} v${plugin.getVersion()} was loaded.`, {type: "success"});
|
||||
BDEvents.dispatch("plugin-loaded", plugin.getName());
|
||||
};
|
||||
|
||||
PluginModule.prototype.unloadPlugin = function(filenameOrName) {
|
||||
const bdplugin = Object.values(bdplugins).find(p => p.filename == filenameOrName) || bdplugins[filenameOrName];
|
||||
if (!bdplugin) return;
|
||||
const plugin = bdplugin.plugin.getName();
|
||||
if (pluginCookie[plugin]) this.disablePlugin(plugin, true);
|
||||
const error = ContentManager.unloadContent(bdplugins[plugin].filename, "plugin");
|
||||
delete bdplugins[plugin];
|
||||
if (error) {
|
||||
if (settingsCookie["fork-ps-1"]) Utils.showContentErrors({plugins: [error]});
|
||||
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${plugin} could not be unloaded. It may have not been loaded yet.`, {type: "error"});
|
||||
return Utils.err("ContentManager", `${plugin} could not be unloaded. It may have not been loaded yet.`, error);
|
||||
}
|
||||
Utils.log("ContentManager", `${plugin} was unloaded.`);
|
||||
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${plugin} was unloaded.`, {type: "success"});
|
||||
BDEvents.dispatch("plugin-unloaded", plugin);
|
||||
};
|
||||
|
||||
PluginModule.prototype.delete = function(filenameOrName) {
|
||||
const bdplugin = Object.values(bdplugins).find(p => p.filename == filenameOrName) || bdplugins[filenameOrName];
|
||||
if (!bdplugin) return;
|
||||
this.unloadPlugin(bdplugin.filename);
|
||||
const fullPath = require("path").resolve(ContentManager.pluginsFolder, bdplugin.filename);
|
||||
require("fs").unlinkSync(fullPath);
|
||||
};
|
||||
|
||||
PluginModule.prototype.reloadPlugin = function(filenameOrName) {
|
||||
const bdplugin = Object.values(bdplugins).find(p => p.filename == filenameOrName) || bdplugins[filenameOrName];
|
||||
if (!bdplugin) return this.loadPlugin(filenameOrName);
|
||||
const plugin = bdplugin.plugin.getName();
|
||||
const enabled = pluginCookie[plugin];
|
||||
if (enabled) this.stopPlugin(plugin, true);
|
||||
const error = ContentManager.reloadContent(bdplugins[plugin].filename, "plugin");
|
||||
if (error) {
|
||||
if (settingsCookie["fork-ps-1"]) Utils.showContentErrors({plugins: [error]});
|
||||
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${plugin} could not be reloaded.`, {type: "error"});
|
||||
return Utils.err("ContentManager", `${plugin} could not be reloaded.`, error);
|
||||
}
|
||||
if (bdplugins[plugin].plugin.load && typeof(bdplugins[plugin].plugin.load) == "function") bdplugins[plugin].plugin.load();
|
||||
if (enabled) this.startPlugin(plugin, true);
|
||||
Utils.log("ContentManager", `${plugin} v${bdplugins[plugin].plugin.getVersion()} was reloaded.`);
|
||||
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${plugin} v${bdplugins[plugin].plugin.getVersion()} was reloaded.`, {type: "success"});
|
||||
BDEvents.dispatch("plugin-reloaded", plugin);
|
||||
};
|
||||
|
||||
PluginModule.prototype.reload = function(name) {
|
||||
return this.reloadPlugin(name);
|
||||
};
|
||||
|
||||
PluginModule.prototype.edit = function(filenameOrName) {
|
||||
console.log("Edit " + filenameOrName);
|
||||
const bdplugin = Object.values(bdplugins).find(p => p.filename == filenameOrName) || bdplugins[filenameOrName];
|
||||
if (!bdplugin) return;
|
||||
const fullPath = require("path").resolve(ContentManager.pluginsFolder, bdplugin.filename);
|
||||
console.log("Edit " + fullPath);
|
||||
require("electron").shell.openItem(`${fullPath}`);
|
||||
};
|
||||
|
||||
PluginModule.prototype.updatePluginList = function() {
|
||||
const results = ContentManager.loadNewContent("plugin");
|
||||
for (const filename of results.added) this.loadPlugin(filename);
|
||||
for (const name of results.removed) this.unloadPlugin(name);
|
||||
};
|
||||
|
||||
PluginModule.prototype.loadPluginData = function () {
|
||||
const saved = DataStore.getSettingGroup("plugins");
|
||||
if (saved) {
|
||||
Object.assign(pluginCookie, saved);
|
||||
}
|
||||
};
|
||||
|
||||
PluginModule.prototype.savePluginData = function () {
|
||||
DataStore.setSettingGroup("plugins", pluginCookie);
|
||||
};
|
||||
|
||||
PluginModule.prototype.newMessage = function () {
|
||||
const plugins = Object.keys(bdplugins);
|
||||
for (let i = 0; i < plugins.length; i++) {
|
||||
const plugin = bdplugins[plugins[i]].plugin;
|
||||
if (!pluginCookie[plugin.getName()]) continue;
|
||||
if (typeof plugin.onMessage === "function") {
|
||||
try { plugin.onMessage(); }
|
||||
catch (err) { Utils.err("Plugins", "Unable to fire onMessage for " + plugin.getName() + ".", err); }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
PluginModule.prototype.channelSwitch = function () {
|
||||
const plugins = Object.keys(bdplugins);
|
||||
for (let i = 0; i < plugins.length; i++) {
|
||||
const plugin = bdplugins[plugins[i]].plugin;
|
||||
if (!pluginCookie[plugin.getName()]) continue;
|
||||
if (typeof plugin.onSwitch === "function") {
|
||||
try { plugin.onSwitch(); }
|
||||
catch (err) { Utils.err("Plugins", "Unable to fire onSwitch for " + plugin.getName() + ".", err); }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
PluginModule.prototype.rawObserver = function(e) {
|
||||
const plugins = Object.keys(bdplugins);
|
||||
for (let i = 0; i < plugins.length; i++) {
|
||||
const plugin = bdplugins[plugins[i]].plugin;
|
||||
if (!pluginCookie[plugin.getName()]) continue;
|
||||
if (typeof plugin.observer === "function") {
|
||||
try { plugin.observer(e); }
|
||||
catch (err) { Utils.err("Plugins", "Unable to fire observer for " + plugin.getName() + ".", err); }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default new PluginModule();
|
|
@ -0,0 +1,85 @@
|
|||
import {settingsCookie} from "../0globals";
|
||||
import BDV2 from "./v2";
|
||||
import webpackModules from "./webpackModules";
|
||||
import Utils from "./utils";
|
||||
import DOM from "./domtools";
|
||||
|
||||
import V2C_PublicServers from "../ui/publicservers/publicServers";
|
||||
import Layer from "../ui/publicservers/layer";
|
||||
|
||||
export default new class V2_PublicServers {
|
||||
|
||||
constructor() {
|
||||
this._appendButton = this._appendButton.bind(this);
|
||||
}
|
||||
|
||||
get component() {
|
||||
return BDV2.react.createElement(Layer, {rootId: "pubslayerroot", id: "pubslayer"}, BDV2.react.createElement(V2C_PublicServers, {rootId: "pubslayerroot"}));
|
||||
}
|
||||
|
||||
get root() {
|
||||
const _root = document.getElementById("pubslayerroot");
|
||||
if (!_root) {
|
||||
if (!this.injectRoot()) return null;
|
||||
return this.root;
|
||||
}
|
||||
return _root;
|
||||
}
|
||||
|
||||
injectRoot() {
|
||||
let [
|
||||
classNameLayers
|
||||
] = [
|
||||
BDModules.get(e => e.layers && e.layer)[0].layers.split(" ")[0]
|
||||
]
|
||||
const layers = DOM.query(".layers, ."+classNameLayers);
|
||||
if (!layers) return false;
|
||||
layers.append(DOM.createElement("<div id='pubslayerroot'>"));
|
||||
return true;
|
||||
}
|
||||
|
||||
render() {
|
||||
const root = this.root;
|
||||
if (!root) {
|
||||
console.log("FAILED TO LOCATE ROOT: .layers");
|
||||
return;
|
||||
}
|
||||
BDV2.reactDom.render(this.component, root);
|
||||
}
|
||||
|
||||
get button() {
|
||||
const btn = DOM.createElement(`<div id="bd-pub-li" class="${BDV2.guildClasses.listItem}">`);
|
||||
if (!settingsCookie["bda-gs-1"]) btn.style.display = "none";
|
||||
const label = DOM.createElement(`<div id="bd-pub-button" class="${"wrapper-25eVIn " + BDV2.guildClasses.circleButtonMask}">public</div>`);
|
||||
label.addEventListener("click", () => {this.render();});
|
||||
btn.append(label);
|
||||
return btn;
|
||||
}
|
||||
|
||||
_appendButton() {
|
||||
let [
|
||||
classNameScroller
|
||||
] = [
|
||||
BDModules.get(e => e.scroller && e.scrollbarWidth)[0].scroller
|
||||
]
|
||||
if (DOM.query("#bd-pub-li")) return;
|
||||
const wrapper = BDV2.guildClasses.wrapper.split(" ")[0];
|
||||
const guilds = DOM.query(`.${wrapper} .${classNameScroller} >:first-child`);
|
||||
DOM.after(guilds, this.button);
|
||||
}
|
||||
|
||||
addButton() {
|
||||
if (this.guildPatch) return;
|
||||
const GuildList = webpackModules.find(m => m.default && m.default.displayName == "NavigableGuilds");
|
||||
const GuildListOld = webpackModules.findByDisplayName("Guilds");
|
||||
if (!GuildList && !GuildListOld) Utils.warn("PublicServer", "Can't find GuildList component");
|
||||
this.guildPatch = Utils.monkeyPatch(GuildList ? GuildList : GuildListOld.prototype, GuildList ? "default" : "render", {after: this._appendButton});
|
||||
this._appendButton();
|
||||
}
|
||||
|
||||
removeButton() {
|
||||
this.guildPatch();
|
||||
delete this.guildPatch;
|
||||
DOM.query("#bd-pub-li").remove();
|
||||
}
|
||||
};
|
|
@ -0,0 +1,48 @@
|
|||
import Utils from "./utils";
|
||||
import {settings} from "../0globals";
|
||||
|
||||
const electron = require("electron");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const BrowserWindow = electron.remote.BrowserWindow;
|
||||
const webContents = electron.remote.getCurrentWebContents();
|
||||
|
||||
|
||||
export default new class reactDevTools {
|
||||
constructor() {
|
||||
let extensionPath = "";
|
||||
if (process.platform === "win32") extensionPath = path.resolve(process.env.LOCALAPPDATA, "Google/Chrome/User Data");
|
||||
else if (process.platform === "linux") extensionPath = path.resolve(process.env.HOME, ".config/google-chrome");
|
||||
else if (process.platform === "darwin") extensionPath = path.resolve(process.env.HOME, "Library/Application Support/Google/Chrome");
|
||||
else extensionPath = path.resolve(process.env.HOME, ".config/chromium");
|
||||
extensionPath += "/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi/";
|
||||
if (fs.existsSync(extensionPath)) {
|
||||
const versions = fs.readdirSync(extensionPath);
|
||||
extensionPath = path.resolve(extensionPath, versions[versions.length - 1]);
|
||||
}
|
||||
this.extensionPath = extensionPath;
|
||||
this.isExtensionInstalled = fs.existsSync(extensionPath);
|
||||
this.listener = this.listener.bind(this);
|
||||
|
||||
settings["React DevTools"].hidden = !this.isExtensionInstalled;
|
||||
}
|
||||
|
||||
listener() {
|
||||
if (!this.isExtensionInstalled) return;
|
||||
BrowserWindow.removeDevToolsExtension("React Developer Tools");
|
||||
const didInstall = BrowserWindow.addDevToolsExtension(this.extensionPath);
|
||||
|
||||
if (didInstall) Utils.log("React DevTools", "Successfully installed react devtools.");
|
||||
else Utils.err("React DevTools", "Couldn't find react devtools in chrome extensions!");
|
||||
}
|
||||
|
||||
start() {
|
||||
setImmediate(() => webContents.on("devtools-opened", this.listener));
|
||||
if (webContents.isDevToolsOpened()) this.listener();
|
||||
}
|
||||
|
||||
stop() {
|
||||
webContents.removeListener("devtools-opened", this.listener);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,358 @@
|
|||
import {settings, settingsCookie, bdplugins, bdthemes} from "../0globals";
|
||||
import DataStore from "./dataStore";
|
||||
import V2_SettingsPanel_Sidebar from "./settingsPanelSidebar";
|
||||
import Utils from "./utils";
|
||||
import BDV2 from "./v2";
|
||||
import ContentManager from "./contentManager";
|
||||
import BDEvents from "./bdEvents";
|
||||
import coloredText from "./coloredText";
|
||||
import tfHour from "./24hour";
|
||||
import reactDevTools from "./reactDevTools";
|
||||
import DOM from "./domtools";
|
||||
|
||||
import publicServersModule from "./publicServers";
|
||||
import voiceMode from "./voiceMode";
|
||||
import ClassNormalizer from "./classNormalizer";
|
||||
import dMode from "./devMode";
|
||||
|
||||
import Tools from "../ui/tools";
|
||||
import Scroller from "../ui/scroller";
|
||||
import SectionedSettingsPanel from "../ui/sectionedSettingsPanel";
|
||||
import SettingsPanel from "../ui/settingsPanel";
|
||||
import CssEditor from "../ui/cssEditor";
|
||||
import CardList from "../ui/addonlist";
|
||||
import V2C_PresenceSettings from "../ui/presenceSettings";
|
||||
import CustomRichPresence from "./CustomRichPresence";
|
||||
|
||||
export default new class V2_SettingsPanel {
|
||||
|
||||
constructor() {
|
||||
this.sideBarOnClick = this.sideBarOnClick.bind(this);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.updateSettings = this.updateSettings.bind(this);
|
||||
this.sidebar = new V2_SettingsPanel_Sidebar(this.sideBarOnClick);
|
||||
// this.buildPluginProps = this.buildPluginProps.bind(this);
|
||||
// this.buildThemeProps = this.buildThemeProps.bind(this);
|
||||
this.showOriginal = this.showOriginal.bind(this);
|
||||
}
|
||||
|
||||
get root() {
|
||||
const _root = DOM.query("#bd-settingspane-container");
|
||||
if (!_root) {
|
||||
if (!this.injectRoot()) return null;
|
||||
return this.root;
|
||||
}
|
||||
return _root;
|
||||
}
|
||||
|
||||
injectRoot() {
|
||||
let [
|
||||
classNameLayer,
|
||||
classSidebar
|
||||
] = [
|
||||
BDModules.get(e => e.layer && e.animating)[0].layer.split(" ")[0],
|
||||
BDModules.get(e => e.standardSidebarView)[0]
|
||||
]
|
||||
const sidebar = DOM.query("."+classNameLayer+" ."+classSidebar.standardSidebarView.split(" ")[0]+", ."+classNameLayer+" .ui-standard-sidebar-view");
|
||||
if (!sidebar) return false;
|
||||
const root = DOM.createElement(`<div id="bd-settingspane-container" class="${classSidebar.contentRegion} content-region">`);
|
||||
sidebar.append(root);
|
||||
|
||||
Utils.onRemoved(root, () => {
|
||||
BDV2.reactDom.unmountComponentAtNode(root);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
get coreSettings() {
|
||||
const settings = this.getSettings("core");
|
||||
const categories = [...new Set(settings.map(s => s.category))];
|
||||
const sections = categories.map(c => {return {title: c, settings: settings.filter(s => s.category == c)};});
|
||||
return sections;
|
||||
}
|
||||
|
||||
get lightcordSettings() {
|
||||
const settings = this.getSettings("lightcord");
|
||||
const categories = [...new Set(settings.map(s => s.category))];
|
||||
const sections = categories.map(c => {return {title: c, settings: settings.filter(s => s.category == c)};});
|
||||
return sections;
|
||||
}
|
||||
|
||||
get PresenceSettings() {
|
||||
return this.getSettings("status")
|
||||
}
|
||||
|
||||
getSettings(category) {
|
||||
return Object.keys(settings).reduce((arr, key) => {
|
||||
const setting = settings[key];
|
||||
if (setting.cat === category && setting.implemented && !setting.hidden) {
|
||||
setting.text = key;
|
||||
arr.push(setting);
|
||||
}
|
||||
return arr;
|
||||
}, []);
|
||||
}
|
||||
|
||||
sideBarOnClick(id) {
|
||||
const contentRegion = DOM.query(".contentRegion-3nDuYy, .content-region");
|
||||
contentRegion.style.display = "none";
|
||||
this.root.style.display = "";
|
||||
switch (id) {
|
||||
case "core":
|
||||
this.renderCoreSettings();
|
||||
break;
|
||||
case "customcss":
|
||||
this.renderCustomCssEditor();
|
||||
break;
|
||||
case "plugins":
|
||||
case "themes":
|
||||
this.renderAddonPane(id);
|
||||
break;
|
||||
case "lightcord":
|
||||
this.renderLightCordSettings()
|
||||
break
|
||||
case "status":
|
||||
this.renderPresenceSettings()
|
||||
}
|
||||
}
|
||||
|
||||
onClick() {}
|
||||
|
||||
onChange(id, checked) {
|
||||
this.updateSettings(id, checked);
|
||||
}
|
||||
|
||||
updateSettings(id, enabled) {
|
||||
settingsCookie[id] = enabled;
|
||||
|
||||
if (id == "bda-gs-2") {
|
||||
if (enabled) DOM.addClass(document.body, "bd-minimal");
|
||||
else DOM.removeClass(document.body, "bd-minimal");
|
||||
}
|
||||
|
||||
if (id == "bda-gs-3") {
|
||||
if (enabled) DOM.addClass(document.body, "bd-minimal-chan");
|
||||
else DOM.removeClass(document.body, "bd-minimal-chan");
|
||||
}
|
||||
|
||||
if (id == "bda-gs-1") {
|
||||
if (enabled) publicServersModule.addButton();
|
||||
else publicServersModule.removeButton();
|
||||
}
|
||||
|
||||
if (id == "bda-gs-4") {
|
||||
if (enabled) voiceMode.start();
|
||||
else voiceMode.stop();
|
||||
}
|
||||
|
||||
if (id == "bda-gs-5") {
|
||||
if (enabled) DOM.addClass(DOM.query("#app-mount"), "bda-dark");
|
||||
else DOM.removeClass(DOM.query("#app-mount"), "bda-dark");
|
||||
}
|
||||
|
||||
if (enabled && id == "bda-gs-6") tfHour.inject24Hour();
|
||||
|
||||
if (id == "bda-gs-7") {
|
||||
if (enabled) coloredText.injectColoredText();
|
||||
else coloredText.removeColoredText();
|
||||
}
|
||||
|
||||
if (id == "fork-ps-4") {
|
||||
if (enabled) ClassNormalizer.start();
|
||||
else ClassNormalizer.stop();
|
||||
}
|
||||
|
||||
if (id == "fork-ps-5") {
|
||||
if (enabled) {
|
||||
ContentManager.watchContent("plugin");
|
||||
ContentManager.watchContent("theme");
|
||||
}
|
||||
else {
|
||||
ContentManager.unwatchContent("plugin");
|
||||
ContentManager.unwatchContent("theme");
|
||||
}
|
||||
}
|
||||
|
||||
if (id == "fork-wp-1") {
|
||||
Utils.setWindowPreference("transparent", enabled);
|
||||
if (enabled) Utils.setWindowPreference("backgroundColor", null);
|
||||
else Utils.setWindowPreference("backgroundColor", "#2f3136");
|
||||
}
|
||||
|
||||
|
||||
if (id == "bda-gs-8") {
|
||||
if (enabled) dMode.startDebugListener();
|
||||
else dMode.stopDebugListener();
|
||||
}
|
||||
|
||||
if (id == "fork-dm-1") {
|
||||
if (enabled) dMode.startCopySelector();
|
||||
else dMode.stopCopySelector();
|
||||
}
|
||||
|
||||
if (id === "reactDevTools") {
|
||||
if (enabled) reactDevTools.start();
|
||||
else reactDevTools.stop();
|
||||
}
|
||||
if (id === "lightcord-1") {
|
||||
if (enabled) window.lightcordSettings.devMode = true
|
||||
else window.lightcordSettings.devMode = false
|
||||
}
|
||||
if (id === "lightcord-2") {
|
||||
if (enabled) window.lightcordSettings.callRingingBeat = true
|
||||
else window.lightcordSettings.callRingingBeat = false
|
||||
}
|
||||
|
||||
if (id === "lightcord-presence-1") {
|
||||
if (enabled) CustomRichPresence.enable()
|
||||
else CustomRichPresence.disable()
|
||||
}
|
||||
|
||||
this.saveSettings();
|
||||
}
|
||||
|
||||
async initializeSettings() {
|
||||
if (settingsCookie.reactDevTools) reactDevTools.start();
|
||||
if (settingsCookie["bda-gs-2"]) DOM.addClass(document.body, "bd-minimal");
|
||||
if (settingsCookie["bda-gs-3"]) DOM.addClass(document.body, "bd-minimal-chan");
|
||||
if (settingsCookie["bda-gs-1"]) publicServersModule.addButton();
|
||||
if (settingsCookie["bda-gs-4"]) voiceMode.start();
|
||||
if (settingsCookie["bda-gs-5"]) DOM.addClass(DOM.query("#app-mount"), "bda-dark");
|
||||
if (settingsCookie["bda-gs-6"]) tfHour.inject24Hour();
|
||||
if (settingsCookie["bda-gs-7"]) coloredText.injectColoredText();
|
||||
if (settingsCookie["fork-ps-4"]) ClassNormalizer.start();
|
||||
if (settingsCookie["lightcord-1"]) window.lightcordSettings.devMode = true
|
||||
if (settingsCookie["lightcord-2"]) window.lightcordSettings.callRingingBeat = true
|
||||
if (settingsCookie["lightcord-presence-1"]) CustomRichPresence.enable()
|
||||
|
||||
if (settingsCookie["fork-ps-5"]) {
|
||||
ContentManager.watchContent("plugin");
|
||||
ContentManager.watchContent("theme");
|
||||
}
|
||||
|
||||
if (settingsCookie["bda-gs-8"]) dMode.startDebugListener();
|
||||
if (settingsCookie["fork-dm-1"]) dMode.startCopySelector();
|
||||
|
||||
this.saveSettings();
|
||||
}
|
||||
|
||||
saveSettings() {
|
||||
DataStore.setSettingGroup("settings", settingsCookie);
|
||||
}
|
||||
|
||||
loadSettings() {
|
||||
Object.assign(settingsCookie, DataStore.getSettingGroup("settings"));
|
||||
}
|
||||
|
||||
showOriginal() {
|
||||
BDV2.reactDom.unmountComponentAtNode(this.root);
|
||||
this.root.style.display = "none";
|
||||
DOM.query("."+BDModules.get(e => e.contentRegion)[0].contentRegion.split(" ")[0]+", .content-region").style.display = "";
|
||||
}
|
||||
|
||||
renderSidebar() {
|
||||
const tabs = document.querySelectorAll("[class*='side-'] > [class*='item-']");
|
||||
for (const element of tabs) {
|
||||
element.removeEventListener("click", this.showOriginal);
|
||||
element.addEventListener("click", this.showOriginal);
|
||||
}
|
||||
this.sidebar.render();
|
||||
}
|
||||
|
||||
get coreComponent() {
|
||||
return BDV2.react.createElement(Scroller, {contentColumn: true, fade: true, dark: true},
|
||||
BDV2.react.createElement(SectionedSettingsPanel, {key: "cspanel", onChange: this.onChange, sections: this.coreSettings}),
|
||||
BDV2.react.createElement(Tools, {key: "tools"})
|
||||
);
|
||||
}
|
||||
|
||||
get lightcordComponent() {
|
||||
return BDV2.react.createElement(Scroller, {contentColumn: true, fade: true, dark: true},
|
||||
BDV2.react.createElement(SectionedSettingsPanel, {key: "lspannel", onChange: this.onChange, sections: this.lightcordSettings}),
|
||||
BDV2.react.createElement(Tools, {key: "tools"})
|
||||
);
|
||||
}
|
||||
|
||||
get PresenceComponent() {
|
||||
return BDV2.react.createElement(Scroller, {contentColumn: true, fade: true, dark: true},
|
||||
BDV2.react.createElement(V2C_PresenceSettings, {
|
||||
key: "lspannel",
|
||||
onChange: this.onChange,
|
||||
settings: this.PresenceSettings
|
||||
}),
|
||||
BDV2.react.createElement(Tools, {key: "tools"})
|
||||
);
|
||||
}
|
||||
|
||||
get customCssComponent() {
|
||||
return BDV2.react.createElement(Scroller, {contentColumn: true, fade: true, dark: true},
|
||||
BDV2.react.createElement(CssEditor, {key: "csseditor"}),
|
||||
BDV2.react.createElement(Tools, {key: "tools"})
|
||||
);
|
||||
}
|
||||
|
||||
renderCoreSettings() {
|
||||
const root = this.root;
|
||||
if (!root) return Utils.err("SettingsPanel", "FAILED TO LOCATE ROOT: .layer-3QrUeG .standardSidebarView-3F1I7i");
|
||||
BDV2.reactDom.render(this.coreComponent, root);
|
||||
}
|
||||
|
||||
renderLightCordSettings() {
|
||||
const root = this.root;
|
||||
if (!root) return Utils.err("SettingsPanel", "FAILED TO LOCATE ROOT: .layer-3QrUeG .standardSidebarView-3F1I7i");
|
||||
BDV2.reactDom.render(this.lightcordComponent, root);
|
||||
}
|
||||
|
||||
renderPresenceSettings() {
|
||||
const root = this.root;
|
||||
if (!root) return Utils.err("SettingsPanel", "FAILED TO LOCATE ROOT: .layer-3QrUeG .standardSidebarView-3F1I7i");
|
||||
BDV2.reactDom.render(this.PresenceComponent, root);
|
||||
}
|
||||
|
||||
renderCustomCssEditor() {
|
||||
const root = this.root;
|
||||
if (!root) return Utils.err("SettingsPanel", "FAILED TO LOCATE ROOT: .layer-3QrUeG .standardSidebarView-3F1I7i");
|
||||
BDV2.reactDom.render(this.customCssComponent, root);
|
||||
}
|
||||
|
||||
// renderAddonPane(type) {
|
||||
// const root = this.root;
|
||||
// if (!root) return Utils.err("SettingsPanel", "FAILED TO LOCATE ROOT: .layer-3QrUeG .standardSidebarView-3F1I7i");
|
||||
// BDV2.reactDom.render(this.contentComponent(type), root);
|
||||
// }
|
||||
|
||||
renderAddonPane(type) {
|
||||
if (!this.root) return Utils.err("SettingsPanel", "FAILED TO LOCATE ROOT: .layer-3QrUeG .standardSidebarView-3F1I7i");
|
||||
// I know this shouldn't be here, but when it isn't,
|
||||
// React refuses to change the button when going
|
||||
// between plugins and themes page... something
|
||||
// to debug later.
|
||||
class ContentList extends BDV2.react.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.prefix = this.props.type.replace("s", "");
|
||||
this.onChange = this.onChange.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
BDEvents.on(`${this.prefix}-reloaded`, this.onChange);
|
||||
BDEvents.on(`${this.prefix}-loaded`, this.onChange);
|
||||
BDEvents.on(`${this.prefix}-unloaded`, this.onChange);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
BDEvents.off(`${this.prefix}-reloaded`, this.onChange);
|
||||
BDEvents.off(`${this.prefix}-loaded`, this.onChange);
|
||||
BDEvents.off(`${this.prefix}-unloaded`, this.onChange);
|
||||
}
|
||||
|
||||
onChange() {
|
||||
this.props.onChange(this.props.type);
|
||||
}
|
||||
|
||||
render() {return this.props.children;}
|
||||
}
|
||||
const list = type === "plugins" ? Object.values(bdplugins) : Object.values(bdthemes);
|
||||
return BDV2.reactDom.render(BDV2.react.createElement(ContentList, {type, onChange: this.sideBarOnClick}, BDV2.react.createElement(CardList, {type, list})), this.root);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,77 @@
|
|||
import {bbdChangelog} from "../0globals";
|
||||
import Utils from "./utils";
|
||||
import BDV2 from "./v2";
|
||||
import DOM from "./domtools";
|
||||
|
||||
import SideBar from "../ui/sidebar";
|
||||
import History from "../ui/icons/history";
|
||||
import TooltipWrap from "../ui/tooltipWrap";
|
||||
|
||||
export default class V2_SettingsPanel_Sidebar {
|
||||
|
||||
constructor(onClick) {
|
||||
this.onClick = onClick;
|
||||
}
|
||||
|
||||
get items() {
|
||||
return [{
|
||||
text: "Lightcord",
|
||||
id: "lightcord"
|
||||
}, {
|
||||
text: "Settings",
|
||||
id: "core"
|
||||
}, {
|
||||
text: "Plugins",
|
||||
id: "plugins"
|
||||
}, {
|
||||
text: "Themes",
|
||||
id: "themes"
|
||||
}, {
|
||||
text: "Custom CSS",
|
||||
id: "customcss"
|
||||
}, {
|
||||
text: "Presence",
|
||||
id: "status"
|
||||
}];
|
||||
}
|
||||
|
||||
get component() {
|
||||
//<TooltipWrap color="black" side="top" text={title}>
|
||||
|
||||
const changelogButton = BDV2.react.createElement(TooltipWrap, {color: "black", side: "top", text: "Changelog"},
|
||||
BDV2.react.createElement("div", {className: "bd-changelog-button", onClick: () => {Utils.showChangelogModal(bbdChangelog);}},
|
||||
BDV2.react.createElement(History, {className: "bd-icon", size: "16px"})
|
||||
)
|
||||
);
|
||||
return BDV2.react.createElement("span", null, BDV2.react.createElement(SideBar, {onClick: this.onClick, headerText: "Bandaged BD", headerButton: changelogButton, items: this.items}));
|
||||
}
|
||||
|
||||
get root() {
|
||||
const _root = DOM.query("#bd-settings-sidebar");
|
||||
if (!_root) {
|
||||
if (!this.injectRoot()) return null;
|
||||
return this.root;
|
||||
}
|
||||
return _root;
|
||||
}
|
||||
|
||||
injectRoot() {
|
||||
const tabs = DOM.queryAll("[class*='side-'] > [class*='item-']:not([class*=Danger])");
|
||||
const changeLog = tabs[tabs.length - 1];
|
||||
if (!changeLog) return false;
|
||||
changeLog.parentElement.insertBefore(DOM.createElement(`<div id="bd-settings-sidebar">`), changeLog.previousElementSibling);
|
||||
return true;
|
||||
}
|
||||
|
||||
render() {
|
||||
const root = this.root;
|
||||
if (!root) {
|
||||
console.log("FAILED TO LOCATE ROOT: [class*='side-'] > [class*='item-']:not([class*=Danger])");
|
||||
return;
|
||||
}
|
||||
BDV2.reactDom.render(this.component, root);
|
||||
Utils.onRemoved(root, () => {
|
||||
BDV2.reactDom.unmountComponentAtNode(root);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
import {bdthemeErrors, themeCookie, settingsCookie, bdthemes} from "../0globals";
|
||||
import ContentManager from "./contentManager";
|
||||
import DataStore from "./dataStore";
|
||||
import BDEvents from "./bdEvents";
|
||||
import Utils from "./utils";
|
||||
import DOM from "./domtools";
|
||||
|
||||
class ThemeModule {
|
||||
get folder() {return ContentManager.themesFolder;}
|
||||
}
|
||||
|
||||
ThemeModule.prototype.loadThemes = function () {
|
||||
this.loadThemeData();
|
||||
bdthemeErrors.splice(0, 0, ...ContentManager.loadThemes());
|
||||
const themes = Object.keys(bdthemes);
|
||||
|
||||
for (let i = 0; i < themes.length; i++) {
|
||||
const theme = bdthemes[themes[i]];
|
||||
if (!themeCookie[theme.name]) themeCookie[theme.name] = false;
|
||||
if (themeCookie[theme.name]) DOM.addStyle(DOM.escapeID(theme.id), unescape(theme.css));
|
||||
}
|
||||
for (const theme in themeCookie) {
|
||||
if (!bdthemes[theme]) delete themeCookie[theme];
|
||||
}
|
||||
this.saveThemeData();
|
||||
// if (settingsCookie["fork-ps-5"]) ContentManager.watchContent("theme");
|
||||
};
|
||||
|
||||
ThemeModule.prototype.enableTheme = function(name, reload = false) {
|
||||
themeCookie[name] = true;
|
||||
this.saveThemeData();
|
||||
const theme = bdthemes[name];
|
||||
DOM.addStyle(DOM.escapeID(theme.id), unescape(theme.css));
|
||||
if (settingsCookie["fork-ps-2"] && !reload) Utils.showToast(`${theme.name} v${theme.version} has been applied.`);
|
||||
};
|
||||
|
||||
ThemeModule.prototype.enable = function (name, reload = false) {
|
||||
return this.enableTheme(name, reload);
|
||||
};
|
||||
|
||||
ThemeModule.prototype.disableTheme = function(name, reload = false) {
|
||||
themeCookie[name] = false;
|
||||
this.saveThemeData();
|
||||
const theme = bdthemes[name];
|
||||
DOM.removeStyle(DOM.escapeID(theme.id));
|
||||
if (settingsCookie["fork-ps-2"] && !reload) Utils.showToast(`${theme.name} v${theme.version} has been disabled.`);
|
||||
};
|
||||
|
||||
ThemeModule.prototype.disable = function (name, reload = false) {
|
||||
return this.disableTheme(name, reload);
|
||||
};
|
||||
|
||||
ThemeModule.prototype.toggleTheme = function(theme) {
|
||||
if (themeCookie[theme]) this.disableTheme(theme);
|
||||
else this.enableTheme(theme);
|
||||
};
|
||||
|
||||
ThemeModule.prototype.toggle = function (name, reload = false) {
|
||||
return this.toggleTheme(name, reload);
|
||||
};
|
||||
|
||||
ThemeModule.prototype.loadTheme = function(filename) {
|
||||
const error = ContentManager.loadContent(filename, "theme");
|
||||
if (error) {
|
||||
if (settingsCookie["fork-ps-1"]) Utils.showContentErrors({themes: [error]});
|
||||
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${filename} could not be loaded. It may not have been loaded.`, {type: "error"});
|
||||
return Utils.err("ContentManager", `${filename} could not be loaded.`, error);
|
||||
}
|
||||
const theme = Object.values(bdthemes).find(p => p.filename == filename);
|
||||
Utils.log("ContentManager", `${theme.name} v${theme.version} was loaded.`);
|
||||
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${theme.name} v${theme.version} was loaded.`, {type: "success"});
|
||||
BDEvents.dispatch("theme-loaded", theme.name);
|
||||
};
|
||||
|
||||
ThemeModule.prototype.unloadTheme = function(filenameOrName) {
|
||||
const bdtheme = Object.values(bdthemes).find(p => p.filename == filenameOrName) || bdthemes[filenameOrName];
|
||||
if (!bdtheme) return;
|
||||
const theme = bdtheme.name;
|
||||
if (themeCookie[theme]) this.disableTheme(theme, true);
|
||||
const error = ContentManager.unloadContent(bdthemes[theme].filename, "theme");
|
||||
delete bdthemes[theme];
|
||||
if (error) {
|
||||
if (settingsCookie["fork-ps-1"]) Utils.showContentErrors({themes: [error]});
|
||||
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${theme} could not be unloaded. It may have not been loaded yet.`, {type: "error"});
|
||||
return Utils.err("ContentManager", `${theme} could not be unloaded. It may have not been loaded yet.`, error);
|
||||
}
|
||||
Utils.log("ContentManager", `${theme} was unloaded.`);
|
||||
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${theme} was unloaded.`, {type: "success"});
|
||||
BDEvents.dispatch("theme-unloaded", theme);
|
||||
};
|
||||
|
||||
ThemeModule.prototype.delete = function(filenameOrName) {
|
||||
const bdplugin = Object.values(bdthemes).find(p => p.filename == filenameOrName) || bdthemes[filenameOrName];
|
||||
if (!bdplugin) return;
|
||||
this.unloadTheme(bdplugin.filename);
|
||||
const fullPath = require("path").resolve(ContentManager.pluginsFolder, bdplugin.filename);
|
||||
require("fs").unlinkSync(fullPath);
|
||||
};
|
||||
|
||||
ThemeModule.prototype.reloadTheme = function(filenameOrName) {
|
||||
const bdtheme = Object.values(bdthemes).find(p => p.filename == filenameOrName) || bdthemes[filenameOrName];
|
||||
if (!bdtheme) return this.loadTheme(filenameOrName);
|
||||
const theme = bdtheme.name;
|
||||
const error = ContentManager.reloadContent(bdthemes[theme].filename, "theme");
|
||||
if (themeCookie[theme]) this.disableTheme(theme, true), this.enableTheme(theme, true);
|
||||
if (error) {
|
||||
if (settingsCookie["fork-ps-1"]) Utils.showContentErrors({themes: [error]});
|
||||
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${theme} could not be reloaded.`, {type: "error"});
|
||||
return Utils.err("ContentManager", `${theme} could not be reloaded.`, error);
|
||||
}
|
||||
Utils.log("ContentManager", `${theme} v${bdthemes[theme].version} was reloaded.`);
|
||||
if (settingsCookie["fork-ps-2"]) Utils.showToast(`${theme} v${bdthemes[theme].version} was reloaded.`, {type: "success"});
|
||||
BDEvents.dispatch("theme-reloaded", theme);
|
||||
};
|
||||
|
||||
ThemeModule.prototype.reload = function(name) {
|
||||
return this.reloadTheme(name);
|
||||
};
|
||||
|
||||
ThemeModule.prototype.edit = function(filenameOrName) {
|
||||
const bdplugin = Object.values(bdthemes).find(p => p.filename == filenameOrName) || bdthemes[filenameOrName];
|
||||
if (!bdplugin) return;
|
||||
const fullPath = require("path").resolve(ContentManager.themesFolder, bdplugin.filename);
|
||||
require("electron").shell.openItem(`${fullPath}`);
|
||||
};
|
||||
|
||||
ThemeModule.prototype.updateThemeList = function() {
|
||||
const results = ContentManager.loadNewContent("theme");
|
||||
for (const filename of results.added) this.loadTheme(filename);
|
||||
for (const name of results.removed) this.unloadTheme(name);
|
||||
};
|
||||
|
||||
ThemeModule.prototype.loadThemeData = function() {
|
||||
const saved = DataStore.getSettingGroup("themes");
|
||||
if (saved) {
|
||||
Object.assign(themeCookie, saved);
|
||||
}
|
||||
};
|
||||
|
||||
ThemeModule.prototype.saveThemeData = function () {
|
||||
DataStore.setSettingGroup("themes", themeCookie);
|
||||
};
|
||||
|
||||
export default new ThemeModule();
|
|
@ -0,0 +1,418 @@
|
|||
import {bbdVersion, settingsCookie} from "../0globals";
|
||||
import WebpackModules from "./webpackModules";
|
||||
import BDV2 from "./v2";
|
||||
import DOM from "./domtools";
|
||||
|
||||
export default class Utils {
|
||||
/** Document/window width */
|
||||
static get screenWidth() { return Math.max(document.documentElement.clientWidth, window.innerWidth || 0); }
|
||||
/** Document/window height */
|
||||
static get screenHeight() { return Math.max(document.documentElement.clientHeight, window.innerHeight || 0); }
|
||||
|
||||
static get WindowConfigFile() {
|
||||
return this._windowConfigFile = null;
|
||||
}
|
||||
|
||||
static getAllWindowPreferences() {
|
||||
return {
|
||||
transparent: settingsCookie["fork-wp-1"] || settingsCookie.transparency,
|
||||
frame: settingsCookie.frame
|
||||
};
|
||||
}
|
||||
|
||||
static getWindowPreference(key) {
|
||||
if (key === "transparent") return settingsCookie["fork-wp-1"] || settingsCookie.transparency;
|
||||
if (key === "frame") return settingsCookie.frame;
|
||||
return null;
|
||||
}
|
||||
|
||||
static setWindowPreference(key, value) {
|
||||
if (key === "transparent") return settingsCookie["fork-wp-1"] = settingsCookie.transparency = value;
|
||||
if (key === "frame") return settingsCookie.frame = value;
|
||||
return null;
|
||||
}
|
||||
|
||||
static stripBOM(content) {
|
||||
if (content.charCodeAt(0) === 0xFEFF) {
|
||||
content = content.slice(1);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
static getTextArea() {
|
||||
return DOM.query("."+BDModules.get(e => e.channelTextArea && e.titleWrapper)[0].channelTextArea.split(" ")[0]+" textarea");
|
||||
}
|
||||
|
||||
static insertText(textarea, text) {
|
||||
textarea.focus();
|
||||
textarea.selectionStart = 0;
|
||||
textarea.selectionEnd = textarea.value.length;
|
||||
document.execCommand("insertText", false, text);
|
||||
}
|
||||
|
||||
static escapeID(id) {
|
||||
return id.replace(/^[^a-z]+|[^\w-]+/gi, "-");
|
||||
}
|
||||
|
||||
static log(moduleName, message) {
|
||||
console.log(`%c[BandagedBD]%c [${moduleName}]%c ${message}`, "color: #3a71c1; font-weight: 700;", "color: #3a71c1;", "");
|
||||
}
|
||||
|
||||
static warn(moduleName, message) {
|
||||
console.warn(`%c[BandagedBD]%c [${moduleName}]%c ${message}`, "color: #E8A400; font-weight: 700;", "color: #E8A400;", "");
|
||||
}
|
||||
|
||||
static err(moduleName, message, error) {
|
||||
console.log(`%c[BandagedBD]%c [${moduleName}]%c ${message}`, "color: red; font-weight: 700;", "color: red;", "");
|
||||
if (error) {
|
||||
console.groupCollapsed("%cError: " + error.message, "color: red;");
|
||||
console.error(error.stack);
|
||||
console.groupEnd();
|
||||
}
|
||||
}
|
||||
|
||||
static escape(s) {
|
||||
return s.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
||||
}
|
||||
|
||||
static testJSON(data) {
|
||||
try {
|
||||
return JSON.parse(data);
|
||||
}
|
||||
catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static isEmpty(obj) {
|
||||
if (obj == null || obj == undefined || obj == "") return true;
|
||||
if (typeof(obj) !== "object") return false;
|
||||
if (Array.isArray(obj)) return obj.length == 0;
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static suppressErrors(method, message) {
|
||||
return (...params) => {
|
||||
try { return method(...params); }
|
||||
catch (e) { this.err("SuppressedError", "Error occurred in " + message, e); }
|
||||
};
|
||||
}
|
||||
|
||||
static monkeyPatch(what, methodName, options) {
|
||||
const {before, after, instead, once = false, silent = false, force = false} = options;
|
||||
const displayName = options.displayName || what.displayName || what[methodName].displayName || what.name || what.constructor.displayName || what.constructor.name;
|
||||
if (!silent) console.log("patch", methodName, "of", displayName); // eslint-disable-line no-console
|
||||
if (!what[methodName]) {
|
||||
if (force) what[methodName] = function() {};
|
||||
else return console.error(methodName, "does not exist for", displayName); // eslint-disable-line no-console
|
||||
}
|
||||
const origMethod = what[methodName];
|
||||
const cancel = () => {
|
||||
if (!silent) console.log("unpatch", methodName, "of", displayName); // eslint-disable-line no-console
|
||||
what[methodName] = origMethod;
|
||||
};
|
||||
what[methodName] = function() {
|
||||
const data = {
|
||||
thisObject: this,
|
||||
methodArguments: arguments,
|
||||
cancelPatch: cancel,
|
||||
originalMethod: origMethod,
|
||||
callOriginalMethod: () => data.returnValue = data.originalMethod.apply(data.thisObject, data.methodArguments)
|
||||
};
|
||||
if (instead) {
|
||||
const tempRet = Utils.suppressErrors(instead, "`instead` callback of " + what[methodName].displayName)(data);
|
||||
if (tempRet !== undefined) data.returnValue = tempRet;
|
||||
}
|
||||
else {
|
||||
if (before) Utils.suppressErrors(before, "`before` callback of " + what[methodName].displayName)(data);
|
||||
data.callOriginalMethod();
|
||||
if (after) Utils.suppressErrors(after, "`after` callback of " + what[methodName].displayName)(data);
|
||||
}
|
||||
if (once) cancel();
|
||||
return data.returnValue;
|
||||
};
|
||||
Object.assign(what[methodName], origMethod);
|
||||
what[methodName].__monkeyPatched = true;
|
||||
what[methodName].displayName = displayName;
|
||||
if (!what[methodName].__originalMethod) {
|
||||
what[methodName].__originalMethod = origMethod;
|
||||
what[methodName].toString = function() {return origMethod.toString();};
|
||||
}
|
||||
return cancel;
|
||||
}
|
||||
|
||||
static onRemoved(node, callback) {
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
for (let m = 0; m < mutations.length; m++) {
|
||||
const mutation = mutations[m];
|
||||
const nodes = Array.from(mutation.removedNodes);
|
||||
const directMatch = nodes.indexOf(node) > -1;
|
||||
const parentMatch = nodes.some(parent => parent.contains(node));
|
||||
if (directMatch || parentMatch) {
|
||||
observer.disconnect();
|
||||
callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document.body, {subtree: true, childList: true});
|
||||
}
|
||||
|
||||
static getNestedProp(obj, path) {
|
||||
return path.split(/\s?\.\s?/).reduce(function(obj, prop) {
|
||||
return obj && obj[prop];
|
||||
}, obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* This shows a toast similar to android towards the bottom of the screen.
|
||||
*
|
||||
* @param {string} content The string to show in the toast.
|
||||
* @param {object} options Options object. Optional parameter.
|
||||
* @param {string} options.type Changes the type of the toast stylistically and semantically. Choices: "", "info", "success", "danger"/"error", "warning"/"warn". Default: ""
|
||||
* @param {boolean} options.icon Determines whether the icon should show corresponding to the type. A toast without type will always have no icon. Default: true
|
||||
* @param {number} options.timeout Adjusts the time (in ms) the toast should be shown for before disappearing automatically. Default: 3000
|
||||
*/
|
||||
static showToast(content, options = {}) {
|
||||
if (!document.querySelector(".bd-toasts")) {
|
||||
const container = document.querySelector("."+BDModules.get(e => e.sidebar && e.hasNotice)[0].sidebar.split(" ")[9]+" + div") || null;
|
||||
const memberlist = container ? container.querySelector("."+BDModules.get(e => e.membersWrap)[0].membersWrap) : null;
|
||||
const form = container ? container.querySelector("form") : null;
|
||||
const left = container ? container.getBoundingClientRect().left : 310;
|
||||
const right = memberlist ? memberlist.getBoundingClientRect().left : 0;
|
||||
const width = right ? right - container.getBoundingClientRect().left : Utils.screenWidth - left - 240;
|
||||
const bottom = form ? form.offsetHeight : 80;
|
||||
const toastWrapper = document.createElement("div");
|
||||
toastWrapper.classList.add("bd-toasts");
|
||||
toastWrapper.style.setProperty("left", left + "px");
|
||||
toastWrapper.style.setProperty("width", width + "px");
|
||||
toastWrapper.style.setProperty("bottom", bottom + "px");
|
||||
document.querySelector("#app-mount").appendChild(toastWrapper);
|
||||
}
|
||||
const {type = "", icon = true, timeout = 3000} = options;
|
||||
const toastElem = document.createElement("div");
|
||||
toastElem.classList.add("bd-toast");
|
||||
if (type) toastElem.classList.add("toast-" + type);
|
||||
if (type && icon) toastElem.classList.add("icon");
|
||||
toastElem.innerText = content;
|
||||
document.querySelector(".bd-toasts").appendChild(toastElem);
|
||||
setTimeout(() => {
|
||||
toastElem.classList.add("closing");
|
||||
setTimeout(() => {
|
||||
toastElem.remove();
|
||||
if (!document.querySelectorAll(".bd-toasts .bd-toast").length) document.querySelector(".bd-toasts").remove();
|
||||
}, 300);
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
static alert(title, content) {
|
||||
let modalModule = BDModules.get(e => e.modal && e.inner && !e.hideOnFullscreen)[0]
|
||||
let headerModule = BDModules.get(e => e.header && e.responsiveWidthMobile && e.hideOnFullscreen)[0]
|
||||
let footer2Module = BDModules.get(e => e.header && e.responsiveWidthMobile && e.focusLock)[0]
|
||||
const modal = DOM.createElement(`<div class="bd-modal-wrapper theme-dark">
|
||||
<div class="bd-backdrop ${BDModules.get(e => e.backdrop && e.backdropWithLayer)[0].backdrop}"></div>
|
||||
<div class="bd-modal ${modalModule.modal}">
|
||||
<div class="bd-modal-inner ${modalModule.inner}">
|
||||
<div class="header ${headerModule.header}">
|
||||
<div class="title">${title}</div>
|
||||
</div>
|
||||
<div class="bd-modal-body">
|
||||
<div class="scroller-wrap fade">
|
||||
<div class="scroller">
|
||||
${content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer ${headerModule.footer} ${footer2Module.footer}">
|
||||
<button type="button">Okay</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`);
|
||||
modal.querySelector(".footer button").addEventListener("click", () => {
|
||||
DOM.addClass(modal, "closing");
|
||||
setTimeout(() => { modal.remove(); }, 300);
|
||||
});
|
||||
modal.querySelector(".bd-backdrop").addEventListener("click", () => {
|
||||
DOM.addClass(modal, "closing");
|
||||
setTimeout(() => { modal.remove(); }, 300);
|
||||
});
|
||||
DOM.query("#app-mount").append(modal);
|
||||
}
|
||||
|
||||
static showContentErrors({plugins: pluginErrors = [], themes: themeErrors = []}) {
|
||||
if (!pluginErrors || !themeErrors) return;
|
||||
if (!pluginErrors.length && !themeErrors.length) return;
|
||||
let modalModule = BDModules.get(e => e.modal && e.inner && !e.hideOnFullscreen)[0]
|
||||
let headerModule = BDModules.get(e => e.header && e.responsiveWidthMobile && e.hideOnFullscreen)[0]
|
||||
let footer2Module = BDModules.get(e => e.header && e.responsiveWidthMobile && e.focusLock)[0]
|
||||
const modal = DOM.createElement(`<div class="bd-modal-wrapper theme-dark">
|
||||
<div class="bd-backdrop ${BDModules.get(e => e.backdrop && e.backdropWithLayer)[0].backdrop}"></div>
|
||||
<div class="bd-modal bd-content-modal ${modalModule.modal}">
|
||||
<div class="bd-modal-inner ${modalModule.inner}">
|
||||
<div class="header ${headerModule.header}"><div class="title">Content Errors</div></div>
|
||||
<div class="bd-modal-body">
|
||||
<div class="tab-bar-container">
|
||||
<div class="tab-bar TOP">
|
||||
<div class="tab-bar-item">Plugins</div>
|
||||
<div class="tab-bar-item">Themes</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-header">
|
||||
<div class="table-column column-name">Name</div>
|
||||
<div class="table-column column-message">Message</div>
|
||||
<div class="table-column column-error">Error</div>
|
||||
</div>
|
||||
<div class="scroller-wrap fade">
|
||||
<div class="scroller">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer ${headerModule.footer} ${footer2Module.footer}">
|
||||
<button type="button">Okay</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`);
|
||||
|
||||
function generateTab(errors) {
|
||||
const container = DOM.createElement(`<div class="errors">`);
|
||||
for (const err of errors) {
|
||||
const error = DOM.createElement(`<div class="error">
|
||||
<div class="table-column column-name">${err.name ? err.name : err.file}</div>
|
||||
<div class="table-column column-message">${err.message}</div>
|
||||
<div class="table-column column-error"><a class="error-link" href="">${err.error ? err.error.message : ""}</a></div>
|
||||
</div>`);
|
||||
container.append(error);
|
||||
if (err.error) {
|
||||
error.querySelectorAll("a").forEach(el => el.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
Utils.err("ContentManager", `Error details for ${err.name ? err.name : err.file}.`, err.error);
|
||||
}));
|
||||
}
|
||||
}
|
||||
return container;
|
||||
}
|
||||
|
||||
const tabs = [generateTab(pluginErrors), generateTab(themeErrors)];
|
||||
|
||||
modal.querySelectorAll(".tab-bar-item").forEach(el => el.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
const selected = modal.querySelector(".tab-bar-item.selected");
|
||||
if (selected) DOM.removeClass(selected, "selected");
|
||||
DOM.addClass(e.target, "selected");
|
||||
const scroller = modal.querySelector(".scroller");
|
||||
scroller.innerHTML = "";
|
||||
scroller.append(tabs[DOM.index(e.target)]);
|
||||
}));
|
||||
|
||||
modal.querySelector(".footer button").addEventListener("click", () => {
|
||||
DOM.addClass(modal, "closing");
|
||||
setTimeout(() => { modal.remove(); }, 300);
|
||||
});
|
||||
modal.querySelector(".bd-backdrop").addEventListener("click", () => {
|
||||
DOM.addClass(modal, "closing");
|
||||
setTimeout(() => { modal.remove(); }, 300);
|
||||
});
|
||||
DOM.query("#app-mount").append(modal);
|
||||
if (pluginErrors.length) modal.querySelector(".tab-bar-item").click();
|
||||
else modal.querySelectorAll(".tab-bar-item")[1].click();
|
||||
}
|
||||
|
||||
static showChangelogModal(options = {}) {
|
||||
const ModalStack = WebpackModules.findByProps("push", "update", "pop", "popWithKey");
|
||||
const ChangelogClasses = WebpackModules.findByProps("fixed", "improved");
|
||||
const TextElement = WebpackModules.findByDisplayName("Text");
|
||||
const FlexChild = WebpackModules.findByProps("Child");
|
||||
const Titles = WebpackModules.findByProps("Tags", "default");
|
||||
const Changelog = WebpackModules.find(m => m.defaultProps && m.defaultProps.selectable == false);
|
||||
const MarkdownParser = WebpackModules.findByProps("defaultRules", "parse");
|
||||
if (!Changelog || !ModalStack || !ChangelogClasses || !TextElement || !FlexChild || !Titles || !MarkdownParser) return;
|
||||
|
||||
const {image = "https://repository-images.githubusercontent.com/105473537/957b5480-7c26-11e9-8401-50fa820cbae5", description = "", changes = [], title = "BandagedBD", subtitle = `v${bbdVersion}`, footer} = options;
|
||||
const ce = BDV2.React.createElement;
|
||||
const changelogItems = [ce("img", {src: image})];
|
||||
if (description) changelogItems.push(ce("p", null, MarkdownParser.parse(description)));
|
||||
for (let c = 0; c < changes.length; c++) {
|
||||
const entry = changes[c];
|
||||
const type = ChangelogClasses[entry.type] ? ChangelogClasses[entry.type] : ChangelogClasses.added;
|
||||
const margin = c == 0 ? ChangelogClasses.marginTop : "";
|
||||
changelogItems.push(ce("h1", {className: `${type} ${margin}`,}, entry.title));
|
||||
const list = ce("ul", null, entry.items.map(i => ce("li", null, MarkdownParser.parse(i))));
|
||||
changelogItems.push(list);
|
||||
}
|
||||
const renderHeader = function() {
|
||||
return ce(FlexChild.Child, {grow: 1, shrink: 1},
|
||||
ce(Titles.default, {tag: Titles.Tags.H4}, title),
|
||||
ce(TextElement,{size: TextElement.Sizes.SMALL, color: TextElement.Colors.STANDARD, className: ChangelogClasses.date}, subtitle)
|
||||
);
|
||||
};
|
||||
|
||||
const renderFooter = () => {
|
||||
const Anchor = WebpackModules.find(m => m.displayName == "Anchor");
|
||||
const AnchorClasses = WebpackModules.findByProps("anchorUnderlineOnHover") || {anchor: "anchor-3Z-8Bb", anchorUnderlineOnHover: "anchorUnderlineOnHover-2ESHQB"};
|
||||
const joinSupportServer = (click) => {
|
||||
click.preventDefault();
|
||||
click.stopPropagation();
|
||||
ModalStack.pop();
|
||||
BDV2.joinBD2();
|
||||
};
|
||||
const supportLink = Anchor ? ce(Anchor, {onClick: joinSupportServer}, "Join our Discord Server.") : ce("a", {className: `${AnchorClasses.anchor} ${AnchorClasses.anchorUnderlineOnHover}`, onClick: joinSupportServer}, "Join our Discord Server.");
|
||||
const defaultFooter = ce(TextElement,{size: TextElement.Sizes.SMALL, color: TextElement.Colors.STANDARD}, "Need support? ", supportLink);
|
||||
return ce(FlexChild.Child, {grow: 1, shrink: 1}, footer ? footer : defaultFooter);
|
||||
};
|
||||
|
||||
return ModalStack.push(function(props) {
|
||||
return ce(Changelog, Object.assign({
|
||||
className: ChangelogClasses.container,
|
||||
selectable: true,
|
||||
onScroll: _ => _,
|
||||
onClose: _ => _,
|
||||
renderHeader: renderHeader,
|
||||
renderFooter: renderFooter,
|
||||
children: changelogItems
|
||||
}, props));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a generic but very customizable confirmation modal with optional confirm and cancel callbacks.
|
||||
* @param {string} title - title of the modal
|
||||
* @param {(string|ReactElement|Array<string|ReactElement>)} children - a single or mixed array of react elements and strings. Every string is wrapped in Discord's `Markdown` component so strings will show and render properly.
|
||||
* @param {object} [options] - options to modify the modal
|
||||
* @param {boolean} [options.danger=false] - whether the main button should be red or not
|
||||
* @param {string} [options.confirmText=Okay] - text for the confirmation/submit button
|
||||
* @param {string} [options.cancelText=Cancel] - text for the cancel button
|
||||
* @param {callable} [options.onConfirm=NOOP] - callback to occur when clicking the submit button
|
||||
* @param {callable} [options.onCancel=NOOP] - callback to occur when clicking the cancel button
|
||||
* @param {string} [options.key] - key used to identify the modal. If not provided, one is generated and returned
|
||||
* @returns {string} - the key used for this modal
|
||||
*/
|
||||
static showConfirmationModal(title, content, options = {}) {
|
||||
const ModalStack = WebpackModules.findByProps("push", "update", "pop", "popWithKey");
|
||||
const Markdown = WebpackModules.findByDisplayName("Markdown");
|
||||
const ConfirmationModal = WebpackModules.find(m => m.defaultProps && m.key && m.key() == "confirm-modal");
|
||||
if (!ModalStack || !ConfirmationModal || !Markdown) return Utils.alert(title, content);
|
||||
|
||||
const emptyFunction = () => {};
|
||||
const {onConfirm = emptyFunction, onCancel = emptyFunction, confirmText = "Okay", cancelText = "Cancel", danger = false, key = undefined} = options;
|
||||
|
||||
if (!Array.isArray(content)) content = [content];
|
||||
content = content.map(c => typeof(c) === "string" ? BDV2.React.createElement(Markdown, null, c) : c);
|
||||
return ModalStack.push(ConfirmationModal, {
|
||||
header: title,
|
||||
children: content,
|
||||
red: danger,
|
||||
confirmText: confirmText,
|
||||
cancelText: cancelText,
|
||||
onConfirm: onConfirm,
|
||||
onCancel: onCancel
|
||||
}, key);
|
||||
}
|
||||
}
|
||||
|
||||
Utils.showToast = Utils.suppressErrors(Utils.showToast, "Could not show toast.");
|
|
@ -0,0 +1,141 @@
|
|||
import {settings} from "../0globals";
|
||||
|
||||
export default new class V2 {
|
||||
|
||||
constructor() {
|
||||
this.editorDetached = false;
|
||||
this.WebpackModules = (() => {
|
||||
const req = webpackJsonp.push([[], {__extra_id__: (module, exports, req) => module.exports = req}, [["__extra_id__"]]]);
|
||||
delete req.m.__extra_id__;
|
||||
delete req.c.__extra_id__;
|
||||
|
||||
const shouldProtect = theModule => {
|
||||
if (theModule.remove && theModule.set && theModule.clear && theModule.get && !theModule.sort) return true;
|
||||
if (theModule.getToken || theModule.getEmail || theModule.showToken) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
const protect = theModule => {
|
||||
if (theModule.remove && theModule.set && theModule.clear && theModule.get && !theModule.sort) return null;
|
||||
if (!theModule.getToken && !theModule.getEmail && !theModule.showToken) return theModule;
|
||||
const proxy = new Proxy(theModule, {
|
||||
getOwnPropertyDescriptor: function(obj, prop) {
|
||||
if (prop === "getToken" || prop === "getEmail" || prop === "showToken") return undefined;
|
||||
return Object.getOwnPropertyDescriptor(obj, prop);
|
||||
},
|
||||
get: function(obj, func) {
|
||||
if (func == "getToken") return () => "mfa.XCnbKzo0CLIqdJzBnL0D8PfDruqkJNHjwHXtr39UU3F8hHx43jojISyi5jdjO52e9_e9MjmafZFFpc-seOMa";
|
||||
if (func == "getEmail") return () => "puppet11112@gmail.com";
|
||||
if (func == "showToken") return () => true;
|
||||
// if (func == "__proto__") return proxy;
|
||||
return obj[func];
|
||||
}
|
||||
});
|
||||
return proxy;
|
||||
};
|
||||
|
||||
const find = (filter) => {
|
||||
for (const i in req.c) {
|
||||
if (req.c.hasOwnProperty(i)) {
|
||||
const m = req.c[i].exports;
|
||||
if (m && m.__esModule && m.default && filter(m.default)) return protect(m.default);
|
||||
if (m && filter(m)) return protect(m);
|
||||
}
|
||||
}
|
||||
// console.warn("Cannot find loaded module in cache");
|
||||
return null;
|
||||
};
|
||||
|
||||
const findAll = (filter) => {
|
||||
const modules = [];
|
||||
for (const i in req.c) {
|
||||
if (req.c.hasOwnProperty(i)) {
|
||||
const m = req.c[i].exports;
|
||||
if (m && m.__esModule && m.default && filter(m.default)) modules.push(protect(m.default));
|
||||
else if (m && filter(m)) modules.push(protect(m));
|
||||
}
|
||||
}
|
||||
return modules;
|
||||
};
|
||||
|
||||
const findByUniqueProperties = (propNames) => find(module => propNames.every(prop => module[prop] !== undefined));
|
||||
const findByPrototypes = (protoNames) => find(module => module.prototype && protoNames.every(protoProp => module.prototype[protoProp] !== undefined));
|
||||
const findByDisplayName = (displayName) => find(module => module.displayName === displayName);
|
||||
|
||||
return {find, findAll, findByUniqueProperties, findByPrototypes, findByDisplayName};
|
||||
})();
|
||||
|
||||
this.internal = {
|
||||
react: this.WebpackModules.findByUniqueProperties(["Component", "PureComponent", "Children", "createElement", "cloneElement"]),
|
||||
reactDom: this.WebpackModules.findByUniqueProperties(["findDOMNode"])
|
||||
};
|
||||
this.getInternalInstance = e => e[Object.keys(e).find(k => k.startsWith("__reactInternalInstance"))];
|
||||
}
|
||||
|
||||
initialize() {
|
||||
|
||||
}
|
||||
|
||||
joinBD1() {this.InviteActions.acceptInviteAndTransitionToInviteChannel("0Tmfo5ZbORCRqbAd");}
|
||||
leaveBD1() {this.GuildActions.leaveGuild("86004744966914048");}
|
||||
|
||||
joinBD2() {this.InviteActions.acceptInviteAndTransitionToInviteChannel("2HScm8j");}
|
||||
leaveBD2() {this.GuildActions.leaveGuild("280806472928198656");}
|
||||
|
||||
/**
|
||||
* @type {typeof React}
|
||||
*/
|
||||
get react() {return this.internal.react;}
|
||||
get React() {return this.internal.react;}
|
||||
get reactDom() {return this.internal.reactDom;}
|
||||
get ReactDom() {return this.internal.reactDom;}
|
||||
/**
|
||||
* @type {typeof React.Component}
|
||||
*/
|
||||
get reactComponent() {return this.internal.react.Component;}
|
||||
get ReactComponent() {return this.internal.react.Component;}
|
||||
|
||||
get anchorClasses() {return this.WebpackModules.findByUniqueProperties(["anchorUnderlineOnHover"]) || {anchor: "anchor-3Z-8Bb", anchorUnderlineOnHover: "anchorUnderlineOnHover-2ESHQB"};}
|
||||
get slateEditorClasses() {return this.WebpackModules.findByUniqueProperties(["slateTextArea"]);}
|
||||
get messageClasses() {return this.WebpackModules.findByUniqueProperties(["message", "containerCozy"]);}
|
||||
get guildClasses() {
|
||||
const guildsWrapper = BDModules.get(e => e.wrapper && e.unreadMentionsBar)[0];
|
||||
const guilds = BDModules.get(e => e.guildsError && e.selected)[0]
|
||||
const pill = BDModules.get(e => e.blobContainer)[0]
|
||||
return Object.assign({}, guildsWrapper, guilds, pill);
|
||||
}
|
||||
|
||||
get MessageContentComponent() {return this.WebpackModules.find(m => m.defaultProps && m.defaultProps.hasOwnProperty("disableButtons"));}
|
||||
get MessageComponent() {return this.WebpackModules.find(m => m.default && m.default.displayName && m.default.displayName == "Message");}
|
||||
get TimeFormatter() {return this.WebpackModules.findByUniqueProperties(["dateFormat"]);}
|
||||
get TooltipWrapper() {return this.WebpackModules.findByDisplayName("Tooltip");}
|
||||
get NativeModule() {return this.WebpackModules.findByUniqueProperties(["setBadge"]);}
|
||||
get InviteActions() {return this.WebpackModules.findByUniqueProperties(["acceptInvite"]);}
|
||||
get GuildActions() {return this.WebpackModules.findByUniqueProperties(["leaveGuild"]);}
|
||||
get Tooltips() {return this.WebpackModules.find(m => m.hide && m.show && !m.search && !m.submit && !m.search && !m.activateRagingDemon && !m.dismiss);}
|
||||
get KeyGenerator() {return this.WebpackModules.find(m => m.toString && /"binary"/.test(m.toString()));}
|
||||
get LayerStack() {return this.WebpackModules.findByUniqueProperties(["popLayer"]);}
|
||||
get UserStore() {return this.WebpackModules.findByUniqueProperties(["getCurrentUser"]);}
|
||||
get ChannelStore() {return this.WebpackModules.findByUniqueProperties(["getChannel"]);}
|
||||
get ChannelActions() {return this.WebpackModules.findByUniqueProperties(["openPrivateChannel"]);}
|
||||
get PrivateChannelActions() {return this.WebpackModules.findByUniqueProperties(["selectPrivateChannel"]);}
|
||||
|
||||
openDM(userId) {
|
||||
const selfId = this.UserStore.getCurrentUser().id;
|
||||
if (selfId == userId) return;
|
||||
const privateChannelId = this.ChannelStore.getDMFromUserId(userId);
|
||||
if (privateChannelId) return this.PrivateChannelActions.selectPrivateChannel(privateChannelId);
|
||||
this.ChannelActions.openPrivateChannel(selfId, userId);
|
||||
}
|
||||
|
||||
parseSettings(cat) {
|
||||
return Object.keys(settings).reduce((arr, key) => {
|
||||
const setting = settings[key];
|
||||
if (setting.cat === cat && setting.implemented && !setting.hidden) {
|
||||
setting.text = key;
|
||||
arr.push(setting);
|
||||
} return arr;
|
||||
}, []);
|
||||
}
|
||||
|
||||
};
|
|
@ -0,0 +1,25 @@
|
|||
import DOM from "./domtools";
|
||||
|
||||
const style = `
|
||||
.container-2Rl01u {
|
||||
display: none!important;
|
||||
}
|
||||
|
||||
.chat-3bRxxu {
|
||||
display: none!important;
|
||||
}
|
||||
|
||||
.sidebar-2K8pFh {
|
||||
flex-grow: 1!important;
|
||||
}
|
||||
`;
|
||||
|
||||
export default new class VoiceMode {
|
||||
start() {
|
||||
DOM.addStyle("VoiceMode", style);
|
||||
}
|
||||
|
||||
stop() {
|
||||
DOM.removeStyle("VoiceMode");
|
||||
}
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
const req = webpackJsonp.push([[], {__extra_id__: (module, exports, req) => module.exports = req}, [["__extra_id__"]]]);
|
||||
delete req.m.__extra_id__;
|
||||
delete req.c.__extra_id__;
|
||||
const find = (filter) => {
|
||||
for (const i in req.c) {
|
||||
if (req.c.hasOwnProperty(i)) {
|
||||
const m = req.c[i].exports;
|
||||
if (m && m.__esModule && m.default && filter(m.default)) return m.default;
|
||||
if (m && filter(m)) return m;
|
||||
}
|
||||
}
|
||||
// console.warn("Cannot find loaded module in cache");
|
||||
return null;
|
||||
};
|
||||
|
||||
const findAll = (filter) => {
|
||||
const modules = [];
|
||||
for (const i in req.c) {
|
||||
if (req.c.hasOwnProperty(i)) {
|
||||
const m = req.c[i].exports;
|
||||
if (m && m.__esModule && m.default && filter(m.default)) modules.push(m.default);
|
||||
else if (m && filter(m)) modules.push(m);
|
||||
}
|
||||
}
|
||||
return modules;
|
||||
};
|
||||
|
||||
const findByProps = (...propNames) => find(module => propNames.every(prop => module[prop] !== undefined));
|
||||
const findByPrototypes = (...protoNames) => find(module => module.prototype && protoNames.every(protoProp => module.prototype[protoProp] !== undefined));
|
||||
const findByDisplayName = (displayName) => find(module => module.displayName === displayName);
|
||||
|
||||
export default {find, findAll, findByProps, findByPrototypes, findByDisplayName};
|
|
@ -0,0 +1,201 @@
|
|||
import {settingsCookie} from "../0globals";
|
||||
import BDV2 from "../modules/v2";
|
||||
import Utils from "../modules/utils";
|
||||
import DOM from "../modules/domtools";
|
||||
|
||||
import XSvg from "./xSvg";
|
||||
import ReloadIcon from "./reloadIcon";
|
||||
import EditIcon from "./icons/edit";
|
||||
import DeleteIcon from "./icons/delete";
|
||||
import Switch from "./components/switch";
|
||||
import TooltipWrap from "./tooltipWrap";
|
||||
|
||||
const React = BDV2.React;
|
||||
const anchorClasses = BDV2.anchorClasses;
|
||||
|
||||
export default class V2C_PluginCard extends BDV2.reactComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.showSettings = this.showSettings.bind(this);
|
||||
this.setInitialState();
|
||||
this.hasSettings = this.props.addon.plugin && typeof(this.props.addon.plugin.getSettingsPanel) === "function";
|
||||
this.settingsPanel = "";
|
||||
|
||||
this.edit = this.edit.bind(this);
|
||||
this.delete = this.delete.bind(this);
|
||||
this.reload = this.reload.bind(this);
|
||||
}
|
||||
|
||||
setInitialState() {
|
||||
this.state = {
|
||||
checked: this.props.enabled,
|
||||
settings: false,
|
||||
reloads: 0
|
||||
};
|
||||
}
|
||||
|
||||
showSettings() {
|
||||
if (!this.hasSettings) return;
|
||||
this.setState({settings: true});
|
||||
}
|
||||
|
||||
closeSettings() {
|
||||
this.panelRef.current.innerHTML = "";
|
||||
this.setState({settingsOpen: false});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (!this.state.settings) return;
|
||||
if (typeof this.settingsPanel === "object") {
|
||||
this.refs.settingspanel.appendChild(this.settingsPanel);
|
||||
}
|
||||
|
||||
if (!settingsCookie["fork-ps-3"]) return;
|
||||
setImmediate(() => {
|
||||
const isHidden = (container, element) => {
|
||||
const cTop = container.scrollTop;
|
||||
const cBottom = cTop + container.clientHeight;
|
||||
const eTop = element.offsetTop;
|
||||
const eBottom = eTop + element.clientHeight;
|
||||
return (eTop < cTop || eBottom > cBottom);
|
||||
};
|
||||
|
||||
const thisNode = this.refs.cardNode;
|
||||
const container = thisNode.closest(".scroller");
|
||||
if (!isHidden(container, thisNode)) return;
|
||||
const thisNodeOffset = DOM.offset(thisNode);
|
||||
const containerOffset = DOM.offset(container);
|
||||
const original = container.scrollTop;
|
||||
const endPoint = thisNodeOffset.top - containerOffset.top + container.scrollTop - 30;
|
||||
DOM.animate({
|
||||
duration: 300,
|
||||
update: function(progress) {
|
||||
if (endPoint > original) container.scrollTop = original + (progress * (endPoint - original));
|
||||
else container.scrollTop = original - (progress * (original - endPoint));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
getString(value) {
|
||||
if (!value) return "???";
|
||||
return typeof value == "string" ? value : value.toString();
|
||||
}
|
||||
|
||||
get settingsComponent() {
|
||||
try { this.settingsPanel = this.props.addon.plugin.getSettingsPanel(); }
|
||||
catch (err) { Utils.err("Plugins", "Unable to get settings panel for " + this.name + ".", err); }
|
||||
|
||||
return BDV2.react.createElement("div", {className: "bd-card bd-addon-card settings-open ui-switch-item", ref: "cardNode"},
|
||||
BDV2.react.createElement("div", {style: {"float": "right", "cursor": "pointer"}, onClick: () => {
|
||||
this.refs.settingspanel.innerHTML = "";
|
||||
this.setState({settings: false});
|
||||
}},
|
||||
BDV2.react.createElement(XSvg, null)
|
||||
),
|
||||
typeof this.settingsPanel === "object" && BDV2.react.createElement("div", {id: `plugin-settings-${this.name}`, className: "plugin-settings", ref: "settingspanel"}),
|
||||
typeof this.settingsPanel !== "object" && BDV2.react.createElement("div", {id: `plugin-settings-${this.name}`, className: "plugin-settings", ref: "settingspanel", dangerouslySetInnerHTML: {__html: this.settingsPanel}})
|
||||
);
|
||||
}
|
||||
|
||||
buildTitle(name, version, author) {
|
||||
const title = "{{name}} v{{version}} by {{author}}".split(/({{[A-Za-z]+}})/);
|
||||
const nameIndex = title.findIndex(s => s == "{{name}}");
|
||||
if (nameIndex) title[nameIndex] = React.createElement("span", {className: "name bda-name"}, name);
|
||||
const versionIndex = title.findIndex(s => s == "{{version}}");
|
||||
if (nameIndex) title[versionIndex] = React.createElement("span", {className: "version bda-version"}, version);
|
||||
const authorIndex = title.findIndex(s => s == "{{author}}");
|
||||
if (nameIndex) {
|
||||
const props = {className: "author bda-author"};
|
||||
if (author.link || author.id) {
|
||||
props.className += ` ${anchorClasses.anchor} ${anchorClasses.anchorUnderlineOnHover}`;
|
||||
props.target = "_blank";
|
||||
|
||||
if (author.link) props.href = author.link;
|
||||
if (author.id) props.onClick = () => {BDV2.LayerStack.popLayer(); BDV2.openDM(author.id);};
|
||||
}
|
||||
title[authorIndex] = React.createElement(author.link || author.id ? "a" : "span", props, author.name);
|
||||
}
|
||||
return title.flat();
|
||||
}
|
||||
|
||||
makeLink(title, url) {
|
||||
const props = {className: "bda-link bda-link-website", target: "_blank"};
|
||||
if (typeof(url) == "string") props.href = url;
|
||||
if (typeof(url) == "function") props.onClick = (event) => {event.preventDefault(); event.stopPropagation(); url();};
|
||||
return BDV2.react.createElement("a", props, title);
|
||||
}
|
||||
|
||||
makeButton(title, children, action) {
|
||||
return <TooltipWrap color="black" side="top" text={title}>
|
||||
<div className="bd-addon-button" onClick={action}>{children}</div>
|
||||
</TooltipWrap>;
|
||||
}
|
||||
|
||||
get links() {
|
||||
const links = [];
|
||||
const addon = this.props.addon;
|
||||
if (addon.website) links.push(this.makeLink("Website", addon.website));
|
||||
if (addon.source) links.push(this.makeLink("Source", addon.source));
|
||||
if (addon.invite) {
|
||||
links.push(this.makeLink("Support Server", () => {
|
||||
const tester = /\.gg\/(.*)$/;
|
||||
let code = addon.invite;
|
||||
if (tester.test(code)) code = code.match(tester)[1];
|
||||
BDV2.LayerStack.popLayer();
|
||||
BDV2.InviteActions.acceptInviteAndTransitionToInviteChannel(code);
|
||||
}));
|
||||
}
|
||||
if (addon.donate) links.push(this.makeLink("Donate", addon.donate));
|
||||
if (addon.patreon) links.push(this.makeLink("Patreon", addon.patreon));
|
||||
return links;
|
||||
}
|
||||
|
||||
get footer() {
|
||||
const links = this.links;
|
||||
return (links.length || this.hasSettings) && BDV2.react.createElement("div", {className: "bd-card-footer bda-footer"},
|
||||
BDV2.react.createElement("span", {className: "bd-addon-links bda-links"},
|
||||
...(links.map((element, index) => index < links.length - 1 ? [element, " | "] : element).flat())
|
||||
),
|
||||
this.hasSettings && BDV2.react.createElement("button", {onClick: this.showSettings, className: "bd-button bda-settings-button", disabled: !this.state.checked}, "Settings")
|
||||
);
|
||||
}
|
||||
|
||||
onChange() {
|
||||
this.props.toggle && this.props.toggle(this.name);
|
||||
this.setState({checked: !this.state.checked});
|
||||
}
|
||||
|
||||
edit() {this.props.edit(this.name);}
|
||||
delete() {this.props.remove(this.name);}
|
||||
reload() {this.props.reload(this.name);}
|
||||
|
||||
get name() {return this.getString(this.props.addon.plugin ? this.props.addon.plugin.getName() : this.props.addon.name);}
|
||||
get author() {return this.getString(this.props.addon.plugin ? this.props.addon.plugin.getAuthor() : this.props.addon.author);}
|
||||
get description() {return this.getString(this.props.addon.plugin ? this.props.addon.plugin.getDescription() : this.props.addon.description);}
|
||||
get version() {return this.getString(this.props.addon.plugin ? this.props.addon.plugin.getVersion() : this.props.addon.version);}
|
||||
|
||||
render() {
|
||||
if (this.state.settings) return this.settingsComponent;
|
||||
const {authorId, authorLink} = this.props.addon;
|
||||
|
||||
return BDV2.react.createElement("div", {className: "bd-card bd-addon-card settings-closed ui-switch-item"},
|
||||
BDV2.react.createElement("div", {className: "bd-addon-header bda-header"},
|
||||
BDV2.react.createElement("div", {className: "bd-card-title bda-header-title"}, this.buildTitle(this.name, this.version, {name: this.author, id: authorId, link: authorLink})),
|
||||
BDV2.react.createElement("div", {className: "bd-addon-controls bda-controls"},
|
||||
this.props.edit && this.makeButton("Edit", <EditIcon className="bd-icon" />, this.edit),
|
||||
this.props.remove && this.makeButton("Delete", <DeleteIcon className="bd-icon" />, this.delete),
|
||||
this.props.reload && this.makeButton("Reload", <ReloadIcon className="bd-icon" />, this.reload),
|
||||
React.createElement(Switch, {onChange: this.onChange, checked: this.state.checked})
|
||||
)
|
||||
),
|
||||
BDV2.react.createElement("div", {className: "bd-scroller-wrap bda-description-wrap scroller-wrap fade"},
|
||||
BDV2.react.createElement("div", {className: "bd-scroller bd-addon-description bda-description scroller"}, this.description)
|
||||
),
|
||||
this.footer
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
import ErrorBoundary from "./errorBoundary";
|
||||
import ContentColumn from "./contentColumn";
|
||||
import Tools from "./tools";
|
||||
import ReloadIcon from "./reloadIcon";
|
||||
import AddonCard from "./addoncard";
|
||||
import Scroller from "./scroller";
|
||||
import Dropdown from "./components/dropdown";
|
||||
import Search from "./components/search";
|
||||
|
||||
import {settingsCookie, pluginCookie, themeCookie} from "../0globals";
|
||||
import ContentManager from "../modules/contentManager";
|
||||
import BDV2 from "../modules/v2";
|
||||
import pluginModule from "../modules/pluginModule";
|
||||
import themeModule from "../modules/themeModule";
|
||||
import WebpackModules from "../modules/webpackModules";
|
||||
import BdApi from "../modules/bdApi";
|
||||
|
||||
const Tooltip = WebpackModules.findByDisplayName("Tooltip");
|
||||
|
||||
const React = BDV2.react;
|
||||
|
||||
export default class CardList extends BDV2.reactComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {sort: "name", ascending: true, query: ""};
|
||||
this.isPlugins = this.props.type == "plugins";
|
||||
this.cookie = this.isPlugins ? pluginCookie : themeCookie;
|
||||
this.manager = this.isPlugins ? pluginModule : themeModule;
|
||||
|
||||
this.sort = this.sort.bind(this);
|
||||
this.reverse = this.reverse.bind(this);
|
||||
this.search = this.search.bind(this);
|
||||
}
|
||||
|
||||
openFolder() {
|
||||
require("electron").shell.openItem(this.isPlugins ? ContentManager.pluginsFolder : ContentManager.themesFolder);
|
||||
}
|
||||
|
||||
edit(name) {
|
||||
console.log(name);
|
||||
this.manager.edit(name);
|
||||
}
|
||||
|
||||
async delete(name) {
|
||||
const shouldDelete = await this.confirmDelete(name);
|
||||
if (!shouldDelete) return;
|
||||
this.manager.delete(name);
|
||||
}
|
||||
|
||||
confirmDelete(name) {
|
||||
return new Promise(resolve => {
|
||||
BdApi.showConfirmationModal("Are You Sure?", `Are you sure you want to delete ${name}?`, {
|
||||
danger: true,
|
||||
confirmText: "Delete",
|
||||
onConfirm: () => {resolve(true);},
|
||||
onCancel: () => {resolve(false);}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
get sortOptions() {
|
||||
return [
|
||||
{label: "Name", value: "name"},
|
||||
{label: "Author", value: "author"},
|
||||
{label: "Version", value: "version"},
|
||||
{label: "Recently Added", value: "added"},
|
||||
{label: "Last Modified", value: "modified"},
|
||||
{label: "File Size", value: "size"},
|
||||
];
|
||||
}
|
||||
|
||||
get directions() {
|
||||
return [
|
||||
{label: "Ascending", value: true},
|
||||
{label: "Descending", value: false}
|
||||
];
|
||||
}
|
||||
|
||||
reverse(value) {
|
||||
this.setState({ascending: value});
|
||||
}
|
||||
|
||||
sort(value) {
|
||||
this.setState({sort: value});
|
||||
}
|
||||
|
||||
search(event) {
|
||||
this.setState({query: event.target.value.toLocaleLowerCase()});
|
||||
}
|
||||
|
||||
getProps(addon) {
|
||||
return {
|
||||
key: this.getName(addon),
|
||||
enabled: this.cookie[this.getName(addon)],
|
||||
toggle: this.manager.toggle.bind(this.manager),
|
||||
//edit: this.edit.bind(this),
|
||||
remove: this.delete.bind(this),
|
||||
addon: addon
|
||||
};
|
||||
}
|
||||
|
||||
getString(value) {
|
||||
if (!value) return "???";
|
||||
return typeof value == "string" ? value : value.toString();
|
||||
}
|
||||
|
||||
getAddons() {
|
||||
const sortedAddons = this.props.list.sort((a, b) => {
|
||||
const cap = this.state.sort.charAt(0).toUpperCase() + this.state.sort.slice(1);
|
||||
const first = a.plugin && a.plugin[`get${cap}`] ? this.getString(a.plugin[`get${cap}`]()) : a[this.state.sort];
|
||||
const second = b.plugin && b.plugin[`get${cap}`] ? this.getString(b.plugin[`get${cap}`]()) : b[this.state.sort];
|
||||
if (typeof(first) == "string") return first.toLocaleLowerCase().localeCompare(second.toLocaleLowerCase());
|
||||
if (first > second) return 1;
|
||||
if (second > first) return -1;
|
||||
return 0;
|
||||
});
|
||||
if (!this.state.ascending) sortedAddons.reverse();
|
||||
const rendered = [];
|
||||
for (let a = 0; a < sortedAddons.length; a++) {
|
||||
const addon = sortedAddons[a];
|
||||
if (this.state.query) {
|
||||
let matches = null;
|
||||
const name = this.getName(addon);
|
||||
const author = this.getAuthor(addon);
|
||||
const description = this.getDescription(addon);
|
||||
const version = this.getVersion(addon);
|
||||
if (name) matches = name.toLocaleLowerCase().includes(this.state.query);
|
||||
if (author) matches = matches || author.toLocaleLowerCase().includes(this.state.query);
|
||||
if (description) matches = matches || description.toLocaleLowerCase().includes(this.state.query);
|
||||
if (version) matches = matches || version.toLocaleLowerCase().includes(this.state.query);
|
||||
if (!matches) continue;
|
||||
}
|
||||
const props = this.getProps(addon);
|
||||
rendered.push(<ErrorBoundary><AddonCard {...props} reload={!settingsCookie["fork-ps-5"] && this.manager.reload.bind(this.manager)} /></ErrorBoundary>);
|
||||
}
|
||||
return rendered;
|
||||
}
|
||||
|
||||
getName(addon) {return this.getString(addon.plugin ? addon.plugin.getName() : addon.name);}
|
||||
getAuthor(addon) {return this.getString(addon.plugin ? addon.plugin.getAuthor() : addon.author);}
|
||||
getDescription(addon) {return this.getString(addon.plugin ? addon.plugin.getDescription() : addon.description);}
|
||||
getVersion(addon) {return this.getString(addon.plugin ? addon.plugin.getVersion() : addon.version);}
|
||||
|
||||
render() {
|
||||
const refreshIcon = <Tooltip color="black" position="top" text="Reload List">
|
||||
{(props) =>
|
||||
<ReloadIcon {...props} className="bd-icon bd-reload bd-reload-header" size="18px" onClick={async () => {
|
||||
if (this.isPlugins) pluginModule.updatePluginList();
|
||||
else themeModule.updateThemeList();
|
||||
this.forceUpdate();
|
||||
}} />
|
||||
}</Tooltip>;
|
||||
const addonCards = this.getAddons();
|
||||
|
||||
return <Scroller contentColumn={true} fade={true} dark={true}>
|
||||
<ContentColumn title={`${this.props.type.toUpperCase()}—${addonCards.length}`}>
|
||||
<button key="folder-button" className="bd-button bd-pfbtn" onClick={this.openFolder.bind(this)}>Open {this.isPlugins ? "Plugin" : "Theme"} Folder</button>
|
||||
{!settingsCookie["fork-ps-5"] && refreshIcon}
|
||||
<div className="bd-controls bd-addon-controls">
|
||||
<Search onChange={this.search} placeholder={`Search ${this.props.type}...`} />
|
||||
<div className="bd-addon-dropdowns">
|
||||
<div className="bd-select-wrapper">
|
||||
<label className="bd-label">Sort by:</label>
|
||||
<Dropdown options={this.sortOptions} onChange={this.sort} style="transparent" />
|
||||
</div>
|
||||
<div className="bd-select-wrapper">
|
||||
<label className="bd-label">Order:</label>
|
||||
<Dropdown options={this.directions} onChange={this.reverse} style="transparent" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="bda-slist bd-addon-list">{addonCards}</div>
|
||||
</ContentColumn>
|
||||
<Tools key="tools" />
|
||||
</Scroller>;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import BDV2 from "../modules/v2";
|
||||
|
||||
export default class BDLogo extends BDV2.reactComponent {
|
||||
render() {
|
||||
return BDV2.react.createElement(
|
||||
"svg",
|
||||
{height: "100%", width: this.props.size || "16px", className: "bd-logo " + this.props.className, style: {fillRule: "evenodd", clipRule: "evenodd", strokeLinecap: "round", strokeLinejoin: "round"}, viewBox: "0 0 2000 2000"},
|
||||
BDV2.react.createElement("metadata", null),
|
||||
BDV2.react.createElement("defs", null,
|
||||
BDV2.react.createElement("filter", {id: "shadow1"}, BDV2.react.createElement("feDropShadow", {"dx": "20", "dy": "0", "stdDeviation": "20", "flood-color": "rgba(0,0,0,0.35)"})),
|
||||
BDV2.react.createElement("filter", {id: "shadow2"}, BDV2.react.createElement("feDropShadow", {"dx": "15", "dy": "0", "stdDeviation": "20", "flood-color": "rgba(255,255,255,0.15)"})),
|
||||
BDV2.react.createElement("filter", {id: "shadow3"}, BDV2.react.createElement("feDropShadow", {"dx": "10", "dy": "0", "stdDeviation": "20", "flood-color": "rgba(0,0,0,0.35)"}))
|
||||
),
|
||||
BDV2.react.createElement("g", null,
|
||||
BDV2.react.createElement("path", {style: {filter: "url(#shadow3)"}, d: "M1195.44+135.442L1195.44+135.442L997.6+136.442C1024.2+149.742+1170.34+163.542+1193.64+179.742C1264.34+228.842+1319.74+291.242+1358.24+365.042C1398.14+441.642+1419.74+530.642+1422.54+629.642L1422.54+630.842L1422.54+632.042C1422.54+773.142+1422.54+1228.14+1422.54+1369.14L1422.54+1370.34L1422.54+1371.54C1419.84+1470.54+1398.24+1559.54+1358.24+1636.14C1319.74+1709.94+1264.44+1772.34+1193.64+1821.44C1171.04+1837.14+1025.7+1850.54+1000+1863.54L1193.54+1864.54C1539.74+1866.44+1864.54+1693.34+1864.54+1296.64L1864.54+716.942C1866.44+312.442+1541.64+135.442+1195.44+135.442Z", fill: "#171717", opacity: "1"}),
|
||||
BDV2.react.createElement("path", {style: {filter: "url(#shadow2)"}, d: "M1695.54+631.442C1685.84+278.042+1409.34+135.442+1052.94+135.442L361.74+136.442L803.74+490.442L1060.74+490.442C1335.24+490.442+1335.24+835.342+1060.74+835.342L1060.74+1164.84C1150.22+1164.84+1210.53+1201.48+1241.68+1250.87C1306.07+1353+1245.76+1509.64+1060.74+1509.64L361.74+1863.54L1052.94+1864.54C1409.24+1864.54+1685.74+1721.94+1695.54+1368.54C1695.54+1205.94+1651.04+1084.44+1572.64+999.942C1651.04+915.542+1695.54+794.042+1695.54+631.442Z", fill: "#3E82E5", opacity: "1"}),
|
||||
BDV2.react.createElement("path", {style: {filter: "url(#shadow1)"}, d: "M1469.25+631.442C1459.55+278.042+1183.05+135.442+826.65+135.442L135.45+135.442L135.45+1004C135.45+1004+135.427+1255.21+355.626+1255.21C575.825+1255.21+575.848+1004+575.848+1004L577.45+490.442L834.45+490.442C1108.95+490.442+1108.95+835.342+834.45+835.342L664.65+835.342L664.65+1164.84L834.45+1164.84C923.932+1164.84+984.244+1201.48+1015.39+1250.87C1079.78+1353+1019.47+1509.64+834.45+1509.64L135.45+1509.64L135.45+1864.54L826.65+1864.54C1182.95+1864.54+1459.45+1721.94+1469.25+1368.54C1469.25+1205.94+1424.75+1084.44+1346.35+999.942C1424.75+915.542+1469.25+794.042+1469.25+631.442Z", fill: "#FFFFFF", opacity: "1"})
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
import BDV2 from "../modules/v2";
|
||||
|
||||
export default class V2C_Checkbox extends BDV2.reactComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onClick = this.onClick.bind(this);
|
||||
this.setInitialState();
|
||||
}
|
||||
|
||||
setInitialState() {
|
||||
this.state = {
|
||||
checked: this.props.checked || false
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return BDV2.react.createElement(
|
||||
"li",
|
||||
null,
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "checkbox "+BDModules.get(e => e.checkboxElement)[0].checkbox, onClick: this.onClick},
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "checkbox-inner "+BDModules.get(e => e.checkboxInner)[0].checkboxInner},
|
||||
BDV2.react.createElement("input", {className: BDModules.get(e => e.checkboxElement)[0].checkboxElement, checked: this.state.checked, onChange: () => {}, type: "checkbox"}),
|
||||
BDV2.react.createElement("span", null)
|
||||
),
|
||||
BDV2.react.createElement(
|
||||
"span",
|
||||
null,
|
||||
this.props.text
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
onClick() {
|
||||
this.props.onChange(this.props.id, !this.state.checked);
|
||||
this.setState({
|
||||
checked: !this.state.checked
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
import BDV2 from "../../modules/v2";
|
||||
import Arrow from "../icons/downarrow";
|
||||
|
||||
const React = BDV2.React;
|
||||
|
||||
|
||||
export default class Select extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {open: false, value: this.props.value || this.props.options[0].value};
|
||||
this.dropdown = React.createRef();
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.showMenu = this.showMenu.bind(this);
|
||||
this.hideMenu = this.hideMenu.bind(this);
|
||||
}
|
||||
|
||||
showMenu(event) {
|
||||
event.preventDefault();
|
||||
this.setState({open: true}, () => {
|
||||
document.addEventListener("click", this.hideMenu);
|
||||
});
|
||||
}
|
||||
|
||||
hideMenu() {
|
||||
this.setState({open: false}, () => {
|
||||
document.removeEventListener("click", this.hideMenu);
|
||||
});
|
||||
}
|
||||
|
||||
onChange(value) {
|
||||
this.setState({value});
|
||||
if (this.props.onChange) this.props.onChange(value);
|
||||
}
|
||||
|
||||
get selected() {return this.props.options.find(o => o.value == this.state.value);}
|
||||
|
||||
get options() {
|
||||
const selected = this.selected;
|
||||
return <div className="bd-select-options">
|
||||
{this.props.options.map(opt =>
|
||||
<div className={`bd-select-option${selected.value == opt.value ? " selected" : ""}`} onClick={this.onChange.bind(this, opt.value)}>{opt.label}</div>
|
||||
)}
|
||||
</div>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const style = this.props.style == "transparent" ? " bd-select-transparent" : "";
|
||||
const isOpen = this.state.open ? " menu-open" : "";
|
||||
return <div className={`bd-select${style}${isOpen}`} onClick={this.showMenu} ref={this.dropdown}>
|
||||
<div className="bd-select-value">{this.selected.label}</div>
|
||||
<Arrow className="bd-select-arrow" />
|
||||
{this.state.open && this.options}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
// return <div className="bd-select-wrap">
|
||||
// <label className="bd-label">{this.props.label}</label>
|
||||
// <div className={`bd-select${style}${isOpen}`} onClick={this.showMenu} ref={this.dropdown}>
|
||||
// <div className="bd-select-controls">
|
||||
// <div className="bd-select-value">{this.selected.label}</div>
|
||||
// <Arrow className="bd-select-arrow" />
|
||||
// </div>
|
||||
// </div>
|
||||
// {this.state.open && this.options}
|
||||
// </div>;
|
|
@ -0,0 +1,13 @@
|
|||
import BDV2 from "../../modules/v2";
|
||||
import SearchIcon from "../icons/search";
|
||||
|
||||
const React = BDV2.React;
|
||||
|
||||
export default class Search extends React.Component {
|
||||
render() {
|
||||
return <div className="bd-search-wrapper">
|
||||
<input onChange={this.props.onChange} onKeyDown={this.props.onKeyDown} type="text" className="bd-search" placeholder={this.props.placeholder} maxLength="50" />
|
||||
<SearchIcon />
|
||||
</div>;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import BDV2 from "../../modules/v2";
|
||||
|
||||
const React = BDV2.React;
|
||||
|
||||
export default class Switch extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {checked: this.props.checked};
|
||||
this.onChange = this.onChange.bind(this);
|
||||
}
|
||||
|
||||
onChange() {
|
||||
if (this.props.disabled) return;
|
||||
this.props.onChange(!this.state.checked);
|
||||
this.setState({checked: !this.state.checked});
|
||||
}
|
||||
|
||||
render() {
|
||||
const enabledClass = this.props.disabled ? " bd-switch-disabled" : "";
|
||||
const checkedClass = this.state.checked ? " bd-switch-checked" : "";
|
||||
return <div className={`bd-switch` + enabledClass + checkedClass}>
|
||||
<input type="checkbox" id={this.props.id} className={`bd-checkbox`} disabled={this.props.disabled} checked={this.state.checked} onChange={this.onChange} />
|
||||
</div>;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import BDV2 from "../modules/v2";
|
||||
|
||||
export default class V2C_ContentColumn extends BDV2.reactComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
static get displayName() {return "ContentColumn";}
|
||||
|
||||
render() {
|
||||
let contentModule = BDModules.get(e => e.contentColumn)[0]
|
||||
return BDV2.react.createElement(
|
||||
"div",
|
||||
{className: contentModule.contentColumn + " "+contentModule.contentColumnDefault+" content-column default"},
|
||||
BDV2.react.createElement(
|
||||
"h2",
|
||||
{className: "ui-form-title h2 margin-reset margin-bottom-20"},
|
||||
this.props.title
|
||||
),
|
||||
this.props.children
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
import {settingsCookie} from "../0globals";
|
||||
import Settings from "../modules/settingsPanel";
|
||||
import BDV2 from "../modules/v2";
|
||||
import DataStore from "../modules/dataStore";
|
||||
import DOM from "../modules/domtools";
|
||||
|
||||
import SettingsTitle from "./settingsTitle";
|
||||
import Checkbox from "./checkbox";
|
||||
import V2C_CssEditorDetached from "./cssEditorDetached";
|
||||
|
||||
export default class V2C_CssEditor extends BDV2.reactComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const self = this;
|
||||
self.props.lines = 0;
|
||||
self.setInitialState();
|
||||
self.attach = self.attach.bind(self);
|
||||
self.detachedEditor = BDV2.react.createElement(V2C_CssEditorDetached, {attach: self.attach});
|
||||
self.onClick = self.onClick.bind(self);
|
||||
self.updateCss = self.updateCss.bind(self);
|
||||
self.saveCss = self.saveCss.bind(self);
|
||||
self.detach = self.detach.bind(self);
|
||||
}
|
||||
|
||||
setInitialState() {
|
||||
this.state = {
|
||||
detached: this.props.detached || BDV2.editorDetached
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// this.updateLineCount();
|
||||
this.editor = ace.edit("bd-customcss-editor");
|
||||
this.editor.setTheme("ace/theme/monokai");
|
||||
this.editor.session.setMode("ace/mode/css");
|
||||
this.editor.setShowPrintMargin(false);
|
||||
this.editor.setFontSize(14);
|
||||
this.editor.on("change", () => {
|
||||
if (!settingsCookie["bda-css-0"]) return;
|
||||
this.saveCss();
|
||||
this.updateCss();
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.editor.destroy();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const self = this;
|
||||
if (prevState.detached && !self.state.detached) {
|
||||
BDV2.reactDom.unmountComponentAtNode(self.detachedRoot);
|
||||
}
|
||||
}
|
||||
|
||||
codeMirror() {
|
||||
}
|
||||
|
||||
get options() {
|
||||
return {
|
||||
lineNumbers: true,
|
||||
mode: "css",
|
||||
indentUnit: 4,
|
||||
theme: "material",
|
||||
scrollbarStyle: "simple"
|
||||
};
|
||||
}
|
||||
|
||||
get css() {
|
||||
const _ccss = DataStore.getBDData("bdcustomcss");
|
||||
let ccss = "";
|
||||
if (_ccss && _ccss !== "") {
|
||||
ccss = atob(_ccss);
|
||||
}
|
||||
return ccss;
|
||||
}
|
||||
|
||||
updateLineCount() {
|
||||
const lineCount = this.refs.editor.value.split("\n").length;
|
||||
if (lineCount == this.props.lines) return;
|
||||
this.refs.lines.textContent = Array.from(new Array(lineCount), (_, i) => i + 1).join(".\n") + ".";
|
||||
this.props.lines = lineCount;
|
||||
}
|
||||
|
||||
render() {
|
||||
const self = this;
|
||||
|
||||
const {detached} = self.state;
|
||||
let contentModule = BDModules.get(e => e.contentColumn)[0]
|
||||
return BDV2.react.createElement(
|
||||
"div",
|
||||
{className: contentModule.contentColumn+" "+contentModule.contentColumnDefault+" content-column default", style: {padding: "60px 40px 0px"}},
|
||||
detached && BDV2.react.createElement(
|
||||
"div",
|
||||
{id: "editor-detached"},
|
||||
BDV2.react.createElement(SettingsTitle, {text: "Custom CSS Editor"}),
|
||||
BDV2.react.createElement(
|
||||
"h3",
|
||||
null,
|
||||
"Editor Detached"
|
||||
),
|
||||
BDV2.react.createElement(
|
||||
"button",
|
||||
{className: "btn btn-primary", onClick: () => {
|
||||
self.attach();
|
||||
}},
|
||||
"Attach"
|
||||
)
|
||||
),
|
||||
!detached && BDV2.react.createElement(
|
||||
"div",
|
||||
null,
|
||||
BDV2.react.createElement(SettingsTitle, {text: "Custom CSS Editor"}),
|
||||
BDV2.react.createElement("div", {className: "editor-wrapper"},
|
||||
BDV2.react.createElement("div", {id: "bd-customcss-editor", className: "editor", ref: "editor"}, self.css)
|
||||
),
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{id: "bd-customcss-attach-controls"},
|
||||
BDV2.react.createElement(
|
||||
"ul",
|
||||
{className: "checkbox-group"},
|
||||
BDV2.react.createElement(Checkbox, {id: "live-update", text: "Live Update", onChange: this.onChange, checked: settingsCookie["bda-css-0"]})
|
||||
),
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{id: "bd-customcss-detach-controls-button"},
|
||||
BDV2.react.createElement(
|
||||
"button",
|
||||
{style: {borderRadius: "3px 0 0 3px", borderRight: "1px solid #3f4146"}, className: "btn btn-primary", onClick: () => {
|
||||
self.onClick("update");
|
||||
}},
|
||||
"Update"
|
||||
),
|
||||
BDV2.react.createElement(
|
||||
"button",
|
||||
{style: {borderRadius: "0", borderLeft: "1px solid #2d2d2d", borderRight: "1px solid #2d2d2d"}, className: "btn btn-primary", onClick: () => {
|
||||
self.onClick("save");
|
||||
}},
|
||||
"Save"
|
||||
),
|
||||
BDV2.react.createElement(
|
||||
"button",
|
||||
{style: {borderRadius: "0 3px 3px 0", borderLeft: "1px solid #3f4146"}, className: "btn btn-primary", onClick: () => {
|
||||
self.onClick("detach");
|
||||
}},
|
||||
"Detach"
|
||||
),
|
||||
BDV2.react.createElement(
|
||||
"span",
|
||||
{style: {fontSize: "10px", marginLeft: "5px"}},
|
||||
"Unsaved changes are lost on detach"
|
||||
),
|
||||
BDV2.react.createElement("div", {className: "help-text"},
|
||||
"Press ",
|
||||
BDV2.react.createElement("code", {className: "inline"}, "ctrl"),
|
||||
"+",
|
||||
BDV2.react.createElement("span", {className: "inline"}, ","),
|
||||
" with the editor focused to access the editor's settings."
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
onClick(arg) {
|
||||
const self = this;
|
||||
switch (arg) {
|
||||
case "update":
|
||||
self.updateCss();
|
||||
break;
|
||||
case "save":
|
||||
self.saveCss();
|
||||
break;
|
||||
case "detach":
|
||||
self.detach();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onChange(id, checked) {
|
||||
switch (id) {
|
||||
case "live-update":
|
||||
settingsCookie["bda-css-0"] = checked;
|
||||
Settings.saveSettings();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
updateCss() {
|
||||
DOM.removeStyle("customcss");
|
||||
DOM.addStyle("customcss", this.editor.session.getValue());
|
||||
}
|
||||
|
||||
saveCss() {
|
||||
DataStore.setBDData("bdcustomcss", btoa(this.editor.session.getValue()));
|
||||
}
|
||||
|
||||
detach() {
|
||||
const self = this;
|
||||
self.setState({
|
||||
detached: true
|
||||
});
|
||||
const droot = self.detachedRoot;
|
||||
if (!droot) {
|
||||
console.log("FAILED TO INJECT ROOT: .app");
|
||||
return;
|
||||
}
|
||||
BDV2.reactDom.render(self.detachedEditor, droot);
|
||||
}
|
||||
|
||||
get detachedRoot() {
|
||||
const _root = DOM.query("#bd-customcss-detach-container");
|
||||
if (!_root) {
|
||||
if (!this.injectDetachedRoot()) return null;
|
||||
return this.detachedRoot;
|
||||
}
|
||||
return _root;
|
||||
}
|
||||
|
||||
injectDetachedRoot() {
|
||||
const app = DOM.query(".app, ."+BDModules.get(e => e.app && e.layers)[0].app.split(" ")[0]);
|
||||
if (!app) return false;
|
||||
DOM.insertAfter(DOM.createElement(`<div id="bd-customcss-detach-container">`), app);
|
||||
return true;
|
||||
}
|
||||
|
||||
attach() {
|
||||
const self = this;
|
||||
self.setState({
|
||||
detached: false
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
import {settingsCookie} from "../0globals";
|
||||
import Settings from "../modules/settingsPanel";
|
||||
import BDV2 from "../modules/v2";
|
||||
import DataStore from "../modules/dataStore";
|
||||
import DOM from "../modules/domtools";
|
||||
|
||||
import Checkbox from "./checkbox";
|
||||
|
||||
export default class V2C_CssEditorDetached extends BDV2.reactComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const self = this;
|
||||
self.onClick = self.onClick.bind(self);
|
||||
self.updateCss = self.updateCss.bind(self);
|
||||
self.saveCss = self.saveCss.bind(self);
|
||||
self.onChange = self.onChange.bind(self);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
DOM.addClass(DOM.query("#app-mount"), "bd-detached-editor");
|
||||
BDV2.editorDetached = true;
|
||||
// this.updateLineCount();
|
||||
this.editor = ace.edit("bd-customcss-editor-detached");
|
||||
this.editor.setTheme("ace/theme/monokai");
|
||||
this.editor.session.setMode("ace/mode/css");
|
||||
this.editor.setShowPrintMargin(false);
|
||||
this.editor.setFontSize(14);
|
||||
this.editor.on("change", () => {
|
||||
if (!settingsCookie["bda-css-0"]) return;
|
||||
this.saveCss();
|
||||
this.updateCss();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
DOM.removeClass(DOM.query("#app-mount"), "bd-detached-editor");
|
||||
BDV2.editorDetached = false;
|
||||
this.editor.destroy();
|
||||
}
|
||||
|
||||
updateLineCount() {
|
||||
const lineCount = this.refs.editor.value.split("\n").length;
|
||||
if (lineCount == this.props.lines) return;
|
||||
this.refs.lines.textContent = Array.from(new Array(lineCount), (_, i) => i + 1).join(".\n") + ".";
|
||||
this.props.lines = lineCount;
|
||||
}
|
||||
|
||||
get options() {
|
||||
return {
|
||||
lineNumbers: true,
|
||||
mode: "css",
|
||||
indentUnit: 4,
|
||||
theme: "material",
|
||||
scrollbarStyle: "simple"
|
||||
};
|
||||
}
|
||||
|
||||
get css() {
|
||||
const _ccss = DataStore.getBDData("bdcustomcss");
|
||||
let ccss = "";
|
||||
if (_ccss && _ccss !== "") {
|
||||
ccss = atob(_ccss);
|
||||
}
|
||||
return ccss;
|
||||
}
|
||||
|
||||
get root() {
|
||||
const _root = DOM.query("#bd-customcss-detach-container");
|
||||
if (!_root) {
|
||||
if (!this.injectRoot()) return null;
|
||||
return this.detachedRoot;
|
||||
}
|
||||
return _root;
|
||||
}
|
||||
|
||||
injectRoot() {
|
||||
const app = DOM.query(".app, ."+BDModules.get(e => e.app && e.layers)[0].app.split(" ")[0]);
|
||||
if (!app) return false;
|
||||
DOM.insertAfter(DOM.createElement(`<div id="bd-customcss-detach-container">`), app);
|
||||
return true;
|
||||
}
|
||||
|
||||
render() {
|
||||
const self = this;
|
||||
return BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "bd-detached-css-editor", id: "bd-customcss-detach-editor"},
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{id: "bd-customcss-innerpane"},
|
||||
BDV2.react.createElement("div", {className: "editor-wrapper"},
|
||||
BDV2.react.createElement("div", {id: "bd-customcss-editor-detached", className: "editor", ref: "editor"}, self.css)
|
||||
),
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{id: "bd-customcss-attach-controls"},
|
||||
BDV2.react.createElement(
|
||||
"ul",
|
||||
{className: "checkbox-group"},
|
||||
BDV2.react.createElement(Checkbox, {id: "live-update", text: "Live Update", onChange: self.onChange, checked: settingsCookie["bda-css-0"]})
|
||||
),
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{id: "bd-customcss-detach-controls-button"},
|
||||
BDV2.react.createElement(
|
||||
"button",
|
||||
{style: {borderRadius: "3px 0 0 3px", borderRight: "1px solid #3f4146"}, className: "btn btn-primary", onClick: () => {
|
||||
self.onClick("update");
|
||||
}},
|
||||
"Update"
|
||||
),
|
||||
BDV2.react.createElement(
|
||||
"button",
|
||||
{style: {borderRadius: "0", borderLeft: "1px solid #2d2d2d", borderRight: "1px solid #2d2d2d"}, className: "btn btn-primary", onClick: () => {
|
||||
self.onClick("save");
|
||||
}},
|
||||
"Save"
|
||||
),
|
||||
BDV2.react.createElement(
|
||||
"button",
|
||||
{style: {borderRadius: "0 3px 3px 0", borderLeft: "1px solid #3f4146"}, className: "btn btn-primary", onClick: () => {
|
||||
self.onClick("attach");
|
||||
}},
|
||||
"Attach"
|
||||
),
|
||||
BDV2.react.createElement(
|
||||
"span",
|
||||
{style: {fontSize: "10px", marginLeft: "5px"}},
|
||||
"Unsaved changes are lost on attach"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
onChange(id, checked) {
|
||||
switch (id) {
|
||||
case "live-update":
|
||||
settingsCookie["bda-css-0"] = checked;
|
||||
Settings.saveSettings();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onClick(id) {
|
||||
const self = this;
|
||||
switch (id) {
|
||||
case "attach":
|
||||
if (DOM.query("#editor-detached")) self.props.attach();
|
||||
BDV2.reactDom.unmountComponentAtNode(self.root);
|
||||
self.root.remove();
|
||||
break;
|
||||
case "update":
|
||||
self.updateCss();
|
||||
break;
|
||||
case "save":
|
||||
self.saveCss();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
updateCss() {
|
||||
DOM.removeStyle("customcss");
|
||||
DOM.addStyle("customcss", this.editor.session.getValue());
|
||||
}
|
||||
|
||||
saveCss() {
|
||||
DataStore.setBDData("bdcustomcss", btoa(this.editor.session.getValue()));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import BDV2 from "../modules/v2";
|
||||
|
||||
export default class BDErrorBoundary extends BDV2.reactComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {hasError: false};
|
||||
}
|
||||
|
||||
componentDidCatch() {
|
||||
this.setState({hasError: true});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) return BDV2.react.createElement("div", {className: "react-error"}, "Component Error");
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import BDV2 from "../../modules/v2";
|
||||
|
||||
const React = BDV2.React;
|
||||
|
||||
export default class Delete extends React.Component {
|
||||
render() {
|
||||
const size = this.props.size || "24px";
|
||||
return <svg className={this.props.className || ""} fill="#FFFFFF" viewBox="0 0 24 24" style={{width: size, height: size}} onClick={this.props.onClick}>
|
||||
<path fill="none" d="M0 0h24v24H0V0z"/><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zm2.46-7.12l1.41-1.41L12 12.59l2.12-2.12 1.41 1.41L13.41 14l2.12 2.12-1.41 1.41L12 15.41l-2.12 2.12-1.41-1.41L10.59 14l-2.13-2.12zM15.5 4l-1-1h-5l-1 1H5v2h14V4z"/>
|
||||
<path fill="none" d="M0 0h24v24H0z"/>
|
||||
</svg>;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import BDV2 from "../../modules/v2";
|
||||
|
||||
const React = BDV2.React;
|
||||
|
||||
export default class DownArrow extends React.Component {
|
||||
render() {
|
||||
const size = this.props.size || "16px";
|
||||
return <svg className={this.props.className || ""} fill="#FFFFFF" viewBox="0 0 24 24" style={{width: size, height: size}}>
|
||||
<path d="M8.12 9.29L12 13.17l3.88-3.88c.39-.39 1.02-.39 1.41 0 .39.39.39 1.02 0 1.41l-4.59 4.59c-.39.39-1.02.39-1.41 0L6.7 10.7c-.39-.39-.39-1.02 0-1.41.39-.38 1.03-.39 1.42 0z"/>
|
||||
</svg>;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import BDV2 from "../../modules/v2";
|
||||
|
||||
const React = BDV2.React;
|
||||
|
||||
export default class Edit extends React.Component {
|
||||
render() {
|
||||
const size = this.props.size || "24px";
|
||||
return <svg className={this.props.className || ""} viewBox="0 0 24 24" fill="#FFFFFF" style={{width: size, height: size}} onClick={this.props.onClick}>
|
||||
<path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" />
|
||||
<path d="M0 0h24v24H0z" fill="none" />
|
||||
</svg>;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{/* <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="18px" height="18px">
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z"/>
|
||||
</svg> */}
|
||||
|
||||
import BDV2 from "../../modules/v2";
|
||||
|
||||
const React = BDV2.React;
|
||||
|
||||
export default class History extends React.Component {
|
||||
render() {
|
||||
const size = this.props.size || "18px";
|
||||
return <svg viewBox="0 0 24 24" fill="#FFFFFF" className={this.props.className || ""} style={{width: size, height: size}} onClick={this.props.onClick}>
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z"/>
|
||||
</svg>;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import BDV2 from "../../modules/v2";
|
||||
|
||||
const React = BDV2.React;
|
||||
|
||||
export default class Search extends React.Component {
|
||||
render() {
|
||||
const size = this.props.size || "16px";
|
||||
return <svg className={this.props.className || ""} fill="#FFFFFF" viewBox="0 0 24 24" style={{width: size, height: size}}>
|
||||
<path fill="none" d="M0 0h24v24H0V0z"/>
|
||||
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
|
||||
</svg>;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
import BDV2 from "../modules/v2";
|
||||
|
||||
export default class LightcordLogo extends BDV2.reactComponent {
|
||||
render() {
|
||||
return BDV2.react.createElement(
|
||||
"svg",
|
||||
{height: "100%", width: this.props.size, className: "bd-logo " + this.props.className, style: {fillRule: "evenodd", clipRule: "evenodd", strokeLinecap: "round", strokeLinejoin: "round"}, viewBox: "0 0 168 190"},
|
||||
BDV2.react.createElement("metadata", null),
|
||||
BDV2.react.createElement("rect", {
|
||||
x: "14.9",
|
||||
y: "35.3",
|
||||
className: "st0",
|
||||
width: "139.2",
|
||||
height: "97.7",
|
||||
fill: "#FFFFFF"
|
||||
}),
|
||||
BDV2.react.createElement("defs", null, ...[
|
||||
BDV2.react.createElement("linearGradient", {
|
||||
id: "SVGID_1_",
|
||||
gradientUnits: "userSpaceOnUse",
|
||||
x1: "66.7",
|
||||
y1: "112.3",
|
||||
x2: "66.7",
|
||||
y2: "91.2685",
|
||||
gradientTransform: "matrix(1 0 0 -1 0 192)"
|
||||
},
|
||||
BDV2.react.createElement("stop", {
|
||||
offset: "0",
|
||||
style: {
|
||||
stopColor: "#E20613"
|
||||
}
|
||||
}),
|
||||
BDV2.react.createElement("stop", {
|
||||
offset: "1",
|
||||
style: {
|
||||
stopColor: "#731A14"
|
||||
}
|
||||
})
|
||||
),
|
||||
BDV2.react.createElement("linearGradient", {
|
||||
id: "SVGID_2_",
|
||||
gradientUnits: "userSpaceOnUse",
|
||||
x1: "101.7",
|
||||
y1: "112.3",
|
||||
x2: "101.7",
|
||||
y2: "91.2685",
|
||||
gradientTransform: "matrix(1 0 0 -1 0 192)"
|
||||
},
|
||||
BDV2.react.createElement("stop", {
|
||||
offset: "0",
|
||||
style: {
|
||||
stopColor: "#E20613"
|
||||
}
|
||||
}),
|
||||
BDV2.react.createElement("stop", {
|
||||
offset: "1",
|
||||
style: {
|
||||
stopColor: "#731A13"
|
||||
}
|
||||
})
|
||||
),
|
||||
BDV2.react.createElement("linearGradient", {
|
||||
id: "SVGID_3_",
|
||||
gradientUnits: "userSpaceOnUse",
|
||||
x1: "84",
|
||||
y1: "192",
|
||||
x2: "84",
|
||||
y2: "2",
|
||||
gradientTransform: "matrix(1 0 0 -1 0 192)"
|
||||
},
|
||||
BDV2.react.createElement("stop", {
|
||||
offset: "0",
|
||||
style: {
|
||||
stopColor: "#E30613"
|
||||
}
|
||||
}),
|
||||
BDV2.react.createElement("stop", {
|
||||
offset: "1",
|
||||
style: {
|
||||
stopColor: "#731A13"
|
||||
}
|
||||
})
|
||||
),
|
||||
]),
|
||||
BDV2.react.createElement("g", null,
|
||||
BDV2.react.createElement("path", {className: "st1", d: "M66.7,79.7c-5.4,0-9.8,4.7-9.8,10.5s4.4,10.5,9.8,10.5s9.8-4.7,9.8-10.5C76.5,84.4,72.1,79.7,66.7,79.7z", fill: "url(#SVGID_1_)", opacity: "1"}),
|
||||
BDV2.react.createElement("path", {className: "st2", d: "M101.7,79.7c-5.4,0-9.8,4.7-9.8,10.5s4.4,10.5,9.8,10.5s9.8-4.7,9.8-10.5C111.5,84.4,107.1,79.7,101.7,79.7z", fill: "url(#SVGID_2_)", opacity: "1"}),
|
||||
BDV2.react.createElement("path", {className: "st3", d: "M148.4,0H19.6C8.8,0,0,8.8,0,19.6V148c0,10.8,8.8,19.6,19.6,19.6h108.9l-5.1-17.5l12.3,11.3l11.6,10.7 L168,190v-41.9v-9.5v-119C168,8.8,159.2,0,148.4,0z M111.3,124.1c0,0-3.4-4.1-6.3-7.7c12.6-3.5,17.4-11.3,17.4-11.3 c-4,2.6-7.7,4.4-11.1,5.6c-4.8,2-9.5,3.3-14,4.1c-9.2,1.7-17.6,1.3-24.9-0.1c-5.5-1-10.2-2.5-14.1-4.1c-2.2-0.8-4.6-1.9-7.1-3.3 c-0.3-0.2-0.6-0.3-0.9-0.5c-0.1-0.1-0.3-0.2-0.4-0.2c-1.7-1-2.6-1.6-2.6-1.6s4.6,7.6,16.8,11.2c-2.9,3.6-6.4,7.9-6.4,7.9 c-21.2-0.6-29.3-14.5-29.3-14.5c0-30.6,13.8-55.4,13.8-55.4c13.8-10.3,26.9-10,26.9-10l1,1.1C52.8,50.3,45,57.9,45,57.9 s2.1-1.2,5.7-2.7c10.3-4.5,18.4-5.7,21.8-6c0.5-0.1,1.1-0.2,1.6-0.2c5.9-0.7,12.5-0.9,19.4-0.2c9.1,1,18.9,3.7,28.9,9.1 c0,0-7.5-7.2-23.9-12.1l1.3-1.5c0,0,13.1-0.3,26.9,10c0,0,13.8,24.8,13.8,55.4C140.6,109.6,132.5,123.5,111.3,124.1z", fill: "url(#SVGID_3_)", opacity: "1"})
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import BDV2 from "../modules/v2";
|
||||
|
||||
export default class V2C_List extends BDV2.reactComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return BDV2.react.createElement(
|
||||
"ul",
|
||||
{className: this.props.className},
|
||||
this.props.children
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,380 @@
|
|||
import {settingsCookie} from "../0globals";
|
||||
import BDV2 from "../modules/v2";
|
||||
import Utils from "../modules/utils";
|
||||
import DOM from "../modules/domtools";
|
||||
|
||||
import XSvg from "./xSvg";
|
||||
import ReloadIcon from "./reloadIcon";
|
||||
import EditIcon from "./icons/edit";
|
||||
import DeleteIcon from "./icons/delete";
|
||||
import Switch from "./components/switch";
|
||||
import TooltipWrap from "./tooltipWrap";
|
||||
import V2C_SettingsTitle from "./settingsTitle";
|
||||
import V2C_SettingsGroup from "./settingsGroup";
|
||||
|
||||
const React = BDV2.React;
|
||||
const anchorClasses = BDV2.anchorClasses;
|
||||
|
||||
export default class V2C_PresenceSettings extends BDV2.reactComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
console.log(props)
|
||||
}
|
||||
|
||||
updatePreview(data){
|
||||
this.setState({
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
let contentModule = BDModules.get(e => e.contentColumn)[0]
|
||||
return (<div className={contentModule.contentColumn+" "+contentModule.contentColumnDefault+" content-column default"}
|
||||
style={{padding: "60px 40px 0px"}}>
|
||||
<V2C_SettingsGroup title="RichPresence Settings" settings={this.props.settings} onChange={this.props.onChange}/>
|
||||
<V2C_SettingsTitle text="RichPresence"/>
|
||||
<div>
|
||||
{/** options */}
|
||||
{RPCProps.map(e => {
|
||||
return <PresenceSettingRow setting={e} />
|
||||
})}
|
||||
</div>
|
||||
<div>
|
||||
{/** preview */}
|
||||
<RpcPreview settings={this}/>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
}
|
||||
|
||||
class PresenceSettingRow extends BDV2.reactComponent {
|
||||
render(){
|
||||
let setting = this.props.setting
|
||||
|
||||
let rowModule = BDModules.get(e => e.removeKeybind)[0]
|
||||
let marginModule = BDModules.get(e => e.marginBottom20)[0]
|
||||
let marginModule2 = BDModules.get(e => e.defaultMarginh5)[0]
|
||||
let colorModule = BDModules.get(e => e.colorStandard)[0]
|
||||
let sizeModule = BDModules.get(e => e.size32)[0]
|
||||
|
||||
return (<div className={rowModule.row+" "+marginModule.marginBottom20}>
|
||||
<div className="item-rJ_Cmt da-item flexChild-faoVW3 da-flexChild">
|
||||
<h5 className={colorModule.colorStandard+" "+sizeModule.size14+" "+marginModule2.h5+" "+marginModule2.defaultMarginh5}>
|
||||
{setting.title}
|
||||
</h5>
|
||||
<div className="inputWrapper-31_8H8 da-inputWrapper">
|
||||
<input class="inputDefault-_djjkz input-cIJ7To size16-1__VVI" name="state" type="text" placeholder="" maxlength="999" value={setting.default} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider-3573oO da-divider dividerDefault-3rvLe- da-dividerDefault"></div>
|
||||
</div>)
|
||||
}
|
||||
}
|
||||
|
||||
class RpcPreview extends BDV2.reactComponent {
|
||||
constructor(props = {}){
|
||||
super(props)
|
||||
this.state = {
|
||||
active: "profile"
|
||||
}
|
||||
this.tabs = []
|
||||
}
|
||||
|
||||
changeTab(tab){
|
||||
let ancientTab = this.state.active
|
||||
if(ancientTab === tab.props.id)return
|
||||
|
||||
this.tabs.forEach(e => {
|
||||
e.setActive(false)
|
||||
})
|
||||
this.setState({
|
||||
active: tab.id
|
||||
})
|
||||
}
|
||||
|
||||
render(){
|
||||
return (<div className="lc-tabWrapper">
|
||||
<div className="lc-tabnav" style={{flex: "0 1 auto"}}>
|
||||
<Tab onClick={this.changeTab.bind(this)} preview={this} title="Full Profile" id="profile"/>
|
||||
<Tab onClick={this.changeTab.bind(this)} preview={this} title="User Popout" id="popout"/>
|
||||
</div>
|
||||
<Profile />
|
||||
</div>)
|
||||
}
|
||||
|
||||
isActive(tab){
|
||||
return this.state.active === tab
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} tmplate
|
||||
* @param {any} data
|
||||
*/
|
||||
renderTemplate(tmplate, data){
|
||||
Object.keys(data).forEach(k => {
|
||||
tmplate.replace(new RegExp("{{"+k+"}}", "g"), data[k])
|
||||
})
|
||||
return tmplate
|
||||
}
|
||||
}
|
||||
|
||||
class Tab extends BDV2.reactComponent {
|
||||
constructor(props){
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
active: props.preview.isActive(props.id)
|
||||
}
|
||||
props.preview.tabs.push(this)
|
||||
}
|
||||
|
||||
setActive(isActive){
|
||||
this.setState({
|
||||
active: !!isActive
|
||||
})
|
||||
}
|
||||
|
||||
render(){
|
||||
let className = `lc-navItem`
|
||||
if(this.state.active){
|
||||
className += ` lc-navItemActive`
|
||||
}else{
|
||||
className += ` lc-navItemInactive`
|
||||
}
|
||||
return (<div className={className} onClick={()=>{
|
||||
this.props.onClick(this)
|
||||
this.setActive(true)
|
||||
}}>
|
||||
{this.props.title}
|
||||
</div>)
|
||||
}
|
||||
}
|
||||
|
||||
class Status extends BDV2.reactComponent {
|
||||
render(){
|
||||
let status = BDModules.get(e => e.default && e.default.getPresence)[0].default.getPresence().status
|
||||
if(status === "invisible")status = "offline"
|
||||
let className = "pointerEvents-2zdfdO da-pointerEvents"
|
||||
return <rect width="16" height="16" x="60" y="60" fill="#ffffff" mask={`url(#svg-mask-status-${status})`} className={className}></rect>
|
||||
}
|
||||
}
|
||||
|
||||
class Profile extends BDV2.ReactComponent {
|
||||
render(){
|
||||
let user = BDModules.get(e => e.default && e.default.getCurrentUser)[0].default.getCurrentUser()
|
||||
let avatarURL = user.getAvatarURL(user.avatar.startsWith("a_") ? "gif" : "png")
|
||||
let [
|
||||
flexModule1,
|
||||
stylingModule1,
|
||||
rootModule1,
|
||||
avatarModule1,
|
||||
nameTagModule1
|
||||
] = [
|
||||
BDModules.get(e => e.flex && e._horizontal)[0],
|
||||
BDModules.get(e => e.vertical && e.alignStretch)[0],
|
||||
BDModules.get(e => e.topSectionStreaming)[0],
|
||||
BDModules.get(e => e.pointerEvents)[0],
|
||||
BDModules.get(e => e.bot)[0]
|
||||
]
|
||||
return [
|
||||
<div className="lc-tab">
|
||||
<div class={`${flexModule1.flex} ${stylingModule1.vertical} ${stylingModule1.justifyStart} ${stylingModule1.alignStretch} ${stylingModule1.noWrap} ${rootModule1.root}`} style={{flex: "1 1 auto"}}>
|
||||
<div class={rootModule1.topSectionPlaying}>
|
||||
<header class={rootModule1.header}>
|
||||
<div class={`${rootModule1.avatar} ${avatarModule1.wrapper}`} role="img" aria-hidden="true" style={{width: "80px", height: "80px"}}>
|
||||
<svg width="92" height="80" viewBox="0 0 92 80" class="mask-1l8v16 da-mask svg-2V3M55 da-svg" aria-hidden="true">
|
||||
<foreignObject x="0" y="0" width="80" height="80" mask="url(#svg-mask-avatar-status-round-80)">
|
||||
<img src={avatarURL} alt=" " class={avatarModule1.avatar} aria-hidden="true" />
|
||||
</foreignObject>
|
||||
<Status />
|
||||
</svg>
|
||||
</div>
|
||||
<div class={`${rootModule1.headerInfo}`}>
|
||||
<div class={`${rootModule1.nameTag} ${nameTagModule1.nameTag}`}>
|
||||
<span class={`${rootModule1.username} ${rootModule1.username}`}>{user.username}</span>
|
||||
<span class={rootModule1.discriminator}>#{user.discriminator}</span>
|
||||
</div>
|
||||
<div class={`${flexModule1.flex} ${flexModule1.horizontal} ${stylingModule1.justifyStart} ${stylingModule1.alignStretch} ${stylingModule1.noWrap} ${rootModule1.profileBadges}`} style={{flex: "1 1 auto"}}>
|
||||
<Badges />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class={rootModule1.headerFill}>
|
||||
<div class="activityProfile-2bJRaP da-activityProfile activity-1ythUs da-activity">
|
||||
<h3 class="headerTextNormal-2mGWX3 headerText-1HLrL7 marginBottom8-AtZOdT da-headerTextNormal da-headerText da-marginBottom8 base-1x0h_U da-base size12-3cLvbJ">En train de jouer</h3>
|
||||
<div class="bodyNormal-2D39hT body-ZAhrcj flex-1O1GKY alignStart-H-X2h- da-bodyNormal da-body da-flex da-alignStart">
|
||||
<div class="assets-VMAukC da-assets">
|
||||
<img alt="" src="https://cdn.discordapp.com/app-assets/708687209372581888/711293441526726747.png" class="assetsLargeImageProfile-3YXDex assetsLargeImage-eYwpTX noUserDrag-5Mb43F da-assetsLargeImageProfile da-assetsLargeImage da-noUserDrag assetsLargeMaskProfile-1Qkfen da-assetsLargeMaskProfile"/>
|
||||
<img alt="" src="https://cdn.discordapp.com/app-assets/708687209372581888/711293441526726747.png" class="assetsSmallImageProfile-3JcsV1 assetsSmallImage-3_3Bzj noUserDrag-5Mb43F da-assetsSmallImageProfile da-assetsSmallImage da-noUserDrag"/>
|
||||
</div>
|
||||
<div class="contentImagesProfile-1Mz07W content-3JfFJh da-contentImagesProfile da-content" style={{flex: "1 1 auto"}}>
|
||||
<h3 class="nameNormal-2lqVQK ellipsis-1XUmPN textRow-19NEd_ da-nameNormal da-ellipsis da-textRow base-1x0h_U da-base size14-e6ZScH" title="Deroku Vanity">
|
||||
<span class="activityName-1IaRLn da-activityName">Deroku Vanity</span>
|
||||
</h3>
|
||||
<div title="Lightcord test" class="details-38sfDr ellipsis-1XUmPN textRow-19NEd_ da-details da-ellipsis da-textRow">
|
||||
Lightcord test
|
||||
</div>
|
||||
<div class="state-Tt0LO3 ellipsis-1XUmPN textRow-19NEd_ da-state da-ellipsis da-textRow">
|
||||
<span title="gay comme phorcys">gay comme phorcys</span>
|
||||
</div>
|
||||
<div class="timestamp-VjAZmo ellipsis-1XUmPN textRow-19NEd_ da-timestamp da-ellipsis da-textRow">
|
||||
50 min 24 s écoulées
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class={rootModule1.body}>
|
||||
<div class="scrollerWrap-2lJEkd da-scrollerWrap scrollerFade-1Ijw5y da-scrollerFade">
|
||||
<div class="scroller-2FKFPG da-scroller">
|
||||
<div class="userInfoSection-2acyCx da-userInfoSection">
|
||||
<div class="userInfoSectionHeader-CBvMDh da-userInfoSectionHeader">Note</div>
|
||||
<div class="note-3kmerW da-note note-QfFU8y da-note">
|
||||
<textarea placeholder="Clique pour ajouter une note" maxlength="256" autocorrect="off" class="scrollbarGhostHairline-1mSOM1 scrollbar-3dvm_9 da-scrollbarGhostHairline da-scrollbar" style={{height: "40px"}} disabled></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="userInfoSection-2acyCx da-userInfoSection">
|
||||
<div class="connectedAccounts-repVzS da-connectedAccounts">
|
||||
<div class="flex-1xMQg5 flex-1O1GKY da-flex da-flex horizontal-1ae9ci horizontal-2EEEnY flex-1O1GKY directionRow-3v3tfG justifyStart-2NDFzi alignCenter-1dQNNs noWrap-3jynv6 connectedAccount-36nQx7 da-connectedAccount" style={{flex: "0 1 auto"}}>
|
||||
<img alt="Logo Twitter" class="connectedAccountIcon-3P3V6F da-connectedAccountIcon" src="/assets/4662875160dc4c56954003ebda995414.png" />
|
||||
<div class="connectedAccountNameInner-1phBvE da-connectedAccountNameInner">
|
||||
<div class="connectedAccountName-f8AEe2 da-connectedAccountName">jen_wina</div>
|
||||
<span>
|
||||
<div class="flowerStarContainer-3zDVtj da-flowerStarContainer connectedAccountVerifiedIcon-3aZz_K da-connectedAccountVerifiedIcon" style={{width: "16px", height: "16px"}}>
|
||||
<svg class="flowerStar-1GeTsn da-flowerStar" aria-hidden="true" width="16" height="16" viewBox="0 0 16 15.2">
|
||||
<path fill="#4f545c" fill-rule="evenodd" d="m16 7.6c0 .79-1.28 1.38-1.52 2.09s.44 2 0 2.59-1.84.35-2.46.8-.79 1.84-1.54 2.09-1.67-.8-2.47-.8-1.75 1-2.47.8-.92-1.64-1.54-2.09-2-.18-2.46-.8.23-1.84 0-2.59-1.54-1.3-1.54-2.09 1.28-1.38 1.52-2.09-.44-2 0-2.59 1.85-.35 2.48-.8.78-1.84 1.53-2.12 1.67.83 2.47.83 1.75-1 2.47-.8.91 1.64 1.53 2.09 2 .18 2.46.8-.23 1.84 0 2.59 1.54 1.3 1.54 2.09z"></path>
|
||||
</svg>
|
||||
<div class="childContainer-1wxZNh da-childContainer">
|
||||
<svg aria-hidden="true" width="16" height="16" viewBox="0 0 16 15.2">
|
||||
<path d="M7.4,11.17,4,8.62,5,7.26l2,1.53L10.64,4l1.36,1Z" fill="#ffffff"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<a class="anchor-3Z-8Bb da-anchor anchorUnderlineOnHover-2ESHQB da-anchorUnderlineOnHover" rel="noreferrer noopener" target="_blank" role="button" tabindex="0">
|
||||
<svg class="connectedAccountOpenIcon-2cNbq5 da-connectedAccountOpenIcon" aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M10 5V3H5.375C4.06519 3 3 4.06519 3 5.375V18.625C3 19.936 4.06519 21 5.375 21H18.625C19.936 21 21 19.936 21 18.625V14H19V19H5V5H10Z"></path>
|
||||
<path fill="currentColor" d="M21 2.99902H14V4.99902H17.586L9.29297 13.292L10.707 14.706L19 6.41302V9.99902H21V2.99902Z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
<div class="lc-fadeOverlay"></div>
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
class Badges extends BDV2.reactComponent {
|
||||
render(){
|
||||
let user = BDModules.get(e => e.default && e.default.getCurrentUser)[0].default.getCurrentUser()
|
||||
let rootModule = BDModules.get(e => e.topSectionStreaming)[0]
|
||||
let UserFlags = BDModules.get(e => e.UserFlags)[0].UserFlags
|
||||
let badges = []
|
||||
let serialized = []
|
||||
|
||||
for(let flagName in UserFlags){
|
||||
if(user.hasFlag(UserFlags[flagName]))serialized.push(flagName)
|
||||
}
|
||||
|
||||
for(let flagName of serialized){
|
||||
let searchable = `profileBadge${flagName.toLowerCase().replace(/_/g, " ").split(" ").map(e => e[0].toUpperCase()+e.slice(1)).join("")}`
|
||||
searchable = searchable.replace("HypesquadOnline", "HypeSquadOnline")
|
||||
console.log(searchable, rootModule[searchable])
|
||||
if(!rootModule[searchable])continue
|
||||
badges.push(<Badge name={searchable}/>)
|
||||
}
|
||||
|
||||
if(user.hasPremiumSubscription){
|
||||
badges.push(<Badge name="profileBadgePremium" />)
|
||||
}
|
||||
|
||||
return badges
|
||||
}
|
||||
}
|
||||
|
||||
class Badge extends BDV2.reactComponent {
|
||||
render(){
|
||||
let rootModule1 = BDModules.get(e => e.topSectionStreaming)[0]
|
||||
|
||||
return (<div class={rootModule1.profileBadgeWrapper}>
|
||||
<div>
|
||||
<div class="" role="button" tabindex="0">
|
||||
<div class={`profileBadge-2BqF-Z da-profileBadge ${rootModule1[this.props.name]}`}>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const RPCProps = [
|
||||
{
|
||||
title: "State",
|
||||
id: "state",
|
||||
default: "Browsing Discord",
|
||||
type: "text"
|
||||
},
|
||||
{
|
||||
title: "Details",
|
||||
id: "details",
|
||||
default: "Lightcord",
|
||||
type: "text"
|
||||
}
|
||||
]
|
||||
|
||||
const Constants = {
|
||||
PresenceViewer: {
|
||||
USER_ACTIVITY_HEADER_WATCHING: "Watching {name}",
|
||||
USER_ACTIVITY_HEADER_PLAYING: "Playing a game",
|
||||
USER_ACTIVITY_STATE_SIZE: "({count} of {max})",
|
||||
USER_ACTIVITY_TIMESTAMP_END: "{hours, plural, =-1 {} other {{hours}:}}{minutes, plural, =-1 {} other {{minutes}:}}{seconds, plural, =-1 {} other {{seconds}}} left",
|
||||
USER_ACTIVITY_TIMESTAMP_START: "{hours, plural, =-1 {} other {{hours}:}}{minutes, plural, =-1 {} other {{minutes}:}}{seconds, plural, =-1 {} other {{seconds}}} elapsed",
|
||||
USER_ACTIVITY_ACTION_NOTIFY_ME: "Notify Me",
|
||||
USER_ACTIVITY_ACTION_ASK_TO_JOIN: "Ask to Join",
|
||||
USER_ACTIVITY_ACTION_SPECTATE: "Spectate",
|
||||
SECTION_TITLE: "Rich Presence Visualizer",
|
||||
SECTION_MORE_INFO: "Rich Presence lets your game surface exciting game data on your players' profiles, and lets them play together with chat invites, Ask to Join, and Spectate. See exactly how your text and art will look on a user's profile.",
|
||||
PROFILE: "Full Profile",
|
||||
USER_POPOUT: "User Popout",
|
||||
ACTIVITY_FEED: "Games Tab",
|
||||
NONE: "None",
|
||||
SHOW_CODE: "Show Code",
|
||||
MOBILE_ALERT: "Rich Presence Visualizer only available on desktop",
|
||||
PARTY_ID_MUST_BE_UNIQUE: "Party ID can't match Join or Spectate Secrets.",
|
||||
SECRETS_MUST_BE_UNIQUE: "Join and Spectate Secrets must be unique strings."
|
||||
},
|
||||
Tooltips: {
|
||||
STATE: "[type: char*]\nThe user's current party status",
|
||||
DETAILS: "[type: char*]\nWhat the player is currently doing",
|
||||
START_TIMESTAMP: '[type: int64_t]\nEpoch seconds for game start - including will show time as "elapsed"',
|
||||
END_TIMESTAMP: '[type: int64_t]\nEpoch seconds for game end - including will show time as "remaining"\t',
|
||||
LARGE_IMAGE_KEY: "[type: char*]\nKey of the uploaded image for the large profile artwork",
|
||||
LARGE_IMAGE_TEXT: "[type: char*]\nTooltip for the largeImageKey",
|
||||
SMALL_IMAGE_KEY: "[type: char*]\nKey of the uploaded image for the small profile artwork",
|
||||
SMALL_IMAGE_TEXT: "[type: char*]\nTooltip for the smallImageKey",
|
||||
PARTY_ID: "[type: char*]\nId of the player's party, lobby, or group",
|
||||
PARTY_SIZE: "[type: int]\nCurrent size of the player's party, lobby, or group",
|
||||
PARTY_MAX: "[type: int]\nMaximum size of the player's party, lobby, or group\t",
|
||||
SPECTATE_SECRET: "[type: char*]\nUnique hashed string for Spectate button",
|
||||
JOIN_SECRET: "[type: char*]\nUnique hashed string for chat invitations and Ask to Join"
|
||||
},
|
||||
UserProfile: {
|
||||
USER_INFO: "User Info",
|
||||
MUTUAL_SERVERS: "Mutual Servers",
|
||||
MUTUAL_FRIENDS: "Mutual Friends",
|
||||
NOTE: "Note",
|
||||
ADD_NOTE: "Click to add note"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
import BDV2 from "../../modules/v2";
|
||||
import DOM from "../../modules/domtools";
|
||||
|
||||
export default class V2C_Layer extends BDV2.reactComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.keyupListener = this.keyupListener.bind(this);
|
||||
}
|
||||
|
||||
keyupListener(e) {
|
||||
if (e.which === 27) {
|
||||
BDV2.reactDom.unmountComponentAtNode(this.refs.root.parentNode);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener("keyup", this.keyupListener);
|
||||
|
||||
const thisNode = DOM.query(`#${this.props.id}`);
|
||||
DOM.animate({
|
||||
duration: 200,
|
||||
update: function(progress) {
|
||||
thisNode.style.transform = `scale(${1.1 - 0.1 * progress}) translateZ(0px)`;
|
||||
thisNode.style.opacity = progress;
|
||||
if (progress == 1) {
|
||||
setImmediate(() => {
|
||||
thisNode.style.transform = "";
|
||||
thisNode.style.opacity = "";
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener("keyup", this.keyupListener);
|
||||
|
||||
const thisNode = DOM.query(`#${this.props.id}`);
|
||||
DOM.animate({
|
||||
duration: 200,
|
||||
update: function(progress) {
|
||||
thisNode.style.transform = `scale(${1.1 - 0.1 * (1 - progress)}) translateZ(0px)`;
|
||||
thisNode.style.opacity = 1 - progress;
|
||||
if (progress == 1) {
|
||||
setImmediate(() => {
|
||||
thisNode.remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const layer = DOM.query(".publicServersOpen");
|
||||
layer.classList.remove("publicServersOpen");
|
||||
DOM.animate({
|
||||
duration: 200,
|
||||
update: function(progress) {
|
||||
layer.style.transform = `scale(${0.07 * progress + 0.93}) translateZ(0px)`;
|
||||
layer.style.opacity = progress;
|
||||
if (progress == 1) {
|
||||
setImmediate(() => {
|
||||
layer.style.transform = "";
|
||||
layer.style.opacity = "";
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const layer = DOM.query("[class*=\"layer-\"]");
|
||||
layer.classList.add("publicServersOpen");
|
||||
DOM.animate({
|
||||
duration: 200,
|
||||
update: function(progress) {
|
||||
layer.style.transform = `scale(${0.07 * (1 - progress) + 0.93}) translateZ(0px)`;
|
||||
layer.style.opacity = 1 - progress;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "layer bd-layer "+BDModules.get(e => e.layer && e.animating)[0].layer, id: this.props.id, ref: "root", style: {opacity: 0, transform: "scale(1.1) translateZ(0px)"}},
|
||||
this.props.children
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,420 @@
|
|||
import BDV2 from "../../modules/v2";
|
||||
|
||||
import Tools from "../tools";
|
||||
import SettingsTitle from "../settingsTitle";
|
||||
import TabBarSeparator from "../tabBarSeparator";
|
||||
import TabBarHeader from "../tabBarHeader";
|
||||
import TabBarItem from "../tabBarItem";
|
||||
|
||||
import ServerCard from "./serverCard";
|
||||
import SidebarView from "./sidebarView";
|
||||
|
||||
export default class V2C_PublicServers extends BDV2.reactComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.setInitialState();
|
||||
this.close = this.close.bind(this);
|
||||
this.changeCategory = this.changeCategory.bind(this);
|
||||
this.search = this.search.bind(this);
|
||||
this.searchKeyDown = this.searchKeyDown.bind(this);
|
||||
this.checkConnection = this.checkConnection.bind(this);
|
||||
this.join = this.join.bind(this);
|
||||
this.connect = this.connect.bind(this);
|
||||
|
||||
this.GuildStore = BDV2.WebpackModules.findByUniqueProperties(["getGuilds"]);
|
||||
this.AvatarDefaults = BDV2.WebpackModules.findByUniqueProperties(["getUserAvatarURL", "DEFAULT_AVATARS"]);
|
||||
this.InviteActions = BDV2.WebpackModules.findByUniqueProperties(["acceptInvite"]);
|
||||
this.SortedGuildStore = BDV2.WebpackModules.findByUniqueProperties(["getSortedGuilds"]);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.checkConnection();
|
||||
}
|
||||
|
||||
setInitialState() {
|
||||
this.state = {
|
||||
selectedCategory: -1,
|
||||
title: "Loading...",
|
||||
loading: true,
|
||||
servers: [],
|
||||
next: null,
|
||||
connection: {
|
||||
state: 0,
|
||||
user: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
close() {
|
||||
BDV2.reactDom.unmountComponentAtNode(document.getElementById(this.props.rootId));
|
||||
}
|
||||
|
||||
search(query, clear) {
|
||||
const self = this;
|
||||
fetch(`${self.endPoint}${query}${query ? "&schema=new" : "?schema=new"}`, {
|
||||
method: "get"
|
||||
}).then(async res => {
|
||||
if(res.status !== 200)throw await res.text()
|
||||
let data = await res.json()
|
||||
|
||||
let servers = data.results.reduce((arr, server) => {
|
||||
server.joined = false;
|
||||
arr.push(server);
|
||||
// arr.push(<ServerCard server={server} join={self.join}/>);
|
||||
return arr;
|
||||
}, []);
|
||||
|
||||
if (!clear) {
|
||||
servers = self.state.servers.concat(servers);
|
||||
}
|
||||
else {
|
||||
//servers.unshift(self.bdServer);
|
||||
}
|
||||
|
||||
let end = data.size + data.from;
|
||||
data.next = `?from=${end}`;
|
||||
if (self.state.term) data.next += `&term=${self.state.term}`;
|
||||
if (self.state.selectedCategory) data.next += `&category=${self.categoryButtons[self.state.selectedCategory]}`;
|
||||
if (end >= data.total) {
|
||||
end = data.total;
|
||||
data.next = null;
|
||||
}
|
||||
|
||||
let title = `Showing 1-${end} of ${data.total} results in ${self.categoryButtons[self.state.selectedCategory]}`;
|
||||
if (self.state.term) title += ` for ${self.state.term}`;
|
||||
|
||||
self.setState({
|
||||
loading: false,
|
||||
title: title,
|
||||
servers: servers,
|
||||
next: data.next
|
||||
});
|
||||
|
||||
if (clear) {
|
||||
//console.log(self);
|
||||
self.refs.sbv.refs.contentScroller.scrollTop = 0;
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.error(err)
|
||||
return self.setState({
|
||||
loading: false,
|
||||
title: "Failed to load servers. Check console for details"
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
async join(serverCard) {
|
||||
if (serverCard.props.pinned) return this.InviteActions.acceptInvite(serverCard.props.invite_code);
|
||||
|
||||
await fetch(`${this.joinEndPoint}/${serverCard.props.server.identifier}`,{
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
mode: "cors",
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
});
|
||||
|
||||
serverCard.setState({joined: true});
|
||||
}
|
||||
|
||||
connect() {
|
||||
const self = this;
|
||||
const options = self.windowOptions;
|
||||
options.x = Math.round(window.screenX + window.innerWidth / 2 - options.width / 2);
|
||||
options.y = Math.round(window.screenY + window.innerHeight / 2 - options.height / 2);
|
||||
|
||||
self.joinWindow = new (window.require("electron").remote.BrowserWindow)(options);
|
||||
const url = "https://auth.discordservers.com/connect?scopes=guilds.join&previousUrl=https://auth.discordservers.com/info";
|
||||
self.joinWindow.webContents.on("did-navigate", (event, url) => {
|
||||
if (url != "https://auth.discordservers.com/info") return;
|
||||
self.joinWindow.close();
|
||||
self.checkConnection();
|
||||
});
|
||||
self.joinWindow.loadURL(url);
|
||||
}
|
||||
|
||||
get windowOptions() {
|
||||
return {
|
||||
width: 500,
|
||||
height: 550,
|
||||
backgroundColor: "#282b30",
|
||||
show: true,
|
||||
resizable: false,
|
||||
maximizable: false,
|
||||
minimizable: false,
|
||||
alwaysOnTop: true,
|
||||
frame: false,
|
||||
center: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
get bdServer() {
|
||||
const server = {
|
||||
name: "BetterDiscord",
|
||||
online: "7500+",
|
||||
members: "20000+",
|
||||
categories: ["community", "programming", "support"],
|
||||
description: "Official BetterDiscord server for support etc",
|
||||
identifier: "86004744966914048",
|
||||
iconUrl: "https://cdn.discordapp.com/icons/86004744966914048/292e7f6bfff2b71dfd13e508a859aedd.webp",
|
||||
nativejoin: true,
|
||||
invite_code: "0Tmfo5ZbORCRqbAd",
|
||||
pinned: true
|
||||
};
|
||||
const guildList = this.SortedGuildStore.getFlattenedGuildIds();
|
||||
const defaultList = this.AvatarDefaults.DEFAULT_AVATARS;
|
||||
return BDV2.react.createElement(ServerCard, {server: server, pinned: true, join: this.join, guildList: guildList, fallback: defaultList[Math.floor(Math.random() * 5)]});
|
||||
}
|
||||
|
||||
get endPoint() {
|
||||
return "https://search.discordservers.com";
|
||||
}
|
||||
|
||||
get joinEndPoint() {
|
||||
return "https://j.discordservers.com";
|
||||
}
|
||||
|
||||
get connectEndPoint() {
|
||||
return "https://join.discordservers.com/connect";
|
||||
}
|
||||
|
||||
async checkConnection() {
|
||||
const self = this;
|
||||
try {
|
||||
const response = await fetch(`https://auth.discordservers.com/info`,{
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
mode: "cors",
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
});
|
||||
const data = await response.json();
|
||||
self.setState({
|
||||
selectedCategory: 0,
|
||||
connection: {
|
||||
state: 2,
|
||||
user: data
|
||||
}
|
||||
});
|
||||
self.search("", true);
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
self.setState({
|
||||
title: "Not connected to discordservers.com!",
|
||||
loading: true,
|
||||
selectedCategory: -1,
|
||||
connection: {
|
||||
state: 1,
|
||||
user: null
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return BDV2.react.createElement(SidebarView, {ref: "sbv"}, this.component);
|
||||
}
|
||||
|
||||
get component() {
|
||||
return {
|
||||
sidebar: {
|
||||
component: this.sidebar
|
||||
},
|
||||
content: {
|
||||
component: this.content
|
||||
},
|
||||
tools: {
|
||||
component: BDV2.react.createElement(Tools, {key: "pt", ref: "tools", onClick: this.close})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
get sidebar() {
|
||||
return BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "sidebar", key: "ps"},
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "ui-tab-bar SIDE"},
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "ui-tab-bar-header", style: {fontSize: "16px"}},
|
||||
"Public Servers"
|
||||
),
|
||||
BDV2.react.createElement(TabBarSeparator, null),
|
||||
this.searchInput,
|
||||
BDV2.react.createElement(TabBarSeparator, null),
|
||||
BDV2.react.createElement(TabBarHeader, {text: "Categories"}),
|
||||
this.categoryButtons.map((value, index) => {
|
||||
return BDV2.react.createElement(TabBarItem, {id: index, onClick: this.changeCategory, key: index, text: value, selected: this.state.selectedCategory === index});
|
||||
}),
|
||||
BDV2.react.createElement(TabBarSeparator, null),
|
||||
this.footer,
|
||||
this.connection
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
get searchInput() {
|
||||
return BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "ui-form-item"},
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "ui-text-input flex-vertical", style: {width: "172px", marginLeft: "10px"}},
|
||||
BDV2.react.createElement("input", {ref: "searchinput", onKeyDown: this.searchKeyDown, onChange: () => {}, type: "text", className: "input default", placeholder: "Search...", maxLength: "50"})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
searchKeyDown(e) {
|
||||
const self = this;
|
||||
if (self.state.loading || e.which !== 13) return;
|
||||
self.setState({
|
||||
loading: true,
|
||||
title: "Loading...",
|
||||
term: e.target.value
|
||||
});
|
||||
let query = `?term=${e.target.value}`;
|
||||
if (self.state.selectedCategory !== 0) {
|
||||
query += `&category=${self.categoryButtons[self.state.selectedCategory]}`;
|
||||
}
|
||||
self.search(query, true);
|
||||
}
|
||||
|
||||
get categoryButtons() {
|
||||
return ["All", "FPS Games", "MMO Games", "Strategy Games", "MOBA Games", "RPG Games", "Tabletop Games", "Sandbox Games", "Simulation Games", "Music", "Community", "Language", "Programming", "Other"];
|
||||
}
|
||||
|
||||
changeCategory(id) {
|
||||
const self = this;
|
||||
if (self.state.loading) return;
|
||||
self.refs.searchinput.value = "";
|
||||
self.setState({
|
||||
loading: true,
|
||||
selectedCategory: id,
|
||||
title: "Loading...",
|
||||
term: null
|
||||
});
|
||||
if (id === 0) {
|
||||
self.search("", true);
|
||||
return;
|
||||
}
|
||||
self.search(`?category=${self.categoryButtons[id]}`, true);
|
||||
}
|
||||
|
||||
get content() {
|
||||
const self = this;
|
||||
const guildList = this.SortedGuildStore.getFlattenedGuildIds();
|
||||
const defaultList = this.AvatarDefaults.DEFAULT_AVATARS;
|
||||
if (self.state.connection.state === 1) return self.notConnected;
|
||||
let columnModule = BDModules.get(e => e.contentColumnDefault)[0]
|
||||
return [BDV2.react.createElement(
|
||||
"div",
|
||||
{ref: "content", key: "pc", className: columnModule.contentColumn+" "+columnModule.contentColumn+" content-column default"},
|
||||
BDV2.react.createElement(SettingsTitle, {text: self.state.title}),
|
||||
self.bdServer,
|
||||
self.state.servers.map((server) => {
|
||||
return BDV2.react.createElement(ServerCard, {key: server.identifier, server: server, join: self.join, guildList: guildList, fallback: defaultList[Math.floor(Math.random() * 5)]});
|
||||
}),
|
||||
self.state.next && BDV2.react.createElement(
|
||||
"button",
|
||||
{type: "button", onClick: () => {
|
||||
if (self.state.loading) return;self.setState({loading: true}); self.search(self.state.next, false);
|
||||
}, className: "ui-button filled brand small grow", style: {width: "100%", marginTop: "10px", marginBottom: "10px"}},
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "ui-button-contents"},
|
||||
self.state.loading ? "Loading" : "Load More"
|
||||
)
|
||||
),
|
||||
self.state.servers.length > 0 && BDV2.react.createElement(SettingsTitle, {text: self.state.title})
|
||||
)];
|
||||
}
|
||||
|
||||
get notConnected() {
|
||||
const self = this;
|
||||
//return BDV2.react.createElement(SettingsTitle, { text: self.state.title });
|
||||
let columnModule = BDModules.get(e => e.contentColumnDefault)[0]
|
||||
return [BDV2.react.createElement(
|
||||
"div",
|
||||
{key: "ncc", ref: "content", className: columnModule.contentColumn+" "+columnModule.contentColumn+" content-column default"},
|
||||
BDV2.react.createElement(
|
||||
"h2",
|
||||
{className: "ui-form-title h2 margin-reset margin-bottom-20"},
|
||||
"Not connected to discordservers.com!",
|
||||
BDV2.react.createElement(
|
||||
"button",
|
||||
{
|
||||
onClick: self.connect,
|
||||
type: "button",
|
||||
className: "ui-button filled brand small grow",
|
||||
style: {
|
||||
display: "inline-block",
|
||||
minHeight: "18px",
|
||||
marginLeft: "10px",
|
||||
lineHeight: "14px"
|
||||
}
|
||||
},
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "ui-button-contents"},
|
||||
"Connect"
|
||||
)
|
||||
)
|
||||
), self.bdServer
|
||||
)];
|
||||
}
|
||||
|
||||
get footer() {
|
||||
return BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "ui-tab-bar-header"},
|
||||
BDV2.react.createElement(
|
||||
"a",
|
||||
{href: "https://discordservers.com", target: "_blank"},
|
||||
"Discordservers.com"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
get connection() {
|
||||
const self = this;
|
||||
const {connection} = self.state;
|
||||
if (connection.state !== 2) return BDV2.react.createElement("span", null);
|
||||
|
||||
return BDV2.react.createElement(
|
||||
"span",
|
||||
null,
|
||||
BDV2.react.createElement(TabBarSeparator, null),
|
||||
BDV2.react.createElement(
|
||||
"span",
|
||||
{style: {color: "#b9bbbe", fontSize: "10px", marginLeft: "10px"}},
|
||||
"Connected as: ",
|
||||
`${connection.user.username}#${connection.user.discriminator}`
|
||||
),
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{style: {padding: "5px 10px 0 10px"}},
|
||||
BDV2.react.createElement(
|
||||
"button",
|
||||
{style: {width: "100%", minHeight: "20px"}, type: "button", className: "ui-button filled brand small grow"},
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "ui-button-contents", onClick: self.connect},
|
||||
"Reconnect"
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
import BDV2 from "../../modules/v2";
|
||||
|
||||
export default class V2C_ServerCard extends BDV2.reactComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
if (!this.props.server.iconUrl) this.props.server.iconUrl = this.props.fallback;
|
||||
this.state = {
|
||||
imageError: false,
|
||||
joined: this.props.guildList.includes(this.props.server.identifier)
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {server} = this.props;
|
||||
let cardModule = BDModules.get(e => e.card && e.cardPrimary)[0]
|
||||
let flexModule = BDModules.get(e => e.flexChild && e._horizontalReverse)[0]
|
||||
let wrapModule = BDModules.get(e => e.noWrap && !e.streamerModeEnabled)[0]
|
||||
return BDV2.react.createElement(
|
||||
"div", // cardPrimary-1Hv-to
|
||||
{className: `${cardModule.card} ${cardModule.cardPrimary} ${BDModules.get(e => e.marginBottom8)[0].marginBottom8} bd-server-card${server.pinned ? " bd-server-card-pinned" : ""}`},
|
||||
// BDV2.react.createElement(
|
||||
// "div",
|
||||
// { className: "flex-1xMQg5 flex-1O1GKY horizontal-1ae9ci horizontal-2EEEnY flex-1O1GKY directionRow-3v3tfG justifyStart-2yIZo0 alignStretch-1hwxMa noWrap-3jynv6" },
|
||||
BDV2.react.createElement("img", {ref: "img", className: "bd-server-image", src: server.iconUrl, onError: this.handleError.bind(this)}),
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{className: flexModule.flexChild+" bd-server-content"},
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{className: flexModule.horizontal+" "+wrapModule.noWrap+" bd-server-header"},
|
||||
BDV2.react.createElement(
|
||||
"h5",
|
||||
{className: "h5-18_1nd defaultColor-1_ajX0 margin-reset bd-server-name"},
|
||||
server.name
|
||||
),
|
||||
BDV2.react.createElement(
|
||||
"h5",
|
||||
{className: "h5-18_1nd defaultColor-1_ajX0 margin-reset bd-server-member-count"},
|
||||
server.members,
|
||||
" Members"
|
||||
)
|
||||
),
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{className: flexModule.horizontal+" "+wrapModule.noWrap},
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "scrollerWrap-2lJEkd scrollerThemed-2oenus themeGhostHairline-DBD-2d scrollerFade-1Ijw5y bd-server-description-container"},
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "scroller-2FKFPG scroller bd-server-description"},
|
||||
server.description
|
||||
)
|
||||
)
|
||||
),
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "flex-1xMQg5 flex-1O1GKY horizontal-1ae9ci horizontal-2EEEnY directionRow-3v3tfG noWrap-3jynv6 bd-server-footer"},
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "flexChild-faoVW3 bd-server-tags", style: {flex: "1 1 auto"}},
|
||||
server.categories.join(", ")
|
||||
),
|
||||
this.state.joined && BDV2.react.createElement(
|
||||
"button",
|
||||
{type: "button", className: "button-38aScr lookFilled-1Gx00P colorBrand-3pXr91 sizeMin-1mJd1x grow-q77ONN colorGreen-29iAKY", style: {minHeight: "12px", marginTop: "4px", backgroundColor: "#3ac15c"}},
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "ui-button-contents"},
|
||||
"Joined"
|
||||
)
|
||||
),
|
||||
server.error && BDV2.react.createElement(
|
||||
"button",
|
||||
{type: "button", className: "button-38aScr lookFilled-1Gx00P colorBrand-3pXr91 sizeMin-1mJd1x grow-q77ONN disabled-9aF2ug", style: {minHeight: "12px", marginTop: "4px", backgroundColor: "#c13a3a"}},
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "ui-button-contents"},
|
||||
"Error"
|
||||
)
|
||||
),
|
||||
!server.error && !this.state.joined && BDV2.react.createElement(
|
||||
"button",
|
||||
{type: "button", className: "button-38aScr lookFilled-1Gx00P colorBrand-3pXr91 sizeMin-1mJd1x grow-q77ONN", style: {minHeight: "12px", marginTop: "4px"}, onClick: () => {this.join();}},
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "ui-button-contents"},
|
||||
"Join"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
// )
|
||||
);
|
||||
}
|
||||
|
||||
handleError() {
|
||||
this.props.server.iconUrl = this.props.fallback;
|
||||
this.setState({imageError: true});
|
||||
}
|
||||
|
||||
join() {
|
||||
this.props.join(this);
|
||||
//this.setState({joined: true});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import BDV2 from "../../modules/v2";
|
||||
|
||||
import Scroller from "../scroller";
|
||||
|
||||
export default class V2C_SidebarView extends BDV2.reactComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {sidebar, content, tools} = this.props.children;
|
||||
return BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "standardSidebarView-3F1I7i ui-standard-sidebar-view"},
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "sidebarRegion-VFTUkN sidebar-region"},
|
||||
BDV2.react.createElement(Scroller, {key: "sidebarScroller", ref: "sidebarScroller", sidebar: true, fade: sidebar.fade || true, dark: sidebar.dark || true}, sidebar.component)
|
||||
),
|
||||
BDV2.react.createElement("div", {className: "contentRegion-3nDuYy content-region"},
|
||||
BDV2.react.createElement("div", {className: "contentTransitionWrap-3hqOEW content-transition-wrap"},
|
||||
BDV2.react.createElement("div", {className: "scrollerWrap-2lJEkd firefoxFixScrollFlex-cnI2ix contentRegionScrollerWrap-3YZXdm content-region-scroller-wrap scrollerThemed-2oenus themeGhost-28MSn0 scrollerTrack-1ZIpsv"},
|
||||
BDV2.react.createElement("div", {className: "scroller-2FKFPG firefoxFixScrollFlex-cnI2ix contentRegionScroller-26nc1e content-region-scroller scroller", ref: "contentScroller"},
|
||||
BDV2.react.createElement("div", {className: "contentColumn-2hrIYH contentColumnDefault-1VQkGM content-column default"}, content.component),
|
||||
tools.component
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import BDV2 from "../modules/v2";
|
||||
|
||||
export default class V2C_ReloadIcon extends BDV2.reactComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return BDV2.react.createElement("svg", {
|
||||
xmlns: "http://www.w3.org/2000/svg",
|
||||
viewBox: "0 0 24 24",
|
||||
fill: "#dcddde",
|
||||
className: "bd-reload " + this.props.className,
|
||||
onClick: this.props.onClick,
|
||||
style: {width: this.props.size || "24px", height: this.props.size || "24px"}
|
||||
},
|
||||
BDV2.react.createElement("path", {d: "M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"}),
|
||||
BDV2.react.createElement("path", {fill: "none", d: "M0 0h24v24H0z"})
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import BDV2 from "../modules/v2";
|
||||
|
||||
export default class V2C_Scroller extends BDV2.reactComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
//scrollerWrap-2lJEkd scrollerThemed-2oenus themeGhostHairline-DBD-2d scrollerFade-1Ijw5y
|
||||
let scrollerModule1 = BDModules.get(e => e.scrollerHorizontal)[0]
|
||||
let scrollerModule2 = BDModules.get(e => e.sidebarRegionScroller)[0]
|
||||
let wrapperClass = `${scrollerModule1.scrollerWrap} ${scrollerModule1.scrollerThemed} ${scrollerModule1.themeGhostHairline}${this.props.fade ? " "+scrollerModule1.scrollerFade : ""}`;
|
||||
let scrollerClass = scrollerModule1.scroller+" scroller"; /* fuck */
|
||||
if (this.props.sidebar) scrollerClass += ` ${scrollerModule2.sidebarRegionScroller} sidebar-region-scroller`
|
||||
if (this.props.contentColumn) {
|
||||
scrollerClass += " "+scrollerModule2.contentRegionScroller+" content-region-scroller"; /* fuck */
|
||||
wrapperClass = `${scrollerModule1.scrollerWrap} ${scrollerModule2.contentRegionScrollerWrap} content-region-scroller-wrap ${scrollerModule1.scrollerThemed} ${scrollerModule1.themeGhost} ${scrollerModule1.scrollerTrack}`;
|
||||
}
|
||||
const {children} = this.props;
|
||||
return BDV2.react.createElement(
|
||||
"div",
|
||||
{key: "scrollerwrap", className: wrapperClass},
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{key: "scroller", ref: "scroller", className: scrollerClass},
|
||||
children
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import BDV2 from "../modules/v2";
|
||||
|
||||
import SettingsGroup from "./settingsGroup";
|
||||
|
||||
export default class V2C_SectionedSettingsPanel extends BDV2.reactComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let columnModule = BDModules.get(e => e.contentColumnDefault)[0]
|
||||
return BDV2.react.createElement(
|
||||
"div", {className: columnModule.contentColumn+" "+columnModule.contentColumnDefault+" content-column default"},
|
||||
this.props.sections.map(section => {
|
||||
return BDV2.react.createElement(SettingsGroup, Object.assign({}, section, {onChange: this.props.onChange}));
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import {settingsCookie} from "../0globals";
|
||||
import BDV2 from "../modules/v2";
|
||||
|
||||
import SettingsTitle from "./settingsTitle";
|
||||
import Switch from "./switch";
|
||||
|
||||
export default class V2C_SettingsGroup extends BDV2.reactComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {title, settings, button} = this.props;
|
||||
const buttonComponent = button ? BDV2.react.createElement("button", {key: "title-button", className: "bd-pfbtn", onClick: button.onClick}, button.title) : null;
|
||||
return [BDV2.react.createElement(SettingsTitle, {text: title}),
|
||||
buttonComponent,
|
||||
settings.map(setting => {
|
||||
return BDV2.react.createElement(Switch, {id: setting.id, key: setting.id, data: setting, checked: settingsCookie[setting.id], onChange: (id, checked) => {
|
||||
this.props.onChange(id, checked);
|
||||
}});
|
||||
})];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import {settingsCookie} from "../0globals";
|
||||
import BDV2 from "../modules/v2";
|
||||
|
||||
import SettingsTitle from "./settingsTitle";
|
||||
import Switch from "./switch";
|
||||
|
||||
export default class V2C_SettingsPanel extends BDV2.reactComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {settings} = this.props;
|
||||
let columnModule = BDModules.get(e => e.contentColumnDefault)[0]
|
||||
return BDV2.react.createElement(
|
||||
"div",
|
||||
{className: columnModule.contentColumn+" "+columnModule.contentColumnDefault+" content-column default"},
|
||||
BDV2.react.createElement(SettingsTitle, {text: this.props.title}),
|
||||
this.props.button && BDV2.react.createElement("button", {key: "title-button", className: "bd-pfbtn", onClick: this.props.button.onClick}, this.props.button.title),
|
||||
settings.map(setting => {
|
||||
return BDV2.react.createElement(Switch, {id: setting.id, key: setting.id, data: setting, checked: settingsCookie[setting.id], onChange: (id, checked) => {
|
||||
this.props.onChange(id, checked);
|
||||
}});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import BDV2 from "../modules/v2";
|
||||
|
||||
export default class V2C_SettingsTitle extends BDV2.reactComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
//h2-2gWE-o title-3sZWYQ size16-14cGz5 height20-mO2eIN weightSemiBold-NJexzi da-h2 da-title da-size16 da-height20 da-weightSemiBold defaultColor-1_ajX0 da-defaultColor marginTop60-3PGbtK da-marginTop60 marginBottom20-32qID7 da-marginBottom20
|
||||
render() {
|
||||
return BDV2.react.createElement(
|
||||
"h2",
|
||||
{className: "ui-form-title h2 margin-reset margin-bottom-20 marginTop60-3PGbtK da-marginTop6"},
|
||||
this.props.text
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
import BDV2 from "../modules/v2";
|
||||
|
||||
import TabBarSeparator from "./tabBarSeparator";
|
||||
import TabBarHeader from "./tabBarHeader";
|
||||
import TabBarItem from "./tabBarItem";
|
||||
|
||||
export default class V2C_SideBar extends BDV2.reactComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const si = document.querySelector("[class*=side-] > [class*=selected]");
|
||||
if (si) this.scn = si.className;
|
||||
const ns = document.querySelector("[class*=side-] > [class*='item-']:not([class*=selected])");
|
||||
if (ns) this.nscn = ns.className;
|
||||
const tabs = document.querySelectorAll("[class*='side-'] > [class*='item-']");
|
||||
for (const element of tabs) {
|
||||
element.addEventListener("click", () => {
|
||||
this.setState({
|
||||
selected: null
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.setInitialState();
|
||||
this.onClick = this.onClick.bind(this);
|
||||
this.setSelected = this.setSelected.bind(this);
|
||||
}
|
||||
|
||||
setInitialState() {
|
||||
const self = this;
|
||||
self.state = {
|
||||
selected: null,
|
||||
items: self.props.items
|
||||
};
|
||||
|
||||
const initialSelection = self.props.items.find(item => {
|
||||
return item.selected;
|
||||
});
|
||||
if (initialSelection) {
|
||||
self.state.selected = initialSelection.id;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const self = this;
|
||||
const {headerText} = self.props;
|
||||
const {items, selected} = self.state;
|
||||
return BDV2.react.createElement(
|
||||
"div",
|
||||
null,
|
||||
BDV2.react.createElement(TabBarSeparator, null),
|
||||
BDV2.react.createElement(TabBarHeader, {text: headerText, button: this.props.headerButton}),
|
||||
items.map(item => {
|
||||
const {id, text} = item;
|
||||
return BDV2.react.createElement(TabBarItem, {key: id, selected: selected === id, text: text, id: id, onClick: self.onClick});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
setSelected(e) {
|
||||
e.target.className = this.scn;
|
||||
}
|
||||
|
||||
onClick(id) {
|
||||
const si = document.querySelector("[class*=side] > [class*=selected]");
|
||||
if (si) {
|
||||
si.removeEventListener("click", this.setSelected);
|
||||
si.addEventListener("click", this.setSelected);
|
||||
si.className = this.nscn;
|
||||
}
|
||||
|
||||
this.setState({selected: null});
|
||||
this.setState({selected: id});
|
||||
|
||||
if (this.props.onClick) this.props.onClick(id);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import BDV2 from "../modules/v2";
|
||||
import Switch from "./components/switch";
|
||||
|
||||
export default class SwitchItem extends BDV2.reactComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
}
|
||||
|
||||
onChange() {
|
||||
this.props.checked = !this.props.checked;
|
||||
this.props.onChange(this.props.id, this.props.checked);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {text, info} = this.props.data;
|
||||
return BDV2.react.createElement("div", {className: "ui-flex flex-vertical flex-justify-start flex-align-stretch flex-nowrap ui-switch-item"},
|
||||
BDV2.react.createElement("div", {className: "ui-flex flex-horizontal flex-justify-start flex-align-stretch flex-nowrap"},
|
||||
BDV2.react.createElement("h3", {className: "ui-form-title h3 margin-reset margin-reset ui-flex-child"}, text),
|
||||
BDV2.react.createElement(Switch, {onChange: this.onChange, checked: this.props.checked})
|
||||
),
|
||||
BDV2.react.createElement("div", {className: "ui-form-text style-description margin-top-4", style: {flex: "1 1 auto"}}, info)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import BDV2 from "../modules/v2";
|
||||
|
||||
export default class V2C_TabBarHeader extends BDV2.reactComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return BDV2.react.createElement("div",{className: "ui-tab-bar-header"}, this.props.text, this.props.button);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import BDV2 from "../modules/v2";
|
||||
|
||||
export default class V2C_TabBarItem extends BDV2.reactComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.setInitialState();
|
||||
this.onClick = this.onClick.bind(this);
|
||||
}
|
||||
|
||||
setInitialState() {
|
||||
this.state = {
|
||||
selected: this.props.selected || false
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return BDV2.react.createElement(
|
||||
"div",
|
||||
{className: `ui-tab-bar-item${this.props.selected ? " selected" : ""}`, onClick: this.onClick},
|
||||
this.props.text
|
||||
);
|
||||
}
|
||||
|
||||
onClick() {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(this.props.id);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import BDV2 from "../modules/v2";
|
||||
|
||||
export default class V2C_TabBarSeparator extends BDV2.reactComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return BDV2.react.createElement("div", {className: "ui-tab-bar-separator margin-top-8 margin-bottom-8"});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import BDV2 from "../modules/v2";
|
||||
|
||||
import XSvg from "./xSvg";
|
||||
|
||||
export default class V2C_Tools extends BDV2.reactComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onClick = this.onClick.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
let toolsModule = BDModules.get(e => e.toolsContainer)[0]
|
||||
let containerModule = BDModules.get(e => e.container && e.closeButton && e.closeButtonBold)[0]
|
||||
return BDV2.react.createElement("div", {className: "tools-container "+toolsModule.toolsContainer},
|
||||
BDV2.react.createElement("div", {className: "tools "+toolsModule.tools},
|
||||
BDV2.react.createElement("div", {className: containerModule.container},
|
||||
BDV2.react.createElement("div",
|
||||
{className: "btn-close "+containerModule.closeButton, onClick: this.onClick},
|
||||
BDV2.react.createElement(XSvg, null)
|
||||
),
|
||||
BDV2.react.createElement(
|
||||
"div",
|
||||
{className: "esc-text "+containerModule.keybind},
|
||||
"ESC"
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
onClick() {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick();
|
||||
}
|
||||
const closeButton = document.querySelector("."+BDModules.get(e => e.closeButton && e.keybindBold)[0].closeButton.split(" ")[0]);
|
||||
if (closeButton) closeButton.click();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
/**
|
||||
* Tooltip that automatically show and hide themselves on mouseenter and mouseleave events.
|
||||
* Will also remove themselves if the node to watch is removed from DOM through
|
||||
* a MutationObserver.
|
||||
*
|
||||
* Note this is not using Discord's internals but normal DOM manipulation and emulates
|
||||
* Discord's own tooltips as closely as possible.
|
||||
*
|
||||
* @module EmulatedTooltip
|
||||
* @version 0.0.1
|
||||
*/
|
||||
|
||||
import Utils from "../modules/utils";
|
||||
import WebpackModules from "../modules/webpackModules";
|
||||
|
||||
const TooltipClasses = WebpackModules.findByProps("tooltip", "tooltipBlack");
|
||||
const TooltipLayers = WebpackModules.findByProps("layer", "layerContainer");
|
||||
|
||||
const getClass = function(sideOrColor) {
|
||||
const upperCase = sideOrColor[0].toUpperCase() + sideOrColor.slice(1);
|
||||
const tooltipClass = TooltipClasses[`tooltip${upperCase}`];
|
||||
if (tooltipClass) return tooltipClass;
|
||||
return null;
|
||||
};
|
||||
|
||||
const classExists = function(sideOrColor) {
|
||||
return getClass(sideOrColor) ? true : false;
|
||||
};
|
||||
|
||||
const toPx = function(value) {
|
||||
return `${value}px`;
|
||||
};
|
||||
|
||||
/* <div class="layer-v9HyYc da-layer" style="left: 234.5px; bottom: 51px;">
|
||||
<div class="tooltip-2QfLtc da-tooltip tooltipTop-XDDSxx tooltipBlack-PPG47z">
|
||||
<div class="tooltipPointer-3ZfirK da-tooltipPointer"></div>
|
||||
User Settings
|
||||
</div>
|
||||
</div> */
|
||||
|
||||
export default class EmulatedTooltip {
|
||||
/**
|
||||
*
|
||||
* @constructor
|
||||
* @param {(HTMLElement|jQuery)} node - DOM node to monitor and show the tooltip on
|
||||
* @param {string} tip - string to show in the tooltip
|
||||
* @param {object} options - additional options for the tooltip
|
||||
* @param {string} [options.style=black] - correlates to the discord styling/colors (black, brand, green, grey, red, yellow)
|
||||
* @param {string} [options.side=top] - can be any of top, right, bottom, left
|
||||
* @param {boolean} [options.preventFlip=false] - prevents moving the tooltip to the opposite side if it is too big or goes offscreen
|
||||
* @param {boolean} [options.disabled=false] - whether the tooltip should be disabled from showing on hover
|
||||
*/
|
||||
constructor(node, text, options = {}) {
|
||||
const {style = "black", side = "top", preventFlip = false, disabled = false} = options;
|
||||
this.node = node instanceof jQuery ? node[0] : node;
|
||||
this.label = text;
|
||||
this.style = style.toLowerCase();
|
||||
this.side = side.toLowerCase();
|
||||
this.preventFlip = preventFlip;
|
||||
this.disabled = disabled;
|
||||
|
||||
if (!classExists(this.side)) return Utils.err("EmulatedTooltip", `Side ${this.side} does not exist.`);
|
||||
if (!classExists(this.style)) return Utils.err("EmulatedTooltip", `Style ${this.style} does not exist.`);
|
||||
|
||||
this.element = document.createElement("div");
|
||||
this.element.className = TooltipLayers.layer + " " + TooltipLayers.disabledPointerEvents;
|
||||
|
||||
this.tooltipElement = document.createElement("div");
|
||||
this.tooltipElement.className = `${TooltipClasses.tooltip} ${getClass(this.style)}`;
|
||||
|
||||
this.labelElement = document.createElement("div");
|
||||
this.labelElement.className = TooltipClasses.tooltipContent
|
||||
|
||||
const pointerElement = document.createElement("div");
|
||||
pointerElement.className = TooltipClasses.tooltipPointer;
|
||||
|
||||
this.tooltipElement.append(pointerElement);
|
||||
this.tooltipElement.append(this.labelElement);
|
||||
this.element.append(this.tooltipElement);
|
||||
|
||||
this.node.addEventListener("mouseenter", () => {
|
||||
if (this.disabled) return;
|
||||
this.show();
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
const nodes = Array.from(mutation.removedNodes);
|
||||
const directMatch = nodes.indexOf(this.node) > -1;
|
||||
const parentMatch = nodes.some(parent => parent.contains(this.node));
|
||||
if (directMatch || parentMatch) {
|
||||
this.hide();
|
||||
observer.disconnect();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe(document.body, {subtree: true, childList: true});
|
||||
});
|
||||
|
||||
this.node.addEventListener("mouseleave", () => {
|
||||
this.hide();
|
||||
});
|
||||
}
|
||||
|
||||
/** Container where the tooltip will be appended. */
|
||||
get container() { return document.querySelector("."+BDModules.get(e => e.popouts)[0].popouts.split(" ")[0]+" ~ ."+BDModules.get(e => e.layerContainer)[0].layerContainer.split(" ")[0]); }
|
||||
/** Boolean representing if the tooltip will fit on screen above the element */
|
||||
get canShowAbove() { return this.node.getBoundingClientRect().top - this.element.offsetHeight >= 0; }
|
||||
/** Boolean representing if the tooltip will fit on screen below the element */
|
||||
get canShowBelow() { return this.node.getBoundingClientRect().top + this.node.offsetHeight + this.element.offsetHeight <= Utils.screenHeight; }
|
||||
/** Boolean representing if the tooltip will fit on screen to the left of the element */
|
||||
get canShowLeft() { return this.node.getBoundingClientRect().left - this.element.offsetWidth >= 0; }
|
||||
/** Boolean representing if the tooltip will fit on screen to the right of the element */
|
||||
get canShowRight() { return this.node.getBoundingClientRect().left + this.node.offsetWidth + this.element.offsetWidth <= Utils.screenWidth; }
|
||||
|
||||
/** Hides the tooltip. Automatically called on mouseleave. */
|
||||
hide() {
|
||||
this.element.remove();
|
||||
this.tooltipElement.className = this._className;
|
||||
}
|
||||
|
||||
/** Shows the tooltip. Automatically called on mouseenter. Will attempt to flip if position was wrong. */
|
||||
show() {
|
||||
this.tooltipElement.className = `${TooltipClasses.tooltip} ${getClass(this.style)}`;
|
||||
this.labelElement.textContent = this.label;
|
||||
this.container.append(this.element);
|
||||
|
||||
if (this.side == "top") {
|
||||
if (this.canShowAbove || (!this.canShowAbove && this.preventFlip)) this.showAbove();
|
||||
else this.showBelow();
|
||||
}
|
||||
|
||||
if (this.side == "bottom") {
|
||||
if (this.canShowBelow || (!this.canShowBelow && this.preventFlip)) this.showBelow();
|
||||
else this.showAbove();
|
||||
}
|
||||
|
||||
if (this.side == "left") {
|
||||
if (this.canShowLeft || (!this.canShowLeft && this.preventFlip)) this.showLeft();
|
||||
else this.showRight();
|
||||
}
|
||||
|
||||
if (this.side == "right") {
|
||||
if (this.canShowRight || (!this.canShowRight && this.preventFlip)) this.showRight();
|
||||
else this.showLeft();
|
||||
}
|
||||
}
|
||||
|
||||
/** Force showing the tooltip above the node. */
|
||||
showAbove() {
|
||||
this.tooltipElement.classList.add(getClass("top"));
|
||||
this.element.style.setProperty("top", toPx(this.node.getBoundingClientRect().top - this.element.offsetHeight - 10));
|
||||
this.centerHorizontally();
|
||||
}
|
||||
|
||||
/** Force showing the tooltip below the node. */
|
||||
showBelow() {
|
||||
this.tooltipElement.classList.add(getClass("bottom"));
|
||||
this.element.style.setProperty("top", toPx(this.node.getBoundingClientRect().top + this.node.offsetHeight + 10));
|
||||
this.centerHorizontally();
|
||||
}
|
||||
|
||||
/** Force showing the tooltip to the left of the node. */
|
||||
showLeft() {
|
||||
this.tooltipElement.classList.add(getClass("left"));
|
||||
this.element.style.setProperty("left", toPx(this.node.getBoundingClientRect().left - this.element.offsetWidth - 10));
|
||||
this.centerVertically();
|
||||
}
|
||||
|
||||
/** Force showing the tooltip to the right of the node. */
|
||||
showRight() {
|
||||
this.tooltipElement.classList.add(getClass("right"));
|
||||
this.element.style.setProperty("left", toPx(this.node.getBoundingClientRect().left + this.node.offsetWidth + 10));
|
||||
this.centerVertically();
|
||||
}
|
||||
|
||||
centerHorizontally() {
|
||||
const nodecenter = this.node.getBoundingClientRect().left + (this.node.offsetWidth / 2);
|
||||
this.element.style.setProperty("left", toPx(nodecenter - (this.element.offsetWidth / 2)));
|
||||
}
|
||||
|
||||
centerVertically() {
|
||||
const nodecenter = this.node.getBoundingClientRect().top + (this.node.offsetHeight / 2);
|
||||
this.element.style.setProperty("top", toPx(nodecenter - (this.element.offsetHeight / 2)));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import BDV2 from "../modules/v2";
|
||||
import Tooltip from "./tooltip";
|
||||
|
||||
export default class extends BDV2.reactComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const {style = "black", side = "top", text = ""} = this.props;
|
||||
this.node = BDV2.reactDom.findDOMNode(this);
|
||||
this.tooltip = new Tooltip(this.node, text, {style, side});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.tooltip.hide();
|
||||
delete this.tooltip;
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import BDV2 from "../modules/v2";
|
||||
|
||||
export default class V2C_XSvg extends BDV2.reactComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return BDV2.react.createElement(
|
||||
"svg",
|
||||
{xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 12 12", style: {width: "18px", height: "18px"}},
|
||||
BDV2.react.createElement(
|
||||
"g",
|
||||
{className: "background", fill: "none", fillRule: "evenodd"},
|
||||
BDV2.react.createElement("path", {d: "M0 0h12v12H0"}),
|
||||
BDV2.react.createElement("path", {className: "fill", fill: "#dcddde", d: "M9.5 3.205L8.795 2.5 6 5.295 3.205 2.5l-.705.705L5.295 6 2.5 8.795l.705.705L6 6.705 8.795 9.5l.705-.705L6.705 6"})
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
const path = require("path");
|
||||
const CircularDependencyPlugin = require("circular-dependency-plugin");
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
|
||||
module.exports = {
|
||||
mode: "development",
|
||||
target: "node",
|
||||
devtool: "eval-cheap-source-map",
|
||||
entry: "./src/index.js",
|
||||
output: {
|
||||
filename: "main.js",
|
||||
path: path.resolve(__dirname, "js"),
|
||||
library: "BetterDiscord",
|
||||
libraryTarget: "commonjs2"
|
||||
},
|
||||
externals: {
|
||||
electron: `electron`,
|
||||
fs: `fs`,
|
||||
path: `path`,
|
||||
events: `events`,
|
||||
rimraf: `rimraf`,
|
||||
yauzl: `yauzl`,
|
||||
mkdirp: `mkdirp`,
|
||||
request: `request`
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".js", ".jsx"],
|
||||
modules: [
|
||||
path.resolve("src", "builtins"),
|
||||
path.resolve("src", "modules")
|
||||
]
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /.jsx?$/,
|
||||
loader: "babel-loader",
|
||||
exclude: /node_modules/,
|
||||
query: {
|
||||
presets: [["@babel/env", {
|
||||
targets: {
|
||||
node: "12.8.1",
|
||||
chrome: "78"
|
||||
}
|
||||
}], "@babel/react"]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new CircularDependencyPlugin({
|
||||
// exclude detection of files based on a RegExp
|
||||
exclude: /a\.js|node_modules/,
|
||||
// add errors to webpack instead of warnings
|
||||
// failOnError: true,
|
||||
// set the current working directory for displaying module paths
|
||||
cwd: process.cwd(),
|
||||
})
|
||||
],
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
compress: {drop_debugger:false}
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
};
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 JeanOUINA
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,35 @@
|
|||
# Lightcord
|
||||
A simple - customizable - Discord Client
|
||||
|
||||
## What's this ?
|
||||
Lightcord is a simple and customizable client for Discord.
|
||||
It includes [BandagedBD](https://github.com/rauenzi/BetterDiscordApp), [Glasscord](https://github.com/AryToNeX/Glasscord) and a discord.js like api.
|
||||
|
||||
## Informations
|
||||
Lightcord doesn't *patch* Discord with it's content. If it would, Discord will update itself and break the patch. That's why Lightcord is a standalone Discord client. Just grab the latest release version you need, and launch it !
|
||||
|
||||
## Installation
|
||||
You can install it from the release tab.
|
||||
|
||||
If you want to `git clone` it and launch it, follow these instructions:
|
||||
```sh
|
||||
git clone https://github.com/JeanOUINA/Lightcord
|
||||
cd Lightcord
|
||||
npm i
|
||||
npm run test
|
||||
```
|
||||
Discord will launch next. You can see you have done it right by looking at the icon.
|
||||
![icon](https://i.imgur.com/rHnsPNO.png)
|
||||
|
||||
## BetterDiscord
|
||||
BetterDiscord (BandagedBD) is already installed (modified version).
|
||||
You can go into your settings manage plugins.
|
||||
|
||||
Because it's more a pain than something good, global emotes are not supported on Lightcord. They have been removed.
|
||||
Only the freeze at the launch because of the downloading is pretty annoying. So I removed them.
|
||||
|
||||
## Plugins & Themes
|
||||
Plugins and themes are not in the standard BetterDiscord folder. They have been moved because betterdiscord supports only stable, ptb and canary release. Using the same directory could cause problems with settings.
|
||||
|
||||
They are located in `%AppData%/LightCord_BD`. This is the main folder for BetterDiscord.
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
const child_process = require("child_process")
|
||||
const path = require("path")
|
||||
const bytenode = require("bytenode")
|
||||
const terser = require("terser")
|
||||
const util = require("util")
|
||||
var rimraf = require("rimraf");
|
||||
let electron
|
||||
try{
|
||||
electron = require("electron")
|
||||
}catch(e){
|
||||
electron = null
|
||||
}
|
||||
let fs = electron ? require("original-fs") : require("fs")
|
||||
|
||||
console.log = (...args) => {
|
||||
process.stdout.write(Buffer.from(util.formatWithOptions({colors: true}, ...args)+"\n", "binary").toString("utf8"))
|
||||
}
|
||||
console.info = (...args) => {
|
||||
console.log(`\x1b[34m[INFO]\x1b[0m`, ...args)
|
||||
}
|
||||
|
||||
async function main(){
|
||||
if(electron)await electron.app.whenReady()
|
||||
|
||||
console.log(__dirname, process.cwd())
|
||||
|
||||
console.info("Reseting existent directory...")
|
||||
child_process.execSync("node remove.js") // why can't electron remove directory ? it just doesn't work and I am required to use electron for the .jsc compilation.
|
||||
await fs.promises.mkdir(__dirname+"/distApp/dist", {"recursive": true})
|
||||
|
||||
console.info("Executing command `tsc`")
|
||||
console.log(child_process.execSync("tsc", {encoding: "binary"}))
|
||||
|
||||
let startDir = path.join(__dirname, "./dist")
|
||||
let newDir = path.join(__dirname, "./distApp/dist")
|
||||
console.info("No error detected. Copying files from "+startDir+".")
|
||||
await fs.promises.mkdir(startDir, {recursive: true})
|
||||
|
||||
async function processNextDir(folder, folders, predicate, compile){
|
||||
for(let file of fs.readdirSync(folder, {withFileTypes: true})){
|
||||
if(file.isFile()){
|
||||
let filepath = path.join(folder, file.name)
|
||||
if(predicate(filepath)){
|
||||
await compile(filepath, path.join(filepath.replace(folders.startDir, folders.newDir)), "..")
|
||||
}else{
|
||||
await fs.promises.copyFile(filepath, filepath.replace(folders.startDir, folders.newDir))
|
||||
}
|
||||
}else if(file.isDirectory()){
|
||||
await fs.promises.mkdir(path.join(folder, file.name).replace(folders.startDir, folders.newDir), {recursive: true})
|
||||
await processNextDir(path.join(folder, file.name), ...Array.from(arguments).slice(1))
|
||||
}
|
||||
}
|
||||
}
|
||||
await processNextDir(startDir, {
|
||||
startDir,
|
||||
newDir
|
||||
}, ((filepath) => filepath.endsWith(".js") && !filepath.endsWith("launcher.js")), (filepath, newpath) => {
|
||||
console.info(`Compiling ${filepath} to ${newpath}c`)
|
||||
bytenode.compileFile(filepath, newpath+"c")
|
||||
}).then(() => {
|
||||
console.info(`Copied files and minified them from ${startDir}.`)
|
||||
}).catch(console.error)
|
||||
|
||||
await processNextDir(path.join(__dirname, "modules"), {
|
||||
startDir: path.join(__dirname, "modules"),
|
||||
newDir: path.join(__dirname, "distApp", "modules")
|
||||
}, (filepath) => {
|
||||
if(filepath.includes("node_modules"))return false
|
||||
if(filepath.endsWith(".node"))return false
|
||||
if(filepath.endsWith(".json"))return false
|
||||
if(filepath.endsWith(".js")){
|
||||
if(filepath.endsWith("mainScreenPreload.js"))return false
|
||||
for(let file of [
|
||||
"discord_cloudsync\\index.js",
|
||||
"discord_desktop_core\\index.js",
|
||||
"discord_dispatch\\index.js",
|
||||
"discord_erlpack\\index.js",
|
||||
"discord_game_utils\\index.js",
|
||||
"discord_krisp\\index.js",
|
||||
"discord_media\\index.js",
|
||||
"discord_modules\\index.js",
|
||||
"discord_overlay2\\index.js",
|
||||
"discord_rpc\\index.js",
|
||||
"discord_spellcheck\\index.js",
|
||||
"discord_utils\\index.js",
|
||||
"discord_voice\\index.js",
|
||||
"discord_desktop_core\\core\\app\\index.js"
|
||||
]){
|
||||
if(filepath.endsWith(file))return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}, (filepath, newpath) => {
|
||||
console.info(`Compiling ${filepath} to ${newpath}c`)
|
||||
bytenode.compileFile(filepath, newpath+"c")
|
||||
}).then(() => {
|
||||
console.info(`Copied files and minified them from ${path.join(__dirname, "modules")}.`)
|
||||
})
|
||||
await processNextDir(startDir, {
|
||||
startDir,
|
||||
newDir
|
||||
}, ((filepath) => false), ()=>{}).then(() => {
|
||||
console.info(`Copied files and minified them from ${startDir}.`)
|
||||
}).catch(console.error)
|
||||
|
||||
let packageJSON = require("./package.json")
|
||||
packageJSON.scripts.build = packageJSON.scripts.build.replace("./distApp", ".")
|
||||
|
||||
fs.writeFileSync(path.join(__dirname, "distApp", "package.json"), JSON.stringify(packageJSON), "utf8")
|
||||
|
||||
console.info(`Installing ${Object.keys(packageJSON.dependencies).length + Object.keys(packageJSON.devDependencies).length} packages...`)
|
||||
console.log(child_process.execSync("npm i", {
|
||||
encoding: "binary",
|
||||
cwd: path.join(__dirname, "distApp")
|
||||
}))
|
||||
}
|
||||
main()
|
||||
.then(() => {
|
||||
if(electron){
|
||||
electron.app.exit()
|
||||
}
|
||||
})
|
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
Binary file not shown.
|
@ -0,0 +1,28 @@
|
|||
const EventEmitter = require('events');
|
||||
const {CloudSync: CloudSyncNative} = require('./discord_cloudsync.node');
|
||||
|
||||
function makeCallback(resolve, reject) {
|
||||
return (err, result) => {
|
||||
if (err != null && err !== '') {
|
||||
reject(new Error(JSON.parse(err)));
|
||||
} else {
|
||||
resolve(result != null && result !== '' ? JSON.parse(result) : null);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class CloudSync extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._cloudSync = new CloudSyncNative(state => this.emit('state', JSON.parse(state)));
|
||||
}
|
||||
|
||||
sync(id, config) {
|
||||
return new Promise((resolve, reject) =>
|
||||
this._cloudSync.command(JSON.stringify({type: 'SYNC', id, config}), makeCallback(resolve, reject))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CloudSync;
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"files": [
|
||||
"discord_cloudsync.node",
|
||||
"index.js",
|
||||
"manifest.json"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
const { EventEmitter } = require("events")
|
||||
|
||||
class Logger extends EventEmitter{
|
||||
constructor(name){
|
||||
super()
|
||||
this.name = name
|
||||
this.on("log", data => {
|
||||
let args = ["%c["+this.name+"]", "\n font-weight: bold;\n color: purple;\n", ...data]
|
||||
console.log(...args)
|
||||
})
|
||||
}
|
||||
|
||||
log(...data){
|
||||
this.emit("log", data)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Logger
|
||||
module.exports.default = Logger
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,123 @@
|
|||
const ModuleLoader = require("./loaders/modules")
|
||||
const { EventEmitter } = require("events")
|
||||
const Logger = require("./Logger")
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
|
||||
const events = exports.events = new EventEmitter()
|
||||
const logger = exports.logger = new Logger("LightCord")
|
||||
|
||||
let hasInit = false
|
||||
let tries = 0
|
||||
|
||||
exports.init = function(){
|
||||
if(hasInit == true){
|
||||
console.warn(new Error("LightCord has already inited."))
|
||||
return
|
||||
}
|
||||
hasInit = true
|
||||
let readyInterval = setInterval(()=>{
|
||||
events.emit("debug", `[INIT] try ${tries++} loading LightCord`)
|
||||
try{
|
||||
if(!global.webpackJsonp)return
|
||||
if(!ModuleLoader.get(4))return
|
||||
clearInterval(readyInterval)
|
||||
privateInit()
|
||||
.then(() => {
|
||||
console.log("Finished loading Lightcord.")
|
||||
})
|
||||
}catch(e){
|
||||
console.error(e)
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
|
||||
let hasPrivateInit = false
|
||||
|
||||
async function privateInit(){
|
||||
if(!hasInit)return
|
||||
if(hasPrivateInit)return
|
||||
hasPrivateInit = true
|
||||
|
||||
//disabling sentry
|
||||
BDModules.get(e => e.getCurrentHub)[0].getCurrentHub().getClient().getOptions().enabled = false
|
||||
|
||||
window.lightcordSettings = {
|
||||
devMode: false,
|
||||
callRingingBeat: true
|
||||
}
|
||||
|
||||
let original = BDModules.get((e) => e.createSound)[0].createSound
|
||||
BDModules.get((e) => e.createSound)[0].createSound = function(sound){
|
||||
let isCalling = sound === "call_ringing_beat" || sound === "call_ringing"
|
||||
if(isCalling){
|
||||
let returned = original(...arguments)
|
||||
Object.defineProperty(returned, "name", {
|
||||
get(){
|
||||
return window.lightcordSettings.callRingingBeat ? "call_ringing_beat" : "call_ringing"
|
||||
},
|
||||
set(data){
|
||||
console.log("Attempting to set call_ringing value. Canceling "+data)
|
||||
}
|
||||
})
|
||||
console.log(returned)
|
||||
return returned
|
||||
}else{
|
||||
return original(...arguments)
|
||||
}
|
||||
}
|
||||
|
||||
let constants = ModuleLoader.get(m=>m.API_HOST)[0]
|
||||
let dispatcher = ModuleLoader.get(m=>m.Dispatcher&&m.default&&m.default.dispatch)[0]
|
||||
require("../../../../../BetterDiscordApp/css/main.css")
|
||||
require("./lightcord.css")
|
||||
|
||||
|
||||
window.$ = window.jQuery = require("./jquery.min.js")
|
||||
require("./ace.js")
|
||||
|
||||
if(!fs.existsSync(BetterDiscordConfig.dataPath))fs.mkdirSync(BetterDiscordConfig.dataPath, {recursive: true})
|
||||
let pluginPath = path.join(BetterDiscordConfig.dataPath, "plugins")
|
||||
let themePath = path.join(BetterDiscordConfig.dataPath, "themes")
|
||||
console.log(`Plugins: ${pluginPath}\nThemes: ${themePath}`)
|
||||
if(!fs.existsSync(pluginPath))fs.mkdirSync(pluginPath, {recursive: true})
|
||||
if(!fs.existsSync(themePath))fs.mkdirSync(themePath, {recursive: true})
|
||||
|
||||
// setting Discord Internal Developer Mode for developement and test purposes.
|
||||
Object.defineProperty(ModuleLoader.get(e => e.default && typeof e.default === "object" && ("isDeveloper" in e.default))[0].default, "isDeveloper", {
|
||||
get(){return !!window.lightcordSettings.devMode},
|
||||
set(data){return !!window.lightcordSettings.devMode}
|
||||
})
|
||||
|
||||
const BetterDiscord = window.BetterDiscord = new(require("../../../../../BetterDiscordApp/js/main").default)(BetterDiscordConfig)
|
||||
BetterDiscord.init()
|
||||
|
||||
events.emit("ready")
|
||||
}
|
||||
|
||||
require.extensions[".css"] = (m, filename) => {
|
||||
let content = fs.readFileSync(filename, "binary")
|
||||
let style = document.createElement("style")
|
||||
style.id = btoa(filename)
|
||||
style.innerText = content
|
||||
document.head.appendChild(style)
|
||||
m.exports = {
|
||||
id: style.id,
|
||||
remove(){
|
||||
return style.remove()
|
||||
}
|
||||
}
|
||||
return m.exports
|
||||
}
|
||||
|
||||
const BetterDiscordConfig = window.BetterDiscordConfig = {
|
||||
"local": true,
|
||||
"localServer": "//localhost:8080",
|
||||
"repo": "rauenzi",
|
||||
"branch": "master",
|
||||
"injectorBranch": "injector",
|
||||
"minified": true,
|
||||
"version": "0.3.2",
|
||||
dataPath: (process.platform == "win32" ? process.env.APPDATA : process.platform == "darwin" ? process.env.HOME + "/Library/Preferences" : process.env.XDG_CONFIG_HOME ? process.env.XDG_CONFIG_HOME : process.env.HOME + "/.config") + "/LightCord_BD/",
|
||||
os: process.platform
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,176 @@
|
|||
/** Lightcord Custom */
|
||||
.lc-tabWrapper {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
background: #18191c;
|
||||
border: 1px solid #040405;
|
||||
border-radius: 5px;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.lc-tabnav {
|
||||
top: 0;
|
||||
border-bottom: 1px solid #040405;
|
||||
padding: 0 4px;
|
||||
width: 100%;
|
||||
-ms-flex-item-align: start;
|
||||
align-self: flex-start;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-wrap: nowrap;
|
||||
flex-wrap: nowrap;
|
||||
-webkit-box-pack: start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
-webkit-box-align: stretch;
|
||||
-ms-flex-align: stretch;
|
||||
align-items: stretch;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-orient: horizontal;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
}
|
||||
.lc-tab {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
width: 600px;
|
||||
max-width: 100%;
|
||||
-webkit-box-shadow: 0 0 20px 2px rgba(4,4,5,.3);
|
||||
box-shadow: 0 0 20px 2px rgba(4,4,5,.3);
|
||||
-webkit-transform: scale(.85);
|
||||
transform: scale(.85);
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
-webkit-box-pack: start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
-webkit-box-align: stretch;
|
||||
-ms-flex-align: stretch;
|
||||
align-items: stretch;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
.lc-navItem {
|
||||
padding: 14px 20px;
|
||||
position: relative;
|
||||
font-weight: 500;
|
||||
-webkit-transform: color .125s;
|
||||
transform: color .125s;
|
||||
cursor: pointer;
|
||||
max-height: 100%;
|
||||
}
|
||||
.lc-navItem::after {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 20px;
|
||||
right: 20px;
|
||||
background: #7289da;
|
||||
-webkit-box-shadow: 0 -4px 12px 0 #7289da;
|
||||
box-shadow: 0 -4px 12px 0 #7289da;
|
||||
content: "";
|
||||
height: 2px;
|
||||
-webkit-transition: -webkit-transform .125s;
|
||||
transition: -webkit-transform .125s;
|
||||
transition: transform .125s;
|
||||
transition: transform .125s,-webkit-transform .125s;
|
||||
}
|
||||
.lc-navItemActive {
|
||||
color: #fff;
|
||||
}
|
||||
.lc-navItemActive::after {
|
||||
-webkit-transform: none;
|
||||
transform: none;
|
||||
}
|
||||
.lc-navItemInactive {
|
||||
color: #b9bbbe;
|
||||
}
|
||||
.lc-navItemInactive::after {
|
||||
-webkit-transform: translateY(16px);
|
||||
transform: translateY(16px);
|
||||
}
|
||||
.lc-fadeOverlay {
|
||||
height: 20%;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background-image: -webkit-gradient(linear,left top,left bottom,from(rgba(24,25,28,0)),color-stop(70%,#18191c),to(#18191c));
|
||||
background-image: linear-gradient(180deg,rgba(24,25,28,0),#18191c 70%,#18191c);
|
||||
z-index: 900;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
.lc-topSectionPlaying {
|
||||
background: #7289da;
|
||||
}
|
||||
.lc-body {
|
||||
height: 240px;
|
||||
overflow: visible;
|
||||
background: #2f3136;
|
||||
}
|
||||
.lc-userInfoSection {
|
||||
margin: 0 20px;
|
||||
padding: 20px 0;
|
||||
}
|
||||
.lc-userInfoSectionHeader {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #72767d;
|
||||
margin-bottom: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.lc-note {
|
||||
margin: -4px;
|
||||
color: #b9bbbe;
|
||||
border-radius: 3px;
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
padding: 4px;
|
||||
}
|
||||
.lc-header {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
}
|
||||
.lc-avatar1 {
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
-ms-flex-negative: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.lc-avatar2 {
|
||||
margin-right: 20px;
|
||||
}
|
||||
.lc-profile {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
class Modules {
|
||||
/** use this as less as possible */
|
||||
static get modules(){
|
||||
let cache = webpackJsonp.push([[],{['']:(_,e,r)=>{e.cache=r.c}},[['']]]).cache
|
||||
return Object.values(cache)
|
||||
}
|
||||
static get(ids, modules){
|
||||
if(typeof ids === "function"){
|
||||
return (modules || this.modules).map((mdl) => {
|
||||
if(mdl && typeof mdl.exports !== "undefined"){
|
||||
return mdl.exports
|
||||
}else{
|
||||
return null
|
||||
}
|
||||
}).filter(e => e).filter(ids)
|
||||
}else if(Array.isArray(ids)){
|
||||
modules = modules || this.modules
|
||||
return ids.map(id => this.get(id, modules))
|
||||
}else{
|
||||
modules = modules || this.modules
|
||||
let module = modules.find(e => e.i === ids)
|
||||
if(!module)return undefined
|
||||
return module.exports
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Modules
|
||||
module.exports.default = Modules
|
||||
|
||||
global.BDModules = Modules
|
|
@ -0,0 +1,55 @@
|
|||
'use strict';
|
||||
|
||||
// before we can set up (and export) our constants, we first need to grab bootstrap's constants
|
||||
// so we can merge them in with our constants
|
||||
function init(bootstrapConstants) {
|
||||
const APP_NAME = bootstrapConstants.APP_NAME;
|
||||
const API_ENDPOINT = bootstrapConstants.API_ENDPOINT;
|
||||
const UPDATE_ENDPOINT = bootstrapConstants.UPDATE_ENDPOINT;
|
||||
const APP_ID = bootstrapConstants.APP_ID;
|
||||
|
||||
const DEFAULT_MAIN_WINDOW_ID = 0;
|
||||
const MAIN_APP_DIRNAME = __dirname;
|
||||
|
||||
const UpdaterEvents = {
|
||||
UPDATE_NOT_AVAILABLE: 'UPDATE_NOT_AVAILABLE',
|
||||
CHECKING_FOR_UPDATES: 'CHECKING_FOR_UPDATES',
|
||||
UPDATE_ERROR: 'UPDATE_ERROR',
|
||||
UPDATE_MANUALLY: 'UPDATE_MANUALLY',
|
||||
UPDATE_AVAILABLE: 'UPDATE_AVAILABLE',
|
||||
MODULE_INSTALL_PROGRESS: 'MODULE_INSTALL_PROGRESS',
|
||||
UPDATE_DOWNLOADED: 'UPDATE_DOWNLOADED',
|
||||
MODULE_INSTALLED: 'MODULE_INSTALLED',
|
||||
CHECK_FOR_UPDATES: 'CHECK_FOR_UPDATES',
|
||||
QUIT_AND_INSTALL: 'QUIT_AND_INSTALL',
|
||||
MODULE_INSTALL: 'MODULE_INSTALL',
|
||||
MODULE_QUERY: 'MODULE_QUERY',
|
||||
UPDATER_HISTORY_QUERY_AND_TRUNCATE: 'UPDATER_HISTORY_QUERY_AND_TRUNCATE',
|
||||
UPDATER_HISTORY_RESPONSE: 'UPDATER_HISTORY_RESPONSE'
|
||||
};
|
||||
|
||||
const MenuEvents = {
|
||||
OPEN_HELP: 'menu:open-help',
|
||||
OPEN_SETTINGS: 'menu:open-settings',
|
||||
CHECK_FOR_UPDATES: 'menu:check-for-updates'
|
||||
};
|
||||
|
||||
const exported = {
|
||||
APP_NAME,
|
||||
DEFAULT_MAIN_WINDOW_ID,
|
||||
MAIN_APP_DIRNAME,
|
||||
APP_ID,
|
||||
API_ENDPOINT,
|
||||
UPDATE_ENDPOINT,
|
||||
UpdaterEvents,
|
||||
MenuEvents
|
||||
};
|
||||
|
||||
for (const key of Object.keys(exported)) {
|
||||
module.exports[key] = exported[key];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.getEnableHardwareAcceleration = getEnableHardwareAcceleration;
|
||||
exports.setEnableHardwareAcceleration = setEnableHardwareAcceleration;
|
||||
|
||||
var _electron = require('electron');
|
||||
|
||||
var _appSettings = require('./appSettings');
|
||||
|
||||
const settings = _appSettings();
|
||||
|
||||
function getEnableHardwareAcceleration() {
|
||||
// TODO: This should probably a constant
|
||||
return settings.get('enableHardwareAcceleration', true);
|
||||
}
|
||||
|
||||
function setEnableHardwareAcceleration(enableHardwareAcceleration) {
|
||||
settings.set('enableHardwareAcceleration', enableHardwareAcceleration);
|
||||
settings.save();
|
||||
|
||||
_electron.app.relaunch();
|
||||
_electron.app.exit(0);
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 107 KiB |
|
@ -0,0 +1,104 @@
|
|||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.hasInit = undefined;
|
||||
exports.init = init;
|
||||
|
||||
var _electron = require('electron');
|
||||
|
||||
var _utils = require('./utils');
|
||||
|
||||
var _mainScreen = require('./mainScreen');
|
||||
|
||||
var _appFeatures = require('./appFeatures');
|
||||
|
||||
var _ipcMain = require('./ipcMain');
|
||||
|
||||
var _ipcMain2 = _interopRequireDefault(_ipcMain);
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
const features = (0, _appFeatures.getFeatures)();
|
||||
|
||||
let hasInit = exports.hasInit = false;
|
||||
|
||||
let lastIndex;
|
||||
let appIcons;
|
||||
|
||||
/**
|
||||
* Used on Windows to set the taskbar icon
|
||||
*/
|
||||
function init() {
|
||||
// Only init on win32 platforms
|
||||
if (process.platform !== 'win32') return;
|
||||
|
||||
if (hasInit) {
|
||||
console.warn('appBadge: Has already init! Cancelling init.');
|
||||
return;
|
||||
}
|
||||
exports.hasInit = hasInit = true;
|
||||
|
||||
lastIndex = null;
|
||||
appIcons = [];
|
||||
|
||||
const resourcePath = `app/images/badges`;
|
||||
|
||||
for (let i = 1; i <= 11; i++) {
|
||||
appIcons.push(_utils.exposeModuleResource(resourcePath, `badge-${i}.ico`));
|
||||
}
|
||||
|
||||
// TODO: remove on or after April 2018
|
||||
features.declareSupported('new_app_badge');
|
||||
|
||||
_ipcMain2.default.on('APP_BADGE_SET', (_event, count) => setAppBadge(count));
|
||||
}
|
||||
|
||||
function setAppBadge(count) {
|
||||
const win = _electron.BrowserWindow.fromId((0, _mainScreen.getMainWindowId)());
|
||||
|
||||
const { index, description } = getOverlayIconData(count);
|
||||
|
||||
// Prevent setting a new icon when the icon is the same
|
||||
if (lastIndex !== index) {
|
||||
if (index == null) {
|
||||
win.setOverlayIcon(null, description);
|
||||
} else {
|
||||
win.setOverlayIcon(appIcons[index], description);
|
||||
}
|
||||
|
||||
lastIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* -1 is bullet
|
||||
* 0 is nothing
|
||||
* 1-9 is a number badge
|
||||
* 10+ is `9+`
|
||||
*/
|
||||
function getOverlayIconData(count) {
|
||||
// Unread message badge
|
||||
if (count === -1) {
|
||||
return {
|
||||
index: 10, // this.appIcons.length - 1
|
||||
description: `Unread messages`
|
||||
};
|
||||
}
|
||||
|
||||
// Clear overlay icon
|
||||
if (count === 0) {
|
||||
return {
|
||||
index: null, // null is used to clear the overlay icon
|
||||
description: 'No Notifications'
|
||||
};
|
||||
}
|
||||
|
||||
// Notification badge
|
||||
const index = Math.max(1, Math.min(count, 10)) - 1; // arrays are 0 based
|
||||
return {
|
||||
index,
|
||||
description: `${index} notifications`
|
||||
};
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.hasInit = undefined;
|
||||
exports.init = init;
|
||||
|
||||
var _autoStart = require('./autoStart');
|
||||
|
||||
var autoStart = _interopRequireWildcard(_autoStart);
|
||||
|
||||
var _appSettings = require('./appSettings');
|
||||
|
||||
var _appFeatures = require('./appFeatures');
|
||||
|
||||
var _ipcMain = require('./ipcMain');
|
||||
|
||||
var _ipcMain2 = _interopRequireDefault(_ipcMain);
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
|
||||
|
||||
const settings = _appSettings();
|
||||
const features = (0, _appFeatures.getFeatures)();
|
||||
const NOOP = () => {};
|
||||
|
||||
let hasInit = exports.hasInit = false;
|
||||
|
||||
function init() {
|
||||
if (hasInit) {
|
||||
console.warn('appConfig: Has already init! Cancelling init.');
|
||||
return;
|
||||
}
|
||||
exports.hasInit = hasInit = true;
|
||||
|
||||
// TODO remove on or after March 2018
|
||||
features.declareSupported('app_configs');
|
||||
|
||||
_ipcMain2.default.on('TOGGLE_MINIMIZE_TO_TRAY', (_event, value) => setMinimizeOnClose(value));
|
||||
_ipcMain2.default.on('TOGGLE_OPEN_ON_STARTUP', (_event, value) => toggleRunOnStartup(value));
|
||||
_ipcMain2.default.on('TOGGLE_START_MINIMIZED', (_event, value) => toggleStartMinimized(value));
|
||||
}
|
||||
|
||||
function setMinimizeOnClose(minimizeToTray) {
|
||||
settings.set('MINIMIZE_TO_TRAY', minimizeToTray);
|
||||
}
|
||||
|
||||
function toggleRunOnStartup(openOnStartup) {
|
||||
settings.set('OPEN_ON_STARTUP', openOnStartup);
|
||||
|
||||
if (openOnStartup) {
|
||||
autoStart.install(NOOP);
|
||||
} else {
|
||||
autoStart.uninstall(NOOP);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleStartMinimized(startMinimized) {
|
||||
settings.set('START_MINIMIZED', startMinimized);
|
||||
autoStart.isInstalled(installed => {
|
||||
// Only update the registry for this toggle if the app was already set to autorun
|
||||
if (installed) {
|
||||
autoStart.install(NOOP);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.init = init;
|
||||
exports.getFeatures = getFeatures;
|
||||
|
||||
var _FeatureFlags = require('../common/FeatureFlags');
|
||||
|
||||
var _FeatureFlags2 = _interopRequireDefault(_FeatureFlags);
|
||||
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
|
||||
let features;
|
||||
|
||||
function init() {
|
||||
features = new _FeatureFlags2.default();
|
||||
}
|
||||
|
||||
function getFeatures() {
|
||||
return features;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = require('./bootstrapModules').appSettings;
|
|
@ -0,0 +1,147 @@
|
|||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
|
||||
var _electron = require('electron');
|
||||
|
||||
var _Constants = require('../Constants');
|
||||
|
||||
var Constants = _interopRequireWildcard(_Constants);
|
||||
|
||||
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
|
||||
|
||||
const { MenuEvents } = Constants;
|
||||
const SEPARATOR = { type: 'separator' };
|
||||
|
||||
function getWindow() {
|
||||
let window = _electron.BrowserWindow.getFocusedWindow();
|
||||
if (!window) {
|
||||
const windowList = _electron.BrowserWindow.getAllWindows();
|
||||
if (windowList && windowList[0]) {
|
||||
window = windowList[0];
|
||||
window.show();
|
||||
window.focus();
|
||||
}
|
||||
}
|
||||
return window;
|
||||
}
|
||||
|
||||
exports.default = [{
|
||||
label: 'Lightcord',
|
||||
submenu: [{
|
||||
label: 'About Lightcord',
|
||||
selector: 'orderFrontStandardAboutPanel:'
|
||||
}, {
|
||||
label: 'Check for Updates...',
|
||||
click: () => _electron.app.emit(MenuEvents.CHECK_FOR_UPDATES)
|
||||
}, {
|
||||
label: 'Acknowledgements',
|
||||
click: () => _electron.shell.openExternal('https://discord.com/acknowledgements')
|
||||
}, SEPARATOR, {
|
||||
label: 'Preferences',
|
||||
click: () => _electron.app.emit(MenuEvents.OPEN_SETTINGS),
|
||||
accelerator: 'Command+,'
|
||||
}, SEPARATOR, {
|
||||
label: 'Services',
|
||||
submenu: []
|
||||
}, SEPARATOR, {
|
||||
label: 'Hide Lightcord',
|
||||
selector: 'hide:',
|
||||
accelerator: 'Command+H'
|
||||
}, {
|
||||
label: 'Hide Others',
|
||||
selector: 'hideOtherApplications:',
|
||||
accelerator: 'Command+Alt+H'
|
||||
}, {
|
||||
label: 'Show All',
|
||||
selector: 'unhideAllApplications:'
|
||||
}, SEPARATOR, {
|
||||
label: 'Quit',
|
||||
click: () => _electron.app.quit(),
|
||||
accelerator: 'Command+Q'
|
||||
}]
|
||||
}, {
|
||||
label: 'Edit',
|
||||
submenu: [{
|
||||
role: 'undo',
|
||||
accelerator: 'Command+Z'
|
||||
}, {
|
||||
role: 'redo',
|
||||
accelerator: 'Shift+Command+Z'
|
||||
}, SEPARATOR, {
|
||||
role: 'cut',
|
||||
accelerator: 'Command+X'
|
||||
}, {
|
||||
role: 'copy',
|
||||
accelerator: 'Command+C'
|
||||
}, {
|
||||
role: 'paste',
|
||||
accelerator: 'Command+V'
|
||||
}, {
|
||||
role: 'selectAll',
|
||||
accelerator: 'Command+A'
|
||||
}]
|
||||
}, {
|
||||
label: 'View',
|
||||
submenu: [{
|
||||
label: 'Reload',
|
||||
click: () => {
|
||||
const window = getWindow();
|
||||
if (window) {
|
||||
window.setBackgroundColor(getBackgroundColor())
|
||||
window.webContents.reloadIgnoringCache()
|
||||
window.webContents.once("did-finish-load", () => {
|
||||
window.setBackgroundColor("#00000000")
|
||||
})
|
||||
}
|
||||
},
|
||||
accelerator: 'Command+R'
|
||||
}, {
|
||||
label: 'Toggle Full Screen',
|
||||
click: () => {
|
||||
const window = getWindow();
|
||||
if (window) {
|
||||
window.setFullScreen(!window.isFullScreen());
|
||||
}
|
||||
},
|
||||
accelerator: 'Command+Control+F'
|
||||
}, SEPARATOR, {
|
||||
label: 'Developer',
|
||||
submenu: [{
|
||||
label: 'Toggle Developer Tools',
|
||||
click: () => {
|
||||
const window = getWindow();
|
||||
if (window) {
|
||||
window.toggleDevTools();
|
||||
}
|
||||
},
|
||||
accelerator: 'Alt+Command+I'
|
||||
}]
|
||||
}]
|
||||
}, {
|
||||
label: 'Window',
|
||||
submenu: [{
|
||||
label: 'Minimize',
|
||||
selector: 'performMiniaturize:',
|
||||
accelerator: 'Command+M'
|
||||
}, {
|
||||
label: 'Zoom',
|
||||
selector: 'performZoom:'
|
||||
}, {
|
||||
label: 'Close',
|
||||
accelerator: 'Command+W',
|
||||
selector: 'hide:'
|
||||
}, SEPARATOR, {
|
||||
label: 'Bring All to Front',
|
||||
selector: 'arrangeInFront:'
|
||||
}]
|
||||
}, {
|
||||
label: 'Help',
|
||||
submenu: [{
|
||||
label: 'Lightcord Help',
|
||||
click: () => _electron.app.emit(MenuEvents.OPEN_HELP)
|
||||
}]
|
||||
}];
|
||||
module.exports = exports.default;
|
|
@ -0,0 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
|
||||
var _electron = require('electron');
|
||||
|
||||
const menu = require('./' + process.platform);
|
||||
|
||||
exports.default = _electron.Menu.buildFromTemplate(menu);
|
||||
module.exports = exports.default;
|
|
@ -0,0 +1,85 @@
|
|||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
|
||||
var _electron = require('electron');
|
||||
|
||||
var _Constants = require('../Constants');
|
||||
|
||||
var Constants = _interopRequireWildcard(_Constants);
|
||||
|
||||
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
|
||||
|
||||
const { MenuEvents } = Constants;
|
||||
const SEPARATOR = { type: 'separator' };
|
||||
|
||||
exports.default = [{
|
||||
label: '&File',
|
||||
submenu: [{
|
||||
label: '&Options',
|
||||
click: () => _electron.app.emit(MenuEvents.OPEN_SETTINGS),
|
||||
accelerator: 'Control+,'
|
||||
}, SEPARATOR, {
|
||||
label: 'E&xit',
|
||||
click: () => _electron.app.quit(),
|
||||
accelerator: 'Control+Q'
|
||||
}]
|
||||
}, {
|
||||
label: '&Edit',
|
||||
submenu: [{
|
||||
role: 'undo',
|
||||
accelerator: 'Control+Z'
|
||||
}, {
|
||||
role: 'redo',
|
||||
accelerator: 'Shift+Control+Z'
|
||||
}, SEPARATOR, {
|
||||
role: 'cut',
|
||||
accelerator: 'Control+X'
|
||||
}, {
|
||||
role: 'copy',
|
||||
accelerator: 'Control+C'
|
||||
}, {
|
||||
role: 'paste',
|
||||
accelerator: 'Control+V'
|
||||
}, {
|
||||
role: 'selectAll',
|
||||
accelerator: 'Control+A'
|
||||
}]
|
||||
}, {
|
||||
label: '&View',
|
||||
submenu: [{
|
||||
label: '&Reload',
|
||||
click: () => {
|
||||
let window = _electron.BrowserWindow.getFocusedWindow()
|
||||
window.setBackgroundColor(getBackgroundColor())
|
||||
window.webContents.reloadIgnoringCache()
|
||||
window.webContents.once("did-finish-load", () => {
|
||||
window.setBackgroundColor("#00000000")
|
||||
})
|
||||
},
|
||||
accelerator: 'Control+R'
|
||||
}, {
|
||||
label: 'Toggle &Full Screen',
|
||||
click: () => _electron.BrowserWindow.getFocusedWindow().setFullScreen(!_electron.BrowserWindow.getFocusedWindow().isFullScreen()),
|
||||
accelerator: 'Control+Shift+F'
|
||||
}, SEPARATOR, {
|
||||
label: '&Developer',
|
||||
submenu: [{
|
||||
label: 'Toggle Developer &Tools',
|
||||
click: () => _electron.BrowserWindow.getFocusedWindow().toggleDevTools(),
|
||||
accelerator: 'Control+Shift+I'
|
||||
}]
|
||||
}]
|
||||
}, {
|
||||
label: '&Help',
|
||||
submenu: [{
|
||||
label: 'Check for Updates',
|
||||
click: () => _electron.app.emit(MenuEvents.CHECK_FOR_UPDATES)
|
||||
}, SEPARATOR, {
|
||||
label: 'Lightcord Help',
|
||||
click: () => _electron.app.emit(MenuEvents.OPEN_HELP)
|
||||
}]
|
||||
}];
|
||||
module.exports = exports.default;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue