mirror of
https://github.com/Vendicated/Vencord.git
synced 2025-02-25 07:48:32 +00:00
Merge branch 'main' of https://github.com/Vendicated/Vencord
This commit is contained in:
commit
f8b026dbae
62 changed files with 2473 additions and 465 deletions
9
.github/workflows/build.yml
vendored
9
.github/workflows/build.yml
vendored
|
@ -1,4 +1,4 @@
|
||||||
name: Build latest
|
name: Build DevBuild
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
|
@ -9,6 +9,7 @@ on:
|
||||||
- browser/**
|
- browser/**
|
||||||
- scripts/build/**
|
- scripts/build/**
|
||||||
- package.json
|
- package.json
|
||||||
|
- pnpm-lock.yaml
|
||||||
env:
|
env:
|
||||||
FORCE_COLOR: true
|
FORCE_COLOR: true
|
||||||
|
|
||||||
|
@ -21,7 +22,7 @@ jobs:
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
||||||
|
|
||||||
- name: Use Node.js 18
|
- name: Use Node.js 19
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 19
|
node-version: 19
|
||||||
|
@ -35,7 +36,7 @@ jobs:
|
||||||
|
|
||||||
- name: Sign firefox extension
|
- name: Sign firefox extension
|
||||||
run: |
|
run: |
|
||||||
pnpx web-ext sign --api-key $WEBEXT_USER --api-secret $WEBEXT_SECRET --channel=listed
|
pnpx web-ext sign --api-key $WEBEXT_USER --api-secret $WEBEXT_SECRET --channel=unlisted
|
||||||
env:
|
env:
|
||||||
WEBEXT_USER: ${{ secrets.WEBEXT_USER }}
|
WEBEXT_USER: ${{ secrets.WEBEXT_USER }}
|
||||||
WEBEXT_SECRET: ${{ secrets.WEBEXT_SECRET }}
|
WEBEXT_SECRET: ${{ secrets.WEBEXT_SECRET }}
|
||||||
|
@ -47,7 +48,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
mv dist/*.xpi dist/Vencord-for-Firefox.xpi
|
mv dist/*.xpi dist/Vencord-for-Firefox.xpi
|
||||||
mv dist/extension-v3.zip dist/Vencord-for-Chrome-and-Edge.zip
|
mv dist/extension-v3.zip dist/Vencord-for-Chrome-and-Edge.zip
|
||||||
rm -rf dist/extension-v2-unpacked
|
rm -rf dist/extension-v2-unpacked dist/extension-v2.zip
|
||||||
|
|
||||||
- name: Get some values needed for the release
|
- name: Get some values needed for the release
|
||||||
id: release_values
|
id: release_values
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -18,3 +18,6 @@ lerna-debug.log*
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
||||||
src/userplugins
|
src/userplugins
|
||||||
|
|
||||||
|
ExtensionCache/
|
||||||
|
settings/
|
||||||
|
|
|
@ -19,7 +19,11 @@ If you're a power user who wants to contribute and make plugins or just want to
|
||||||
|
|
||||||
## Installing on Browser
|
## Installing on Browser
|
||||||
|
|
||||||
Install the browser extension for [](https://github.com/Vendicated/Vencord/releases/latest/download/Vencord-for-Chrome-and-Edge.zip), [](https://github.com/Vendicated/Vencord/releases/latest/download/Vencord-for-Firefox.xpi) or [UserScript](https://github.com/Vendicated/Vencord/releases/download/devbuild/Vencord.user.js). Please note that they aren't automatically updated for now, so you will regularely have to reinstall it.
|
[](https://addons.mozilla.org/en-GB/firefox/addon/vencord-web/)
|
||||||
|
|
||||||
|
Or install the browser extension for
|
||||||
|
- [](https://github.com/Vendicated/Vencord/releases/latest/download/Vencord-for-Chrome-and-Edge.zip)
|
||||||
|
- [UserScript](https://github.com/Vendicated/Vencord/releases/download/devbuild/Vencord.user.js) - Please note that QuickCSS, shiki and other plugins making use of external resources will not work with the UserScript.
|
||||||
|
|
||||||
|
|
||||||
You may also build them from source, to do that do the same steps as in the manual regular install method,
|
You may also build them from source, to do that do the same steps as in the manual regular install method,
|
||||||
|
|
107
browser/GMPolyfill.js
Normal file
107
browser/GMPolyfill.js
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function fetchOptions(url) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const opt = {
|
||||||
|
method: "OPTIONS",
|
||||||
|
url: url,
|
||||||
|
};
|
||||||
|
opt.onload = resp => resolve(resp.responseHeaders);
|
||||||
|
opt.ontimeout = () => reject("fetch timeout");
|
||||||
|
opt.onerror = () => reject("fetch error");
|
||||||
|
opt.onabort = () => reject("fetch abort");
|
||||||
|
GM_xmlhttpRequest(opt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseHeaders(headers) {
|
||||||
|
if (!headers)
|
||||||
|
return {};
|
||||||
|
const result = {};
|
||||||
|
const headersArr = headers.trim().split("\n");
|
||||||
|
for (var i = 0; i < headersArr.length; i++) {
|
||||||
|
var row = headersArr[i];
|
||||||
|
var index = row.indexOf(":")
|
||||||
|
, key = row.slice(0, index).trim().toLowerCase()
|
||||||
|
, value = row.slice(index + 1).trim();
|
||||||
|
|
||||||
|
if (result[key] === undefined) {
|
||||||
|
result[key] = value;
|
||||||
|
} else if (Array.isArray(result[key])) {
|
||||||
|
result[key].push(value);
|
||||||
|
} else {
|
||||||
|
result[key] = [result[key], value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns true if CORS permits request
|
||||||
|
async function checkCors(url, method) {
|
||||||
|
const headers = parseHeaders(await fetchOptions(url));
|
||||||
|
|
||||||
|
const origin = headers["access-control-allow-origin"];
|
||||||
|
if (origin !== "*" && origin !== window.location.origin) return false;
|
||||||
|
|
||||||
|
const methods = headers["access-control-allow-methods"]?.split(/,\s/g);
|
||||||
|
if (methods && !methods.includes(method)) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function blobTo(to, blob) {
|
||||||
|
if (to === "arrayBuffer" && blob.arrayBuffer) return blob.arrayBuffer();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var fileReader = new FileReader();
|
||||||
|
fileReader.onload = event => resolve(event.target.result);
|
||||||
|
if (to === "arrayBuffer") fileReader.readAsArrayBuffer(blob);
|
||||||
|
else if (to === "text") fileReader.readAsText(blob, "utf-8");
|
||||||
|
else reject("unknown to");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function GM_fetch(url, opt) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
checkCors(url, opt?.method || "GET")
|
||||||
|
.then(can => {
|
||||||
|
if (can) {
|
||||||
|
// https://www.tampermonkey.net/documentation.php?ext=dhdg#GM_xmlhttpRequest
|
||||||
|
const options = opt || {};
|
||||||
|
options.url = url;
|
||||||
|
options.data = options.body;
|
||||||
|
options.responseType = "blob";
|
||||||
|
options.onload = resp => {
|
||||||
|
var blob = resp.response;
|
||||||
|
resp.blob = () => Promise.resolve(blob);
|
||||||
|
resp.arrayBuffer = () => blobTo("arrayBuffer", blob);
|
||||||
|
resp.text = () => blobTo("text", blob);
|
||||||
|
resp.json = async () => JSON.parse(await blobTo("text", blob));
|
||||||
|
resolve(resp);
|
||||||
|
};
|
||||||
|
options.ontimeout = () => reject("fetch timeout");
|
||||||
|
options.onerror = () => reject("fetch error");
|
||||||
|
options.onabort = () => reject("fetch abort");
|
||||||
|
GM_xmlhttpRequest(options);
|
||||||
|
} else {
|
||||||
|
reject("CORS issue");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export const fetch = GM_fetch;
|
|
@ -21,11 +21,5 @@
|
||||||
"web_accessible_resources": ["dist/Vencord.js"],
|
"web_accessible_resources": ["dist/Vencord.js"],
|
||||||
"background": {
|
"background": {
|
||||||
"scripts": ["background.js"]
|
"scripts": ["background.js"]
|
||||||
},
|
|
||||||
"browser_specific_settings": {
|
|
||||||
"gecko": {
|
|
||||||
"id": "vencord-firefox@vendicated.dev",
|
|
||||||
"strict_min_version": "92.0"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
// @supportURL https://github.com/Vendicated/Vencord
|
// @supportURL https://github.com/Vendicated/Vencord
|
||||||
// @license GPL-3.0
|
// @license GPL-3.0
|
||||||
// @match *://*.discord.com/*
|
// @match *://*.discord.com/*
|
||||||
// @grant none
|
// @grant GM_xmlhttpRequest
|
||||||
// @run-at document-start
|
// @run-at document-start
|
||||||
// @compatible chrome Chrome + Tampermonkey or Violentmonkey
|
// @compatible chrome Chrome + Tampermonkey or Violentmonkey
|
||||||
// @compatible firefox Firefox Tampermonkey
|
// @compatible firefox Firefox Tampermonkey
|
||||||
|
|
|
@ -40,6 +40,8 @@
|
||||||
"@types/yazl": "^2.4.2",
|
"@types/yazl": "^2.4.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.44.0",
|
"@typescript-eslint/eslint-plugin": "^5.44.0",
|
||||||
"@typescript-eslint/parser": "^5.44.0",
|
"@typescript-eslint/parser": "^5.44.0",
|
||||||
|
"@vap/core": "0.0.12",
|
||||||
|
"@vap/shiki": "0.10.3",
|
||||||
"console-menu": "^0.1.0",
|
"console-menu": "^0.1.0",
|
||||||
"diff": "^5.1.0",
|
"diff": "^5.1.0",
|
||||||
"discord-types": "^1.3.26",
|
"discord-types": "^1.3.26",
|
||||||
|
@ -50,6 +52,7 @@
|
||||||
"eslint-plugin-path-alias": "^1.0.0",
|
"eslint-plugin-path-alias": "^1.0.0",
|
||||||
"eslint-plugin-simple-import-sort": "^8.0.0",
|
"eslint-plugin-simple-import-sort": "^8.0.0",
|
||||||
"eslint-plugin-unused-imports": "^2.0.0",
|
"eslint-plugin-unused-imports": "^2.0.0",
|
||||||
|
"highlight.js": "10.6.0",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"puppeteer-core": "^19.3.0",
|
"puppeteer-core": "^19.3.0",
|
||||||
"standalone-electron-types": "^1.0.0",
|
"standalone-electron-types": "^1.0.0",
|
||||||
|
|
182
pnpm-lock.yaml
generated
182
pnpm-lock.yaml
generated
|
@ -13,6 +13,8 @@ specifiers:
|
||||||
'@types/yazl': ^2.4.2
|
'@types/yazl': ^2.4.2
|
||||||
'@typescript-eslint/eslint-plugin': ^5.44.0
|
'@typescript-eslint/eslint-plugin': ^5.44.0
|
||||||
'@typescript-eslint/parser': ^5.44.0
|
'@typescript-eslint/parser': ^5.44.0
|
||||||
|
'@vap/core': 0.0.12
|
||||||
|
'@vap/shiki': 0.10.3
|
||||||
console-menu: ^0.1.0
|
console-menu: ^0.1.0
|
||||||
diff: ^5.1.0
|
diff: ^5.1.0
|
||||||
discord-types: ^1.3.26
|
discord-types: ^1.3.26
|
||||||
|
@ -24,6 +26,7 @@ specifiers:
|
||||||
eslint-plugin-simple-import-sort: ^8.0.0
|
eslint-plugin-simple-import-sort: ^8.0.0
|
||||||
eslint-plugin-unused-imports: ^2.0.0
|
eslint-plugin-unused-imports: ^2.0.0
|
||||||
fflate: ^0.7.4
|
fflate: ^0.7.4
|
||||||
|
highlight.js: 10.6.0
|
||||||
moment: ^2.29.4
|
moment: ^2.29.4
|
||||||
puppeteer-core: ^19.3.0
|
puppeteer-core: ^19.3.0
|
||||||
standalone-electron-types: ^1.0.0
|
standalone-electron-types: ^1.0.0
|
||||||
|
@ -39,8 +42,10 @@ devDependencies:
|
||||||
'@types/react': 18.0.25
|
'@types/react': 18.0.25
|
||||||
'@types/react-dom': 18.0.9
|
'@types/react-dom': 18.0.9
|
||||||
'@types/yazl': 2.4.2
|
'@types/yazl': 2.4.2
|
||||||
'@typescript-eslint/eslint-plugin': 5.44.0_fnsv2sbzcckq65bwfk7a5xwslu
|
'@typescript-eslint/eslint-plugin': 5.45.0_czs5uoqkd3podpy6vgtsxfc7au
|
||||||
'@typescript-eslint/parser': 5.44.0_hsf322ms6xhhd4b5ne6lb74y4a
|
'@typescript-eslint/parser': 5.45.0_hsf322ms6xhhd4b5ne6lb74y4a
|
||||||
|
'@vap/core': 0.0.12
|
||||||
|
'@vap/shiki': 0.10.3
|
||||||
console-menu: 0.1.0
|
console-menu: 0.1.0
|
||||||
diff: 5.1.0
|
diff: 5.1.0
|
||||||
discord-types: 1.3.26
|
discord-types: 1.3.26
|
||||||
|
@ -50,7 +55,8 @@ devDependencies:
|
||||||
eslint-plugin-header: 3.1.1_eslint@8.28.0
|
eslint-plugin-header: 3.1.1_eslint@8.28.0
|
||||||
eslint-plugin-path-alias: 1.0.0_m6sma4g6bh67km3q6igf6uxaja_eslint@8.28.0
|
eslint-plugin-path-alias: 1.0.0_m6sma4g6bh67km3q6igf6uxaja_eslint@8.28.0
|
||||||
eslint-plugin-simple-import-sort: 8.0.0_eslint@8.28.0
|
eslint-plugin-simple-import-sort: 8.0.0_eslint@8.28.0
|
||||||
eslint-plugin-unused-imports: 2.0.0_aucl44mjeutxyzmt4nvo2cczya
|
eslint-plugin-unused-imports: 2.0.0_5am2datodjm2qi4eijrjrnoz54
|
||||||
|
highlight.js: 10.6.0
|
||||||
moment: 2.29.4
|
moment: 2.29.4
|
||||||
puppeteer-core: 19.3.0
|
puppeteer-core: 19.3.0
|
||||||
standalone-electron-types: 1.0.0
|
standalone-electron-types: 1.0.0
|
||||||
|
@ -83,9 +89,9 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv: 6.12.6
|
ajv: 6.12.6
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
espree: 9.4.1
|
espree: 9.4.0
|
||||||
globals: 13.18.0
|
globals: 13.17.0
|
||||||
ignore: 5.2.1
|
ignore: 5.2.0
|
||||||
import-fresh: 3.3.0
|
import-fresh: 3.3.0
|
||||||
js-yaml: 4.1.0
|
js-yaml: 4.1.0
|
||||||
minimatch: 3.1.2
|
minimatch: 3.1.2
|
||||||
|
@ -161,7 +167,7 @@ packages:
|
||||||
resolution: {integrity: sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA==}
|
resolution: {integrity: sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/prop-types': 15.7.5
|
'@types/prop-types': 15.7.5
|
||||||
csstype: 3.1.1
|
csstype: 3.1.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/react/18.0.25:
|
/@types/react/18.0.25:
|
||||||
|
@ -169,7 +175,7 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/prop-types': 15.7.5
|
'@types/prop-types': 15.7.5
|
||||||
'@types/scheduler': 0.16.2
|
'@types/scheduler': 0.16.2
|
||||||
csstype: 3.1.1
|
csstype: 3.1.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/scheduler/0.16.2:
|
/@types/scheduler/0.16.2:
|
||||||
|
@ -194,8 +200,8 @@ packages:
|
||||||
'@types/node': 18.11.9
|
'@types/node': 18.11.9
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/eslint-plugin/5.44.0_fnsv2sbzcckq65bwfk7a5xwslu:
|
/@typescript-eslint/eslint-plugin/5.45.0_czs5uoqkd3podpy6vgtsxfc7au:
|
||||||
resolution: {integrity: sha512-j5ULd7FmmekcyWeArx+i8x7sdRHzAtXTkmDPthE4amxZOWKFK7bomoJ4r7PJ8K7PoMzD16U8MmuZFAonr1ERvw==}
|
resolution: {integrity: sha512-CXXHNlf0oL+Yg021cxgOdMHNTXD17rHkq7iW6RFHoybdFgQBjU3yIXhhcPpGwr1CjZlo6ET8C6tzX5juQoXeGA==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@typescript-eslint/parser': ^5.0.0
|
'@typescript-eslint/parser': ^5.0.0
|
||||||
|
@ -205,24 +211,24 @@ packages:
|
||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/parser': 5.44.0_hsf322ms6xhhd4b5ne6lb74y4a
|
'@typescript-eslint/parser': 5.45.0_hsf322ms6xhhd4b5ne6lb74y4a
|
||||||
'@typescript-eslint/scope-manager': 5.44.0
|
'@typescript-eslint/scope-manager': 5.45.0
|
||||||
'@typescript-eslint/type-utils': 5.44.0_hsf322ms6xhhd4b5ne6lb74y4a
|
'@typescript-eslint/type-utils': 5.45.0_hsf322ms6xhhd4b5ne6lb74y4a
|
||||||
'@typescript-eslint/utils': 5.44.0_hsf322ms6xhhd4b5ne6lb74y4a
|
'@typescript-eslint/utils': 5.45.0_hsf322ms6xhhd4b5ne6lb74y4a
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
eslint: 8.28.0
|
eslint: 8.28.0
|
||||||
ignore: 5.2.1
|
ignore: 5.2.0
|
||||||
natural-compare-lite: 1.4.0
|
natural-compare-lite: 1.4.0
|
||||||
regexpp: 3.2.0
|
regexpp: 3.2.0
|
||||||
semver: 7.3.8
|
semver: 7.3.7
|
||||||
tsutils: 3.21.0_typescript@4.9.3
|
tsutils: 3.21.0_typescript@4.9.3
|
||||||
typescript: 4.9.3
|
typescript: 4.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/parser/5.44.0_hsf322ms6xhhd4b5ne6lb74y4a:
|
/@typescript-eslint/parser/5.45.0_hsf322ms6xhhd4b5ne6lb74y4a:
|
||||||
resolution: {integrity: sha512-H7LCqbZnKqkkgQHaKLGC6KUjt3pjJDx8ETDqmwncyb6PuoigYajyAwBGz08VU/l86dZWZgI4zm5k2VaKqayYyA==}
|
resolution: {integrity: sha512-brvs/WSM4fKUmF5Ot/gEve6qYiCMjm6w4HkHPfS6ZNmxTS0m0iNN4yOChImaCkqc1hRwFGqUyanMXuGal6oyyQ==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
|
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||||
|
@ -231,9 +237,9 @@ packages:
|
||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/scope-manager': 5.44.0
|
'@typescript-eslint/scope-manager': 5.45.0
|
||||||
'@typescript-eslint/types': 5.44.0
|
'@typescript-eslint/types': 5.45.0
|
||||||
'@typescript-eslint/typescript-estree': 5.44.0_typescript@4.9.3
|
'@typescript-eslint/typescript-estree': 5.45.0_typescript@4.9.3
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
eslint: 8.28.0
|
eslint: 8.28.0
|
||||||
typescript: 4.9.3
|
typescript: 4.9.3
|
||||||
|
@ -241,16 +247,16 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/scope-manager/5.44.0:
|
/@typescript-eslint/scope-manager/5.45.0:
|
||||||
resolution: {integrity: sha512-2pKml57KusI0LAhgLKae9kwWeITZ7IsZs77YxyNyIVOwQ1kToyXRaJLl+uDEXzMN5hnobKUOo2gKntK9H1YL8g==}
|
resolution: {integrity: sha512-noDMjr87Arp/PuVrtvN3dXiJstQR1+XlQ4R1EvzG+NMgXi8CuMCXpb8JqNtFHKceVSQ985BZhfRdowJzbv4yKw==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 5.44.0
|
'@typescript-eslint/types': 5.45.0
|
||||||
'@typescript-eslint/visitor-keys': 5.44.0
|
'@typescript-eslint/visitor-keys': 5.45.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/type-utils/5.44.0_hsf322ms6xhhd4b5ne6lb74y4a:
|
/@typescript-eslint/type-utils/5.45.0_hsf322ms6xhhd4b5ne6lb74y4a:
|
||||||
resolution: {integrity: sha512-A1u0Yo5wZxkXPQ7/noGkRhV4J9opcymcr31XQtOzcc5nO/IHN2E2TPMECKWYpM3e6olWEM63fq/BaL1wEYnt/w==}
|
resolution: {integrity: sha512-DY7BXVFSIGRGFZ574hTEyLPRiQIvI/9oGcN8t1A7f6zIs6ftbrU0nhyV26ZW//6f85avkwrLag424n+fkuoJ1Q==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: '*'
|
eslint: '*'
|
||||||
|
@ -259,8 +265,8 @@ packages:
|
||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/typescript-estree': 5.44.0_typescript@4.9.3
|
'@typescript-eslint/typescript-estree': 5.45.0_typescript@4.9.3
|
||||||
'@typescript-eslint/utils': 5.44.0_hsf322ms6xhhd4b5ne6lb74y4a
|
'@typescript-eslint/utils': 5.45.0_hsf322ms6xhhd4b5ne6lb74y4a
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
eslint: 8.28.0
|
eslint: 8.28.0
|
||||||
tsutils: 3.21.0_typescript@4.9.3
|
tsutils: 3.21.0_typescript@4.9.3
|
||||||
|
@ -269,13 +275,13 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/types/5.44.0:
|
/@typescript-eslint/types/5.45.0:
|
||||||
resolution: {integrity: sha512-Tp+zDnHmGk4qKR1l+Y1rBvpjpm5tGXX339eAlRBDg+kgZkz9Bw+pqi4dyseOZMsGuSH69fYfPJCBKBrbPCxYFQ==}
|
resolution: {integrity: sha512-QQij+u/vgskA66azc9dCmx+rev79PzX8uDHpsqSjEFtfF2gBUTRCpvYMh2gw2ghkJabNkPlSUCimsyBEQZd1DA==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/typescript-estree/5.44.0_typescript@4.9.3:
|
/@typescript-eslint/typescript-estree/5.45.0_typescript@4.9.3:
|
||||||
resolution: {integrity: sha512-M6Jr+RM7M5zeRj2maSfsZK2660HKAJawv4Ud0xT+yauyvgrsHu276VtXlKDFnEmhG+nVEd0fYZNXGoAgxwDWJw==}
|
resolution: {integrity: sha512-maRhLGSzqUpFcZgXxg1qc/+H0bT36lHK4APhp0AEUVrpSwXiRAomm/JGjSG+kNUio5kAa3uekCYu/47cnGn5EQ==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '*'
|
typescript: '*'
|
||||||
|
@ -283,56 +289,70 @@ packages:
|
||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 5.44.0
|
'@typescript-eslint/types': 5.45.0
|
||||||
'@typescript-eslint/visitor-keys': 5.44.0
|
'@typescript-eslint/visitor-keys': 5.45.0
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
globby: 11.1.0
|
globby: 11.1.0
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
semver: 7.3.8
|
semver: 7.3.7
|
||||||
tsutils: 3.21.0_typescript@4.9.3
|
tsutils: 3.21.0_typescript@4.9.3
|
||||||
typescript: 4.9.3
|
typescript: 4.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/utils/5.44.0_hsf322ms6xhhd4b5ne6lb74y4a:
|
/@typescript-eslint/utils/5.45.0_hsf322ms6xhhd4b5ne6lb74y4a:
|
||||||
resolution: {integrity: sha512-fMzA8LLQ189gaBjS0MZszw5HBdZgVwxVFShCO3QN+ws3GlPkcy9YuS3U4wkT6su0w+Byjq3mS3uamy9HE4Yfjw==}
|
resolution: {integrity: sha512-OUg2JvsVI1oIee/SwiejTot2OxwU8a7UfTFMOdlhD2y+Hl6memUSL4s98bpUTo8EpVEr0lmwlU7JSu/p2QpSvA==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
|
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/json-schema': 7.0.11
|
'@types/json-schema': 7.0.11
|
||||||
'@types/semver': 7.3.13
|
'@types/semver': 7.3.13
|
||||||
'@typescript-eslint/scope-manager': 5.44.0
|
'@typescript-eslint/scope-manager': 5.45.0
|
||||||
'@typescript-eslint/types': 5.44.0
|
'@typescript-eslint/types': 5.45.0
|
||||||
'@typescript-eslint/typescript-estree': 5.44.0_typescript@4.9.3
|
'@typescript-eslint/typescript-estree': 5.45.0_typescript@4.9.3
|
||||||
eslint: 8.28.0
|
eslint: 8.28.0
|
||||||
eslint-scope: 5.1.1
|
eslint-scope: 5.1.1
|
||||||
eslint-utils: 3.0.0_eslint@8.28.0
|
eslint-utils: 3.0.0_eslint@8.28.0
|
||||||
semver: 7.3.8
|
semver: 7.3.7
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
- typescript
|
- typescript
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@typescript-eslint/visitor-keys/5.44.0:
|
/@typescript-eslint/visitor-keys/5.45.0:
|
||||||
resolution: {integrity: sha512-a48tLG8/4m62gPFbJ27FxwCOqPKxsb8KC3HkmYoq2As/4YyjQl1jDbRr1s63+g4FS/iIehjmN3L5UjmKva1HzQ==}
|
resolution: {integrity: sha512-jc6Eccbn2RtQPr1s7th6jJWQHBHI6GBVQkCHoJFQ5UreaKm59Vxw+ynQUPPY2u2Amquc+7tmEoC2G52ApsGNNg==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 5.44.0
|
'@typescript-eslint/types': 5.45.0
|
||||||
eslint-visitor-keys: 3.3.0
|
eslint-visitor-keys: 3.3.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/acorn-jsx/5.3.2_acorn@8.8.1:
|
/@vap/core/0.0.12:
|
||||||
|
resolution: {integrity: sha512-3csHpkE1zUSRTZwl7xIf2uXg1cD4IhhtUm0F6K/dWydc95R5Nj+krB4OTNATuqkewIv/ViCbwjPfkafAgvZQSg==}
|
||||||
|
dependencies:
|
||||||
|
eventemitter3: 4.0.7
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@vap/shiki/0.10.3:
|
||||||
|
resolution: {integrity: sha512-tZPHZxDKEBlorQ2BaprytGfkbo5yKBvdxdAF144p94HCTpjO3ScJk/f319wi7GtV1NE4DV8HBQo/0XpldixWQA==}
|
||||||
|
dependencies:
|
||||||
|
jsonc-parser: 3.2.0
|
||||||
|
vscode-oniguruma: 1.7.0
|
||||||
|
vscode-textmate: 5.2.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/acorn-jsx/5.3.2_acorn@8.8.0:
|
||||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
|
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.8.1
|
acorn: 8.8.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/acorn/8.8.1:
|
/acorn/8.8.0:
|
||||||
resolution: {integrity: sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==}
|
resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -522,7 +542,7 @@ packages:
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/concat-map/0.0.1:
|
/concat-map/0.0.1:
|
||||||
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
|
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/console-menu/0.1.0:
|
/console-menu/0.1.0:
|
||||||
|
@ -553,8 +573,8 @@ packages:
|
||||||
which: 2.0.2
|
which: 2.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/csstype/3.1.1:
|
/csstype/3.1.0:
|
||||||
resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==}
|
resolution: {integrity: sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/debug/2.6.9:
|
/debug/2.6.9:
|
||||||
|
@ -897,7 +917,7 @@ packages:
|
||||||
eslint: 8.28.0
|
eslint: 8.28.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/eslint-plugin-unused-imports/2.0.0_aucl44mjeutxyzmt4nvo2cczya:
|
/eslint-plugin-unused-imports/2.0.0_5am2datodjm2qi4eijrjrnoz54:
|
||||||
resolution: {integrity: sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==}
|
resolution: {integrity: sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -907,7 +927,7 @@ packages:
|
||||||
'@typescript-eslint/eslint-plugin':
|
'@typescript-eslint/eslint-plugin':
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/eslint-plugin': 5.44.0_fnsv2sbzcckq65bwfk7a5xwslu
|
'@typescript-eslint/eslint-plugin': 5.45.0_czs5uoqkd3podpy6vgtsxfc7au
|
||||||
eslint: 8.28.0
|
eslint: 8.28.0
|
||||||
eslint-rule-composer: 0.3.0
|
eslint-rule-composer: 0.3.0
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -971,21 +991,21 @@ packages:
|
||||||
eslint-scope: 7.1.1
|
eslint-scope: 7.1.1
|
||||||
eslint-utils: 3.0.0_eslint@8.28.0
|
eslint-utils: 3.0.0_eslint@8.28.0
|
||||||
eslint-visitor-keys: 3.3.0
|
eslint-visitor-keys: 3.3.0
|
||||||
espree: 9.4.1
|
espree: 9.4.0
|
||||||
esquery: 1.4.0
|
esquery: 1.4.0
|
||||||
esutils: 2.0.3
|
esutils: 2.0.3
|
||||||
fast-deep-equal: 3.1.3
|
fast-deep-equal: 3.1.3
|
||||||
file-entry-cache: 6.0.1
|
file-entry-cache: 6.0.1
|
||||||
find-up: 5.0.0
|
find-up: 5.0.0
|
||||||
glob-parent: 6.0.2
|
glob-parent: 6.0.2
|
||||||
globals: 13.18.0
|
globals: 13.17.0
|
||||||
grapheme-splitter: 1.0.4
|
grapheme-splitter: 1.0.4
|
||||||
ignore: 5.2.1
|
ignore: 5.2.0
|
||||||
import-fresh: 3.3.0
|
import-fresh: 3.3.0
|
||||||
imurmurhash: 0.1.4
|
imurmurhash: 0.1.4
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
is-path-inside: 3.0.3
|
is-path-inside: 3.0.3
|
||||||
js-sdsl: 4.2.0
|
js-sdsl: 4.1.5
|
||||||
js-yaml: 4.1.0
|
js-yaml: 4.1.0
|
||||||
json-stable-stringify-without-jsonify: 1.0.1
|
json-stable-stringify-without-jsonify: 1.0.1
|
||||||
levn: 0.4.1
|
levn: 0.4.1
|
||||||
|
@ -1001,12 +1021,12 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/espree/9.4.1:
|
/espree/9.4.0:
|
||||||
resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==}
|
resolution: {integrity: sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.8.1
|
acorn: 8.8.0
|
||||||
acorn-jsx: 5.3.2_acorn@8.8.1
|
acorn-jsx: 5.3.2_acorn@8.8.0
|
||||||
eslint-visitor-keys: 3.3.0
|
eslint-visitor-keys: 3.3.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
@ -1039,6 +1059,10 @@ packages:
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/eventemitter3/4.0.7:
|
||||||
|
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/extend-shallow/2.0.1:
|
/extend-shallow/2.0.1:
|
||||||
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
|
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
@ -1198,8 +1222,8 @@ packages:
|
||||||
path-is-absolute: 1.0.1
|
path-is-absolute: 1.0.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/globals/13.18.0:
|
/globals/13.17.0:
|
||||||
resolution: {integrity: sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==}
|
resolution: {integrity: sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dependencies:
|
dependencies:
|
||||||
type-fest: 0.20.2
|
type-fest: 0.20.2
|
||||||
|
@ -1212,7 +1236,7 @@ packages:
|
||||||
array-union: 2.1.0
|
array-union: 2.1.0
|
||||||
dir-glob: 3.0.1
|
dir-glob: 3.0.1
|
||||||
fast-glob: 3.2.12
|
fast-glob: 3.2.12
|
||||||
ignore: 5.2.1
|
ignore: 5.2.0
|
||||||
merge2: 1.4.1
|
merge2: 1.4.1
|
||||||
slash: 3.0.0
|
slash: 3.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -1257,6 +1281,10 @@ packages:
|
||||||
kind-of: 4.0.0
|
kind-of: 4.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/highlight.js/10.6.0:
|
||||||
|
resolution: {integrity: sha512-8mlRcn5vk/r4+QcqerapwBYTe+iPL5ih6xrNylxrnBdHQiijDETfXX7VIxC3UiCRiINBJfANBAsPzAvRQj8RpQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/https-proxy-agent/5.0.1:
|
/https-proxy-agent/5.0.1:
|
||||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
@ -1271,8 +1299,8 @@ packages:
|
||||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/ignore/5.2.1:
|
/ignore/5.2.0:
|
||||||
resolution: {integrity: sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==}
|
resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
@ -1423,8 +1451,8 @@ packages:
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/js-sdsl/4.2.0:
|
/js-sdsl/4.1.5:
|
||||||
resolution: {integrity: sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==}
|
resolution: {integrity: sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/js-yaml/4.1.0:
|
/js-yaml/4.1.0:
|
||||||
|
@ -1442,6 +1470,10 @@ packages:
|
||||||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/jsonc-parser/3.2.0:
|
||||||
|
resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/keypress/0.2.1:
|
/keypress/0.2.1:
|
||||||
resolution: {integrity: sha512-HjorDJFNhnM4SicvaUXac0X77NiskggxJdesG72+O5zBKpSqKFCrqmndKVqpu3pFqkla0St6uGk8Ju0sCurrmg==}
|
resolution: {integrity: sha512-HjorDJFNhnM4SicvaUXac0X77NiskggxJdesG72+O5zBKpSqKFCrqmndKVqpu3pFqkla0St6uGk8Ju0sCurrmg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -1797,8 +1829,8 @@ packages:
|
||||||
ret: 0.1.15
|
ret: 0.1.15
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/semver/7.3.8:
|
/semver/7.3.7:
|
||||||
resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==}
|
resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -2053,6 +2085,14 @@ packages:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/vscode-oniguruma/1.7.0:
|
||||||
|
resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/vscode-textmate/5.2.0:
|
||||||
|
resolution: {integrity: sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/webidl-conversions/3.0.1:
|
/webidl-conversions/3.0.1:
|
||||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
|
@ -60,13 +60,18 @@ await Promise.all(
|
||||||
}),
|
}),
|
||||||
esbuild.build({
|
esbuild.build({
|
||||||
...commonOptions,
|
...commonOptions,
|
||||||
|
inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])],
|
||||||
|
define: {
|
||||||
|
"window": "unsafeWindow",
|
||||||
|
...(commonOptions?.define)
|
||||||
|
},
|
||||||
outfile: "dist/Vencord.user.js",
|
outfile: "dist/Vencord.user.js",
|
||||||
banner: {
|
banner: {
|
||||||
js: readFileSync("browser/userscript.meta.js", "utf-8").replace("%version%", `${PackageJSON.version}.${new Date().getTime()}`)
|
js: readFileSync("browser/userscript.meta.js", "utf-8").replace("%version%", `${PackageJSON.version}.${new Date().getTime()}`)
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
// UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local
|
// UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local
|
||||||
js: "Object.defineProperty(window,'Vencord',{get:()=>Vencord});"
|
js: "Object.defineProperty(unsafeWindow,'Vencord',{get:()=>Vencord});"
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
|
|
@ -39,6 +39,7 @@ const {
|
||||||
getDarwinDirs,
|
getDarwinDirs,
|
||||||
getLinuxDirs,
|
getLinuxDirs,
|
||||||
ENTRYPOINT,
|
ENTRYPOINT,
|
||||||
|
question
|
||||||
} = require("./common");
|
} = require("./common");
|
||||||
|
|
||||||
switch (process.platform) {
|
switch (process.platform) {
|
||||||
|
@ -62,15 +63,14 @@ async function install(installations) {
|
||||||
// Attempt to give flatpak perms
|
// Attempt to give flatpak perms
|
||||||
if (selected.isFlatpak) {
|
if (selected.isFlatpak) {
|
||||||
try {
|
try {
|
||||||
const { branch } = selected;
|
|
||||||
const cwd = process.cwd();
|
const cwd = process.cwd();
|
||||||
const globalCmd = `flatpak override ${branch} --filesystem=${cwd}`;
|
const globalCmd = `flatpak override ${selected.branch} --filesystem=${cwd}`;
|
||||||
const userCmd = `flatpak override --user ${branch} --filesystem=${cwd}`;
|
const userCmd = `flatpak override --user ${selected.branch} --filesystem=${cwd}`;
|
||||||
const cmd = selected.location.startsWith("/home")
|
const cmd = selected.location.startsWith("/home")
|
||||||
? userCmd
|
? userCmd
|
||||||
: globalCmd;
|
: globalCmd;
|
||||||
execSync(cmd);
|
execSync(cmd);
|
||||||
console.log("Successfully gave write perms to Discord Flatpak.");
|
console.log("Gave write perms to Discord Flatpak.");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Failed to give write perms to Discord Flatpak.");
|
console.log("Failed to give write perms to Discord Flatpak.");
|
||||||
console.log(
|
console.log(
|
||||||
|
@ -79,6 +79,29 @@ async function install(installations) {
|
||||||
);
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const answer = await question(
|
||||||
|
`Would you like to allow ${selected.branch} to talk to org.freedesktop.Flatpak?\n` +
|
||||||
|
"This is essentially full host access but necessary to spawn git. Without it, the updater will not work\n" +
|
||||||
|
"Consider using the http based updater (using the gui installer) instead if you want to maintain the sandbox.\n" +
|
||||||
|
"[y/N]: "
|
||||||
|
);
|
||||||
|
|
||||||
|
if (["y", "yes", "yeah"].includes(answer.toLowerCase())) {
|
||||||
|
try {
|
||||||
|
const globalCmd = `flatpak override ${selected.branch} --talk-name=org.freedesktop.Flatpak`;
|
||||||
|
const userCmd = `flatpak override --user ${selected.branch} --talk-name=org.freedesktop.Flatpak`;
|
||||||
|
const cmd = selected.location.startsWith("/home")
|
||||||
|
? userCmd
|
||||||
|
: globalCmd;
|
||||||
|
execSync(cmd);
|
||||||
|
console.log("Sucessfully gave talk permission");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to give talk permission\n", err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`Not giving full host access. If you change your mind later, you can run:\nflatpak override ${selected.branch} --talk-name=org.freedesktop.Flatpak`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const version of selected.versions) {
|
for (const version of selected.versions) {
|
||||||
|
|
69
src/api/MessagePopover.ts
Normal file
69
src/api/MessagePopover.ts
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Logger from "@utils/Logger";
|
||||||
|
import { Channel, Message } from "discord-types/general";
|
||||||
|
import type { MouseEventHandler } from "react";
|
||||||
|
|
||||||
|
const logger = new Logger("MessagePopover");
|
||||||
|
|
||||||
|
export interface ButtonItem {
|
||||||
|
key?: string,
|
||||||
|
label: string,
|
||||||
|
icon: React.ComponentType<any>,
|
||||||
|
message: Message,
|
||||||
|
channel: Channel,
|
||||||
|
onClick?: MouseEventHandler<HTMLButtonElement>,
|
||||||
|
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type getButtonItem = (message: Message) => ButtonItem | null;
|
||||||
|
|
||||||
|
export const buttons = new Map<string, getButtonItem>();
|
||||||
|
|
||||||
|
export function addButton(
|
||||||
|
identifier: string,
|
||||||
|
item: getButtonItem,
|
||||||
|
) {
|
||||||
|
buttons.set(identifier, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeButton(identifier: string) {
|
||||||
|
buttons.delete(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function _buildPopoverElements(
|
||||||
|
msg: Message,
|
||||||
|
makeButton: (item: ButtonItem) => React.ComponentType
|
||||||
|
) {
|
||||||
|
const items = [] as React.ComponentType[];
|
||||||
|
|
||||||
|
for (const [identifier, getItem] of buttons.entries()) {
|
||||||
|
try {
|
||||||
|
const item = getItem(msg);
|
||||||
|
if (item) {
|
||||||
|
item.key ??= identifier;
|
||||||
|
items.push(makeButton(item));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`[${identifier}]`, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ import * as $Commands from "./Commands";
|
||||||
import * as $DataStore from "./DataStore";
|
import * as $DataStore from "./DataStore";
|
||||||
import * as $MessageAccessories from "./MessageAccessories";
|
import * as $MessageAccessories from "./MessageAccessories";
|
||||||
import * as $MessageEventsAPI from "./MessageEvents";
|
import * as $MessageEventsAPI from "./MessageEvents";
|
||||||
|
import * as $MessagePopover from "./MessagePopover";
|
||||||
import * as $Notices from "./Notices";
|
import * as $Notices from "./Notices";
|
||||||
import * as $ServerList from "./ServerList";
|
import * as $ServerList from "./ServerList";
|
||||||
|
|
||||||
|
@ -59,6 +60,10 @@ const DataStore = $DataStore;
|
||||||
* An API allowing you to add custom components as message accessories
|
* An API allowing you to add custom components as message accessories
|
||||||
*/
|
*/
|
||||||
const MessageAccessories = $MessageAccessories;
|
const MessageAccessories = $MessageAccessories;
|
||||||
|
/**
|
||||||
|
* An API allowing you to add custom buttons in the message popover
|
||||||
|
*/
|
||||||
|
const MessagePopover = $MessagePopover;
|
||||||
/**
|
/**
|
||||||
* An API allowing you to add badges to user profiles
|
* An API allowing you to add badges to user profiles
|
||||||
*/
|
*/
|
||||||
|
@ -68,4 +73,4 @@ const Badges = $Badges;
|
||||||
*/
|
*/
|
||||||
const ServerList = $ServerList;
|
const ServerList = $ServerList;
|
||||||
|
|
||||||
export { Badges, Commands, DataStore, MessageAccessories, MessageEvents, Notices, ServerList };
|
export { Badges, Commands, DataStore, MessageAccessories, MessageEvents, MessagePopover, Notices, ServerList };
|
||||||
|
|
|
@ -141,14 +141,19 @@ export const Settings = makeProxy(settings);
|
||||||
* Settings hook for React components. Returns a smart settings
|
* Settings hook for React components. Returns a smart settings
|
||||||
* object that automagically triggers a rerender if any properties
|
* object that automagically triggers a rerender if any properties
|
||||||
* are altered
|
* are altered
|
||||||
|
* @param paths An optional list of paths to whitelist for rerenders
|
||||||
* @returns Settings
|
* @returns Settings
|
||||||
*/
|
*/
|
||||||
export function useSettings() {
|
export function useSettings(paths?: string[]) {
|
||||||
const [, forceUpdate] = React.useReducer(() => ({}), {});
|
const [, forceUpdate] = React.useReducer(() => ({}), {});
|
||||||
|
|
||||||
|
const onUpdate: SubscriptionCallback = paths
|
||||||
|
? (value, path) => paths.includes(path) && forceUpdate()
|
||||||
|
: forceUpdate;
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
subscriptions.add(forceUpdate);
|
subscriptions.add(onUpdate);
|
||||||
return () => void subscriptions.delete(forceUpdate);
|
return () => void subscriptions.delete(onUpdate);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return Settings;
|
return Settings;
|
||||||
|
|
|
@ -196,7 +196,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
<div style={{ marginBottom: 8 }}>
|
<div style={{ marginBottom: 8 }}>
|
||||||
<Forms.FormSection>
|
<Forms.FormSection>
|
||||||
<ErrorBoundary message="An error occurred while rendering this plugin's custom InfoComponent">
|
<ErrorBoundary message="An error occurred while rendering this plugin's custom InfoComponent">
|
||||||
<plugin.settingsAboutComponent />
|
<plugin.settingsAboutComponent tempSettings={tempSettings} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</Forms.FormSection>
|
</Forms.FormSection>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -57,7 +57,8 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) {
|
||||||
{themeLinks.map(link => (
|
{themeLinks.map(link => (
|
||||||
<Card style={{
|
<Card style={{
|
||||||
padding: ".5em",
|
padding: ".5em",
|
||||||
marginBottom: ".5em"
|
marginBottom: ".5em",
|
||||||
|
marginTop: ".5em"
|
||||||
}} key={link}>
|
}} key={link}>
|
||||||
<Forms.FormTitle tag="h5" style={{
|
<Forms.FormTitle tag="h5" style={{
|
||||||
overflowWrap: "break-word"
|
overflowWrap: "break-word"
|
||||||
|
@ -95,21 +96,21 @@ export default ErrorBoundary.wrap(function () {
|
||||||
}}>
|
}}>
|
||||||
<Forms.FormTitle tag="h5">Paste links to .css / .theme.css files here</Forms.FormTitle>
|
<Forms.FormTitle tag="h5">Paste links to .css / .theme.css files here</Forms.FormTitle>
|
||||||
<Forms.FormText>One link per line</Forms.FormText>
|
<Forms.FormText>One link per line</Forms.FormText>
|
||||||
<Forms.FormText>Be careful to use the raw links or github.io links!</Forms.FormText>
|
<Forms.FormText>Make sure to use the raw links or github.io links!</Forms.FormText>
|
||||||
<Forms.FormDivider />
|
<Forms.FormDivider />
|
||||||
<Forms.FormTitle tag="h5">Find Themes:</Forms.FormTitle>
|
<Forms.FormTitle tag="h5">Find Themes:</Forms.FormTitle>
|
||||||
<div>
|
<div style={{ marginBottom: ".5em" }}>
|
||||||
<Link style={{ marginRight: ".5em" }} href="https://betterdiscord.app/themes">
|
<Link style={{ marginRight: ".5em" }} href="https://betterdiscord.app/themes">
|
||||||
BetterDiscord Themes
|
BetterDiscord Themes
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="https://github.com/search?q=discord+theme">Github</Link>
|
<Link href="https://github.com/search?q=discord+theme">GitHub</Link>
|
||||||
</div>
|
</div>
|
||||||
<Forms.FormText>If using the BD site, click on "Source" somewhere below the Download button</Forms.FormText>
|
<Forms.FormText>If using the BD site, click on "Source" somewhere below the Download button</Forms.FormText>
|
||||||
<Forms.FormText>In the GitHub repository of your theme, find X.theme.css / X.css, click on it, then click the "Raw" button</Forms.FormText>
|
<Forms.FormText>In the GitHub repository of your theme, find X.theme.css / X.css, click on it, then click the "Raw" button</Forms.FormText>
|
||||||
<Forms.FormText>
|
<Forms.FormText>
|
||||||
If the theme has configuration that requires you to edit the file:
|
If the theme has configuration that requires you to edit the file:
|
||||||
<ul>
|
<ul>
|
||||||
<li>• Make a github account</li>
|
<li>• Make a <Link href="https://github.com/signup">GitHub</Link> account</li>
|
||||||
<li>• Click the fork button on the top right</li>
|
<li>• Click the fork button on the top right</li>
|
||||||
<li>• Edit the file</li>
|
<li>• Edit the file</li>
|
||||||
<li>• Use the link to your own repository instead</li>
|
<li>• Use the link to your own repository instead</li>
|
||||||
|
|
|
@ -179,7 +179,7 @@ function Newer(props: CommonProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function Updater() {
|
function Updater() {
|
||||||
const [repo, err, repoPending] = useAwaiter(getRepo, "Loading...");
|
const [repo, err, repoPending] = useAwaiter(getRepo, { fallbackValue: "Loading..." });
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (err)
|
if (err)
|
||||||
|
|
|
@ -27,7 +27,9 @@ import { Button, Card, Forms, Margins, React, Switch } from "@webpack/common";
|
||||||
const st = (style: string) => `vcSettings${style}`;
|
const st = (style: string) => `vcSettings${style}`;
|
||||||
|
|
||||||
function VencordSettings() {
|
function VencordSettings() {
|
||||||
const [settingsDir, , settingsDirPending] = useAwaiter(() => VencordNative.ipc.invoke<string>(IpcEvents.GET_SETTINGS_DIR), "Loading...");
|
const [settingsDir, , settingsDirPending] = useAwaiter(() => VencordNative.ipc.invoke<string>(IpcEvents.GET_SETTINGS_DIR), {
|
||||||
|
fallbackValue: "Loading..."
|
||||||
|
});
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
|
|
||||||
const [donateImage] = React.useState(
|
const [donateImage] = React.useState(
|
||||||
|
@ -87,8 +89,8 @@ function VencordSettings() {
|
||||||
<Switch
|
<Switch
|
||||||
value={settings.useQuickCss}
|
value={settings.useQuickCss}
|
||||||
onChange={(v: boolean) => settings.useQuickCss = v}
|
onChange={(v: boolean) => settings.useQuickCss = v}
|
||||||
note="Loads styles from your QuickCss file">
|
note="Loads styles from your QuickCSS file">
|
||||||
Use QuickCss
|
Use QuickCSS
|
||||||
</Switch>
|
</Switch>
|
||||||
{!IS_WEB && (
|
{!IS_WEB && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
@ -101,8 +103,8 @@ function VencordSettings() {
|
||||||
<Switch
|
<Switch
|
||||||
value={settings.notifyAboutUpdates}
|
value={settings.notifyAboutUpdates}
|
||||||
onChange={(v: boolean) => settings.notifyAboutUpdates = v}
|
onChange={(v: boolean) => settings.notifyAboutUpdates = v}
|
||||||
note="Shows a Toast on StartUp">
|
note="Shows a toast on startup">
|
||||||
Get notified about new Updates
|
Get notified about new updates
|
||||||
</Switch>
|
</Switch>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
|
@ -129,7 +131,7 @@ function DonateCard({ image }: DonateCardProps) {
|
||||||
<div>
|
<div>
|
||||||
<Forms.FormTitle tag="h5">Support the Project</Forms.FormTitle>
|
<Forms.FormTitle tag="h5">Support the Project</Forms.FormTitle>
|
||||||
<Forms.FormText>
|
<Forms.FormText>
|
||||||
Please consider supporting the Development of Vencord by donating!
|
Please consider supporting the development of Vencord by donating!
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
<DonateButton style={{ transform: "translateX(-1em)" }} />
|
<DonateButton style={{ transform: "translateX(-1em)" }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -89,8 +89,12 @@ export function initIpc(mainWindow: BrowserWindow) {
|
||||||
ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {
|
ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {
|
||||||
const win = new BrowserWindow({
|
const win = new BrowserWindow({
|
||||||
title: "QuickCss Editor",
|
title: "QuickCss Editor",
|
||||||
|
autoHideMenuBar: true,
|
||||||
|
darkTheme: true,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: join(__dirname, "preload.js"),
|
preload: join(__dirname, "preload.js"),
|
||||||
|
contextIsolation: true,
|
||||||
|
nodeIntegration: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await win.loadURL(`data:text/html;base64,${monacoHtml}`);
|
await win.loadURL(`data:text/html;base64,${monacoHtml}`);
|
||||||
|
|
|
@ -28,10 +28,13 @@ const VENCORD_SRC_DIR = join(__dirname, "..");
|
||||||
|
|
||||||
const execFile = promisify(cpExecFile);
|
const execFile = promisify(cpExecFile);
|
||||||
|
|
||||||
|
const isFlatpak = Boolean(process.env.FLATPAK_ID?.includes("discordapp") || process.env.FLATPAK_ID?.includes("Discord"));
|
||||||
|
|
||||||
function git(...args: string[]) {
|
function git(...args: string[]) {
|
||||||
return execFile("git", args, {
|
const opts = { cwd: VENCORD_SRC_DIR };
|
||||||
cwd: VENCORD_SRC_DIR
|
|
||||||
});
|
if (isFlatpak) return execFile("flatpak-spawn", ["--host", "git", ...args], opts);
|
||||||
|
else return execFile("git", args, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getRepo() {
|
async function getRepo() {
|
||||||
|
@ -61,9 +64,13 @@ async function pull() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function build() {
|
async function build() {
|
||||||
const res = await execFile("node", ["scripts/build/build.mjs"], {
|
const opts = { cwd: VENCORD_SRC_DIR };
|
||||||
cwd: VENCORD_SRC_DIR
|
|
||||||
});
|
let res;
|
||||||
|
|
||||||
|
if (isFlatpak) res = await execFile("flatpak-spawn", ["--host", "node", "scripts/build/build.mjs"], opts);
|
||||||
|
else res = await execFile("node", ["scripts/build/build.mjs"], opts);
|
||||||
|
|
||||||
return !res.stderr.includes("Build failed");
|
return !res.stderr.includes("Build failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ require.main!.filename = join(asarPath, discordPkg.main);
|
||||||
// @ts-ignore Untyped method? Dies from cringe
|
// @ts-ignore Untyped method? Dies from cringe
|
||||||
app.setAppPath(asarPath);
|
app.setAppPath(asarPath);
|
||||||
|
|
||||||
|
if (!process.argv.includes("--vanilla")) {
|
||||||
// Repatch after host updates on Windows
|
// Repatch after host updates on Windows
|
||||||
if (process.platform === "win32")
|
if (process.platform === "win32")
|
||||||
require("./patchWin32Updater");
|
require("./patchWin32Updater");
|
||||||
|
@ -108,32 +109,55 @@ electron.app.whenReady().then(() => {
|
||||||
|
|
||||||
|
|
||||||
// Remove CSP
|
// Remove CSP
|
||||||
|
type PolicyResult = Record<string, string[]>;
|
||||||
|
|
||||||
|
const parsePolicy = (policy: string): PolicyResult => {
|
||||||
|
const result: PolicyResult = {};
|
||||||
|
policy.split(";").forEach(directive => {
|
||||||
|
const [directiveKey, ...directiveValue] = directive.trim().split(/\s+/g);
|
||||||
|
if (directiveKey && !Object.prototype.hasOwnProperty.call(result, directiveKey)) {
|
||||||
|
result[directiveKey] = directiveValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
const stringifyPolicy = (policy: PolicyResult): string =>
|
||||||
|
Object.entries(policy)
|
||||||
|
.filter(([, values]) => values?.length)
|
||||||
|
.map(directive => directive.flat().join(" "))
|
||||||
|
.join("; ");
|
||||||
|
|
||||||
function patchCsp(headers: Record<string, string[]>, header: string) {
|
function patchCsp(headers: Record<string, string[]>, header: string) {
|
||||||
if (header in headers) {
|
if (header in headers) {
|
||||||
let patchedHeader = headers[header][0];
|
const csp = parsePolicy(headers[header][0]);
|
||||||
for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src"]) {
|
|
||||||
patchedHeader = patchedHeader.replace(new RegExp(`${directive}.+?;`), `${directive} * blob: data: 'unsafe-inline';`);
|
for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) {
|
||||||
|
csp[directive] = ["*", "blob:", "data:", "'unsafe-inline'"];
|
||||||
}
|
}
|
||||||
// TODO: Restrict this to only imported packages with fixed version.
|
// TODO: Restrict this to only imported packages with fixed version.
|
||||||
// Perhaps auto generate with esbuild
|
// Perhaps auto generate with esbuild
|
||||||
patchedHeader = patchedHeader.replace(/script-src.+?(?=;)/, "$& 'unsafe-eval' https://unpkg.com https://cdnjs.cloudflare.com");
|
csp["script-src"] ??= [];
|
||||||
headers[header] = [patchedHeader];
|
csp["script-src"].push("'unsafe-eval'", "https://unpkg.com", "https://cdnjs.cloudflare.com");
|
||||||
|
headers[header] = [stringifyPolicy(csp)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
electron.session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, url }, cb) => {
|
electron.session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => {
|
||||||
if (responseHeaders) {
|
if (responseHeaders) {
|
||||||
|
if (resourceType === "mainFrame")
|
||||||
patchCsp(responseHeaders, "content-security-policy");
|
patchCsp(responseHeaders, "content-security-policy");
|
||||||
patchCsp(responseHeaders, "content-security-policy-report-only");
|
|
||||||
|
|
||||||
// Fix hosts that don't properly set the content type, such as
|
// Fix hosts that don't properly set the css content type, such as
|
||||||
// raw.githubusercontent.com
|
// raw.githubusercontent.com
|
||||||
if (url.endsWith(".css"))
|
if (resourceType === "stylesheet")
|
||||||
responseHeaders["content-type"] = ["text/css"];
|
responseHeaders["content-type"] = ["text/css"];
|
||||||
}
|
}
|
||||||
cb({ cancel: false, responseHeaders });
|
cb({ cancel: false, responseHeaders });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
console.log("[Vencord] Running in vanilla mode. Not loading Vencord");
|
||||||
|
}
|
||||||
|
|
||||||
console.log("[Vencord] Loading original Discord app.asar");
|
console.log("[Vencord] Loading original Discord app.asar");
|
||||||
// Legacy Vencord Injector requires "../app.asar". However, because we
|
// Legacy Vencord Injector requires "../app.asar". However, because we
|
||||||
|
|
33
src/plugins/apiMessagePopover.ts
Normal file
33
src/plugins/apiMessagePopover.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "MessagePopoverAPI",
|
||||||
|
description: "API to add buttons to message popovers.",
|
||||||
|
authors: [Devs.KingFish],
|
||||||
|
patches: [{
|
||||||
|
find: "Messages.MESSAGE_UTILITIES_A11Y_LABEL",
|
||||||
|
replacement: {
|
||||||
|
match: /(message:(.).{0,100}Fragment,\{children:\[)(.{0,90}renderPopout:.{0,200}message_reaction_emoji_picker.+?return (.{1,3})\(.{0,30}"add-reaction")/,
|
||||||
|
replace: "$1...Vencord.Api.MessagePopover._buildPopoverElements($2,$4),$3"
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
});
|
|
@ -30,7 +30,7 @@ const assetManager = mapMangledModuleLazy(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const rpcManager = findByCodeLazy(".APPLICATION_RPC(");
|
const lookupRpcApp = findByCodeLazy(".APPLICATION_RPC(");
|
||||||
|
|
||||||
async function lookupAsset(applicationId: string, key: string): Promise<string> {
|
async function lookupAsset(applicationId: string, key: string): Promise<string> {
|
||||||
return (await assetManager.getAsset(applicationId, [key, undefined]))[0];
|
return (await assetManager.getAsset(applicationId, [key, undefined]))[0];
|
||||||
|
@ -39,7 +39,7 @@ async function lookupAsset(applicationId: string, key: string): Promise<string>
|
||||||
const apps: any = {};
|
const apps: any = {};
|
||||||
async function lookupApp(applicationId: string): Promise<string> {
|
async function lookupApp(applicationId: string): Promise<string> {
|
||||||
const socket: any = {};
|
const socket: any = {};
|
||||||
await rpcManager.lookupApp(socket, applicationId);
|
await lookupRpcApp(socket, applicationId);
|
||||||
return socket.application;
|
return socket.application;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,8 +71,8 @@ export default definePlugin({
|
||||||
"canUseEmojisEverywhere"
|
"canUseEmojisEverywhere"
|
||||||
].map(func => {
|
].map(func => {
|
||||||
return {
|
return {
|
||||||
match: new RegExp(`${func}:function\\(.+?}`),
|
match: new RegExp(`${func}:function\\(.+?\\{`),
|
||||||
replace: `${func}:function(e){return true;}`
|
replace: "$&return true;"
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -80,8 +80,8 @@ export default definePlugin({
|
||||||
find: "canUseAnimatedEmojis:function",
|
find: "canUseAnimatedEmojis:function",
|
||||||
predicate: () => Settings.plugins.FakeNitro.enableStickerBypass === true,
|
predicate: () => Settings.plugins.FakeNitro.enableStickerBypass === true,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /canUseStickersEverywhere:function\(.+?}/,
|
match: /canUseStickersEverywhere:function\(.+?\{/,
|
||||||
replace: "canUseStickersEverywhere:function(e){return true;}"
|
replace: "$&return true;"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -101,8 +101,8 @@ export default definePlugin({
|
||||||
"canStreamMidQuality"
|
"canStreamMidQuality"
|
||||||
].map(func => {
|
].map(func => {
|
||||||
return {
|
return {
|
||||||
match: new RegExp(`${func}:function\\(.+?}`),
|
match: new RegExp(`${func}:function\\(.+?\\{`),
|
||||||
replace: `${func}:function(e){return true;}`
|
replace: "$&return true;"
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,11 +17,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { get, set } from "@api/DataStore";
|
import { get, set } from "@api/DataStore";
|
||||||
|
import { addButton, removeButton } from "@api/MessagePopover";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import Logger from "@utils/Logger";
|
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { ChannelStore, FluxDispatcher } from "@webpack/common";
|
import { ChannelStore, FluxDispatcher } from "@webpack/common";
|
||||||
import { Message } from "discord-types/general";
|
|
||||||
|
|
||||||
let style: HTMLStyleElement;
|
let style: HTMLStyleElement;
|
||||||
|
|
||||||
|
@ -49,13 +48,7 @@ export default definePlugin({
|
||||||
name: "HideAttachments",
|
name: "HideAttachments",
|
||||||
description: "Hide attachments and Embeds for individual messages via hover button",
|
description: "Hide attachments and Embeds for individual messages via hover button",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
patches: [{
|
dependencies: ["MessagePopoverAPI"],
|
||||||
find: "Messages.MESSAGE_UTILITIES_A11Y_LABEL",
|
|
||||||
replacement: {
|
|
||||||
match: /(message:(.).{0,100}Fragment,\{children:\[)(.{0,40}renderPopout:.{0,200}message_reaction_emoji_picker.+?return (.{1,3})\(.{0,30}"add-reaction")/,
|
|
||||||
replace: "$1Vencord.Plugins.plugins.HideAttachments.renderButton($2, $4),$3"
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
style = document.createElement("style");
|
style = document.createElement("style");
|
||||||
|
@ -64,11 +57,26 @@ export default definePlugin({
|
||||||
|
|
||||||
await getHiddenMessages();
|
await getHiddenMessages();
|
||||||
await this.buildCss();
|
await this.buildCss();
|
||||||
|
|
||||||
|
addButton("HideAttachments", msg => {
|
||||||
|
if (!msg.attachments.length && !msg.embeds.length) return null;
|
||||||
|
|
||||||
|
const isHidden = hiddenMessages.has(msg.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: isHidden ? "Show Attachments" : "Hide Attachments",
|
||||||
|
icon: isHidden ? ImageVisible : ImageInvisible,
|
||||||
|
message: msg,
|
||||||
|
channel: ChannelStore.getChannel(msg.channel_id),
|
||||||
|
onClick: () => this.toggleHide(msg.id)
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
style.remove();
|
style.remove();
|
||||||
hiddenMessages.clear();
|
hiddenMessages.clear();
|
||||||
|
removeButton("HideAttachments");
|
||||||
},
|
},
|
||||||
|
|
||||||
async buildCss() {
|
async buildCss() {
|
||||||
|
@ -86,26 +94,6 @@ export default definePlugin({
|
||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
|
|
||||||
renderButton(msg: Message, makeItem: (data: any) => React.ComponentType) {
|
|
||||||
try {
|
|
||||||
if (!msg.attachments.length && !msg.embeds.length) return null;
|
|
||||||
|
|
||||||
const isHidden = hiddenMessages.has(msg.id);
|
|
||||||
|
|
||||||
return makeItem({
|
|
||||||
key: "HideAttachments",
|
|
||||||
label: isHidden ? "Show Attachments" : "Hide Attachments",
|
|
||||||
icon: isHidden ? ImageVisible : ImageInvisible,
|
|
||||||
message: msg,
|
|
||||||
channel: ChannelStore.getChannel(msg.channel_id),
|
|
||||||
onClick: () => this.toggleHide(msg.id)
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
new Logger("HideAttachments").error(err);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async toggleHide(id: string) {
|
async toggleHide(id: string) {
|
||||||
const ids = await getHiddenMessages();
|
const ids = await getHiddenMessages();
|
||||||
if (!ids.delete(id))
|
if (!ids.delete(id))
|
|
@ -1,178 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2022 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as DataStore from "@api/DataStore";
|
|
||||||
import { Devs } from "@utils/constants";
|
|
||||||
import definePlugin from "@utils/types";
|
|
||||||
import { findByPropsLazy } from "@webpack";
|
|
||||||
|
|
||||||
interface MatchAndReplace {
|
|
||||||
match: RegExp;
|
|
||||||
replace: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Used to re-render the Registered Games tab to update how our button looks like */
|
|
||||||
const RunningGameStoreModule = findByPropsLazy("IgnoreActivities_reRenderGames");
|
|
||||||
|
|
||||||
let ignoredActivitiesCache: string[] = [];
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "IgnoreActivities",
|
|
||||||
authors: [Devs.Nuckyz],
|
|
||||||
description: "Ignore certain activities (like games) from showing up on your status. You can configure which ones are ignored from the Registered Games tab.",
|
|
||||||
patches: [{
|
|
||||||
find: ".Messages.SETTINGS_GAMES_OVERLAY_ON",
|
|
||||||
replacement: [{
|
|
||||||
match: /;(.\.renderOverlayToggle=function\(\).+?\)};)/,
|
|
||||||
replace: (_, mod) => {
|
|
||||||
/** Modify the renderOverlayToggle button to remove unneded stuff and render the component the way we want */
|
|
||||||
const renderIgnoreActivitiesToggle = ([
|
|
||||||
/** Remove overlay warn related stuff */
|
|
||||||
{ match: /,.{1,2}=.{1,2}\.overlayWarn/, replace: "" },
|
|
||||||
{ match: /,.{1,2}=.{1,2}\?\(0,.{1,2}\.jsx\)\(.{1,20}Messages\.SETTINGS_GAMES_OVERLAY_WARNING.{1,100}null/, replace: "" },
|
|
||||||
/** Remove overlay status related stuff */
|
|
||||||
{ match: /,.{1,2}=.{1,2}\?.{1,50}Messages\.SETTINGS_GAMES_OVERLAY_OFF/, replace: "" },
|
|
||||||
{ match: /[^[]{1,2},\(0,.{1,2}\.jsx\)\("div".{1,20}\(\)\.overlayStatusText.+}\),/, replace: "" },
|
|
||||||
/** Change the method name to renderIgnoreActivitiesToggle */
|
|
||||||
{ match: /renderOverlayToggle/, replace: "renderIgnoreActivitiesToggle" },
|
|
||||||
/** Create an easily accessable variable to use the game props and then replace the boolean to determine if the button is activated or not with our custom function */
|
|
||||||
{ match: /((.)=this\.props\.game)(.{1,70})=.{1,2}overlay/, replace: "$1,IgnoreActivities_gameProps=$2$3=Vencord.Plugins.plugins.IgnoreActivities.isActivityEnabled(IgnoreActivities_gameProps)" },
|
|
||||||
/** Change the handler for clicking the button */
|
|
||||||
{ match: /.\.handleOverlayToggle/, replace: "() => Vencord.Plugins.plugins.IgnoreActivities.handleActivityToggle(IgnoreActivities_gameProps)" },
|
|
||||||
/** Change the button on component to our custom */
|
|
||||||
{ match: /(\(0,.{1,2}\.jsx\)\()(.{2})\..(.{1,50}\.overlayToggleIconOn)/, replace: "$1$2.IgnoreActivities_toggleOn$3" },
|
|
||||||
/** Change the button off component to our custom */
|
|
||||||
{ match: /(\(0,.{1,2}\.jsx\)\()(.{2})\..{1}(.{1,50}\.overlayToggleIconOff)/, replace: "$1$2.IgnoreActivities_toggleOff$3" },
|
|
||||||
/** Change the tooltip text */
|
|
||||||
{ match: /text:.{2}\..\.Messages\.SETTINGS_GAMES_TOGGLE_OVERLAY/, replace: 'text:"Toggle activity"' },
|
|
||||||
/** Change the aria-label text */
|
|
||||||
{ match: /"aria-label":.{2}\..\.Messages\.SETTINGS_GAMES_TOGGLE_OVERLAY/, replace: '"aria-label":"Toggle activity"' }
|
|
||||||
] as MatchAndReplace[])
|
|
||||||
.reduce((current, { match, replace }) => current.replace(match, replace), mod);
|
|
||||||
|
|
||||||
/** Return the default renderOverlayToggle and our custom one */
|
|
||||||
return `;${mod}${renderIgnoreActivitiesToggle}`;
|
|
||||||
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
/** Render our ignore activity component */
|
|
||||||
match: /(this.renderLastPlayed\(\)]}\),this.renderOverlayToggle\(\))/,
|
|
||||||
replace: "$1,this.renderIgnoreActivitiesToggle()"
|
|
||||||
}]
|
|
||||||
}, {
|
|
||||||
/** Patch the RunningGameStore to export the method to re-render the Registered Games tab */
|
|
||||||
find: '.displayName="RunningGameStore"',
|
|
||||||
replacement: {
|
|
||||||
match: /(.:\(\)=>.{2})(.+function (.{2})\(\){.+\.dispatch\({type:"RUNNING_GAMES_CHANGE")/,
|
|
||||||
replace: "$1,IgnoreActivities_reRenderGames:()=>$3$2"
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
find: "M8.67872 19H11V21H7V23H17V21H13V19H20C21.103 19 22 18.104 22 17V6C22 5.89841 21.9924 5.79857 21.9777 5.70101L20 7.67872V15H12.6787L8.67872 19ZM13.1496 6H4V15H4.14961L2.00515 17.1445C2.00174 17.0967 2 17.0486 2 17V6C2 4.897 2.897 4 4 4H15.1496L13.1496 6Z",
|
|
||||||
replacement: {
|
|
||||||
match: /(.:\(\)=>.)(.+)(function (.)\(.{1,10}\.width.+\)\)})/s,
|
|
||||||
replace: (_, exports, restOfFunction, component) => {
|
|
||||||
/** Modify the overlayToggleOff component to how we want */
|
|
||||||
const renderIgnoreActivitiesToggleOff = ([
|
|
||||||
/** Change the method name to IgnoreActivities_toggleOffToExport */
|
|
||||||
{ match: /function ./, replace: "function IgnoreActivities_toggleOffToExport" },
|
|
||||||
/** Change the svg path to our custom one */
|
|
||||||
{ match: /M8.67872 19H11V21H7V23H17V21H13V19H20C21.103 19 22 18.104 22 17V6C22 5.89841 21.9924 5.79857 21.9777 5.70101L20 7.67872V15H12.6787L8.67872 19ZM13.1496 6H4V15H4.14961L2.00515 17.1445C2.00174 17.0967 2 17.0486 2 17V6C2 4.897 2.897 4 4 4H15.1496L13.1496 6Z/, replace: "M 16 8 C 7.664063 8 1.25 15.34375 1.25 15.34375 L 0.65625 16 L 1.25 16.65625 C 1.25 16.65625 7.097656 23.324219 14.875 23.9375 C 15.246094 23.984375 15.617188 24 16 24 C 16.382813 24 16.753906 23.984375 17.125 23.9375 C 24.902344 23.324219 30.75 16.65625 30.75 16.65625 L 31.34375 16 L 30.75 15.34375 C 30.75 15.34375 24.335938 8 16 8 Z M 16 10 C 18.203125 10 20.234375 10.601563 22 11.40625 C 22.636719 12.460938 23 13.675781 23 15 C 23 18.613281 20.289063 21.582031 16.78125 21.96875 C 16.761719 21.972656 16.738281 21.964844 16.71875 21.96875 C 16.480469 21.980469 16.242188 22 16 22 C 15.734375 22 15.476563 21.984375 15.21875 21.96875 C 11.710938 21.582031 9 18.613281 9 15 C 9 13.695313 9.351563 12.480469 9.96875 11.4375 L 9.9375 11.4375 C 11.71875 10.617188 13.773438 10 16 10 Z M 16 12 C 14.34375 12 13 13.34375 13 15 C 13 16.65625 14.34375 18 16 18 C 17.65625 18 19 16.65625 19 15 C 19 13.34375 17.65625 12 16 12 Z M 7.25 12.9375 C 7.09375 13.609375 7 14.285156 7 15 C 7 16.753906 7.5 18.394531 8.375 19.78125 C 5.855469 18.324219 4.105469 16.585938 3.53125 16 C 4.011719 15.507813 5.351563 14.203125 7.25 12.9375 Z M 24.75 12.9375 C 26.648438 14.203125 27.988281 15.507813 28.46875 16 C 27.894531 16.585938 26.144531 18.324219 23.625 19.78125 C 24.5 18.394531 25 16.753906 25 15 C 25 14.285156 24.90625 13.601563 24.75 12.9375 Z" },
|
|
||||||
/** Modify the view box to not cut our svg */
|
|
||||||
{ match: /viewBox:"0 0 24 24"/, replace: 'viewBox:"0 0 32 26"' },
|
|
||||||
/** Change the rectangle coordinates to match the middle of our svg */
|
|
||||||
{ match: /x:"2"/, replace: 'x:"3"' },
|
|
||||||
{ match: /y:"20"/, replace: 'y:"26"' },
|
|
||||||
] as MatchAndReplace[])
|
|
||||||
.reduce((current, { match, replace }) => current.replace(match, replace), component);
|
|
||||||
|
|
||||||
/** Export our custom svg */
|
|
||||||
return `${exports},IgnoreActivities_toggleOff:()=>IgnoreActivities_toggleOffToExport${restOfFunction}${component}${renderIgnoreActivitiesToggleOff}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
find: "M4 2.5C2.897 2.5 2 3.397 2 4.5V15.5C2 16.604 2.897 17.5 4 17.5H11V19.5H7V21.5H17V19.5H13V17.5H20C21.103 17.5 22 16.604 22 15.5V4.5C22 3.397 21.103 2.5 20 2.5H4ZM20 4.5V13.5H4V4.5H20Z",
|
|
||||||
replacement: {
|
|
||||||
match: /(.:\(\)=>.)(.+)(function (.)\(.{1,10}\.width.+\)\)})/,
|
|
||||||
replace: (_, exports, restOfFunction, component) => {
|
|
||||||
/** Modify the overlayToggleOn svg to how we want */
|
|
||||||
const renderIgnoreActivitiesToggleOn = ([
|
|
||||||
/** Change the method name to IgnoreActivities_toggleOnToExport */
|
|
||||||
{ match: /function ./, replace: "function IgnoreActivities_toggleOnToExport" },
|
|
||||||
/** Change the svg path to our custom one */
|
|
||||||
{ match: /M4 2.5C2.897 2.5 2 3.397 2 4.5V15.5C2 16.604 2.897 17.5 4 17.5H11V19.5H7V21.5H17V19.5H13V17.5H20C21.103 17.5 22 16.604 22 15.5V4.5C22 3.397 21.103 2.5 20 2.5H4ZM20 4.5V13.5H4V4.5H20Z/, replace: "M 16 8 C 7.664063 8 1.25 15.34375 1.25 15.34375 L 0.65625 16 L 1.25 16.65625 C 1.25 16.65625 7.097656 23.324219 14.875 23.9375 C 15.246094 23.984375 15.617188 24 16 24 C 16.382813 24 16.753906 23.984375 17.125 23.9375 C 24.902344 23.324219 30.75 16.65625 30.75 16.65625 L 31.34375 16 L 30.75 15.34375 C 30.75 15.34375 24.335938 8 16 8 Z M 16 10 C 18.203125 10 20.234375 10.601563 22 11.40625 C 22.636719 12.460938 23 13.675781 23 15 C 23 18.613281 20.289063 21.582031 16.78125 21.96875 C 16.761719 21.972656 16.738281 21.964844 16.71875 21.96875 C 16.480469 21.980469 16.242188 22 16 22 C 15.734375 22 15.476563 21.984375 15.21875 21.96875 C 11.710938 21.582031 9 18.613281 9 15 C 9 13.695313 9.351563 12.480469 9.96875 11.4375 L 9.9375 11.4375 C 11.71875 10.617188 13.773438 10 16 10 Z M 16 12 C 14.34375 12 13 13.34375 13 15 C 13 16.65625 14.34375 18 16 18 C 17.65625 18 19 16.65625 19 15 C 19 13.34375 17.65625 12 16 12 Z M 7.25 12.9375 C 7.09375 13.609375 7 14.285156 7 15 C 7 16.753906 7.5 18.394531 8.375 19.78125 C 5.855469 18.324219 4.105469 16.585938 3.53125 16 C 4.011719 15.507813 5.351563 14.203125 7.25 12.9375 Z M 24.75 12.9375 C 26.648438 14.203125 27.988281 15.507813 28.46875 16 C 27.894531 16.585938 26.144531 18.324219 23.625 19.78125 C 24.5 18.394531 25 16.753906 25 15 C 25 14.285156 24.90625 13.601563 24.75 12.9375 Z" },
|
|
||||||
/** Modify the view box to not cut our svg */
|
|
||||||
{ match: /viewBox:"0 0 24 24"/, replace: 'viewBox:"0 0 32 26"' },
|
|
||||||
] as MatchAndReplace[])
|
|
||||||
.reduce((current, { match, replace }) => current.replace(match, replace), component);
|
|
||||||
|
|
||||||
/** Export our custom svg */
|
|
||||||
return `${exports},IgnoreActivities_toggleOn:()=>IgnoreActivities_toggleOnToExport${restOfFunction}${component}${renderIgnoreActivitiesToggleOn}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
/** Patch the LocalActivityStore to filter our ignored activities before they get pushed into the array */
|
|
||||||
find: '.displayName="LocalActivityStore"',
|
|
||||||
replacement: {
|
|
||||||
match: /((.)\.push\(.\({type:.\..{1,3}\.LISTENING.+?;)/,
|
|
||||||
replace: "$1$2=$2.filter(Vencord.Plugins.plugins.IgnoreActivities.isActivityEnabled);"
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
|
|
||||||
async start() {
|
|
||||||
ignoredActivitiesCache = (await DataStore.get<string[]>("IgnoreActivities_ignoredActivities")) ?? [];
|
|
||||||
|
|
||||||
if (ignoredActivitiesCache.length !== 0) {
|
|
||||||
const gamesSeen: Record<string, any>[] = RunningGameStoreModule.Z.getGamesSeen();
|
|
||||||
|
|
||||||
for (const [index, ignoredActivity] of ignoredActivitiesCache.entries()) {
|
|
||||||
if (!gamesSeen.some(game => (game.id !== undefined && game.id === ignoredActivity) || game.exePath === ignoredActivity)) {
|
|
||||||
ignoredActivitiesCache.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await DataStore.set("IgnoreActivities_ignoredActivities", ignoredActivitiesCache);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
isActivityEnabled(props: Record<string, any>) {
|
|
||||||
/** LocalActivityStore games have a "type" prop */
|
|
||||||
if ("type" in props) {
|
|
||||||
if (props.application_id !== undefined) return !ignoredActivitiesCache.includes(props.application_id);
|
|
||||||
else {
|
|
||||||
const exePath = RunningGameStoreModule.Z.getRunningGames().find(game => game.name === props.name)?.exePath;
|
|
||||||
if (exePath) return !ignoredActivitiesCache.includes(exePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/** Registered Games tab games have an "exePath" prop */
|
|
||||||
else if ("exePath" in props) {
|
|
||||||
if (props.id !== undefined) return !ignoredActivitiesCache.includes(props.id);
|
|
||||||
else return !ignoredActivitiesCache.includes(props.exePath);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
async handleActivityToggle(props: Record<string, any>) {
|
|
||||||
const id = props.id ?? props.exePath;
|
|
||||||
if (id === undefined) return;
|
|
||||||
|
|
||||||
if (ignoredActivitiesCache.includes(id)) ignoredActivitiesCache.splice(ignoredActivitiesCache.indexOf(id, 1));
|
|
||||||
else ignoredActivitiesCache.push(id);
|
|
||||||
RunningGameStoreModule.IgnoreActivities_reRenderGames();
|
|
||||||
await DataStore.set("IgnoreActivities_ignoredActivities", ignoredActivitiesCache);
|
|
||||||
}
|
|
||||||
});
|
|
220
src/plugins/ignoreActivities.tsx
Normal file
220
src/plugins/ignoreActivities.tsx
Normal file
|
@ -0,0 +1,220 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as DataStore from "@api/DataStore";
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import { useForceUpdater } from "@utils/misc";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
import { Tooltip } from "webpack/common";
|
||||||
|
|
||||||
|
enum ActivitiesTypes {
|
||||||
|
Game,
|
||||||
|
Embedded
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IgnoredActivity {
|
||||||
|
id: string;
|
||||||
|
type: ActivitiesTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RegisteredGamesClasses = findByPropsLazy("overlayToggleIconOff", "overlayToggleIconOn");
|
||||||
|
const PreviewBadgeClasses = findByPropsLazy("previewBadge", "previewBadgeIcon");
|
||||||
|
const BaseShapeRoundClasses = findByPropsLazy("baseShapeRound", "baseShapeRoundLeft", "baseShapeRoundRight");
|
||||||
|
const RunningGameStore = findByPropsLazy("getRunningGames", "getGamesSeen");
|
||||||
|
|
||||||
|
function ToggleIconOff() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className={RegisteredGamesClasses.overlayToggleIconOff}
|
||||||
|
height="24"
|
||||||
|
width="24"
|
||||||
|
viewBox="0 0 32 26"
|
||||||
|
aria-hidden={true}
|
||||||
|
role="img"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
fill="none"
|
||||||
|
fillRule="evenodd"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
className={RegisteredGamesClasses.fill}
|
||||||
|
fill="currentColor"
|
||||||
|
d="M 16 8 C 7.664063 8 1.25 15.34375 1.25 15.34375 L 0.65625 16 L 1.25 16.65625 C 1.25 16.65625 7.097656 23.324219 14.875 23.9375 C 15.246094 23.984375 15.617188 24 16 24 C 16.382813 24 16.753906 23.984375 17.125 23.9375 C 24.902344 23.324219 30.75 16.65625 30.75 16.65625 L 31.34375 16 L 30.75 15.34375 C 30.75 15.34375 24.335938 8 16 8 Z M 16 10 C 18.203125 10 20.234375 10.601563 22 11.40625 C 22.636719 12.460938 23 13.675781 23 15 C 23 18.613281 20.289063 21.582031 16.78125 21.96875 C 16.761719 21.972656 16.738281 21.964844 16.71875 21.96875 C 16.480469 21.980469 16.242188 22 16 22 C 15.734375 22 15.476563 21.984375 15.21875 21.96875 C 11.710938 21.582031 9 18.613281 9 15 C 9 13.695313 9.351563 12.480469 9.96875 11.4375 L 9.9375 11.4375 C 11.71875 10.617188 13.773438 10 16 10 Z M 16 12 C 14.34375 12 13 13.34375 13 15 C 13 16.65625 14.34375 18 16 18 C 17.65625 18 19 16.65625 19 15 C 19 13.34375 17.65625 12 16 12 Z M 7.25 12.9375 C 7.09375 13.609375 7 14.285156 7 15 C 7 16.753906 7.5 18.394531 8.375 19.78125 C 5.855469 18.324219 4.105469 16.585938 3.53125 16 C 4.011719 15.507813 5.351563 14.203125 7.25 12.9375 Z M 24.75 12.9375 C 26.648438 14.203125 27.988281 15.507813 28.46875 16 C 27.894531 16.585938 26.144531 18.324219 23.625 19.78125 C 24.5 18.394531 25 16.753906 25 15 C 25 14.285156 24.90625 13.601563 24.75 12.9375 Z"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
className={RegisteredGamesClasses.fill}
|
||||||
|
x="3"
|
||||||
|
y="26"
|
||||||
|
width="26"
|
||||||
|
height="2"
|
||||||
|
transform="rotate(-45 2 20)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToggleIconOn() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className={RegisteredGamesClasses.overlayToggleIconOn}
|
||||||
|
height="24"
|
||||||
|
width="24"
|
||||||
|
viewBox="0 0 32 26"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
className={RegisteredGamesClasses.fill}
|
||||||
|
d="M 16 8 C 7.664063 8 1.25 15.34375 1.25 15.34375 L 0.65625 16 L 1.25 16.65625 C 1.25 16.65625 7.097656 23.324219 14.875 23.9375 C 15.246094 23.984375 15.617188 24 16 24 C 16.382813 24 16.753906 23.984375 17.125 23.9375 C 24.902344 23.324219 30.75 16.65625 30.75 16.65625 L 31.34375 16 L 30.75 15.34375 C 30.75 15.34375 24.335938 8 16 8 Z M 16 10 C 18.203125 10 20.234375 10.601563 22 11.40625 C 22.636719 12.460938 23 13.675781 23 15 C 23 18.613281 20.289063 21.582031 16.78125 21.96875 C 16.761719 21.972656 16.738281 21.964844 16.71875 21.96875 C 16.480469 21.980469 16.242188 22 16 22 C 15.734375 22 15.476563 21.984375 15.21875 21.96875 C 11.710938 21.582031 9 18.613281 9 15 C 9 13.695313 9.351563 12.480469 9.96875 11.4375 L 9.9375 11.4375 C 11.71875 10.617188 13.773438 10 16 10 Z M 16 12 C 14.34375 12 13 13.34375 13 15 C 13 16.65625 14.34375 18 16 18 C 17.65625 18 19 16.65625 19 15 C 19 13.34375 17.65625 12 16 12 Z M 7.25 12.9375 C 7.09375 13.609375 7 14.285156 7 15 C 7 16.753906 7.5 18.394531 8.375 19.78125 C 5.855469 18.324219 4.105469 16.585938 3.53125 16 C 4.011719 15.507813 5.351563 14.203125 7.25 12.9375 Z M 24.75 12.9375 C 26.648438 14.203125 27.988281 15.507813 28.46875 16 C 27.894531 16.585938 26.144531 18.324219 23.625 19.78125 C 24.5 18.394531 25 16.753906 25 15 C 25 14.285156 24.90625 13.601563 24.75 12.9375 Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToggleActivityComponent({ activity }: { activity: IgnoredActivity; }) {
|
||||||
|
const forceUpdate = useForceUpdater();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip text="Toggle activity">
|
||||||
|
{({ onMouseLeave, onMouseEnter }) => (
|
||||||
|
<div
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
className={RegisteredGamesClasses.overlayToggleIcon}
|
||||||
|
role="button"
|
||||||
|
aria-label="Toggle activity"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={e => handleActivityToggle(e, activity, forceUpdate)}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
ignoredActivitiesCache.has(activity.id)
|
||||||
|
? <ToggleIconOff />
|
||||||
|
: <ToggleIconOn />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToggleActivityComponentWithBackground({ activity }: { activity: IgnoredActivity; }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`${PreviewBadgeClasses.previewBadge} ${BaseShapeRoundClasses.baseShapeRound}`}
|
||||||
|
style={{ padding: "0 2px" }}
|
||||||
|
>
|
||||||
|
<ToggleActivityComponent activity={activity} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleActivityToggle(e: React.MouseEvent<HTMLDivElement, MouseEvent>, activity: IgnoredActivity, forceUpdateComponent: () => void) {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (ignoredActivitiesCache.has(activity.id)) ignoredActivitiesCache.delete(activity.id);
|
||||||
|
else ignoredActivitiesCache.set(activity.id, activity);
|
||||||
|
forceUpdateComponent();
|
||||||
|
saveCacheToDatastore();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveCacheToDatastore() {
|
||||||
|
await DataStore.set("IgnoreActivities_ignoredActivities", ignoredActivitiesCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ignoredActivitiesCache = new Map<IgnoredActivity["id"], IgnoredActivity>();
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "IgnoreActivities",
|
||||||
|
authors: [Devs.Nuckyz],
|
||||||
|
description: "Ignore certain activities (like games and actual activities) from showing up on your status. You can configure which ones are ignored from the Registered Games and Activities tabs.",
|
||||||
|
patches: [{
|
||||||
|
find: ".Messages.SETTINGS_GAMES_OVERLAY_ON",
|
||||||
|
replacement: {
|
||||||
|
match: /(this.renderLastPlayed\(\)]}\),this.renderOverlayToggle\(\))/,
|
||||||
|
replace: "$1,Vencord.Plugins.plugins.IgnoreActivities.renderToggleGameActivityButton(this.props)"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
find: ".Messages.NEW,name",
|
||||||
|
replacement: {
|
||||||
|
match: /\(\)\.badgeContainer.+?.\?\(0,.\.jsx\)\(.{1,2},{name:(?<props>.)\.name}\):null/,
|
||||||
|
replace: "$&,Vencord.Plugins.plugins.IgnoreActivities.renderToggleActivityButton($<props>)"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
find: '.displayName="LocalActivityStore"',
|
||||||
|
replacement: {
|
||||||
|
match: /((.)\.push\(.\({type:.\..{1,3}\.LISTENING.+?;)/,
|
||||||
|
replace: "$1$2=$2.filter(Vencord.Plugins.plugins.IgnoreActivities.isActivityEnabled);"
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
const ignoredActivitiesData = await DataStore.get<string[] | Map<IgnoredActivity["id"], IgnoredActivity>>("IgnoreActivities_ignoredActivities") ?? new Map<IgnoredActivity["id"], IgnoredActivity>();
|
||||||
|
/** Migrate old data */
|
||||||
|
if (Array.isArray(ignoredActivitiesData)) {
|
||||||
|
for (const id of ignoredActivitiesData) {
|
||||||
|
ignoredActivitiesCache.set(id, { id, type: ActivitiesTypes.Game });
|
||||||
|
}
|
||||||
|
|
||||||
|
await saveCacheToDatastore();
|
||||||
|
} else ignoredActivitiesCache = ignoredActivitiesData;
|
||||||
|
|
||||||
|
if (ignoredActivitiesCache.size !== 0) {
|
||||||
|
const gamesSeen: { id?: string; exePath: string; }[] = RunningGameStore.getGamesSeen();
|
||||||
|
|
||||||
|
for (const ignoredActivity of ignoredActivitiesCache.values()) {
|
||||||
|
if (ignoredActivity.type !== ActivitiesTypes.Game) continue;
|
||||||
|
|
||||||
|
if (!gamesSeen.some(game => game.id === ignoredActivity.id || game.exePath === ignoredActivity.id)) {
|
||||||
|
/** Custom added game which no longer exists */
|
||||||
|
ignoredActivitiesCache.delete(ignoredActivity.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await saveCacheToDatastore();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
renderToggleGameActivityButton(props: { game: { id?: string; exePath: string; } | null; }) {
|
||||||
|
if (!props.game) return (null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ErrorBoundary noop>
|
||||||
|
<ToggleActivityComponent activity={{ id: props.game.id ?? props.game.exePath, type: ActivitiesTypes.Game }} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderToggleActivityButton(props: { id: string; }) {
|
||||||
|
return (
|
||||||
|
<ErrorBoundary noop>
|
||||||
|
<ToggleActivityComponentWithBackground activity={{ id: props.id, type: ActivitiesTypes.Embedded }} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
isActivityEnabled(props: { type: number; application_id?: string; name?: string; }) {
|
||||||
|
if (props.type === 0) {
|
||||||
|
if (props.application_id !== undefined) return !ignoredActivitiesCache.has(props.application_id);
|
||||||
|
else {
|
||||||
|
const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath;
|
||||||
|
if (exePath) return !ignoredActivitiesCache.has(exePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
|
@ -40,7 +40,7 @@ interface PreviousChannel {
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "KeepCurrentChannel",
|
name: "KeepCurrentChannel",
|
||||||
description: "Attempt to navigate the channel you were in before switching accounts or loading Discord.",
|
description: "Attempt to navigate to the channel you were in before switching accounts or loading Discord.",
|
||||||
authors: [Devs.Nuckyz],
|
authors: [Devs.Nuckyz],
|
||||||
|
|
||||||
isSwitchingAccount: false,
|
isSwitchingAccount: false,
|
||||||
|
|
|
@ -35,11 +35,13 @@ function MemberCount() {
|
||||||
|
|
||||||
if (!c) return null;
|
if (!c) return null;
|
||||||
|
|
||||||
let total = String(c[0]);
|
let total = c[0].toLocaleString();
|
||||||
if (total === "0" && c[1] > 0) {
|
if (total === "0" && c[1] > 0) {
|
||||||
total = "Loading...";
|
total = "Loading...";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const online = c[1].toLocaleString();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex id="vc-membercount" style={{
|
<Flex id="vc-membercount" style={{
|
||||||
marginTop: "1em",
|
marginTop: "1em",
|
||||||
|
@ -49,7 +51,7 @@ function MemberCount() {
|
||||||
alignContent: "center",
|
alignContent: "center",
|
||||||
gap: 0
|
gap: 0
|
||||||
}}>
|
}}>
|
||||||
<Tooltip text={`${c[1]} Online`} position="bottom">
|
<Tooltip text={`${online} Online`} position="bottom">
|
||||||
{props => (
|
{props => (
|
||||||
<div {...props}>
|
<div {...props}>
|
||||||
<span
|
<span
|
||||||
|
@ -62,11 +64,11 @@ function MemberCount() {
|
||||||
marginRight: "0.5em"
|
marginRight: "0.5em"
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span style={{ color: "var(--status-green-600)" }}>{c[1]}</span>
|
<span style={{ color: "var(--status-green-600)" }}>{online}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip text={`${c[0] || "?"} Total Members`} position="bottom">
|
<Tooltip text={`${total} Total Members`} position="bottom">
|
||||||
{props => (
|
{props => (
|
||||||
<div {...props}>
|
<div {...props}>
|
||||||
<span
|
<span
|
||||||
|
@ -91,7 +93,7 @@ function MemberCount() {
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "MemberCount",
|
name: "MemberCount",
|
||||||
description: "Shows the amount of online & total members in the server member list",
|
description: "Shows the amount of online & total members in the server member list",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven, Devs.Commandtechno],
|
||||||
|
|
||||||
patches: [{
|
patches: [{
|
||||||
find: ".isSidebarVisible,",
|
find: ".isSidebarVisible,",
|
||||||
|
|
35
src/plugins/nsfwGateBypass.ts
Normal file
35
src/plugins/nsfwGateBypass.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 OpenAsar
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "NSFWGateBypass",
|
||||||
|
description: "Allows you to access NSFW channels without setting/verifying your age",
|
||||||
|
authors: [Devs.Commandtechno],
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: ".nsfwAllowed=null",
|
||||||
|
replacement: {
|
||||||
|
match: /(\w+)\.nsfwAllowed=/,
|
||||||
|
replace: "$1.nsfwAllowed=true;",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
|
@ -39,11 +39,10 @@ export default function PronounsChatComponentWrapper({ message }: { message: Mes
|
||||||
}
|
}
|
||||||
|
|
||||||
function PronounsChatComponent({ message }: { message: Message; }) {
|
function PronounsChatComponent({ message }: { message: Message; }) {
|
||||||
const [result, , isPending] = useAwaiter(
|
const [result, , isPending] = useAwaiter(() => fetchPronouns(message.author.id), {
|
||||||
() => fetchPronouns(message.author.id),
|
fallbackValue: null,
|
||||||
null,
|
onError: e => console.error("Fetching pronouns failed: ", e)
|
||||||
e => console.error("Fetching pronouns failed: ", e)
|
});
|
||||||
);
|
|
||||||
|
|
||||||
// If the promise completed, the result was not "unspecified", and there is a mapping for the code, then return a span with the pronouns
|
// If the promise completed, the result was not "unspecified", and there is a mapping for the code, then return a span with the pronouns
|
||||||
if (!isPending && result && result !== "unspecified" && PronounMapping[result]) {
|
if (!isPending && result && result !== "unspecified" && PronounMapping[result]) {
|
||||||
|
|
|
@ -45,11 +45,10 @@ function ProfilePronouns(
|
||||||
leProps: UserProfilePronounsProps;
|
leProps: UserProfilePronounsProps;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const [result, , isPending] = useAwaiter(
|
const [result, , isPending] = useAwaiter(() => fetchPronouns(userId), {
|
||||||
() => fetchPronouns(userId),
|
fallbackValue: null,
|
||||||
null,
|
onError: e => console.error("Fetching pronouns failed: ", e),
|
||||||
e => console.error("Fetching pronouns failed: ", e)
|
});
|
||||||
);
|
|
||||||
|
|
||||||
// If the promise completed, the result was not "unspecified", and there is a mapping for the code, then render
|
// If the promise completed, the result was not "unspecified", and there is a mapping for the code, then render
|
||||||
if (!isPending && result && result !== "unspecified" && PronounMapping[result]) {
|
if (!isPending && result && result !== "unspecified" && PronounMapping[result]) {
|
||||||
|
|
|
@ -16,9 +16,11 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { addButton, removeButton } from "@api/MessagePopover";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findLazy } from "@webpack";
|
import { findLazy } from "@webpack";
|
||||||
|
import { ChannelStore } from "@webpack/common";
|
||||||
|
|
||||||
const ComponentDispatch = findLazy(m => m.emitter?._events?.INSERT_TEXT);
|
const ComponentDispatch = findLazy(m => m.emitter?._events?.INSERT_TEXT);
|
||||||
|
|
||||||
|
@ -26,29 +28,22 @@ export default definePlugin({
|
||||||
name: "QuickMention",
|
name: "QuickMention",
|
||||||
authors: [Devs.kemo],
|
authors: [Devs.kemo],
|
||||||
description: "Adds a quick mention button to the message actions bar",
|
description: "Adds a quick mention button to the message actions bar",
|
||||||
|
dependencies: ["MessagePopoverAPI"],
|
||||||
|
|
||||||
patches: [
|
start() {
|
||||||
{
|
addButton("QuickMention", msg => {
|
||||||
find: "Messages.MESSAGE_UTILITIES_A11Y_LABEL",
|
return {
|
||||||
replacement: {
|
label: "Quick Mention",
|
||||||
match: /(null,)(.{1,3}&&!.{1,3}\?(.{1,3})\(\{key:"reply",label:.{1,10}\.Messages\.MESSAGE_ACTION_REPLY,icon:.{1,10},channel:(.+?),message:(.+?),onClick:.+?\}\))/,
|
icon: this.Icon,
|
||||||
replace: (m, post, og, functionName, channelVar, messageVar) => {
|
message: msg,
|
||||||
|
channel: ChannelStore.getChannel(msg.channel_id),
|
||||||
const functionSig =
|
onClick: () => ComponentDispatch.dispatchToLastSubscribed("INSERT_TEXT", { rawText: `<@${msg.author.id}> ` })
|
||||||
`${functionName}({
|
};
|
||||||
key: "QuickMention",
|
});
|
||||||
label: "Mention",
|
},
|
||||||
icon: Vencord.Plugins.plugins.QuickMention.Icon,
|
stop() {
|
||||||
channel: ${channelVar},
|
removeButton("QuickMention");
|
||||||
message: ${messageVar},
|
},
|
||||||
onClick: ()=> Vencord.Plugins.plugins.QuickMention.onClick(${messageVar})
|
|
||||||
})`;
|
|
||||||
|
|
||||||
return `${post}${functionSig},${og}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
Icon: () => (
|
Icon: () => (
|
||||||
<svg
|
<svg
|
||||||
|
@ -63,6 +58,4 @@ export default definePlugin({
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
|
|
||||||
onClick: (message: any) => ComponentDispatch.dispatchToLastSubscribed("INSERT_TEXT", { rawText: `<@${message.author.id}> ` })
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,7 +21,7 @@ import definePlugin from "@utils/types";
|
||||||
import { Menu } from "@webpack/common";
|
import { Menu } from "@webpack/common";
|
||||||
|
|
||||||
const Engines = {
|
const Engines = {
|
||||||
Google: "https://www.google.com/searchbyimage?image_url=",
|
Google: "https://lens.google.com/uploadbyurl?url=",
|
||||||
Yandex: "https://yandex.com/images/search?rpt=imageview&url=",
|
Yandex: "https://yandex.com/images/search?rpt=imageview&url=",
|
||||||
SauceNAO: "https://saucenao.com/search.php?url=",
|
SauceNAO: "https://saucenao.com/search.php?url=",
|
||||||
IQDB: "https://iqdb.org/?url=",
|
IQDB: "https://iqdb.org/?url=",
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import { classes, useAwaiter } from "@utils/misc";
|
import { classes, useAwaiter } from "@utils/misc";
|
||||||
import { findLazy } from "@webpack";
|
import { findLazy } from "@webpack";
|
||||||
import { Forms, Text, UserStore } from "@webpack/common";
|
import { Forms, React, Text, UserStore } from "@webpack/common";
|
||||||
import type { KeyboardEvent } from "react";
|
import type { KeyboardEvent } from "react";
|
||||||
|
|
||||||
import { addReview, getReviews } from "../Utils/ReviewDBAPI";
|
import { addReview, getReviews } from "../Utils/ReviewDBAPI";
|
||||||
|
@ -27,7 +27,13 @@ import ReviewComponent from "./ReviewComponent";
|
||||||
const Classes = findLazy(m => typeof m.textarea === "string");
|
const Classes = findLazy(m => typeof m.textarea === "string");
|
||||||
|
|
||||||
export default function ReviewsView({ userId }: { userId: string; }) {
|
export default function ReviewsView({ userId }: { userId: string; }) {
|
||||||
const [reviews, _, isLoading, refetch] = useAwaiter(() => getReviews(userId), []);
|
const [refetchCount, setRefetchCount] = React.useState(0);
|
||||||
|
const [reviews, _, isLoading] = useAwaiter(() => getReviews(userId), {
|
||||||
|
fallbackValue: [],
|
||||||
|
deps: [refetchCount],
|
||||||
|
});
|
||||||
|
|
||||||
|
const dirtyRefetch = () => setRefetchCount(refetchCount + 1);
|
||||||
|
|
||||||
if (isLoading) return null;
|
if (isLoading) return null;
|
||||||
|
|
||||||
|
@ -40,7 +46,7 @@ export default function ReviewsView({ userId }: { userId: string; }) {
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
if (res === 0 || res === 1) {
|
if (res === 0 || res === 1) {
|
||||||
(target as HTMLInputElement).value = ""; // clear the input
|
(target as HTMLInputElement).value = ""; // clear the input
|
||||||
refetch();
|
dirtyRefetch();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -62,7 +68,7 @@ export default function ReviewsView({ userId }: { userId: string; }) {
|
||||||
<ReviewComponent
|
<ReviewComponent
|
||||||
key={review.id}
|
key={review.id}
|
||||||
review={review}
|
review={review}
|
||||||
refetch={refetch}
|
refetch={dirtyRefetch}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{reviews?.length === 0 && (
|
{reviews?.length === 0 && (
|
||||||
|
|
74
src/plugins/shikiCodeblocks/api/languages.ts
Normal file
74
src/plugins/shikiCodeblocks/api/languages.ts
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ILanguageRegistration } from "@vap/shiki";
|
||||||
|
|
||||||
|
export const VPC_REPO = "Vap0r1ze/vapcord";
|
||||||
|
export const VPC_REPO_COMMIT = "88a7032a59cca40da170926651b08201ea3b965a";
|
||||||
|
export const vpcRepoAssets = `https://raw.githubusercontent.com/${VPC_REPO}/${VPC_REPO_COMMIT}/assets/shiki-codeblocks`;
|
||||||
|
export const vpcRepoGrammar = (fileName: string) => `${vpcRepoAssets}/${fileName}`;
|
||||||
|
export const vpcRepoLanguages = `${vpcRepoAssets}/languages.json`;
|
||||||
|
|
||||||
|
export interface Language {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
devicon?: string;
|
||||||
|
grammarUrl: string,
|
||||||
|
grammar?: ILanguageRegistration["grammar"];
|
||||||
|
scopeName: string;
|
||||||
|
aliases?: string[];
|
||||||
|
custom?: boolean;
|
||||||
|
}
|
||||||
|
export interface LanguageJson {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
fileName: string;
|
||||||
|
devicon?: string;
|
||||||
|
scopeName: string;
|
||||||
|
aliases?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const languages: Record<string, Language> = {};
|
||||||
|
|
||||||
|
export const loadLanguages = async () => {
|
||||||
|
const langsJson: LanguageJson[] = await fetch(vpcRepoLanguages).then(res => res.json());
|
||||||
|
const loadedLanguages = Object.fromEntries(
|
||||||
|
langsJson.map(lang => [lang.id, {
|
||||||
|
...lang,
|
||||||
|
grammarUrl: vpcRepoGrammar(lang.fileName),
|
||||||
|
}])
|
||||||
|
);
|
||||||
|
Object.assign(languages, loadedLanguages);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getGrammar = (lang: Language): Promise<NonNullable<ILanguageRegistration["grammar"]>> => {
|
||||||
|
if (lang.grammar) return Promise.resolve(lang.grammar);
|
||||||
|
return fetch(lang.grammarUrl).then(res => res.json());
|
||||||
|
};
|
||||||
|
|
||||||
|
const aliasCache = new Map<string, Language>();
|
||||||
|
export function resolveLang(idOrAlias: string) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(languages, idOrAlias)) return languages[idOrAlias];
|
||||||
|
|
||||||
|
const lang = Object.values(languages).find(lang => lang.aliases?.includes(idOrAlias));
|
||||||
|
|
||||||
|
if (!lang) return null;
|
||||||
|
|
||||||
|
aliasCache.set(idOrAlias, lang);
|
||||||
|
return lang;
|
||||||
|
}
|
119
src/plugins/shikiCodeblocks/api/shiki.ts
Normal file
119
src/plugins/shikiCodeblocks/api/shiki.ts
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import { shikiOnigasmSrc, shikiWorkerSrc } from "@utils/dependencies";
|
||||||
|
import { WorkerClient } from "@vap/core/ipc";
|
||||||
|
import type { IShikiTheme, IThemedToken } from "@vap/shiki";
|
||||||
|
|
||||||
|
import { dispatchTheme } from "../hooks/useTheme";
|
||||||
|
import type { ShikiSpec } from "../types";
|
||||||
|
import { getGrammar, languages, loadLanguages, resolveLang } from "./languages";
|
||||||
|
import { themes } from "./themes";
|
||||||
|
|
||||||
|
const themeUrls = Object.values(themes);
|
||||||
|
|
||||||
|
let resolveClient: (client: WorkerClient<ShikiSpec>) => void;
|
||||||
|
|
||||||
|
export const shiki = {
|
||||||
|
client: null as WorkerClient<ShikiSpec> | null,
|
||||||
|
currentTheme: null as IShikiTheme | null,
|
||||||
|
currentThemeUrl: null as string | null,
|
||||||
|
timeoutMs: 10000,
|
||||||
|
languages,
|
||||||
|
themes,
|
||||||
|
loadedThemes: new Set<string>(),
|
||||||
|
loadedLangs: new Set<string>(),
|
||||||
|
clientPromise: new Promise<WorkerClient<ShikiSpec>>(resolve => resolveClient = resolve),
|
||||||
|
|
||||||
|
init: async (initThemeUrl: string | undefined) => {
|
||||||
|
/** https://stackoverflow.com/q/58098143 */
|
||||||
|
const workerBlob = await fetch(shikiWorkerSrc).then(res => res.blob());
|
||||||
|
|
||||||
|
const client = shiki.client = new WorkerClient<ShikiSpec>(
|
||||||
|
"shiki-client",
|
||||||
|
"shiki-host",
|
||||||
|
workerBlob,
|
||||||
|
{ name: "ShikiWorker" },
|
||||||
|
);
|
||||||
|
await client.init();
|
||||||
|
|
||||||
|
const themeUrl = initThemeUrl || themeUrls[0];
|
||||||
|
|
||||||
|
await loadLanguages();
|
||||||
|
await client.run("setOnigasm", { wasm: shikiOnigasmSrc });
|
||||||
|
await client.run("setHighlighter", { theme: themeUrl, langs: [] });
|
||||||
|
shiki.loadedThemes.add(themeUrl);
|
||||||
|
await shiki._setTheme(themeUrl);
|
||||||
|
resolveClient(client);
|
||||||
|
},
|
||||||
|
_setTheme: async (themeUrl: string) => {
|
||||||
|
shiki.currentThemeUrl = themeUrl;
|
||||||
|
const { themeData } = await shiki.client!.run("getTheme", { theme: themeUrl });
|
||||||
|
shiki.currentTheme = JSON.parse(themeData);
|
||||||
|
dispatchTheme({ id: themeUrl, theme: shiki.currentTheme });
|
||||||
|
},
|
||||||
|
loadTheme: async (themeUrl: string) => {
|
||||||
|
const client = await shiki.clientPromise;
|
||||||
|
if (shiki.loadedThemes.has(themeUrl)) return;
|
||||||
|
|
||||||
|
await client.run("loadTheme", { theme: themeUrl });
|
||||||
|
|
||||||
|
shiki.loadedThemes.add(themeUrl);
|
||||||
|
},
|
||||||
|
setTheme: async (themeUrl: string) => {
|
||||||
|
await shiki.clientPromise;
|
||||||
|
themeUrl ||= themeUrls[0];
|
||||||
|
if (!shiki.loadedThemes.has(themeUrl)) await shiki.loadTheme(themeUrl);
|
||||||
|
|
||||||
|
await shiki._setTheme(themeUrl);
|
||||||
|
},
|
||||||
|
loadLang: async (langId: string) => {
|
||||||
|
const client = await shiki.clientPromise;
|
||||||
|
const lang = resolveLang(langId);
|
||||||
|
|
||||||
|
if (!lang || shiki.loadedLangs.has(lang.id)) return;
|
||||||
|
|
||||||
|
await client.run("loadLanguage", {
|
||||||
|
lang: {
|
||||||
|
...lang,
|
||||||
|
grammar: lang.grammar ?? await getGrammar(lang),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
shiki.loadedLangs.add(lang.id);
|
||||||
|
},
|
||||||
|
tokenizeCode: async (code: string, langId: string): Promise<IThemedToken[][]> => {
|
||||||
|
const client = await shiki.clientPromise;
|
||||||
|
const lang = resolveLang(langId);
|
||||||
|
if (!lang) return [];
|
||||||
|
|
||||||
|
if (!shiki.loadedLangs.has(lang.id)) await shiki.loadLang(lang.id);
|
||||||
|
|
||||||
|
return await client.run("codeToThemedTokens", {
|
||||||
|
code,
|
||||||
|
lang: langId,
|
||||||
|
theme: shiki.currentThemeUrl ?? themeUrls[0],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
shiki.currentTheme = null;
|
||||||
|
shiki.currentThemeUrl = null;
|
||||||
|
dispatchTheme({ id: null, theme: null });
|
||||||
|
shiki.client?.destroy();
|
||||||
|
}
|
||||||
|
};
|
67
src/plugins/shikiCodeblocks/api/themes.ts
Normal file
67
src/plugins/shikiCodeblocks/api/themes.ts
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { IShikiTheme } from "@vap/shiki";
|
||||||
|
|
||||||
|
export const SHIKI_REPO = "shikijs/shiki";
|
||||||
|
export const SHIKI_REPO_COMMIT = "0b28ad8ccfbf2615f2d9d38ea8255416b8ac3043";
|
||||||
|
export const shikiRepoTheme = (name: string) => `https://raw.githubusercontent.com/${SHIKI_REPO}/${SHIKI_REPO_COMMIT}/packages/shiki/themes/${name}.json`;
|
||||||
|
|
||||||
|
export const themes = {
|
||||||
|
// Default
|
||||||
|
DarkPlus: shikiRepoTheme("dark-plus"),
|
||||||
|
|
||||||
|
// Dev Choices
|
||||||
|
MaterialCandy: "https://raw.githubusercontent.com/millsp/material-candy/master/material-candy.json",
|
||||||
|
|
||||||
|
// More from Shiki repo
|
||||||
|
DraculaSoft: shikiRepoTheme("dracula-soft"),
|
||||||
|
Dracula: shikiRepoTheme("dracula"),
|
||||||
|
GithubDarkDimmed: shikiRepoTheme("github-dark-dimmed"),
|
||||||
|
GithubDark: shikiRepoTheme("github-dark"),
|
||||||
|
GithubLight: shikiRepoTheme("github-light"),
|
||||||
|
LightPlus: shikiRepoTheme("light-plus"),
|
||||||
|
MaterialDarker: shikiRepoTheme("material-darker"),
|
||||||
|
MaterialDefault: shikiRepoTheme("material-default"),
|
||||||
|
MaterialLighter: shikiRepoTheme("material-lighter"),
|
||||||
|
MaterialOcean: shikiRepoTheme("material-ocean"),
|
||||||
|
MaterialPalenight: shikiRepoTheme("material-palenight"),
|
||||||
|
MinDark: shikiRepoTheme("min-dark"),
|
||||||
|
MinLight: shikiRepoTheme("min-light"),
|
||||||
|
Monokai: shikiRepoTheme("monokai"),
|
||||||
|
Nord: shikiRepoTheme("nord"),
|
||||||
|
OneDarkPro: shikiRepoTheme("one-dark-pro"),
|
||||||
|
Poimandres: shikiRepoTheme("poimandres"),
|
||||||
|
RosePineDawn: shikiRepoTheme("rose-pine-dawn"),
|
||||||
|
RosePineMoon: shikiRepoTheme("rose-pine-moon"),
|
||||||
|
RosePine: shikiRepoTheme("rose-pine"),
|
||||||
|
SlackDark: shikiRepoTheme("slack-dark"),
|
||||||
|
SlackOchin: shikiRepoTheme("slack-ochin"),
|
||||||
|
SolarizedDark: shikiRepoTheme("solarized-dark"),
|
||||||
|
SolarizedLight: shikiRepoTheme("solarized-light"),
|
||||||
|
VitesseDark: shikiRepoTheme("vitesse-dark"),
|
||||||
|
VitesseLight: shikiRepoTheme("vitesse-light"),
|
||||||
|
CssVariables: shikiRepoTheme("css-variables"),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const themeCache = new Map<string, IShikiTheme>();
|
||||||
|
|
||||||
|
export const getTheme = (url: string): Promise<IShikiTheme> => {
|
||||||
|
if (themeCache.has(url)) return Promise.resolve(themeCache.get(url)!);
|
||||||
|
return fetch(url).then(res => res.json());
|
||||||
|
};
|
46
src/plugins/shikiCodeblocks/components/ButtonRow.tsx
Normal file
46
src/plugins/shikiCodeblocks/components/ButtonRow.tsx
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Clipboard } from "@webpack/common";
|
||||||
|
|
||||||
|
import { cl } from "../utils/misc";
|
||||||
|
import { CopyButton } from "./CopyButton";
|
||||||
|
|
||||||
|
export interface ButtonRowProps {
|
||||||
|
theme: import("./Highlighter").ThemeBase;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ButtonRow({ content, theme }: ButtonRowProps) {
|
||||||
|
const buttons: JSX.Element[] = [];
|
||||||
|
|
||||||
|
if (Clipboard.SUPPORTS_COPY) {
|
||||||
|
buttons.push(
|
||||||
|
<CopyButton
|
||||||
|
content={content}
|
||||||
|
className={cl("btn")}
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.accentBgColor,
|
||||||
|
color: theme.accentFgColor,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className={cl("btns")}>{buttons}</div>;
|
||||||
|
}
|
93
src/plugins/shikiCodeblocks/components/Code.tsx
Normal file
93
src/plugins/shikiCodeblocks/components/Code.tsx
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { IThemedToken } from "@vap/shiki";
|
||||||
|
import { hljs } from "@webpack/common";
|
||||||
|
|
||||||
|
import { cl } from "../utils/misc";
|
||||||
|
import { ThemeBase } from "./Highlighter";
|
||||||
|
|
||||||
|
export interface CodeProps {
|
||||||
|
theme: ThemeBase;
|
||||||
|
useHljs: boolean;
|
||||||
|
lang?: string;
|
||||||
|
content: string;
|
||||||
|
tokens: IThemedToken[][] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Code = ({
|
||||||
|
theme,
|
||||||
|
useHljs,
|
||||||
|
lang,
|
||||||
|
content,
|
||||||
|
tokens,
|
||||||
|
}: CodeProps) => {
|
||||||
|
let lines!: JSX.Element[];
|
||||||
|
|
||||||
|
if (useHljs) {
|
||||||
|
try {
|
||||||
|
const { value: hljsHtml } = hljs.highlight(lang!, content, true);
|
||||||
|
lines = hljsHtml
|
||||||
|
.split("\n")
|
||||||
|
.map((line, i) => <span key={i} dangerouslySetInnerHTML={{ __html: line }} />);
|
||||||
|
} catch {
|
||||||
|
lines = content.split("\n").map(line => <span>{line}</span>);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const renderTokens =
|
||||||
|
tokens ??
|
||||||
|
content
|
||||||
|
.split("\n")
|
||||||
|
.map(line => [{ color: theme.plainColor, content: line } as IThemedToken]);
|
||||||
|
|
||||||
|
lines = renderTokens.map(line => {
|
||||||
|
// [Cynthia] this makes it so when you highlight the codeblock
|
||||||
|
// empty lines are also selected and copied when you Ctrl+C.
|
||||||
|
if (line.length === 0) {
|
||||||
|
return <span>{"\n"}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{line.map(({ content, color, fontStyle }, i) => (
|
||||||
|
<span
|
||||||
|
key={i}
|
||||||
|
style={{
|
||||||
|
color,
|
||||||
|
fontStyle: (fontStyle ?? 0) & 1 ? "italic" : undefined,
|
||||||
|
fontWeight: (fontStyle ?? 0) & 2 ? "bold" : undefined,
|
||||||
|
textDecoration: (fontStyle ?? 0) & 4 ? "underline" : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const codeTableRows = lines.map((line, i) => (
|
||||||
|
<tr key={i}>
|
||||||
|
<td style={{ color: theme.plainColor }}>{i + 1}</td>
|
||||||
|
<td>{line}</td>
|
||||||
|
</tr>
|
||||||
|
));
|
||||||
|
|
||||||
|
return <table className={cl("table")}>{...codeTableRows}</table>;
|
||||||
|
};
|
41
src/plugins/shikiCodeblocks/components/CopyButton.tsx
Normal file
41
src/plugins/shikiCodeblocks/components/CopyButton.tsx
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useCopyCooldown } from "../hooks/useCopyCooldown";
|
||||||
|
|
||||||
|
export interface CopyButtonProps extends React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CopyButton({ content, ...props }: CopyButtonProps) {
|
||||||
|
const [copyCooldown, copy] = useCopyCooldown(1000);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
{...props}
|
||||||
|
style={{
|
||||||
|
...props.style,
|
||||||
|
cursor: copyCooldown ? "default" : undefined,
|
||||||
|
}}
|
||||||
|
onClick={() => copy(content)}
|
||||||
|
>
|
||||||
|
{copyCooldown ? "Copied!" : "Copy"}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
42
src/plugins/shikiCodeblocks/components/Header.tsx
Normal file
42
src/plugins/shikiCodeblocks/components/Header.tsx
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Language } from "../api/languages";
|
||||||
|
import { DeviconSetting } from "../types";
|
||||||
|
import { cl } from "../utils/misc";
|
||||||
|
|
||||||
|
export interface HeaderProps {
|
||||||
|
langName?: string;
|
||||||
|
useDevIcon: DeviconSetting;
|
||||||
|
shikiLang: Language | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Header({ langName, useDevIcon, shikiLang }: HeaderProps) {
|
||||||
|
if (!langName) return <></>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cl("lang")}>
|
||||||
|
{useDevIcon !== DeviconSetting.Disabled && shikiLang?.devicon && (
|
||||||
|
<i
|
||||||
|
className={`devicon-${shikiLang.devicon}${useDevIcon === DeviconSetting.Color ? " colored" : ""}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{langName}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
131
src/plugins/shikiCodeblocks/components/Highlighter.tsx
Normal file
131
src/plugins/shikiCodeblocks/components/Highlighter.tsx
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { useAwaiter } from "@utils/misc";
|
||||||
|
import { useIntersection } from "@utils/react";
|
||||||
|
import { hljs, React } from "@webpack/common";
|
||||||
|
|
||||||
|
import { resolveLang } from "../api/languages";
|
||||||
|
import { shiki } from "../api/shiki";
|
||||||
|
import { useShikiSettings } from "../hooks/useShikiSettings";
|
||||||
|
import { useTheme } from "../hooks/useTheme";
|
||||||
|
import { hex2Rgb } from "../utils/color";
|
||||||
|
import { cl, shouldUseHljs } from "../utils/misc";
|
||||||
|
import { ButtonRow } from "./ButtonRow";
|
||||||
|
import { Code } from "./Code";
|
||||||
|
import { Header } from "./Header";
|
||||||
|
|
||||||
|
export interface ThemeBase {
|
||||||
|
plainColor: string;
|
||||||
|
accentBgColor: string;
|
||||||
|
accentFgColor: string;
|
||||||
|
backgroundColor: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HighlighterProps {
|
||||||
|
lang?: string;
|
||||||
|
content: string;
|
||||||
|
isPreview: boolean;
|
||||||
|
tempSettings?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createHighlighter = (props: HighlighterProps) => (
|
||||||
|
<pre className={cl("container")}>
|
||||||
|
<ErrorBoundary>
|
||||||
|
<Highlighter {...props} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
</pre>
|
||||||
|
);
|
||||||
|
export const Highlighter = ({
|
||||||
|
lang,
|
||||||
|
content,
|
||||||
|
isPreview,
|
||||||
|
tempSettings,
|
||||||
|
}: HighlighterProps) => {
|
||||||
|
const {
|
||||||
|
tryHljs,
|
||||||
|
useDevIcon,
|
||||||
|
bgOpacity,
|
||||||
|
} = useShikiSettings(["tryHljs", "useDevIcon", "bgOpacity"], tempSettings);
|
||||||
|
const { id: currentThemeId, theme: currentTheme } = useTheme();
|
||||||
|
|
||||||
|
const shikiLang = lang ? resolveLang(lang) : null;
|
||||||
|
const useHljs = shouldUseHljs({ lang, tryHljs });
|
||||||
|
|
||||||
|
const [rootRef, isIntersecting] = useIntersection(true);
|
||||||
|
|
||||||
|
const [tokens] = useAwaiter(async () => {
|
||||||
|
if (!shikiLang || useHljs || !isIntersecting) return null;
|
||||||
|
return await shiki.tokenizeCode(content, lang!);
|
||||||
|
}, {
|
||||||
|
fallbackValue: null,
|
||||||
|
deps: [lang, content, currentThemeId, isIntersecting],
|
||||||
|
});
|
||||||
|
|
||||||
|
const themeBase: ThemeBase = {
|
||||||
|
plainColor: currentTheme?.fg || "var(--text-normal)",
|
||||||
|
accentBgColor:
|
||||||
|
currentTheme?.colors?.["statusBar.background"] || (useHljs ? "#7289da" : "#007BC8"),
|
||||||
|
accentFgColor: currentTheme?.colors?.["statusBar.foreground"] || "#FFF",
|
||||||
|
backgroundColor:
|
||||||
|
currentTheme?.colors?.["editor.background"] || "var(--background-secondary)",
|
||||||
|
};
|
||||||
|
|
||||||
|
let langName;
|
||||||
|
if (lang) langName = useHljs ? hljs?.getLanguage?.(lang)?.name : shikiLang?.name;
|
||||||
|
|
||||||
|
const preClasses = [cl("root")];
|
||||||
|
if (!langName) preClasses.push(cl("plain"));
|
||||||
|
if (isPreview) preClasses.push(cl("preview"));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={rootRef}
|
||||||
|
className={preClasses.join(" ")}
|
||||||
|
style={{
|
||||||
|
backgroundColor: useHljs
|
||||||
|
? themeBase.backgroundColor
|
||||||
|
: `rgba(${hex2Rgb(themeBase.backgroundColor)
|
||||||
|
.concat(bgOpacity / 100)
|
||||||
|
.join(", ")})`,
|
||||||
|
color: themeBase.plainColor,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<code>
|
||||||
|
<Header
|
||||||
|
langName={langName}
|
||||||
|
useDevIcon={useDevIcon}
|
||||||
|
shikiLang={shikiLang}
|
||||||
|
/>
|
||||||
|
<Code
|
||||||
|
theme={themeBase}
|
||||||
|
useHljs={useHljs}
|
||||||
|
lang={lang}
|
||||||
|
content={content}
|
||||||
|
tokens={tokens}
|
||||||
|
/>
|
||||||
|
{!isPreview && <ButtonRow
|
||||||
|
content={content}
|
||||||
|
theme={themeBase}
|
||||||
|
/>}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
34
src/plugins/shikiCodeblocks/hooks/useCopyCooldown.ts
Normal file
34
src/plugins/shikiCodeblocks/hooks/useCopyCooldown.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Clipboard, React } from "@webpack/common";
|
||||||
|
|
||||||
|
export function useCopyCooldown(cooldown: number) {
|
||||||
|
const [copyCooldown, setCopyCooldown] = React.useState(false);
|
||||||
|
|
||||||
|
function copy(text: string) {
|
||||||
|
Clipboard.copy(text);
|
||||||
|
setCopyCooldown(true);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setCopyCooldown(false);
|
||||||
|
}, cooldown);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [copyCooldown, copy] as const;
|
||||||
|
}
|
47
src/plugins/shikiCodeblocks/hooks/useShikiSettings.ts
Normal file
47
src/plugins/shikiCodeblocks/hooks/useShikiSettings.ts
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useSettings } from "@api/settings";
|
||||||
|
import { React } from "@webpack/common";
|
||||||
|
|
||||||
|
import { shiki } from "../api/shiki";
|
||||||
|
import { ShikiSettings } from "../types";
|
||||||
|
|
||||||
|
export function useShikiSettings(settingKeys: (keyof ShikiSettings)[], overrides?: Record<string, any>) {
|
||||||
|
const settings = useSettings(settingKeys.map(key => `plugins.ShikiCodeblocks.${key}`)).plugins.ShikiCodeblocks as ShikiSettings;
|
||||||
|
const [isLoading, setLoading] = React.useState(false);
|
||||||
|
|
||||||
|
const withOverrides = { ...settings, ...overrides };
|
||||||
|
const themeUrl = withOverrides.customTheme || withOverrides.theme;
|
||||||
|
|
||||||
|
if (overrides) {
|
||||||
|
const willChangeTheme = shiki.currentThemeUrl && themeUrl !== shiki.currentThemeUrl;
|
||||||
|
const noOverrides = Object.keys(overrides).length === 0;
|
||||||
|
|
||||||
|
if (isLoading && (!willChangeTheme || noOverrides)) setLoading(false);
|
||||||
|
if ((!isLoading && willChangeTheme)) {
|
||||||
|
setLoading(true);
|
||||||
|
shiki.setTheme(themeUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...withOverrides,
|
||||||
|
isThemeLoading: themeUrl !== shiki.currentThemeUrl,
|
||||||
|
};
|
||||||
|
}
|
49
src/plugins/shikiCodeblocks/hooks/useTheme.ts
Normal file
49
src/plugins/shikiCodeblocks/hooks/useTheme.ts
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { React } from "@webpack/common";
|
||||||
|
|
||||||
|
type Shiki = typeof import("../api/shiki").shiki;
|
||||||
|
interface ThemeState {
|
||||||
|
id: Shiki["currentThemeUrl"],
|
||||||
|
theme: Shiki["currentTheme"],
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTheme: ThemeState = {
|
||||||
|
id: null,
|
||||||
|
theme: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const themeSetters = new Set<React.Dispatch<React.SetStateAction<ThemeState>>>();
|
||||||
|
|
||||||
|
export const useTheme = (): ThemeState => {
|
||||||
|
const [, setTheme] = React.useState<ThemeState>(currentTheme);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
themeSetters.add(setTheme);
|
||||||
|
return () => void themeSetters.delete(setTheme);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return currentTheme;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function dispatchTheme(state: ThemeState) {
|
||||||
|
if (currentTheme.id === state.id) return;
|
||||||
|
Object.assign(currentTheme, state);
|
||||||
|
themeSetters.forEach(setTheme => setTheme(state));
|
||||||
|
}
|
164
src/plugins/shikiCodeblocks/index.ts
Normal file
164
src/plugins/shikiCodeblocks/index.ts
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import { parseUrl } from "@utils/misc";
|
||||||
|
import { wordsFromPascal, wordsToTitle } from "@utils/text";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
|
||||||
|
import previewExampleText from "~fileContent/previewExample.tsx";
|
||||||
|
import cssText from "~fileContent/style.css";
|
||||||
|
|
||||||
|
import { Settings } from "../../Vencord";
|
||||||
|
import { shiki } from "./api/shiki";
|
||||||
|
import { themes } from "./api/themes";
|
||||||
|
import { createHighlighter } from "./components/Highlighter";
|
||||||
|
import { DeviconSetting, HljsSetting, ShikiSettings, StyleSheets } from "./types";
|
||||||
|
import { clearStyles, removeStyle, setStyle } from "./utils/createStyle";
|
||||||
|
|
||||||
|
const themeNames = Object.keys(themes);
|
||||||
|
const devIconCss = "@import url('https://cdn.jsdelivr.net/gh/devicons/devicon@v2.10.1/devicon.min.css');";
|
||||||
|
|
||||||
|
const getSettings = () => Settings.plugins.ShikiCodeblocks as ShikiSettings;
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "ShikiCodeblocks",
|
||||||
|
description: "Brings vscode-style codeblocks into Discord, powered by Shiki",
|
||||||
|
authors: [Devs.Vap],
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: "codeBlock:{react:function",
|
||||||
|
replacement: {
|
||||||
|
match: /codeBlock:\{react:function\((.),(.),(.)\)\{/,
|
||||||
|
replace: "$&return Vencord.Plugins.plugins.ShikiCodeblocks.renderHighlighter($1,$2,$3);",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
start: async () => {
|
||||||
|
setStyle(cssText, StyleSheets.Main);
|
||||||
|
if (getSettings().useDevIcon !== DeviconSetting.Disabled)
|
||||||
|
setStyle(devIconCss, StyleSheets.DevIcons);
|
||||||
|
|
||||||
|
await shiki.init(getSettings().customTheme || getSettings().theme);
|
||||||
|
},
|
||||||
|
stop: () => {
|
||||||
|
shiki.destroy();
|
||||||
|
clearStyles();
|
||||||
|
},
|
||||||
|
settingsAboutComponent: ({ tempSettings }) => createHighlighter({
|
||||||
|
lang: "tsx",
|
||||||
|
content: previewExampleText,
|
||||||
|
isPreview: true,
|
||||||
|
tempSettings,
|
||||||
|
}),
|
||||||
|
options: {
|
||||||
|
theme: {
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
description: "Default themes",
|
||||||
|
options: themeNames.map(themeName => ({
|
||||||
|
label: wordsToTitle(wordsFromPascal(themeName)),
|
||||||
|
value: themes[themeName],
|
||||||
|
default: themes[themeName] === themes.DarkPlus,
|
||||||
|
})),
|
||||||
|
disabled: () => !!getSettings().customTheme,
|
||||||
|
onChange: shiki.setTheme,
|
||||||
|
},
|
||||||
|
customTheme: {
|
||||||
|
type: OptionType.STRING,
|
||||||
|
description: "A link to a custom vscode theme",
|
||||||
|
placeholder: themes.MaterialCandy,
|
||||||
|
isValid: value => {
|
||||||
|
if (!value) return true;
|
||||||
|
const url = parseUrl(value);
|
||||||
|
if (!url) return "Must be a valid URL";
|
||||||
|
|
||||||
|
if (!url.pathname.endsWith(".json")) return "Must be a json file";
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
onChange: value => shiki.setTheme(value || getSettings().theme),
|
||||||
|
},
|
||||||
|
tryHljs: {
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
description: "Use the more lightweight default Discord highlighter and theme.",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "Never",
|
||||||
|
value: HljsSetting.Never,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Prefer Shiki instead of Highlight.js",
|
||||||
|
value: HljsSetting.Secondary,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Prefer Highlight.js instead of Shiki",
|
||||||
|
value: HljsSetting.Primary,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Always",
|
||||||
|
value: HljsSetting.Always,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
useDevIcon: {
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
description: "How to show language icons on codeblocks",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "Disabled",
|
||||||
|
value: DeviconSetting.Disabled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Colorless",
|
||||||
|
value: DeviconSetting.Greyscale,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Colored",
|
||||||
|
value: DeviconSetting.Color,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
onChange: (newValue: DeviconSetting) => {
|
||||||
|
if (newValue === DeviconSetting.Disabled) removeStyle(StyleSheets.DevIcons);
|
||||||
|
else setStyle(devIconCss, StyleSheets.DevIcons);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bgOpacity: {
|
||||||
|
type: OptionType.SLIDER,
|
||||||
|
description: "Background opacity",
|
||||||
|
markers: [0, 20, 40, 60, 80, 100],
|
||||||
|
default: 100,
|
||||||
|
componentProps: {
|
||||||
|
stickToMarkers: false,
|
||||||
|
onValueRender: null, // Defaults to percentage
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// exports
|
||||||
|
shiki,
|
||||||
|
createHighlighter,
|
||||||
|
renderHighlighter: ({ lang, content }: { lang: string; content: string; }) => {
|
||||||
|
return createHighlighter({
|
||||||
|
lang,
|
||||||
|
content,
|
||||||
|
isPreview: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
13
src/plugins/shikiCodeblocks/previewExample.tsx
Normal file
13
src/plugins/shikiCodeblocks/previewExample.tsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
/* eslint-disable header/header */
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const handleClick = async () =>
|
||||||
|
console.log((await import("@webpack/common")).Clipboard.copy("\u200b"));
|
||||||
|
|
||||||
|
export const Example: React.FC<{
|
||||||
|
real: boolean,
|
||||||
|
shigged?: number,
|
||||||
|
}> = ({ real, shigged }) => <>
|
||||||
|
<p>{`Shigg${real ? `ies${shigged === 0x1B ? "t" : ""}` : "y"}`}</p>
|
||||||
|
<button onClick={handleClick}>Click Me</button>
|
||||||
|
</>;
|
103
src/plugins/shikiCodeblocks/style.css
Normal file
103
src/plugins/shikiCodeblocks/style.css
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
.shiki-root {
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
/* fallback background */
|
||||||
|
background-color: var(--background-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-root code {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 0.5em;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.125rem;
|
||||||
|
text-indent: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-root [class^='devicon-'],
|
||||||
|
.shiki-root [class*=' devicon-'] {
|
||||||
|
margin-right: 8px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-plain code {
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-btns {
|
||||||
|
font-size: 1em;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-root:hover .shiki-btns {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-btn {
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-btn~.shiki-btn {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-btn:last-child {
|
||||||
|
border-radius: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-spinner-container {
|
||||||
|
align-items: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
justify-content: center;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-preview {
|
||||||
|
margin-bottom: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-lang {
|
||||||
|
padding: 0 5px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: capitalize;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-table tr {
|
||||||
|
height: 19px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-root td:first-child {
|
||||||
|
border-right: 1px solid transparent;
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-right: 8px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki-root td:last-child {
|
||||||
|
padding-left: 8px;
|
||||||
|
word-break: break-word;
|
||||||
|
width: 100%;
|
||||||
|
}
|
78
src/plugins/shikiCodeblocks/types.ts
Normal file
78
src/plugins/shikiCodeblocks/types.ts
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ILanguageRegistration,
|
||||||
|
IShikiTheme,
|
||||||
|
IThemedToken,
|
||||||
|
IThemeRegistration,
|
||||||
|
} from "@vap/shiki";
|
||||||
|
|
||||||
|
import type { Settings } from "../../Vencord";
|
||||||
|
|
||||||
|
/** This must be atleast a subset of the `@vap/shiki-worker` spec */
|
||||||
|
export type ShikiSpec = {
|
||||||
|
setOnigasm: ({ wasm }: { wasm: string; }) => Promise<void>;
|
||||||
|
setHighlighter: ({ theme, langs }: {
|
||||||
|
theme: IThemeRegistration | void;
|
||||||
|
langs: ILanguageRegistration[];
|
||||||
|
}) => Promise<void>;
|
||||||
|
loadTheme: ({ theme }: {
|
||||||
|
theme: string | IShikiTheme;
|
||||||
|
}) => Promise<void>;
|
||||||
|
getTheme: ({ theme }: { theme: string; }) => Promise<{ themeData: string; }>;
|
||||||
|
loadLanguage: ({ lang }: { lang: ILanguageRegistration; }) => Promise<void>;
|
||||||
|
codeToThemedTokens: ({
|
||||||
|
code,
|
||||||
|
lang,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
|
code: string;
|
||||||
|
lang?: string;
|
||||||
|
theme?: string;
|
||||||
|
}) => Promise<IThemedToken[][]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum StyleSheets {
|
||||||
|
Main = "MAIN",
|
||||||
|
DevIcons = "DEVICONS",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum HljsSetting {
|
||||||
|
Never = "NEVER",
|
||||||
|
Secondary = "SECONDARY",
|
||||||
|
Primary = "PRIMARY",
|
||||||
|
Always = "ALWAYS",
|
||||||
|
}
|
||||||
|
export enum DeviconSetting {
|
||||||
|
Disabled = "DISABLED",
|
||||||
|
Greyscale = "GREYSCALE",
|
||||||
|
Color = "COLOR"
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommonSettings = {
|
||||||
|
[K in keyof Settings["plugins"][string]as K extends `${infer V}` ? K : never]: Settings["plugins"][string][K];
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ShikiSettings extends CommonSettings {
|
||||||
|
theme: string;
|
||||||
|
customTheme: string;
|
||||||
|
tryHljs: HljsSetting;
|
||||||
|
useDevIcon: DeviconSetting;
|
||||||
|
bgOpacity: number;
|
||||||
|
}
|
32
src/plugins/shikiCodeblocks/utils/color.ts
Normal file
32
src/plugins/shikiCodeblocks/utils/color.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function hex2Rgb(hex: string) {
|
||||||
|
hex = hex.slice(1);
|
||||||
|
if (hex.length < 6)
|
||||||
|
hex = hex
|
||||||
|
.split("")
|
||||||
|
.map(c => c + c)
|
||||||
|
.join("");
|
||||||
|
if (hex.length === 6) hex += "ff";
|
||||||
|
if (hex.length > 6) hex = hex.slice(0, 6);
|
||||||
|
return hex
|
||||||
|
.split(/(..)/)
|
||||||
|
.filter(Boolean)
|
||||||
|
.map(c => parseInt(c, 16));
|
||||||
|
}
|
36
src/plugins/shikiCodeblocks/utils/createStyle.ts
Normal file
36
src/plugins/shikiCodeblocks/utils/createStyle.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const styles = new Map<string, HTMLStyleElement>();
|
||||||
|
|
||||||
|
export function setStyle(css: string, id: string) {
|
||||||
|
const style = document.createElement("style");
|
||||||
|
style.innerText = css;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
styles.set(id, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeStyle(id: string) {
|
||||||
|
styles.get(id)?.remove();
|
||||||
|
return styles.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const clearStyles = () => {
|
||||||
|
styles.forEach(style => style.remove());
|
||||||
|
styles.clear();
|
||||||
|
};
|
50
src/plugins/shikiCodeblocks/utils/misc.ts
Normal file
50
src/plugins/shikiCodeblocks/utils/misc.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { hljs } from "@webpack/common";
|
||||||
|
|
||||||
|
import { resolveLang } from "../api/languages";
|
||||||
|
import { HighlighterProps } from "../components/Highlighter";
|
||||||
|
import { HljsSetting, ShikiSettings } from "../types";
|
||||||
|
|
||||||
|
export const cl = (className: string) => `shiki-${className}`;
|
||||||
|
|
||||||
|
export const shouldUseHljs = ({
|
||||||
|
lang,
|
||||||
|
tryHljs,
|
||||||
|
}: {
|
||||||
|
lang: HighlighterProps["lang"],
|
||||||
|
tryHljs: ShikiSettings["tryHljs"],
|
||||||
|
}) => {
|
||||||
|
const hljsLang = lang ? hljs?.getLanguage?.(lang) : null;
|
||||||
|
const shikiLang = lang ? resolveLang(lang) : null;
|
||||||
|
const langName = shikiLang?.name;
|
||||||
|
|
||||||
|
switch (tryHljs) {
|
||||||
|
case HljsSetting.Always:
|
||||||
|
return true;
|
||||||
|
case HljsSetting.Primary:
|
||||||
|
return !!hljsLang || lang === "";
|
||||||
|
case HljsSetting.Secondary:
|
||||||
|
return !langName && !!hljsLang;
|
||||||
|
case HljsSetting.Never:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
74
src/plugins/sortFriendRequests.tsx
Normal file
74
src/plugins/sortFriendRequests.tsx
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Flex } from "@components/Flex";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { RelationshipStore } from "@webpack/common";
|
||||||
|
import { User } from "discord-types/general";
|
||||||
|
import { Settings } from "Vencord";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "SortFriendRequests",
|
||||||
|
authors: [Devs.Megu],
|
||||||
|
description: "Sorts friend requests by date of receipt",
|
||||||
|
|
||||||
|
patches: [{
|
||||||
|
find: ".PENDING_INCOMING||",
|
||||||
|
replacement: [{
|
||||||
|
match: /\.sortBy\(\(function\((\w)\){return \w{1,3}\.comparator}\)\)/,
|
||||||
|
// If the row type is 3 or 4 (pendinng incoming or outgoing), sort by date of receipt
|
||||||
|
// Otherwise, use the default comparator
|
||||||
|
replace: (_, row) => `.sortBy((function(${row}) {
|
||||||
|
return ${row}.type === 3 || ${row}.type === 4
|
||||||
|
? -Vencord.Plugins.plugins.SortFriendRequests.getSince(${row}.user)
|
||||||
|
: ${row}.comparator
|
||||||
|
}))`
|
||||||
|
}, {
|
||||||
|
predicate: () => Settings.plugins.SortFriendRequests.showDates,
|
||||||
|
match: /(user:(\w{1,3}),.{10,30}),subText:(\w{1,3}),(.{10,30}userInfo}\))/,
|
||||||
|
// Show dates in the friend request list
|
||||||
|
replace: (_, pre, user, subText, post) => `${pre},
|
||||||
|
subText: Vencord.Plugins.plugins.SortFriendRequests.makeSubtext(${subText}, ${user}),
|
||||||
|
${post}`
|
||||||
|
}]
|
||||||
|
}],
|
||||||
|
|
||||||
|
getSince(user: User) {
|
||||||
|
return new Date(RelationshipStore.getSince(user.id));
|
||||||
|
},
|
||||||
|
|
||||||
|
makeSubtext(text: string, user: User) {
|
||||||
|
const since = this.getSince(user);
|
||||||
|
return (
|
||||||
|
<Flex flexDirection="row" style={{ gap: 0, flexWrap: "wrap", lineHeight: "0.9rem" }}>
|
||||||
|
<span>{text}</span>
|
||||||
|
{!isNaN(since.getTime()) && <span>Received — {since.toDateString()}</span>}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
options: {
|
||||||
|
showDates: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Show dates on friend requests",
|
||||||
|
default: false,
|
||||||
|
restartNeeded: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
147
src/plugins/viewRaw.tsx
Normal file
147
src/plugins/viewRaw.tsx
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { addButton, removeButton } from "@api/MessagePopover";
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { Flex } from "@components/Flex";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import { copyWithToast } from "@utils/misc";
|
||||||
|
import { closeModal, ModalCloseButton, ModalContent, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
import { Button, ChannelStore, Forms, Margins, Parser } from "@webpack/common";
|
||||||
|
import { Message } from "discord-types/general";
|
||||||
|
|
||||||
|
|
||||||
|
const CopyIcon = () => {
|
||||||
|
return <svg viewBox="0 0 512.002 512.002" fill="currentColor" aria-hidden="true" width="22" height="22">
|
||||||
|
<path d="M462.002,92.002h-42.001V50c0-27.57-22.43-50-50-50h-320c-27.57,0-50,22.43-50,50v320.002c0,27.57,22.43,50,50,50h42.001 v42c0,27.57,22.43,50,50,50h320c27.57,0,50-22.43,50-50v-320C512.001,114.432,489.573,92.002,462.002,92.002z M50.001,400.002 c-16.542,0-30-13.458-30-30V50c0-16.542,13.458-30,30-30h320c16.542,0,30,13.458,30,30v320.002c0,16.542-13.458,30-30,30H50.001z M492.002,462.002c0,16.542-13.458,30-30,30h-320c-16.542,0-30-13.458-30-30v-42h257.999c27.57,0,50-22.43,50-50v-258h42.001 c16.542,0,30,13.458,30,30V462.002z" />
|
||||||
|
<path d="M462.024,457.002H170.98c-5.522,0-10,4.478-10,10c0,5.523,4.478,10,10,10h291.043c5.522,0,10-4.477,10-10 S467.546,457.002,462.024,457.002z" />
|
||||||
|
<path d="M142.25,457.002h-0.27c-5.522,0-10,4.478-10,10c0,5.523,4.478,10,10,10h0.27c5.523,0,10-4.477,10-10 S147.773,457.002,142.25,457.002z" />
|
||||||
|
<path d="M110.035,35h-0.27c-5.522,0-10,4.478-10,10s4.478,10,10,10h0.27c5.522,0,10-4.478,10-10S115.558,35,110.035,35z" />
|
||||||
|
<path d="M81.036,35H50.001c-5.522,0-10,4.478-10,10s4.478,10,10,10h31.034c5.523,0,10.001-4.478,10.001-10S86.558,35,81.036,35z" />
|
||||||
|
<path d="M122.084,246.829l-0.008-0.008l-14.8-30.002c5.407-2.305,10.07-5.975,13.466-10.537c3.725-5.006,5.931-11.08,5.931-17.604 c0-8.588-3.248-15.902-9.743-21.965c-3.196-2.992-6.739-5.242-10.627-6.744c-3.891-1.506-8.097-2.258-12.612-2.258H64.936 c-2.793,0-5.327,1.145-7.163,2.996c-1.832,1.85-2.968,4.396-2.968,7.205v82.777c0,2.896,1.137,5.338,2.893,7.105 c0.962,0.969,2.107,1.728,3.349,2.246c1.249,0.52,2.597,0.797,3.953,0.797c2.49,0,5.028-0.92,7.096-2.967 c1.004-0.99,1.761-2.09,2.271-3.297c0.516-1.221,0.771-2.516,0.771-3.885v-31.139h10.844l17.862,36.184l-0.004,0.005 c1.315,2.699,3.416,4.4,5.763,5.201c1.275,0.434,2.616,0.596,3.938,0.502c1.317-0.094,2.618-0.44,3.812-1.022 c2.307-1.123,4.237-3.123,5.179-5.881h0.002c0.45-1.307,0.639-2.609,0.564-3.904C123.023,249.327,122.684,248.058,122.084,246.829 z M106.124,190.85c-0.438,1.795-1.473,3.546-3.189,5.07c-1.205,1.039-2.538,1.838-3.996,2.395c-1.45,0.553-3.02,0.865-4.704,0.934 v-0.006H75.138v-21.221H93.69c3.19,0,5.9,0.893,7.984,2.328c1.83,1.26,3.187,2.939,3.96,4.791 C106.393,186.957,106.591,188.942,106.124,190.85z" />
|
||||||
|
<path d="M364.199,163.706c-1.12-2.354-3.136-4.285-5.777-5.254l-0.055-0.016c-2.721-0.916-5.49-0.688-7.823,0.439 c-2.349,1.135-4.243,3.172-5.193,5.861v0.008l-19.568,55.744l-19.571-55.816h-0.001c-0.705-2.352-2.14-4.141-3.935-5.322 c-1.589-1.047-3.458-1.609-5.345-1.656c-1.887-0.047-3.801,0.42-5.479,1.43c-1.938,1.164-3.553,3.031-4.445,5.637h0.002 l-19.564,55.73l-19.569-55.746l-0.008-0.016c-0.997-2.779-2.938-4.752-5.235-5.846c-1.222-0.584-2.552-0.918-3.899-0.99 c-1.34-0.074-2.701,0.115-3.991,0.578c-2.347,0.842-4.429,2.57-5.701,5.24c-1.206,2.529-1.354,5.119-0.441,7.766l0.005,0.009 l29.181,83.133l0.016,0.047c1.186,3.139,3.318,5.213,5.775,6.234c1.141,0.473,2.355,0.717,3.581,0.73 c1.219,0.016,2.438-0.197,3.595-0.635c2.602-0.982,4.914-3.084,6.238-6.268l0.039-0.109l19.595-55.779l20.043,56.877l0.047,0.125 c0.967,2.25,2.404,3.912,4.071,4.99c1.666,1.078,3.544,1.563,5.408,1.465c1.854-0.096,3.686-0.764,5.274-1.992 c1.666-1.289,3.059-3.191,3.899-5.686l29.251-83.133C365.546,168.825,365.317,166.053,364.199,163.706z" />
|
||||||
|
<path d="M226.7,247.37l-0.008-0.031l-8.818-21.24l-0.003,0.002l-25.528-61.617h0.001c-1.024-2.709-3.167-4.654-5.674-5.645 c-1.165-0.461-2.429-0.715-3.708-0.742c-1.28-0.027-2.565,0.178-3.773,0.635c-2.393,0.906-4.479,2.764-5.647,5.719l0.005,0.002 l-25.54,61.719h-0.003l-8.818,21.168v0.008c-1.076,2.607-1,5.383-0.008,7.783c0.994,2.408,2.91,4.433,5.51,5.527l0.038,0.023 c2.619,1.035,5.435,1.064,7.886,0.041c2.295-0.955,4.242-2.813,5.384-5.6l6.147-14.877h37.566l6.148,14.877 c1.175,2.844,3.171,4.674,5.459,5.609c1.261,0.518,2.601,0.752,3.932,0.73c1.324-0.021,2.642-0.295,3.865-0.801 c2.418-0.998,4.496-2.9,5.579-5.547l0.008-0.016C227.723,252.523,227.723,249.945,226.7,247.37z M172.552,219.938l10.344-25.01 l10.416,25.01H172.552z" />
|
||||||
|
</svg>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function sortObject<T extends object>(obj: T): T {
|
||||||
|
return Object.fromEntries(Object.entries(obj).sort(([k1], [k2]) => k1.localeCompare(k2))) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanMessage(msg: Message) {
|
||||||
|
const clone = sortObject(JSON.parse(JSON.stringify(msg)));
|
||||||
|
for (const key in clone.author) {
|
||||||
|
switch (key) {
|
||||||
|
case "id":
|
||||||
|
case "username":
|
||||||
|
case "usernameNormalized":
|
||||||
|
case "discriminator":
|
||||||
|
case "avatar":
|
||||||
|
case "bot":
|
||||||
|
case "system":
|
||||||
|
case "publicFlags":
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// phone number, email, etc
|
||||||
|
delete clone.author[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// message logger added properties
|
||||||
|
const cloneAny = clone as any;
|
||||||
|
delete cloneAny.editHistory;
|
||||||
|
delete cloneAny.deleted;
|
||||||
|
cloneAny.attachments?.forEach(a => delete a.deleted);
|
||||||
|
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CodeBlock(props: { content: string, lang: string; }) {
|
||||||
|
return (
|
||||||
|
// make text selectable
|
||||||
|
<div style={{ userSelect: "text" }}>
|
||||||
|
{Parser.defaultRules.codeBlock.react(props, null, {})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openViewRawModal(msg: Message) {
|
||||||
|
msg = cleanMessage(msg);
|
||||||
|
const msgJson = JSON.stringify(msg, null, 4);
|
||||||
|
|
||||||
|
const key = openModal(props => (
|
||||||
|
<ErrorBoundary>
|
||||||
|
<ModalRoot {...props} size={ModalSize.LARGE}>
|
||||||
|
<ModalHeader>
|
||||||
|
<Forms.FormTitle tag="h1">View Raw</Forms.FormTitle>
|
||||||
|
<ModalCloseButton onClick={() => closeModal(key)} />
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalContent style={{ padding: "1em" }}>
|
||||||
|
<Flex style={{ marginBottom: "1em", marginTop: "1em" }}>
|
||||||
|
<Button onClick={() => copyWithToast(msg.content, "Content copied to clipboard!")}>
|
||||||
|
Copy Raw Content
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => copyWithToast(msgJson, "Message data copied to clipboard!")}>
|
||||||
|
Copy Message JSON
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{!!msg.content && (
|
||||||
|
<>
|
||||||
|
<Forms.FormTitle tag="h5">Content</Forms.FormTitle>
|
||||||
|
<CodeBlock content={msg.content} lang="" />
|
||||||
|
<Forms.FormDivider classes={Margins.marginBottom20} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Forms.FormTitle tag="h5">Message Data</Forms.FormTitle>
|
||||||
|
<CodeBlock content={msgJson} lang="json" />
|
||||||
|
</ModalContent >
|
||||||
|
</ModalRoot >
|
||||||
|
</ErrorBoundary >
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "ViewRaw",
|
||||||
|
description: "Copy and view the raw content/data of any message.",
|
||||||
|
authors: [Devs.KingFish, Devs.Ven],
|
||||||
|
dependencies: ["MessagePopoverAPI"],
|
||||||
|
|
||||||
|
start() {
|
||||||
|
addButton("ViewRaw", msg => {
|
||||||
|
return {
|
||||||
|
label: "View Raw (Left Click) / Copy Raw (Right Click)",
|
||||||
|
icon: CopyIcon,
|
||||||
|
message: msg,
|
||||||
|
channel: ChannelStore.getChannel(msg.channel_id),
|
||||||
|
onClick: () => openViewRawModal(msg),
|
||||||
|
onContextMenu: e => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
copyWithToast(msg.content);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
removeButton("CopyRawMessage");
|
||||||
|
}
|
||||||
|
});
|
|
@ -156,5 +156,17 @@ export const Devs = Object.freeze({
|
||||||
Luna: {
|
Luna: {
|
||||||
name: "Luny",
|
name: "Luny",
|
||||||
id: 821472922140803112n
|
id: 821472922140803112n
|
||||||
}
|
},
|
||||||
|
Vap: {
|
||||||
|
name: "Vap0r1ze",
|
||||||
|
id: 454072114492866560n
|
||||||
|
},
|
||||||
|
KingFish: {
|
||||||
|
name: "King Fish",
|
||||||
|
id: 499400512559382538n
|
||||||
|
},
|
||||||
|
Commandtechno: {
|
||||||
|
name: "Commandtechno",
|
||||||
|
id: 296776625432035328n,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -74,3 +74,7 @@ export interface ApngFrameData {
|
||||||
frames: ApngFrame[];
|
frames: ApngFrame[];
|
||||||
playTime: number;
|
playTime: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shikiWorkerDist = "https://unpkg.com/@vap/shiki-worker@0.0.8/dist";
|
||||||
|
export const shikiWorkerSrc = `${shikiWorkerDist}/${IS_DEV ? "index.js" : "index.min.js"}`;
|
||||||
|
export const shikiOnigasmSrc = "https://unpkg.com/@vap/shiki@0.10.3/dist/onig.wasm";
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { React } from "@webpack/common";
|
import { Clipboard, React, Toasts } from "@webpack/common";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a lazy function. On first call, the value is computed.
|
* Makes a lazy function. On first call, the value is computed.
|
||||||
|
@ -28,7 +28,12 @@ export function makeLazy<T>(factory: () => T): () => T {
|
||||||
return () => cache ?? (cache = factory());
|
return () => cache ?? (cache = factory());
|
||||||
}
|
}
|
||||||
|
|
||||||
type AwaiterRes<T> = [T, any, boolean, () => void];
|
type AwaiterRes<T> = [T, any, boolean];
|
||||||
|
interface AwaiterOpts<T> {
|
||||||
|
fallbackValue: T,
|
||||||
|
deps?: unknown[],
|
||||||
|
onError?(e: any): void,
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Await a promise
|
* Await a promise
|
||||||
* @param factory Factory
|
* @param factory Factory
|
||||||
|
@ -36,26 +41,31 @@ type AwaiterRes<T> = [T, any, boolean, () => void];
|
||||||
* @returns [value, error, isPending]
|
* @returns [value, error, isPending]
|
||||||
*/
|
*/
|
||||||
export function useAwaiter<T>(factory: () => Promise<T>): AwaiterRes<T | null>;
|
export function useAwaiter<T>(factory: () => Promise<T>): AwaiterRes<T | null>;
|
||||||
export function useAwaiter<T>(factory: () => Promise<T>, fallbackValue: T): AwaiterRes<T>;
|
export function useAwaiter<T>(factory: () => Promise<T>, providedOpts: AwaiterOpts<T>): AwaiterRes<T>;
|
||||||
export function useAwaiter<T>(factory: () => Promise<T>, fallbackValue: null, onError: (e: unknown) => unknown): AwaiterRes<T>;
|
export function useAwaiter<T>(factory: () => Promise<T>, providedOpts?: AwaiterOpts<T | null>): AwaiterRes<T | null> {
|
||||||
export function useAwaiter<T>(factory: () => Promise<T>, fallbackValue: T | null = null, onError?: (e: unknown) => unknown): AwaiterRes<T | null> {
|
const opts: Required<AwaiterOpts<T | null>> = Object.assign({
|
||||||
|
fallbackValue: null,
|
||||||
|
deps: [],
|
||||||
|
onError: null,
|
||||||
|
}, providedOpts);
|
||||||
const [state, setState] = React.useState({
|
const [state, setState] = React.useState({
|
||||||
value: fallbackValue,
|
value: opts.fallbackValue,
|
||||||
error: null,
|
error: null,
|
||||||
pending: true
|
pending: true
|
||||||
});
|
});
|
||||||
const [signal, setSignal] = React.useState(0);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
let isAlive = true;
|
let isAlive = true;
|
||||||
|
if (!state.pending) setState({ ...state, pending: true });
|
||||||
|
|
||||||
factory()
|
factory()
|
||||||
.then(value => isAlive && setState({ value, error: null, pending: false }))
|
.then(value => isAlive && setState({ value, error: null, pending: false }))
|
||||||
.catch(error => isAlive && (setState({ value: null, error, pending: false }), onError?.(error)));
|
.catch(error => isAlive && (setState({ value: null, error, pending: false }), opts.onError?.(error)));
|
||||||
|
|
||||||
return () => void (isAlive = false);
|
return () => void (isAlive = false);
|
||||||
}, [signal]);
|
}, opts.deps);
|
||||||
|
|
||||||
return [state.value, state.error, state.pending, () => setSignal(signal + 1)];
|
return [state.value, state.error, state.pending];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -175,5 +185,46 @@ export function suppressErrors<F extends Function>(name: string, func: F, thisOb
|
||||||
*/
|
*/
|
||||||
export function makeCodeblock(text: string, language?: string) {
|
export function makeCodeblock(text: string, language?: string) {
|
||||||
const chars = "```";
|
const chars = "```";
|
||||||
return `${chars}${language || ""}\n${text}\n${chars}`;
|
return `${chars}${language || ""}\n${text.replaceAll("```", "\\`\\`\\`")}\n${chars}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function copyWithToast(text: string, toastMessage = "Copied to clipboard!") {
|
||||||
|
if (Clipboard.SUPPORTS_COPY) {
|
||||||
|
Clipboard.copy(text);
|
||||||
|
} else {
|
||||||
|
toastMessage = "Your browser does not support copying to clipboard";
|
||||||
|
}
|
||||||
|
Toasts.show({
|
||||||
|
message: toastMessage,
|
||||||
|
id: Toasts.genId(),
|
||||||
|
type: Toasts.Type.SUCCESS
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if obj is a true object: of type "object" and not null or array
|
||||||
|
*/
|
||||||
|
export function isObject(obj: unknown): obj is object {
|
||||||
|
return typeof obj === "object" && obj !== null && !Array.isArray(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns null if value is not a URL, otherwise return URL object.
|
||||||
|
* Avoids having to wrap url checks in a try/catch
|
||||||
|
*/
|
||||||
|
export function parseUrl(urlString: string): URL | null {
|
||||||
|
try {
|
||||||
|
return new URL(urlString);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether an element is on screen
|
||||||
|
*/
|
||||||
|
export const checkIntersecting = (el: Element) => {
|
||||||
|
const elementBox = el.getBoundingClientRect();
|
||||||
|
const documentHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
|
||||||
|
return !(elementBox.bottom < 0 || elementBox.top - documentHeight >= 0);
|
||||||
|
};
|
||||||
|
|
62
src/utils/react.ts
Normal file
62
src/utils/react.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { React } from "@webpack/common";
|
||||||
|
|
||||||
|
import { checkIntersecting } from "./misc";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an element is on screen
|
||||||
|
* @param intersectOnly If `true`, will only update the state when the element comes into view
|
||||||
|
* @returns [refCallback, isIntersecting]
|
||||||
|
*/
|
||||||
|
export const useIntersection = (intersectOnly = false): [
|
||||||
|
refCallback: React.RefCallback<Element>,
|
||||||
|
isIntersecting: boolean,
|
||||||
|
] => {
|
||||||
|
const observerRef = React.useRef<IntersectionObserver | null>(null);
|
||||||
|
const [isIntersecting, setIntersecting] = React.useState(false);
|
||||||
|
|
||||||
|
const refCallback = (element: Element | null) => {
|
||||||
|
observerRef.current?.disconnect();
|
||||||
|
observerRef.current = null;
|
||||||
|
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
if (checkIntersecting(element)) {
|
||||||
|
setIntersecting(true);
|
||||||
|
if (intersectOnly) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
observerRef.current = new IntersectionObserver(entries => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.target !== element) continue;
|
||||||
|
if (entry.isIntersecting && intersectOnly) {
|
||||||
|
setIntersecting(true);
|
||||||
|
observerRef.current?.disconnect();
|
||||||
|
observerRef.current = null;
|
||||||
|
} else {
|
||||||
|
setIntersecting(entry.isIntersecting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
observerRef.current.observe(element);
|
||||||
|
};
|
||||||
|
|
||||||
|
return [refCallback, isIntersecting];
|
||||||
|
};
|
36
src/utils/text.ts
Normal file
36
src/utils/text.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Utils for readable text transformations eg: `toTitle(fromKebab())`
|
||||||
|
|
||||||
|
// Case style to words
|
||||||
|
export const wordsFromCamel = (text: string) => text.split(/(?=[A-Z])/).map(w => w.toLowerCase());
|
||||||
|
export const wordsFromSnake = (text: string) => text.toLowerCase().split("_");
|
||||||
|
export const wordsFromKebab = (text: string) => text.toLowerCase().split("-");
|
||||||
|
export const wordsFromPascal = (text: string) => text.split(/(?=[A-Z])/).map(w => w.toLowerCase());
|
||||||
|
export const wordsFromTitle = (text: string) => text.toLowerCase().split(" ");
|
||||||
|
|
||||||
|
// Words to case style
|
||||||
|
export const wordsToCamel = (words: string[]) =>
|
||||||
|
words.map((w, i) => (i ? w[0].toUpperCase() + w.slice(1) : w)).join("");
|
||||||
|
export const wordsToSnake = (words: string[]) => words.join("_").toUpperCase();
|
||||||
|
export const wordsToKebab = (words: string[]) => words.join("-").toLowerCase();
|
||||||
|
export const wordsToPascal = (words: string[]) =>
|
||||||
|
words.map(w => w[0].toUpperCase() + w.slice(1)).join("");
|
||||||
|
export const wordsToTitle = (words: string[]) =>
|
||||||
|
words.map(w => w[0].toUpperCase() + w.slice(1)).join(" ");
|
|
@ -27,6 +27,7 @@ export default function definePlugin<P extends PluginDef>(p: P & Record<string,
|
||||||
export interface PatchReplacement {
|
export interface PatchReplacement {
|
||||||
match: string | RegExp;
|
match: string | RegExp;
|
||||||
replace: string | ((match: string, ...groups: string[]) => string);
|
replace: string | ((match: string, ...groups: string[]) => string);
|
||||||
|
predicate?(): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Patch {
|
export interface Patch {
|
||||||
|
@ -89,7 +90,9 @@ export interface PluginDef {
|
||||||
* Allows you to specify a custom Component that will be rendered in your
|
* Allows you to specify a custom Component that will be rendered in your
|
||||||
* plugin's settings page
|
* plugin's settings page
|
||||||
*/
|
*/
|
||||||
settingsAboutComponent?: React.ComponentType;
|
settingsAboutComponent?: React.ComponentType<{
|
||||||
|
tempSettings?: Record<string, any>;
|
||||||
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum OptionType {
|
export enum OptionType {
|
||||||
|
|
|
@ -37,6 +37,8 @@ export const ReactDOM: typeof import("react-dom") = findByPropsLazy("createPorta
|
||||||
export const RestAPI = findByPropsLazy("getAPIBaseURL", "get");
|
export const RestAPI = findByPropsLazy("getAPIBaseURL", "get");
|
||||||
export const moment: typeof import("moment") = findByPropsLazy("parseTwoDigitYear");
|
export const moment: typeof import("moment") = findByPropsLazy("parseTwoDigitYear");
|
||||||
|
|
||||||
|
export const hljs: typeof import("highlight.js") = findByPropsLazy("highlight");
|
||||||
|
|
||||||
export const MessageStore = findByPropsLazy("getRawMessages") as Omit<Stores.MessageStore, "getMessages"> & {
|
export const MessageStore = findByPropsLazy("getRawMessages") as Omit<Stores.MessageStore, "getMessages"> & {
|
||||||
getMessages(chanId: string): any;
|
getMessages(chanId: string): any;
|
||||||
};
|
};
|
||||||
|
@ -50,7 +52,10 @@ export let UserStore: Stores.UserStore;
|
||||||
export let SelectedChannelStore: Stores.SelectedChannelStore;
|
export let SelectedChannelStore: Stores.SelectedChannelStore;
|
||||||
export let SelectedGuildStore: any;
|
export let SelectedGuildStore: any;
|
||||||
export let ChannelStore: Stores.ChannelStore;
|
export let ChannelStore: Stores.ChannelStore;
|
||||||
export let RelationshipStore: Stores.RelationshipStore;
|
export let RelationshipStore: Stores.RelationshipStore & {
|
||||||
|
/** Get the date (as a string) that the relationship was created */
|
||||||
|
getSince(userId: string): string;
|
||||||
|
};
|
||||||
|
|
||||||
export const Forms = {} as {
|
export const Forms = {} as {
|
||||||
FormTitle: Components.FormTitle;
|
FormTitle: Components.FormTitle;
|
||||||
|
|
|
@ -137,6 +137,7 @@ function patchPush() {
|
||||||
|
|
||||||
// @ts-ignore we change all patch.replacement to array in plugins/index
|
// @ts-ignore we change all patch.replacement to array in plugins/index
|
||||||
for (const replacement of patch.replacement) {
|
for (const replacement of patch.replacement) {
|
||||||
|
if (replacement.predicate && !replacement.predicate()) continue;
|
||||||
const lastMod = mod;
|
const lastMod = mod;
|
||||||
const lastCode = code;
|
const lastCode = code;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue