Experimental Tabs again - added menu option

This commit is contained in:
Jean Ouina 2020-07-07 23:47:11 +02:00
parent a25bbefb1f
commit 22d8ecd7d7
7 changed files with 434 additions and 346 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
import { remote } from "electron";
import { remote } from "electron"
import BDV2 from "./modules/v2";
import WebpackModules from "./modules/webpackModules";
@ -95,6 +95,7 @@ export const settings = {
"Ad Block": {id: "lightcord-4", info: "Block any BOT that dms you with an invite link. Even in an embed.", implemented: true, hidden: false, cat: "lightcord", category: "Lightcord"},
"Enable Lightcord Servers": {id: "lightcord-5", info: "Enable Lightcord's servers. Disabling this will disable custom badges.", implemented: true, hidden: false, cat: "lightcord", category: "Lightcord"},
"Disable typing": {id: "lightcord-7", info: "Don't let other see you're typing.", implemented: true, hidden: false, cat: "lightcord", category: "Lightcord"},
"Lightcord Tabs": {id: "lightcord-8", info: "Allows you to launch multiple instances of Lightcord in the same window (EXPERIMENTAL).", implemented: true, hidden: false, cat: "lightcord", category: "Lightcord"},
/** Lightcord Window */
"Always-on-Top": {id: "lightcord-3", info: "Enable window's Always-on-Top mode, where Lightcord stays on top of other applications.", implemented: true, hidden: false, cat: "lightcord", category: "Window"},

View File

@ -1,336 +1,343 @@
import {bdConfig, bdplugins, bdthemes, settingsCookie} from "../0globals";
import pluginModule from "./pluginModule";
import themeModule from "./themeModule";
import Utils from "./utils";
import * as crypto from "crypto"
import dataStore from "./dataStore";
import pluginCertifier, { encryptSettingsCache, decryptSettingsCache, processFile } from "./pluginCertifier";
import { captureRejectionSymbol } from "events";
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 let addonCache = {}
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/")));}
loadAddonCertifierCache(){
if(typeof dataStore.getSettingGroup("PluginCertifierHashes") !== "string"){
dataStore.setSettingGroup("PluginCertifierHashes", encryptSettingsCache("{}"))
}else{
try{
addonCache = JSON.parse(decryptSettingsCache(dataStore.getSettingGroup("PluginCertifierHashes")))
}catch(e){
dataStore.setSettingGroup("PluginCertifierHashes", encryptSettingsCache("{}"))
addonCache = {}
}
}
Object.keys(addonCache)
.forEach(key => {
let value = addonCache[key]
if(!value || typeof value !== "object" || Array.isArray(value))return delete addonCache[key]
let props = [{
key: "timestamp",
type: "number"
}, {
key: "result",
type: "object"
}, {
key: "hash",
type: "string"
}]
for(let prop of props){
if(!(prop.key in value) || typeof value[prop.key] !== prop.type){
delete addonCache[key]
return
}
}
if(value.hash !== key){
delete addonCache[key]
return
}
if(value.result.suspect){ // refetch from remote to be sure you're up to date.
delete addonCache[key]
return
}
})
this.saveAddonCache()
}
saveAddonCache(){
dataStore.setSettingGroup("PluginCertifierHashes", encryptSettingsCache(JSON.stringify(addonCache)))
}
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) await pluginModule.loadPlugin(filename);
else await themeModule.loadTheme(filename);
}
if (eventType == "change") {
if (isPlugin) await pluginModule.reloadPlugin(filename);
else await 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 {
// Utils.warn("Module Not Exported", `${meta.name}, please start setting module.exports`);
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 : ""
};
}
async loadContent(filename, type) {
if (typeof(filename) === "undefined" || typeof(type) === "undefined") return;
const isPlugin = type === "plugin";
const baseFolder = isPlugin ? this.pluginsFolder : this.themesFolder;
if(settingsCookie["fork-ps-6"]){
let result = await new Promise(resolve => {
processFile(path.resolve(baseFolder, filename), (result) => {
console.log(result)
resolve(result)
}, (hash) => {
resolve({
suspect: false,
hash: hash,
filename: filename,
name: filename
})
}, true)
})
if(result){
addonCache[result.hash] = {
timestamp: Date.now(),
hash: result.hash,
result: result
}
this.saveAddonCache()
if(result.suspect){
return {
name: filename,
file: filename,
message: "This plugin might be dangerous ("+result.harm+").",
error: new Error("This plugin might be dangerous ("+result.harm+").")
}
}
}
}
try {window.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 = window.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 window.require.cache[window.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 {window.require.cache[window.require.resolve(path.resolve(baseFolder, filename))];}
catch (err) {return false;}
return true;
}
async reloadContent(filename, type) {
const cantUnload = this.unloadContent(filename, type);
if (cantUnload) return cantUnload;
return await 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};
}
async 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 = await this.loadContent(filename, type);
if (error) errors.push(error);
}
return errors;
}
loadPlugins() {return this.loadAllContent("plugin");}
loadThemes() {return this.loadAllContent("theme");}
};
/**
* Don't expose contentManager - could be dangerous for now
import {bdConfig, bdplugins, bdthemes, settingsCookie} from "../0globals";
import pluginModule from "./pluginModule";
import themeModule from "./themeModule";
import Utils from "./utils";
import * as crypto from "crypto"
import dataStore from "./dataStore";
import pluginCertifier, { encryptSettingsCache, decryptSettingsCache, processFile } from "./pluginCertifier";
import { captureRejectionSymbol } from "events";
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 let addonCache = {}
let hasPatched = false
export default new class ContentManager {
constructor() {
this.timeCache = {};
this.watchers = {};
}
patchExtensions(){
if(hasPatched)return
hasPatched = true
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/")));}
loadAddonCertifierCache(){
if(typeof dataStore.getSettingGroup("PluginCertifierHashes") !== "string"){
dataStore.setSettingGroup("PluginCertifierHashes", encryptSettingsCache("{}"))
}else{
try{
addonCache = JSON.parse(decryptSettingsCache(dataStore.getSettingGroup("PluginCertifierHashes")))
}catch(e){
dataStore.setSettingGroup("PluginCertifierHashes", encryptSettingsCache("{}"))
addonCache = {}
}
}
Object.keys(addonCache)
.forEach(key => {
let value = addonCache[key]
if(!value || typeof value !== "object" || Array.isArray(value))return delete addonCache[key]
let props = [{
key: "timestamp",
type: "number"
}, {
key: "result",
type: "object"
}, {
key: "hash",
type: "string"
}]
for(let prop of props){
if(!(prop.key in value) || typeof value[prop.key] !== prop.type){
delete addonCache[key]
return
}
}
if(value.hash !== key){
delete addonCache[key]
return
}
if(value.result.suspect){ // refetch from remote to be sure you're up to date.
delete addonCache[key]
return
}
})
this.saveAddonCache()
}
saveAddonCache(){
dataStore.setSettingGroup("PluginCertifierHashes", encryptSettingsCache(JSON.stringify(addonCache)))
}
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) await pluginModule.loadPlugin(filename);
else await themeModule.loadTheme(filename);
}
if (eventType == "change") {
if (isPlugin) await pluginModule.reloadPlugin(filename);
else await 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 {
// Utils.warn("Module Not Exported", `${meta.name}, please start setting module.exports`);
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 : ""
};
}
async loadContent(filename, type) {
if (typeof(filename) === "undefined" || typeof(type) === "undefined") return;
const isPlugin = type === "plugin";
const baseFolder = isPlugin ? this.pluginsFolder : this.themesFolder;
if(settingsCookie["fork-ps-6"]){
let result = await new Promise(resolve => {
processFile(path.resolve(baseFolder, filename), (result) => {
console.log(result)
resolve(result)
}, (hash) => {
resolve({
suspect: false,
hash: hash,
filename: filename,
name: filename
})
}, true)
})
if(result){
addonCache[result.hash] = {
timestamp: Date.now(),
hash: result.hash,
result: result
}
this.saveAddonCache()
if(result.suspect){
return {
name: filename,
file: filename,
message: "This plugin might be dangerous ("+result.harm+").",
error: new Error("This plugin might be dangerous ("+result.harm+").")
}
}
}
}
try {window.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 = window.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 window.require.cache[window.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 {window.require.cache[window.require.resolve(path.resolve(baseFolder, filename))];}
catch (err) {return false;}
return true;
}
async reloadContent(filename, type) {
const cantUnload = this.unloadContent(filename, type);
if (cantUnload) return cantUnload;
return await 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};
}
async loadAllContent(type) {
this.patchExtensions()
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 = await this.loadContent(filename, type);
if (error) errors.push(error);
}
return errors;
}
loadPlugins() {return this.loadAllContent("plugin");}
loadThemes() {return this.loadAllContent("theme");}
};
/**
* Don't expose contentManager - could be dangerous for now
*/

View File

@ -142,7 +142,7 @@ export default new class V2_SettingsPanel {
}
updateSettings(id, enabled, sidebar) {
settingsCookie[id] = enabled;
if(!["lightcord-8"].includes(id))settingsCookie[id] = enabled;
if (id == "bda-gs-2") {
if (enabled) DOM.addClass(document.body, "bd-minimal");
@ -259,6 +259,13 @@ export default new class V2_SettingsPanel {
disableTyping.disable()
}
}
if (id === "lightcord-8"){
let appSettings = remote.getGlobal("appSettings")
appSettings.set("isTabs", enabled)
appSettings.save()
remote.app.relaunch()
remote.app.exit()
}
this.saveSettings();
}
@ -311,15 +318,33 @@ export default new class V2_SettingsPanel {
}
lightcordComponent(sidebar) {
let appSettings = remote.getGlobal("appSettings")
return [
this.lightcordSettings.map((section, i) => {
return [
(i === 0 ? null : BDV2.react.createElement(MarginTop)),
BDV2.react.createElement("h2", {className: "ui-form-title h2 margin-reset margin-bottom-20"}, section.title),
section.settings.map(setting => {
return BDV2.react.createElement(Switch, {id: setting.id, key: setting.id, data: setting, checked: settingsCookie[setting.id], onChange: (id, checked) => {
let isChecked = settingsCookie[setting.id]
if(setting.id === "lightcord-8")isChecked = appSettings.get("isTabs", false);
let returnValue = BDV2.react.createElement(Switch, {id: setting.id, key: setting.id, data: setting, checked: isChecked, onChange: (id, checked) => {
this.onChange(id, checked, sidebar);
}})
if(setting.id !== "lightcord-8" || !isChecked)return returnValue
return [
returnValue,
React.createElement(Lightcord.Api.Components.inputs.Button, {
color: "green",
look: "outlined",
size: "small",
hoverColor: "brand",
onClick: () => {
DiscordNative.ipc.send("NEW_TAB")
},
wrapper: false,
disabled: false
}, "Open a new Tab")
]
})
]
}),

View File

@ -108,6 +108,11 @@ function startup(bootstrapModules) {
Menu.setApplicationMenu(Menu.buildFromTemplate(applicationMenu));
}
const ipc = require("./ipcMain") // TODO: Fix NEW_TAB
ipc.on("NEW_TAB", () => {
mainScreen.webContentsSend("NEW_TAB")
})
mainScreen = require('./mainScreen');
let version = bootstrapModules.Constants.version

View File

@ -7,9 +7,10 @@ let webviews = new Map()
window.webviews = webviews
function forwardToCurrentWebview(event){
return [event, (...args) => {
return [event, async (...args) => {
let webview = webviews.get(document.querySelector(".chrome-tab[active]"))
if(!webview)return
await webview.ready
webview.send(event, ...args.slice(1))
}]
}
@ -90,8 +91,16 @@ window.onload = () => {
webview.nodeintegration = false
webview.webpreferences = "nativeWindowOpen=yes"
webview.enableblinkfeatures = "EnumerateDevices,AudioOutputDevices"
webview.addEventListener("ipc-message", function(...ev){
ipc.send(ev[0].channel.replace("DISCORD_", ""))
webview.addEventListener("ipc-message", function(...ev){ // TODO: Why don't we receive Ipc Messages, but they get processed anyway (notification, etc) ?
console.log(ev[0].channel)
if(ev[0].channel === "DISCORD_NEW_TAB"){
chromeTabs.addTab({
title: 'Lightcord',
favicon: faviconURL
})
return
}
ipc.send(ev[0].channel.replace("DISCORD_", ""), ev.slice(1))
})
webview.addEventListener('page-title-updated', () => {
let el = Array.from(webviews.entries()).find(e => e[1] === webview)[0]
@ -103,7 +112,10 @@ window.onload = () => {
})
webviews.set(detail.tabEl, webview)
document.querySelector(".documentFull").appendChild(webview)
let r
webview.ready = new Promise(resolve => (r = resolve))
webview.addEventListener("dom-ready", () => {
r()
webview.send("DISCORD_IS_TAB")
})
webview.addEventListener("will-navigate", (e) => {