diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 734bbaca8..e7afec3c6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,5 +1,5 @@ name: Bug/Crash Report -description: Create a bug or crash report for Vencord +description: Create a bug or crash report for Vencord. ALWAYS FIRST USE OUR SUPPORT CHANNEL! ONLY USE THIS FORM IF YOU ARE A CONTRIBUTOR OR WERE TOLD TO DO SO IN THE SUPPORT CHANNEL. labels: [bug] title: "[Bug] " diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml deleted file mode 100644 index 115f7f700..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Feature Request -description: Create a feature request for Vencord. To request new plugins, please use the Discussions tab -labels: [enhancement] -title: "[Feature Request] <title>" - -body: - - type: input - id: discord - attributes: - label: Discord Account - description: Who on Discord is making this request? Not required but encouraged for easier follow-up - placeholder: username#0000 - validations: - required: false - - - type: textarea - id: feature-basic-description - attributes: - label: What is it that you'd like to see? - description: Describe the feature you want added as detailed as possible - placeholder: I think ... would be a cool feature to add. This would be awesome, thanks! - validations: - required: true - - - type: checkboxes - id: agreement-check - attributes: - label: Request Agreement - description: DO NOT USE THIS TEMPLATE FOR PLUGIN REQUESTS!!! For plugin requests, **use discussions** - options: - - label: This is not a plugin request - required: true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d9e2789d7..9ed7d5ca7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,7 +42,7 @@ jobs: - name: Clean up obsolete files run: | - rm -rf dist/*-unpacked Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map + rm -rf dist/*-unpacked dist/monaco Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map - name: Get some values needed for the release id: release_values diff --git a/browser/VencordNativeStub.ts b/browser/VencordNativeStub.ts index db263ee63..6c1a957c5 100644 --- a/browser/VencordNativeStub.ts +++ b/browser/VencordNativeStub.ts @@ -19,9 +19,11 @@ /// <reference path="../src/modules.d.ts" /> /// <reference path="../src/globals.d.ts" /> -import monacoHtml from "~fileContent/../src/components/monacoWin.html"; +import monacoHtmlLocal from "~fileContent/monacoWin.html"; +import monacoHtmlCdn from "~fileContent/../src/main/monacoWin.html"; import * as DataStore from "../src/api/DataStore"; import { debounce } from "../src/utils"; +import { EXTENSION_BASE_URL } from "../src/utils/web-metadata"; import { getTheme, Theme } from "../src/utils/discord"; import { getThemeInfo } from "../src/utils/themes/bd"; @@ -46,7 +48,8 @@ window.VencordNative = { getThemesList: () => DataStore.entries(themeStore).then(entries => entries.map(([name, css]) => getThemeInfo(css, name.toString())) ), - getThemeData: (fileName: string) => DataStore.get(fileName, themeStore) + getThemeData: (fileName: string) => DataStore.get(fileName, themeStore), + getSystemValues: async () => ({}), }, native: { @@ -80,6 +83,7 @@ window.VencordNative = { return; } + win.baseUrl = EXTENSION_BASE_URL; win.setCss = setCssDebounced; win.getCurrentCss = () => VencordNative.quickCss.get(); win.getTheme = () => @@ -87,7 +91,7 @@ window.VencordNative = { ? "vs-light" : "vs-dark"; - win.document.write(monacoHtml); + win.document.write(IS_EXTENSION ? monacoHtmlLocal : monacoHtmlCdn); }, }, diff --git a/browser/background.js b/browser/background.js deleted file mode 100644 index 1f2d5ec17..000000000 --- a/browser/background.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @template T - * @param {T[]} arr - * @param {(v: T) => boolean} predicate - */ -function removeFirst(arr, predicate) { - const idx = arr.findIndex(predicate); - if (idx !== -1) arr.splice(idx, 1); -} - -chrome.webRequest.onHeadersReceived.addListener( - ({ responseHeaders, type, url }) => { - if (!responseHeaders) return; - - if (type === "main_frame") { - // In main frame requests, the CSP needs to be removed to enable fetching of custom css - // as desired by the user - removeFirst(responseHeaders, h => h.name.toLowerCase() === "content-security-policy"); - } else if (type === "stylesheet" && url.startsWith("https://raw.githubusercontent.com/")) { - // Most users will load css from GitHub, but GitHub doesn't set the correct content type, - // so we fix it here - removeFirst(responseHeaders, h => h.name.toLowerCase() === "content-type"); - responseHeaders.push({ - name: "Content-Type", - value: "text/css" - }); - } - return { responseHeaders }; - }, - { urls: ["https://raw.githubusercontent.com/*", "*://*.discord.com/*"], types: ["main_frame", "stylesheet"] }, - ["blocking", "responseHeaders"] -); diff --git a/browser/content.js b/browser/content.js index e47ef8377..4810fe3c8 100644 --- a/browser/content.js +++ b/browser/content.js @@ -4,6 +4,11 @@ if (typeof browser === "undefined") { const script = document.createElement("script"); script.src = browser.runtime.getURL("dist/Vencord.js"); +script.id = "vencord-script"; +Object.assign(script.dataset, { + extensionBaseUrl: browser.runtime.getURL(""), + version: browser.runtime.getManifest().version +}); const style = document.createElement("link"); style.type = "text/css"; diff --git a/browser/manifest.json b/browser/manifest.json index 49536a71c..69bf0cec0 100644 --- a/browser/manifest.json +++ b/browser/manifest.json @@ -28,7 +28,7 @@ "web_accessible_resources": [ { - "resources": ["dist/Vencord.js", "dist/Vencord.css"], + "resources": ["dist/*", "third-party/*"], "matches": ["*://*.discord.com/*"] } ], diff --git a/browser/manifestv2.json b/browser/manifestv2.json index 3cac9450a..a6feada7e 100644 --- a/browser/manifestv2.json +++ b/browser/manifestv2.json @@ -26,11 +26,7 @@ } ], - "background": { - "scripts": ["background.js"] - }, - - "web_accessible_resources": ["dist/Vencord.js", "dist/Vencord.css"], + "web_accessible_resources": ["dist/*", "third-party/*"], "browser_specific_settings": { "gecko": { diff --git a/browser/monaco.ts b/browser/monaco.ts new file mode 100644 index 000000000..ead061d65 --- /dev/null +++ b/browser/monaco.ts @@ -0,0 +1,43 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./patch-worker"; + +import * as monaco from "monaco-editor/esm/vs/editor/editor.main.js"; + +declare global { + const baseUrl: string; + const getCurrentCss: () => Promise<string>; + const setCss: (css: string) => void; + const getTheme: () => string; +} + +const BASE = "/dist/monaco/vs"; + +self.MonacoEnvironment = { + getWorkerUrl(_moduleId: unknown, label: string) { + const path = label === "css" ? "/language/css/css.worker.js" : "/editor/editor.worker.js"; + return new URL(BASE + path, baseUrl).toString(); + } +}; + +getCurrentCss().then(css => { + const editor = monaco.editor.create( + document.getElementById("container")!, + { + value: css, + language: "css", + theme: getTheme(), + } + ); + editor.onDidChangeModelContent(() => + setCss(editor.getValue()) + ); + window.addEventListener("resize", () => { + // make monaco re-layout + editor.layout(); + }); +}); diff --git a/browser/monacoWin.html b/browser/monacoWin.html new file mode 100644 index 000000000..a55b0e547 --- /dev/null +++ b/browser/monacoWin.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <title>Vencord QuickCSS Editor + + + + +
+ + + + diff --git a/browser/patch-worker.js b/browser/patch-worker.js new file mode 100644 index 000000000..428ea6cc0 --- /dev/null +++ b/browser/patch-worker.js @@ -0,0 +1,135 @@ +/* +Copyright 2013 Rob Wu +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +// Target: Chrome 20+ + +// W3-compliant Worker proxy. +// This module replaces the global Worker object. +// When invoked, the default Worker object is called. +// If this call fails with SECURITY_ERR, the script is fetched +// using async XHR, and transparently proxies all calls and +// setters/getters to the new Worker object. +// Note: This script does not magically circumvent the Same origin policy. + +(function () { + 'use strict'; + var Worker_ = window.Worker; + var URL = window.URL || window.webkitURL; + // Create dummy worker for the following purposes: + // 1. Don't override the global Worker object if the fallback isn't + // going to work (future API changes?) + // 2. Use it to trigger early validation of postMessage calls + // Note: Blob constructor is supported since Chrome 20, but since + // some of the used Chrome APIs are only supported as of Chrome 20, + // I don't bother adding a BlobBuilder fallback. + var dummyWorker = new Worker_( + URL.createObjectURL(new Blob([], { type: 'text/javascript' }))); + window.Worker = function Worker(scriptURL) { + if (arguments.length === 0) { + throw new TypeError('Not enough arguments'); + } + try { + return new Worker_(scriptURL); + } catch (e) { + if (e.code === 18/*DOMException.SECURITY_ERR*/) { + return new WorkerXHR(scriptURL); + } else { + throw e; + } + } + }; + // Bind events and replay queued messages + function bindWorker(worker, workerURL) { + if (worker._terminated) { + return; + } + worker.Worker = new Worker_(workerURL); + worker.Worker.onerror = worker._onerror; + worker.Worker.onmessage = worker._onmessage; + var o; + while ((o = worker._replayQueue.shift())) { + worker.Worker[o.method].apply(worker.Worker, o.arguments); + } + while ((o = worker._messageQueue.shift())) { + worker.Worker.postMessage.apply(worker.Worker, o); + } + } + function WorkerXHR(scriptURL) { + var worker = this; + var x = new XMLHttpRequest(); + x.responseType = 'blob'; + x.onload = function () { + // http://stackoverflow.com/a/10372280/938089 + var workerURL = URL.createObjectURL(x.response); + bindWorker(worker, workerURL); + }; + x.open('GET', scriptURL); + x.send(); + worker._replayQueue = []; + worker._messageQueue = []; + } + WorkerXHR.prototype = { + constructor: Worker_, + terminate: function () { + if (!this._terminated) { + this._terminated = true; + if (this.Worker) + this.Worker.terminate(); + } + }, + postMessage: function (message, transfer) { + if (!(this instanceof WorkerXHR)) + throw new TypeError('Illegal invocation'); + if (this.Worker) { + this.Worker.postMessage.apply(this.Worker, arguments); + } else { + // Trigger validation: + dummyWorker.postMessage(message); + // Alright, push the valid message to the queue. + this._messageQueue.push(arguments); + } + } + }; + // Implement the EventTarget interface + [ + 'addEventListener', + 'removeEventListener', + 'dispatchEvent' + ].forEach(function (method) { + WorkerXHR.prototype[method] = function () { + if (!(this instanceof WorkerXHR)) { + throw new TypeError('Illegal invocation'); + } + if (this.Worker) { + this.Worker[method].apply(this.Worker, arguments); + } else { + this._replayQueue.push({ method: method, arguments: arguments }); + } + }; + }); + Object.defineProperties(WorkerXHR.prototype, { + onmessage: { + get: function () { return this._onmessage || null; }, + set: function (func) { + this._onmessage = typeof func === 'function' ? func : null; + } + }, + onerror: { + get: function () { return this._onerror || null; }, + set: function (func) { + this._onerror = typeof func === 'function' ? func : null; + } + } + }); +})(); \ No newline at end of file diff --git a/package.json b/package.json index 118ab7af9..c0b3998f1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.4.7", + "version": "1.5.2", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { @@ -36,11 +36,14 @@ "@vap/shiki": "0.10.5", "eslint-plugin-simple-header": "^1.0.2", "fflate": "^0.7.4", + "gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3", + "monaco-editor": "^0.43.0", "nanoid": "^4.0.2", "usercss-meta": "^0.12.0", "virtual-merge": "^1.0.1" }, "devDependencies": { + "@types/chrome": "^0.0.246", "@types/diff": "^5.0.3", "@types/less": "^3.0.4", "@types/lodash": "^4.14.194", @@ -68,7 +71,8 @@ "stylelint-config-standard": "^33.0.0", "tsx": "^3.12.7", "type-fest": "^3.9.0", - "typescript": "^5.0.4" + "typescript": "^5.0.4", + "zip-local": "^0.3.5" }, "packageManager": "pnpm@8.1.1", "pnpm": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 778ca7536..d62be17f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,9 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + patchedDependencies: '@types/less@3.0.4': hash: krcufrsfhsuxuoj7hocqugs6zi @@ -27,6 +31,12 @@ dependencies: fflate: specifier: ^0.7.4 version: 0.7.4 + gifenc: + specifier: github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3 + version: github.com/mattdesl/gifenc/64842fca317b112a8590f8fef2bf3825da8f6fe3 + monaco-editor: + specifier: ^0.43.0 + version: 0.43.0 nanoid: specifier: ^4.0.2 version: 4.0.2 @@ -38,6 +48,9 @@ dependencies: version: 1.0.1 devDependencies: + '@types/chrome': + specifier: ^0.0.246 + version: 0.0.246 '@types/diff': specifier: ^5.0.3 version: 5.0.3 @@ -122,6 +135,9 @@ devDependencies: typescript: specifier: ^5.0.4 version: 5.0.4 + zip-local: + specifier: ^0.3.5 + version: 0.3.5 packages: @@ -535,10 +551,31 @@ packages: resolution: {integrity: sha512-gAC33DCXYwNTI/k1PxOVHmbbzakUSMbb/DHpoV6rn4pKZtPI1dduULSmAAm/y1ipgIlArnk2JcnQzw4n2tCZHw==} dev: false + /@types/chrome@0.0.246: + resolution: {integrity: sha512-MxGxEomGxsJiL9xe/7ZwVgwdn8XVKWbPvxpVQl3nWOjrS0Ce63JsfzxUc4aU3GvRcUPYsfufHmJ17BFyKxeA4g==} + dependencies: + '@types/filesystem': 0.0.33 + '@types/har-format': 1.2.13 + dev: true + /@types/diff@5.0.3: resolution: {integrity: sha512-amrLbRqTU9bXMCc6uX0sWpxsQzRIo9z6MJPkH1pkez/qOxuqSZVuryJAWoBRq94CeG8JxY+VK4Le9HtjQR5T9A==} dev: true + /@types/filesystem@0.0.33: + resolution: {integrity: sha512-2KedRPzwu2K528vFkoXnnWdsG0MtUwPjuA7pRy4vKxlxHEe8qUDZibYHXJKZZr2Cl/ELdCWYqyb/MKwsUuzBWw==} + dependencies: + '@types/filewriter': 0.0.30 + dev: true + + /@types/filewriter@0.0.30: + resolution: {integrity: sha512-lB98tui0uxc7erbj0serZfJlHKLNJHwBltPnbmO1WRpL5T325GOHRiQfr2E29V2q+S1brDO63Fpdt6vb3bES9Q==} + dev: true + + /@types/har-format@1.2.13: + resolution: {integrity: sha512-PwBsCBD3lDODn4xpje3Y1di0aDJp4Ww7aSfMRVw6ysnxD4I7Wmq2mBkSKaDtN403hqH5sp6c9xQUvFYY3+lkBg==} + dev: true + /@types/json-schema@7.0.11: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true @@ -873,6 +910,10 @@ packages: engines: {node: '>=8'} dev: true + /async@1.5.2: + resolution: {integrity: sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==} + dev: true + /atob@2.1.2: resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} engines: {node: '>= 4.5.0'} @@ -1897,6 +1938,10 @@ packages: resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} dev: true + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + dev: true + /grapheme-splitter@1.0.4: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true @@ -2214,6 +2259,12 @@ packages: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} dev: false + /jszip@2.7.0: + resolution: {integrity: sha512-JIsRKRVC3gTRo2vM4Wy9WBC3TRcfnIZU8k65Phi3izkvPH975FowRYtKGT6PxevA0XnJ/yO8b0QwV0ydVyQwfw==} + dependencies: + pako: 1.0.11 + dev: true + /kind-of@3.2.2: resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} engines: {node: '>=0.10.0'} @@ -2384,6 +2435,10 @@ packages: resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} dev: true + /monaco-editor@0.43.0: + resolution: {integrity: sha512-cnoqwQi/9fml2Szamv1XbSJieGJ1Dc8tENVMD26Kcfl7xGQWp7OBKMjlwKVGYFJ3/AXJjSOGvcqK7Ry/j9BM1Q==} + dev: false + /ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} dev: true @@ -2541,6 +2596,10 @@ packages: engines: {node: '>=6'} dev: true + /pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + dev: true + /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -2692,6 +2751,11 @@ packages: - utf-8-validate dev: true + /q@1.5.1: + resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} + engines: {node: '>=0.6.0', teleport: '>=0.2.0'} + dev: true + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true @@ -3412,6 +3476,17 @@ packages: engines: {node: '>=10'} dev: true -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false + /zip-local@0.3.5: + resolution: {integrity: sha512-GRV3D5TJY+/PqyeRm5CYBs7xVrKTKzljBoEXvocZu0HJ7tPEcgpSOYa2zFIsCZWgKWMuc4U3yMFgFkERGFIB9w==} + dependencies: + async: 1.5.2 + graceful-fs: 4.2.11 + jszip: 2.7.0 + q: 1.5.1 + dev: true + + github.com/mattdesl/gifenc/64842fca317b112a8590f8fef2bf3825da8f6fe3: + resolution: {tarball: https://codeload.github.com/mattdesl/gifenc/tar.gz/64842fca317b112a8590f8fef2bf3825da8f6fe3} + name: gifenc + version: 1.0.3 + dev: false diff --git a/scripts/build/build.mjs b/scripts/build/build.mjs index ce84fed5d..c8978a4b3 100755 --- a/scripts/build/build.mjs +++ b/scripts/build/build.mjs @@ -25,6 +25,8 @@ const defines = { IS_STANDALONE: isStandalone, IS_DEV: JSON.stringify(watch), IS_UPDATER_DISABLED: updaterDisabled, + IS_WEB: false, + IS_EXTENSION: false, VERSION: JSON.stringify(VERSION), BUILD_TIMESTAMP, }; @@ -77,7 +79,6 @@ await Promise.all([ ], define: { ...defines, - IS_WEB: false, IS_DISCORD_DESKTOP: true, IS_VESKTOP: false } @@ -123,7 +124,6 @@ await Promise.all([ ], define: { ...defines, - IS_WEB: false, IS_DISCORD_DESKTOP: false, IS_VESKTOP: true } diff --git a/scripts/build/buildWeb.mjs b/scripts/build/buildWeb.mjs index aab82d069..e4eeb53ea 100644 --- a/scripts/build/buildWeb.mjs +++ b/scripts/build/buildWeb.mjs @@ -17,12 +17,11 @@ * along with this program. If not, see . */ - import esbuild from "esbuild"; -import { zip } from "fflate"; import { readFileSync } from "fs"; -import { appendFile, mkdir, readFile, rm, writeFile } from "fs/promises"; +import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises"; import { join } from "path"; +import Zip from "zip-local"; import { BUILD_TIMESTAMP, commonOpts, globPlugins, VERSION, watch } from "./common.mjs"; @@ -42,6 +41,7 @@ const commonOptions = { target: ["esnext"], define: { IS_WEB: "true", + IS_EXTENSION: "false", IS_STANDALONE: "true", IS_DEV: JSON.stringify(watch), IS_DISCORD_DESKTOP: "false", @@ -52,19 +52,58 @@ const commonOptions = { } }; +const MonacoWorkerEntryPoints = [ + "vs/language/css/css.worker.js", + "vs/editor/editor.worker.js" +]; + +const RnNoiseFiles = [ + "dist/rnnoise.wasm", + "dist/rnnoise_simd.wasm", + "dist/rnnoise/workletProcessor.js", + "LICENSE" +]; + await Promise.all( [ + esbuild.build({ + entryPoints: MonacoWorkerEntryPoints.map(entry => `node_modules/monaco-editor/esm/${entry}`), + bundle: true, + minify: true, + format: "iife", + outbase: "node_modules/monaco-editor/esm/", + outdir: "dist/monaco" + }), + esbuild.build({ + entryPoints: ["browser/monaco.ts"], + bundle: true, + minify: true, + format: "iife", + outfile: "dist/monaco/index.js", + loader: { + ".ttf": "file" + } + }), esbuild.build({ ...commonOptions, outfile: "dist/browser.js", footer: { js: "//# sourceURL=VencordWeb" }, }), + esbuild.build({ + ...commonOptions, + outfile: "dist/extension.js", + define: { + ...commonOptions?.define, + IS_EXTENSION: "true", + }, + footer: { js: "//# sourceURL=VencordWeb" }, + }), esbuild.build({ ...commonOptions, inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])], define: { - "window": "unsafeWindow", - ...(commonOptions?.define) + ...(commonOptions?.define), + window: "unsafeWindow", }, outfile: "dist/Vencord.user.js", banner: { @@ -79,12 +118,41 @@ await Promise.all( ); /** - * @type {(target: string, files: string[], shouldZip: boolean) => Promise} + * @type {(dir: string) => Promise} */ -async function buildPluginZip(target, files, shouldZip) { +async function globDir(dir) { + const files = []; + + for (const child of await readdir(dir, { withFileTypes: true })) { + const p = join(dir, child.name); + if (child.isDirectory()) + files.push(...await globDir(p)); + else + files.push(p); + } + + return files; +} + +/** + * @type {(dir: string, basePath?: string) => Promise>} + */ +async function loadDir(dir, basePath = "") { + const files = await globDir(dir); + return Object.fromEntries(await Promise.all(files.map(async f => [f.slice(basePath.length), await readFile(f)]))); +} + +/** + * @type {(target: string, files: string[]) => Promise} + */ +async function buildExtension(target, files, noMonaco = false) { const entries = { - "dist/Vencord.js": await readFile("dist/browser.js"), - "dist/Vencord.css": await readFile("dist/browser.css"), + "dist/Vencord.js": await readFile("dist/extension.js"), + "dist/Vencord.css": await readFile("dist/extension.css"), + ...(noMonaco ? {} : await loadDir("dist/monaco")), + ...Object.fromEntries(await Promise.all(RnNoiseFiles.map(async file => + [`third-party/rnnoise/${file.replace(/^dist\//, "")}`, await readFile(`node_modules/@sapphi-red/web-noise-suppressor/${file}`)] + ))), ...Object.fromEntries(await Promise.all(files.map(async f => { let content = await readFile(join("browser", f)); if (f.startsWith("manifest")) { @@ -100,31 +168,15 @@ async function buildPluginZip(target, files, shouldZip) { }))), }; - if (shouldZip) { - return new Promise((resolve, reject) => { - zip(entries, {}, (err, data) => { - if (err) { - reject(err); - } else { - const out = join("dist", target); - writeFile(out, data).then(() => { - console.info("Extension written to " + out); - resolve(); - }).catch(reject); - } - }); - }); - } else { - await rm(target, { recursive: true, force: true }); - await Promise.all(Object.entries(entries).map(async ([file, content]) => { - const dest = join("dist", target, file); - const parentDirectory = join(dest, ".."); - await mkdir(parentDirectory, { recursive: true }); - await writeFile(dest, content); - })); + await rm(target, { recursive: true, force: true }); + await Promise.all(Object.entries(entries).map(async ([file, content]) => { + const dest = join("dist", target, file); + const parentDirectory = join(dest, ".."); + await mkdir(parentDirectory, { recursive: true }); + await writeFile(dest, content); + })); - console.info("Unpacked Extension written to dist/" + target); - } + console.info("Unpacked Extension written to dist/" + target); } const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content => { @@ -142,8 +194,9 @@ const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content await Promise.all([ appendCssRuntime, - buildPluginZip("extension.zip", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"], true), - buildPluginZip("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"], false), - buildPluginZip("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"], false), + buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]), + buildExtension("firefox-unpacked", ["content.js", "manifestv2.json", "icon.png"], true), ]); +Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension.zip"); +console.info("Packed Chromium Extension written to dist/extension.zip"); diff --git a/scripts/generatePluginList.ts b/scripts/generatePluginList.ts index b788178cd..e8aa33a46 100644 --- a/scripts/generatePluginList.ts +++ b/scripts/generatePluginList.ts @@ -18,7 +18,8 @@ import { Dirent, readdirSync, readFileSync, writeFileSync } from "fs"; import { access, readFile } from "fs/promises"; -import { join } from "path"; +import { join, sep } from "path"; +import { normalize as posixNormalize, sep as posixSep } from "path/posix"; import { BigIntLiteral, createSourceFile, Identifier, isArrayLiteralExpression, isCallExpression, isExportAssignment, isIdentifier, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isStringLiteral, isVariableStatement, NamedDeclaration, NodeArray, ObjectLiteralExpression, ScriptTarget, StringLiteral, SyntaxKind } from "typescript"; import { getPluginTarget } from "./utils.mjs"; @@ -39,6 +40,7 @@ interface PluginData { required: boolean; enabledByDefault: boolean; target: "discordDesktop" | "vencordDesktop" | "web" | "dev"; + filePath: string; } const devs = {} as Record; @@ -165,6 +167,12 @@ async function parseFile(fileName: string) { data.target = target as any; } + data.filePath = posixNormalize(fileName) + .split(sep) + .join(posixSep) + .replace(/\/index\.([jt]sx?)$/, "") + .replace(/^src\/plugins\//, ""); + let readme = ""; try { readme = readFileSync(join(fileName, "..", "README.md"), "utf-8"); diff --git a/src/VencordNative.ts b/src/VencordNative.ts index 5426fa359..398cdc42c 100644 --- a/src/VencordNative.ts +++ b/src/VencordNative.ts @@ -22,7 +22,8 @@ export default { deleteTheme: (fileName: string) => invoke(IpcEvents.DELETE_THEME, fileName), getThemesDir: () => invoke(IpcEvents.GET_THEMES_DIR), getThemesList: () => invoke>(IpcEvents.GET_THEMES_LIST), - getThemeData: (fileName: string) => invoke(IpcEvents.GET_THEME_DATA, fileName) + getThemeData: (fileName: string) => invoke(IpcEvents.GET_THEME_DATA, fileName), + getSystemValues: () => invoke>(IpcEvents.GET_THEME_SYSTEM_VALUES), }, updater: { diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index a24045efd..26364fc94 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -28,8 +28,8 @@ interface BaseIconProps extends IconProps { interface IconProps extends SVGProps { className?: string; - height?: number; - width?: number; + height?: string | number; + width?: string | number; } function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren) { @@ -97,7 +97,7 @@ export function OpenExternalIcon(props: IconProps) { > @@ -121,9 +121,13 @@ export function InfoIcon(props: IconProps) { - + ); } @@ -139,8 +143,8 @@ export function OwnerCrownIcon(props: IconProps) { > @@ -159,8 +163,6 @@ export function ScreenshareIcon(props: IconProps) { > @@ -198,8 +200,39 @@ export function Microphone(props: IconProps) { className={classes(props.className, "vc-microphone")} viewBox="0 0 24 24" > - - + + ); } + +export function CogWheel(props: IconProps) { + return ( + + + + ); +} + +export function ReplyIcon(props: IconProps) { + return ( + + + + ); +} diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index 3712f3de1..22657e730 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -22,6 +22,7 @@ import * as DataStore from "@api/DataStore"; import { showNotice } from "@api/Notices"; import { Settings, useSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; +import { CogWheel, InfoIcon } from "@components/Icons"; import PluginModal from "@components/PluginSettings/PluginModal"; import { AddonCard } from "@components/VencordSettings/AddonCard"; import { SettingsTab } from "@components/VencordSettings/shared"; @@ -30,9 +31,9 @@ import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import { classes, isObjectEmpty } from "@utils/misc"; import { openModalLazy } from "@utils/modal"; -import { LazyComponent, useAwaiter } from "@utils/react"; +import { useAwaiter } from "@utils/react"; import { Plugin } from "@utils/types"; -import { findByCode, findByPropsLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { Alerts, Button, Card, Forms, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common"; import Plugins from "~plugins"; @@ -46,8 +47,6 @@ const logger = new Logger("PluginSettings", "#a6d189"); const InputStyles = findByPropsLazy("inputDefault", "inputWrapper"); const ButtonClasses = findByPropsLazy("button", "disabled", "enabled"); -const CogWheel = LazyComponent(() => findByCode("18.564C15.797 19.099 14.932 19.498 14 19.738V22H10V19.738C9.069")); -const InfoIcon = LazyComponent(() => findByCode("4.4408921e-16 C4.4771525,-1.77635684e-15 4.4408921e-16")); function showErrorToast(message: string) { Toasts.show({ @@ -163,7 +162,7 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on } /> diff --git a/src/components/ThemeSettings/ThemesTab.tsx b/src/components/ThemeSettings/ThemesTab.tsx index d5f054fef..d6e910baf 100644 --- a/src/components/ThemeSettings/ThemesTab.tsx +++ b/src/components/ThemeSettings/ThemesTab.tsx @@ -24,6 +24,7 @@ import { Flex } from "@components/Flex"; import { Link } from "@components/Link"; import { AddonCard } from "@components/VencordSettings/AddonCard"; import { SettingsTab, wrapTab } from "@components/VencordSettings/shared"; +import { IsFirefox } from "@utils/constants"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { openModal } from "@utils/modal"; @@ -354,12 +355,14 @@ function ThemesTab() { > Load missing Themes - + {!IsFirefox && ( + + )} diff --git a/src/components/VencordSettings/VencordTab.tsx b/src/components/VencordSettings/VencordTab.tsx index a80b79a4b..520e4daad 100644 --- a/src/components/VencordSettings/VencordTab.tsx +++ b/src/components/VencordSettings/VencordTab.tsx @@ -21,6 +21,7 @@ import { Settings, useSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import DonateButton from "@components/DonateButton"; import { ErrorCard } from "@components/ErrorCard"; +import { IsFirefox } from "@utils/constants"; import { Margins } from "@utils/margins"; import { identity } from "@utils/misc"; import { relaunch, showItemInFolder } from "@utils/native"; @@ -109,12 +110,14 @@ function VencordSettings() { Restart Client )} - + {!IsFirefox && ( + + )} {!IS_WEB && (