Merge branch 'dev' into translation
This commit is contained in:
commit
2d6baf00db
436 changed files with 4878 additions and 1667 deletions
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
|
|
@ -23,10 +23,10 @@ jobs:
|
|||
|
||||
- uses: pnpm/action-setup@v3
|
||||
|
||||
- name: Use Node.js 20
|
||||
- name: Use Node.js 24
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
|
|
@ -72,6 +72,7 @@ jobs:
|
|||
gh release upload latest --clobber dist/release/*
|
||||
|
||||
- name: Upload Plugin JSONs to Equibored repo
|
||||
if: github.repository == 'Equicord/Equicord'
|
||||
run: |
|
||||
git config --global user.name "GitHub-Actions"
|
||||
git config --global user.email actions@github.com
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
name: Build
|
||||
name: Build Branches
|
||||
|
||||
on:
|
||||
push:
|
||||
|
|
@ -28,6 +28,7 @@ jobs:
|
|||
Build:
|
||||
name: Build Equicord
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'Equicord/Equicord'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
@ -37,11 +38,11 @@ jobs:
|
|||
- uses: pnpm/action-setup@v3
|
||||
if: github.ref_name != 'bun'
|
||||
|
||||
- name: Use Node.js 20
|
||||
- name: Use Node.js 24
|
||||
if: github.ref_name != 'bun'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
|
|
@ -64,9 +65,8 @@ jobs:
|
|||
pnpm generateVencordPluginJson dist/vencordplugins.json
|
||||
pnpm generateDevsList dist/devs.json
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
if: github.ref_name == 'bun'
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
if: github.ref_name == 'bun'
|
||||
with:
|
||||
bun-version: latest
|
||||
- name: Install dependencies
|
||||
6
.github/workflows/nixosBuild.yml
vendored
6
.github/workflows/nixosBuild.yml
vendored
|
|
@ -20,10 +20,10 @@ jobs:
|
|||
|
||||
- uses: pnpm/action-setup@v3
|
||||
|
||||
- name: Use Node.js 20
|
||||
- name: Use Node.js 24
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
|
|
@ -50,7 +50,7 @@ jobs:
|
|||
cp browser/browser.* release
|
||||
cp Equicord.user.{js,js.LEGAL.txt} release
|
||||
|
||||
# copy the plugin data jsons, the extension zips and the desktop/vesktop asars
|
||||
# copy the plugin data jsons, the extension zips and the desktop/equibop asars
|
||||
cp *.{json,zip,asar} release
|
||||
|
||||
# legacy un-asared files
|
||||
|
|
|
|||
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
|
|
@ -62,11 +62,11 @@ jobs:
|
|||
if: steps.check.outputs.release == 'true'
|
||||
uses: pnpm/action-setup@v3
|
||||
|
||||
- name: Use Node.js 20
|
||||
- name: Use Node.js 24
|
||||
if: steps.check.outputs.release == 'true'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
|
|
|
|||
4
.github/workflows/reportBrokenPlugins.yml
vendored
4
.github/workflows/reportBrokenPlugins.yml
vendored
|
|
@ -33,10 +33,10 @@ jobs:
|
|||
|
||||
- uses: pnpm/action-setup@v3 # Install pnpm using packageManager key in package.json
|
||||
|
||||
- name: Use Node.js 20
|
||||
- name: Use Node.js 24
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
|
|
|
|||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
|
|
@ -18,10 +18,10 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v3 # Install pnpm using packageManager key in package.json
|
||||
|
||||
- name: Use Node.js 20
|
||||
- name: Use Node.js 24
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
|
|
|
|||
54
package.json
54
package.json
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "equicord",
|
||||
"private": "true",
|
||||
"version": "1.14.6.2",
|
||||
"version": "1.14.7.1",
|
||||
"description": "The other cutest Discord client mod",
|
||||
"homepage": "https://github.com/Equicord/Equicord#readme",
|
||||
"bugs": {
|
||||
|
|
@ -55,47 +55,47 @@
|
|||
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
|
||||
"idb": "8.0.3",
|
||||
"jsqr": "1.4.0",
|
||||
"monaco-editor": "^0.54.0",
|
||||
"monaco-editor": "^0.55.1",
|
||||
"nanoid": "^5.1.6",
|
||||
"native-file-system-adapter": "^3.0.1",
|
||||
"usercss-meta": "^0.12.0",
|
||||
"virtual-merge": "^1.0.1"
|
||||
"virtual-merge": "^1.0.1",
|
||||
"yaml-js": "^0.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron/asar": "^4.0.1",
|
||||
"@stylistic/eslint-plugin": "^5.6.0",
|
||||
"@types/adm-zip": "^0.5.7",
|
||||
"@types/chrome": "^0.1.30",
|
||||
"@types/diff": "^7.0.2",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/node": "^24.10.1",
|
||||
"@electron/asar": "^4.2.0",
|
||||
"@stylistic/eslint-plugin": "^5.10.0",
|
||||
"@types/chrome": "^0.1.40",
|
||||
"@types/lodash": "^4.17.24",
|
||||
"@types/node": "^25.6.0",
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@types/yazl": "^3.3.0",
|
||||
"@types/yazl": "^3.3.1",
|
||||
"@vencord/discord-types": "link:packages/discord-types",
|
||||
"diff": "^8.0.2",
|
||||
"esbuild": "^0.27.0",
|
||||
"eslint": "9.39.1",
|
||||
"diff": "^9.0.0",
|
||||
"esbuild": "^0.28.0",
|
||||
"eslint": "10.2.0",
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-simple-header": "^1.2.1",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"eslint-plugin-unused-imports": "^4.3.0",
|
||||
"eslint-plugin-simple-import-sort": "^13.0.0",
|
||||
"eslint-plugin-unused-imports": "^4.4.1",
|
||||
"highlight.js": "11.11.1",
|
||||
"html-minifier-terser": "^7.2.0",
|
||||
"moment": "^2.22.2",
|
||||
"p-limit": "^7.3.0",
|
||||
"puppeteer-core": "^24.30.0",
|
||||
"standalone-electron-types": "^34.2.0",
|
||||
"stylelint": "^16.25.0",
|
||||
"stylelint-config-standard": "^39.0.1",
|
||||
"puppeteer-core": "^24.41.0",
|
||||
"standalone-electron-types": "^41.2.1",
|
||||
"stylelint": "^17.8.0",
|
||||
"stylelint-config-standard": "^40.0.0",
|
||||
"svgo": "^4.0.1",
|
||||
"ts-patch": "^3.3.0",
|
||||
"ts-pattern": "^5.6.0",
|
||||
"tsx": "^4.20.6",
|
||||
"type-fest": "^5.2.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.47.0",
|
||||
"typescript-transform-paths": "^3.5.5",
|
||||
"ts-pattern": "^5.9.0",
|
||||
"tsx": "^4.21.0",
|
||||
"type-fest": "^5.5.0",
|
||||
"typescript": "^6.0.2",
|
||||
"typescript-eslint": "^8.58.2",
|
||||
"typescript-transform-paths": "^3.5.6",
|
||||
"zip-local": "^0.3.5"
|
||||
},
|
||||
"packageManager": "pnpm@10.33.0",
|
||||
|
|
@ -126,4 +126,4 @@
|
|||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
}
|
||||
1857
pnpm-lock.yaml
generated
1857
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -77,7 +77,7 @@ const globNativesPlugin = {
|
|||
});
|
||||
|
||||
build.onLoad({ filter, namespace: "import-natives" }, async () => {
|
||||
const pluginDirs = ["plugins", "userplugins", "equicordplugins"];
|
||||
const pluginDirs = ["plugins", "equicordplugins", "userplugins"];
|
||||
let code = "";
|
||||
let natives = "\n";
|
||||
let i = 0;
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ export const globPlugins = kind => ({
|
|||
});
|
||||
|
||||
build.onLoad({ filter, namespace: "import-plugins" }, async () => {
|
||||
const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins", "equicordplugins", "equicordplugins/_api"];
|
||||
const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "equicordplugins", "equicordplugins/_api", "userplugins"];
|
||||
let code = "";
|
||||
let pluginsCode = "\n";
|
||||
let metaCode = "\n";
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ export interface PluginData {
|
|||
name: string;
|
||||
description: string;
|
||||
tags: string[];
|
||||
searchTerms: string[];
|
||||
authors: Dev[];
|
||||
dependencies: string[];
|
||||
hasPatches: boolean;
|
||||
|
|
@ -185,7 +186,8 @@ export async function parseFile(fileName: string) {
|
|||
enabledByDefault: false,
|
||||
required: false,
|
||||
isModified: false,
|
||||
tags: [] as string[]
|
||||
tags: [] as string[],
|
||||
searchTerms: [] as string[],
|
||||
} as PluginData;
|
||||
|
||||
for (const prop of pluginObj.properties) {
|
||||
|
|
@ -244,9 +246,10 @@ export async function parseFile(fileName: string) {
|
|||
});
|
||||
break;
|
||||
case "tags":
|
||||
if (!isArrayLiteralExpression(value)) throw fail("tags is not an array literal");
|
||||
data.tags = value.elements.map(e => {
|
||||
if (!isStringLiteral(e)) throw fail("tags array contains non-string literals");
|
||||
case "searchTerms":
|
||||
if (!isArrayLiteralExpression(value)) throw fail(`${key} is not an array literal`);
|
||||
data[key] = value.elements.map(e => {
|
||||
if (!isStringLiteral(e)) throw fail(`${key} array contains non-string literals`);
|
||||
return e.text;
|
||||
});
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { findByCodeLazy, findLazy } from "webpack";
|
||||
import { findByCodeLazy, findLazy } from "@webpack";
|
||||
|
||||
let defaultSounds: null | string[] = null;
|
||||
const findDefaultSounds = findLazy(module => module.resolve && module.id && module.keys().some(key => key.endsWith(".mp3")), false);
|
||||
|
|
|
|||
|
|
@ -18,97 +18,31 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Logger } from "@utils/Logger";
|
||||
|
||||
const logger = new Logger("DataStore");
|
||||
let loggedFailure = false;
|
||||
const memoryDatabases = new Map<string, Map<string, Map<IDBValidKey, unknown>>>();
|
||||
|
||||
interface MemoryStore {
|
||||
__memoryStore: true;
|
||||
get<T>(key: IDBValidKey): T | undefined;
|
||||
put(value: unknown, key: IDBValidKey): void;
|
||||
delete(key: IDBValidKey): void;
|
||||
clear(): void;
|
||||
getAllKeys<KeyType extends IDBValidKey>(): KeyType[];
|
||||
getAll<T>(): T[];
|
||||
entries<KeyType extends IDBValidKey, ValueType>(): [KeyType, ValueType][];
|
||||
}
|
||||
|
||||
type StoreLike = IDBObjectStore | MemoryStore;
|
||||
|
||||
function handleFailure(error: unknown) {
|
||||
if (loggedFailure) return;
|
||||
loggedFailure = true;
|
||||
logger.error("IndexedDB unavailable, using empty storage.", error);
|
||||
}
|
||||
|
||||
function withFailureFallback<T>(promise: Promise<T>, fallback: T): Promise<T> {
|
||||
return promise.catch(error => {
|
||||
handleFailure(error);
|
||||
return fallback;
|
||||
});
|
||||
}
|
||||
|
||||
function getMemoryStore(dbName: string, storeName: string): MemoryStore {
|
||||
let database = memoryDatabases.get(dbName);
|
||||
if (!database) {
|
||||
database = new Map();
|
||||
memoryDatabases.set(dbName, database);
|
||||
}
|
||||
|
||||
let data = database.get(storeName);
|
||||
if (!data) {
|
||||
data = new Map<IDBValidKey, unknown>();
|
||||
database.set(storeName, data);
|
||||
}
|
||||
|
||||
return {
|
||||
__memoryStore: true,
|
||||
get: <T>(key: IDBValidKey) => data.get(key) as T | undefined,
|
||||
put: (value, key) => { data.set(key, value); },
|
||||
delete: key => { data.delete(key); },
|
||||
clear: () => { data.clear(); },
|
||||
getAllKeys: <KeyType extends IDBValidKey>() => Array.from(data.keys()) as KeyType[],
|
||||
getAll: <T>() => Array.from(data.values()) as T[],
|
||||
entries: <KeyType extends IDBValidKey, ValueType>() => Array.from(data.entries()) as [KeyType, ValueType][]
|
||||
};
|
||||
}
|
||||
|
||||
function isMemoryStore(store: StoreLike): store is MemoryStore {
|
||||
return "__memoryStore" in store;
|
||||
}
|
||||
|
||||
export function promisifyRequest<T = undefined>(
|
||||
request: IDBRequest<T> | IDBTransaction,
|
||||
): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const target = request as IDBRequest<T> & IDBTransaction;
|
||||
target.oncomplete = target.onsuccess = () => resolve(target.result);
|
||||
target.onabort = target.onerror = () => reject(target.error);
|
||||
// @ts-expect-error - file size hacks
|
||||
request.oncomplete = request.onsuccess = () => resolve(request.result);
|
||||
// @ts-expect-error - file size hacks
|
||||
request.onabort = request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
export function createStore(dbName: string, storeName: string): UseStore {
|
||||
const request = indexedDB.open(dbName);
|
||||
request.onupgradeneeded = () => request.result.createObjectStore(storeName);
|
||||
const dbp = promisifyRequest(request).catch(error => {
|
||||
handleFailure(error);
|
||||
return null;
|
||||
});
|
||||
const memoryStore = getMemoryStore(dbName, storeName);
|
||||
const dbp = promisifyRequest(request);
|
||||
|
||||
return async (txMode, callback) => {
|
||||
const db = await dbp;
|
||||
if (!db) return callback(memoryStore);
|
||||
|
||||
return callback(db.transaction(storeName, txMode).objectStore(storeName));
|
||||
};
|
||||
return (txMode, callback) =>
|
||||
dbp.then(db =>
|
||||
callback(db.transaction(storeName, txMode).objectStore(storeName)),
|
||||
);
|
||||
}
|
||||
|
||||
export type UseStore = <T>(
|
||||
txMode: IDBTransactionMode,
|
||||
callback: (store: StoreLike) => T | PromiseLike<T>,
|
||||
callback: (store: IDBObjectStore) => T | PromiseLike<T>,
|
||||
) => Promise<T>;
|
||||
|
||||
let defaultGetStoreFunc: UseStore | undefined;
|
||||
|
|
@ -120,68 +54,88 @@ function defaultGetStore() {
|
|||
return defaultGetStoreFunc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value by its key.
|
||||
*
|
||||
* @param key
|
||||
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||
*/
|
||||
export function get<T = any>(
|
||||
key: IDBValidKey,
|
||||
customStore = defaultGetStore(),
|
||||
): Promise<T | undefined> {
|
||||
return withFailureFallback(customStore("readonly", store => {
|
||||
if (isMemoryStore(store)) return store.get(key) as T | undefined;
|
||||
return promisifyRequest(store.get(key));
|
||||
}), undefined);
|
||||
return customStore("readonly", store => promisifyRequest(store.get(key)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value with a key.
|
||||
*
|
||||
* @param key
|
||||
* @param value
|
||||
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||
*/
|
||||
export function set(
|
||||
key: IDBValidKey,
|
||||
value: any,
|
||||
customStore = defaultGetStore(),
|
||||
): Promise<void> {
|
||||
return withFailureFallback(customStore("readwrite", store => {
|
||||
return customStore("readwrite", store => {
|
||||
store.put(value, key);
|
||||
if (isMemoryStore(store)) return;
|
||||
|
||||
return promisifyRequest(store.transaction);
|
||||
}), undefined);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set multiple values at once. This is faster than calling set() multiple times.
|
||||
* It's also atomic – if one of the pairs can't be added, none will be added.
|
||||
*
|
||||
* @param entries Array of entries, where each entry is an array of `[key, value]`.
|
||||
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||
*/
|
||||
export function setMany(
|
||||
entries: [IDBValidKey, any][],
|
||||
customStore = defaultGetStore(),
|
||||
): Promise<void> {
|
||||
return withFailureFallback(customStore("readwrite", store => {
|
||||
return customStore("readwrite", store => {
|
||||
entries.forEach(entry => store.put(entry[1], entry[0]));
|
||||
if (isMemoryStore(store)) return;
|
||||
|
||||
return promisifyRequest(store.transaction);
|
||||
}), undefined);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple values by their keys
|
||||
*
|
||||
* @param keys
|
||||
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||
*/
|
||||
export function getMany<T = any>(
|
||||
keys: IDBValidKey[],
|
||||
customStore = defaultGetStore(),
|
||||
): Promise<T[]> {
|
||||
return withFailureFallback(customStore("readonly", store => {
|
||||
if (isMemoryStore(store)) {
|
||||
return keys.map(key => store.get(key) as T | undefined);
|
||||
}
|
||||
|
||||
return Promise.all(keys.map(key => promisifyRequest(store.get(key))));
|
||||
}), []);
|
||||
return customStore("readonly", store =>
|
||||
Promise.all(keys.map(key => promisifyRequest(store.get(key)))),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a value. This lets you see the old value and update it as an atomic operation.
|
||||
*
|
||||
* @param key
|
||||
* @param updater A callback that takes the old value and returns a new value.
|
||||
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||
*/
|
||||
export function update<T = any>(
|
||||
key: IDBValidKey,
|
||||
updater: (oldValue: T | undefined) => T,
|
||||
customStore = defaultGetStore(),
|
||||
): Promise<void> {
|
||||
return withFailureFallback(customStore(
|
||||
return customStore(
|
||||
"readwrite",
|
||||
store => {
|
||||
if (isMemoryStore(store)) {
|
||||
store.put(updater(store.get(key) as T | undefined), key);
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
store =>
|
||||
// Need to create the promise manually.
|
||||
// If I try to chain promises, the transaction closes in browsers
|
||||
// that use a promise polyfill (IE10/11).
|
||||
new Promise((resolve, reject) => {
|
||||
store.get(key).onsuccess = function () {
|
||||
try {
|
||||
store.put(updater(this.result), key);
|
||||
|
|
@ -190,42 +144,52 @@ export function update<T = any>(
|
|||
reject(err);
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
), undefined);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a particular key from the store.
|
||||
*
|
||||
* @param key
|
||||
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||
*/
|
||||
export function del(
|
||||
key: IDBValidKey,
|
||||
customStore = defaultGetStore(),
|
||||
): Promise<void> {
|
||||
return withFailureFallback(customStore("readwrite", store => {
|
||||
return customStore("readwrite", store => {
|
||||
store.delete(key);
|
||||
if (isMemoryStore(store)) return;
|
||||
|
||||
return promisifyRequest(store.transaction);
|
||||
}), undefined);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete multiple keys at once.
|
||||
*
|
||||
* @param keys List of keys to delete.
|
||||
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||
*/
|
||||
export function delMany(
|
||||
keys: IDBValidKey[],
|
||||
customStore = defaultGetStore(),
|
||||
): Promise<void> {
|
||||
return withFailureFallback(customStore("readwrite", store => {
|
||||
keys.forEach(key => store.delete(key));
|
||||
if (isMemoryStore(store)) return;
|
||||
|
||||
return customStore("readwrite", (store: IDBObjectStore) => {
|
||||
keys.forEach((key: IDBValidKey) => store.delete(key));
|
||||
return promisifyRequest(store.transaction);
|
||||
}), undefined);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all values in the store.
|
||||
*
|
||||
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||
*/
|
||||
export function clear(customStore = defaultGetStore()): Promise<void> {
|
||||
return withFailureFallback(customStore("readwrite", store => {
|
||||
return customStore("readwrite", store => {
|
||||
store.clear();
|
||||
if (isMemoryStore(store)) return;
|
||||
|
||||
return promisifyRequest(store.transaction);
|
||||
}), undefined);
|
||||
});
|
||||
}
|
||||
|
||||
function eachCursor(
|
||||
|
|
@ -240,12 +204,16 @@ function eachCursor(
|
|||
return promisifyRequest(store.transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all keys in the store.
|
||||
*
|
||||
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||
*/
|
||||
export function keys<KeyType extends IDBValidKey>(
|
||||
customStore = defaultGetStore(),
|
||||
): Promise<KeyType[]> {
|
||||
return withFailureFallback(customStore("readonly", store => {
|
||||
if (isMemoryStore(store)) return store.getAllKeys<KeyType>();
|
||||
|
||||
return customStore("readonly", store => {
|
||||
// Fast path for modern browsers
|
||||
if (store.getAllKeys) {
|
||||
return promisifyRequest(
|
||||
store.getAllKeys() as unknown as IDBRequest<KeyType[]>,
|
||||
|
|
@ -253,45 +221,59 @@ export function keys<KeyType extends IDBValidKey>(
|
|||
}
|
||||
|
||||
const items: KeyType[] = [];
|
||||
|
||||
return eachCursor(store, cursor =>
|
||||
items.push(cursor.key as KeyType),
|
||||
).then(() => items);
|
||||
}), [] as KeyType[]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all values in the store.
|
||||
*
|
||||
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||
*/
|
||||
export function values<T = any>(customStore = defaultGetStore()): Promise<T[]> {
|
||||
return withFailureFallback(customStore("readonly", store => {
|
||||
if (isMemoryStore(store)) return store.getAll<T>();
|
||||
|
||||
return customStore("readonly", store => {
|
||||
// Fast path for modern browsers
|
||||
if (store.getAll) {
|
||||
return promisifyRequest(store.getAll() as IDBRequest<T[]>);
|
||||
}
|
||||
|
||||
const items: T[] = [];
|
||||
|
||||
return eachCursor(store, cursor => items.push(cursor.value as T)).then(
|
||||
() => items,
|
||||
);
|
||||
}), [] as T[]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all entries in the store. Each entry is an array of `[key, value]`.
|
||||
*
|
||||
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||
*/
|
||||
export function entries<KeyType extends IDBValidKey, ValueType = any>(
|
||||
customStore = defaultGetStore(),
|
||||
): Promise<[KeyType, ValueType][]> {
|
||||
return withFailureFallback(customStore("readonly", store => {
|
||||
if (isMemoryStore(store)) return store.entries<KeyType, ValueType>();
|
||||
|
||||
return customStore("readonly", store => {
|
||||
// Fast path for modern browsers
|
||||
// (although, hopefully we'll get a simpler path some day)
|
||||
if (store.getAll && store.getAllKeys) {
|
||||
return Promise.all([
|
||||
promisifyRequest(
|
||||
store.getAllKeys() as unknown as IDBRequest<KeyType[]>,
|
||||
),
|
||||
promisifyRequest(store.getAll() as IDBRequest<ValueType[]>),
|
||||
]).then(([keys, values]) => keys.map((key, i) => [key, values[i]] as [KeyType, ValueType]));
|
||||
]).then(([keys, values]) => keys.map((key, i) => [key, values[i]]));
|
||||
}
|
||||
|
||||
const items: [KeyType, ValueType][] = [];
|
||||
return eachCursor(store, cursor =>
|
||||
items.push([cursor.key as KeyType, cursor.value]),
|
||||
).then(() => items);
|
||||
}), [] as [KeyType, ValueType][]);
|
||||
|
||||
return customStore("readonly", store =>
|
||||
eachCursor(store, cursor =>
|
||||
items.push([cursor.key as KeyType, cursor.value]),
|
||||
).then(() => items),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/Messag
|
|||
import { addNicknameIcon, removeNicknameIcon } from "@api/NicknameIcons";
|
||||
import { Settings, SettingsStore } from "@api/Settings";
|
||||
import { disableStyle, enableStyle } from "@api/Styles";
|
||||
import { traceFunction } from "@debug/Tracer";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { onlyOnce } from "@utils/onlyOnce";
|
||||
import { canonicalizeFind, canonicalizeReplacement } from "@utils/patches";
|
||||
|
|
@ -39,7 +40,6 @@ import { patches } from "@webpack/patcher";
|
|||
import Plugins from "~plugins";
|
||||
export { Plugins as plugins };
|
||||
|
||||
import { traceFunction } from "../debug/Tracer";
|
||||
import { addAudioProcessor, removeAudioProcessor } from "./AudioPlayer";
|
||||
import { addChannelToolbarButton, addHeaderBarButton, removeChannelToolbarButton, removeHeaderBarButton } from "./HeaderBar";
|
||||
import { addProfileCollection, removeProfileCollection } from "./ProfileCollections";
|
||||
|
|
|
|||
|
|
@ -16,13 +16,13 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { React, TextInput } from "@webpack/common";
|
||||
import { React, TextInput, useState } from "@webpack/common";
|
||||
|
||||
interface TextInputProps {
|
||||
/**
|
||||
* WARNING: Changing this between renders will have no effect!
|
||||
*/
|
||||
value: string;
|
||||
initialValue: string;
|
||||
/**
|
||||
* This will only be called if the new value passed validate()
|
||||
*/
|
||||
|
|
@ -41,13 +41,15 @@ interface TextInputProps {
|
|||
* A very simple wrapper around Discord's TextInput that validates input and shows
|
||||
* the user an error message and only calls your onChange when the input is valid
|
||||
*/
|
||||
export function CheckedTextInput({ value: initialValue, onChange, validate, placeholder }: TextInputProps) {
|
||||
const [value, setValue] = React.useState(initialValue);
|
||||
const [error, setError] = React.useState<string>();
|
||||
export function CheckedTextInput({ initialValue, onChange, validate, placeholder }: TextInputProps) {
|
||||
const [value, setValue] = useState(initialValue);
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
function handleChange(v: string) {
|
||||
setValue(v);
|
||||
|
||||
const res = validate(v);
|
||||
|
||||
if (res === true) {
|
||||
setError(void 0);
|
||||
onChange(v);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import "./colorStyles.css";
|
|||
import { classNameFactory } from "@utils/css";
|
||||
import { findByCodeLazy } from "@webpack";
|
||||
import { ColorPicker, useMemo } from "@webpack/common";
|
||||
import type tinycolor from "tinycolor2";
|
||||
|
||||
// TinyColor is completely unmangled and it's duplicated in two modules! Fun!
|
||||
const TinyColor: tinycolor.Constructor = findByCodeLazy("this._gradientType=");
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@
|
|||
|
||||
import { showNotice } from "@api/Notices";
|
||||
import { isPluginEnabled, pluginRequiresRestart, startDependenciesRecursive, startPlugin, stopPlugin } from "@api/PluginManager";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { CogWheel, InfoIcon } from "@components/Icons";
|
||||
import { AddonCard } from "@components/settings/AddonCard";
|
||||
import { classNameFactory } from "@utils/css";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { OptionType, Plugin } from "@utils/types";
|
||||
import { React, showToast, Toasts } from "@webpack/common";
|
||||
import { Settings } from "Vencord";
|
||||
|
||||
import { PluginMeta } from "~plugins";
|
||||
|
||||
|
|
|
|||
|
|
@ -60,5 +60,25 @@
|
|||
}
|
||||
|
||||
.vc-plugin-modal-info {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
justify-content: space-between;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.vc-plugin-modal-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
margin-block: 8px 16px;
|
||||
}
|
||||
|
||||
.vc-plugin-modal-tag {
|
||||
align-items: center;
|
||||
}
|
||||
border: 1px solid var(--border-normal);
|
||||
border-radius: 9999px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 6px 12px;
|
||||
color: var(--text-default);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import { proxyLazy } from "@utils/lazy";
|
|||
import { Margins } from "@utils/margins";
|
||||
import { classes, isObjectEmpty } from "@utils/misc";
|
||||
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { OptionType, Plugin } from "@utils/types";
|
||||
import { OptionType, Plugin, PluginTag } from "@utils/types";
|
||||
import { User } from "@vencord/discord-types";
|
||||
import { findComponentByCodeLazy, findCssClassesLazy } from "@webpack";
|
||||
import { Clickable, FluxDispatcher, React, Toasts, Tooltip, useEffect, useMemo, UserStore, UserSummaryItem, UserUtils, useState } from "@webpack/common";
|
||||
|
|
@ -74,6 +74,16 @@ export function makeDummyUser(user: { username: string; id?: string; avatar?: st
|
|||
return newUser;
|
||||
}
|
||||
|
||||
function PluginTags({ tags }: { tags: PluginTag[]; }) {
|
||||
return (
|
||||
<div className={cl("tags")}>
|
||||
{tags.map(tag => (
|
||||
<div key={tag} className={cl("tag")}>{tag}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
|
||||
const pluginSettings = useSettings([`plugins.${plugin.name}.*`]).plugins[plugin.name];
|
||||
const hasSettings = Boolean(pluginSettings && plugin.options && !isObjectEmpty(plugin.options));
|
||||
|
|
@ -167,6 +177,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
|||
<div className={cl("header-content")}>
|
||||
<BaseText size="lg" weight="semibold" className={cl("title")}>{plugin.name}</BaseText>
|
||||
<BaseText size="sm" className={cl("description")}>{plugin.description}</BaseText>
|
||||
{!!plugin.tags?.length && <PluginTags tags={plugin.tags} />}
|
||||
{!!plugin.settingsAboutComponent && (
|
||||
<div className={Margins.top8}>
|
||||
<ErrorBoundary message="An error occurred while rendering this plugin's custom Info Component">
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@ import { Margins } from "@utils/margins";
|
|||
import { classes } from "@utils/misc";
|
||||
import { useAwaiter, useIntersection } from "@utils/react";
|
||||
import { t } from "@utils/translation";
|
||||
import { Alerts, lodash, Parser, React, Select, TextInput, Toasts, Tooltip, useCallback, useMemo, useState } from "@webpack/common";
|
||||
import { PluginTag, PluginTags } from "@utils/types";
|
||||
import { Alerts, lodash, Parser, React, SearchableSelect, Select, TextInput, Toasts, Tooltip, useCallback, useMemo, useRef, useState } from "@webpack/common";
|
||||
import { JSX } from "react";
|
||||
|
||||
import Plugins, { ExcludedPlugins, PluginMeta } from "~plugins";
|
||||
|
|
@ -144,7 +145,8 @@ function ExcludedPluginsList({ search }: { search: string; }) {
|
|||
|
||||
export default function PluginSettings() {
|
||||
const settings = useSettings();
|
||||
const changes = React.useMemo(() => new ChangeList<string>(), []);
|
||||
const changeRef = useRef<ChangeList<string>>(null);
|
||||
const changes = changeRef.current ??= new ChangeList<string>();
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
|
|
@ -195,25 +197,13 @@ export default function PluginSettings() {
|
|||
|
||||
const hasUserPlugins = useMemo(() => !IS_STANDALONE && Object.values(PluginMeta).some(m => m.userPlugin), []);
|
||||
|
||||
const [searchValue, setSearchValue] = useState({ value: "", status: SearchStatus.ALL });
|
||||
const [searchInput, setSearchInput] = useState("");
|
||||
|
||||
const debouncedSetSearch = useMemo(
|
||||
() => debounce((query: string) => setSearchValue(prev => ({ ...prev, value: query })), 150),
|
||||
[]
|
||||
);
|
||||
const [searchValue, setSearchValue] = useState({ value: "", tags: [] as PluginTag[], status: SearchStatus.ALL });
|
||||
|
||||
const search = searchValue.value.toLowerCase();
|
||||
const onSearch = useCallback((query: string) => {
|
||||
setSearchInput(query);
|
||||
debouncedSetSearch(query);
|
||||
}, [debouncedSetSearch]);
|
||||
const onStatusChange = useCallback((status: SearchStatus) => {
|
||||
setSearchValue(prev => ({ ...prev, status }));
|
||||
}, []);
|
||||
const onSearch = (query: string) => setSearchValue(prev => ({ ...prev, value: query }));
|
||||
|
||||
const pluginFilter = useCallback((plugin: typeof Plugins[keyof typeof Plugins], newPluginsSet: Set<string> | null) => {
|
||||
const { status } = searchValue;
|
||||
const { status, tags } = searchValue;
|
||||
const enabled = isPluginEnabled(plugin.name);
|
||||
|
||||
switch (status) {
|
||||
|
|
@ -240,12 +230,14 @@ export default function PluginSettings() {
|
|||
break;
|
||||
}
|
||||
|
||||
if (tags.length && tags.some(t => !plugin.tags?.includes(t))) return false;
|
||||
|
||||
if (!search.length) return true;
|
||||
|
||||
return (
|
||||
plugin.name.toLowerCase().includes(search.replace(/\s+/g, "")) ||
|
||||
plugin.description.toLowerCase().includes(search) ||
|
||||
plugin.tags?.some(t => t.toLowerCase().includes(search))
|
||||
plugin.searchTerms?.some(t => t.toLowerCase().includes(search))
|
||||
);
|
||||
}, [searchValue, search]);
|
||||
|
||||
|
|
@ -407,31 +399,45 @@ export default function PluginSettings() {
|
|||
Filters
|
||||
</HeadingTertiary>
|
||||
|
||||
<div className={classes(Margins.bottom20, cl("filter-controls"))}>
|
||||
<ErrorBoundary noop>
|
||||
<TextInput autoFocus value={searchInput} placeholder="Search for a plugin..." onChange={onSearch} />
|
||||
</ErrorBoundary>
|
||||
<div>
|
||||
<ErrorBoundary noop>
|
||||
<Select
|
||||
options={[
|
||||
{ label: "Show All", value: SearchStatus.ALL, default: true },
|
||||
{ label: "Show Enabled", value: SearchStatus.ENABLED },
|
||||
{ label: "Show Disabled", value: SearchStatus.DISABLED },
|
||||
{ label: "Show Equicord", value: SearchStatus.EQUICORD },
|
||||
{ label: "Show Vencord", value: SearchStatus.VENCORD },
|
||||
{ label: "Show New", value: SearchStatus.NEW },
|
||||
hasUserPlugins && { label: "Show UserPlugins", value: SearchStatus.USER_PLUGINS },
|
||||
{ label: "Show API Plugins", value: SearchStatus.API_PLUGINS },
|
||||
].filter(isTruthy)}
|
||||
serialize={String}
|
||||
select={onStatusChange}
|
||||
isSelected={v => v === searchValue.status}
|
||||
closeOnSelect={true}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
<ErrorBoundary noop>
|
||||
<TextInput
|
||||
inputClassName={cl("filter-control")}
|
||||
placeholder="Search for a plugin..."
|
||||
value={searchValue.value}
|
||||
onChange={onSearch}
|
||||
autoFocus
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
|
||||
<ErrorBoundary noop>
|
||||
<div className={classes(Margins.bottom20, Margins.top8, cl("filter-controls"))}>
|
||||
<Select
|
||||
options={[
|
||||
{ label: "Show All", value: SearchStatus.ALL, default: true },
|
||||
{ label: "Show Enabled", value: SearchStatus.ENABLED },
|
||||
{ label: "Show Disabled", value: SearchStatus.DISABLED },
|
||||
{ label: "Show Equicord", value: SearchStatus.EQUICORD },
|
||||
{ label: "Show Vencord", value: SearchStatus.VENCORD },
|
||||
{ label: "Show New", value: SearchStatus.NEW },
|
||||
hasUserPlugins && { label: "Show UserPlugins", value: SearchStatus.USER_PLUGINS },
|
||||
{ label: "Show API Plugins", value: SearchStatus.API_PLUGINS },
|
||||
].filter(isTruthy)}
|
||||
serialize={String}
|
||||
select={status => setSearchValue(prev => ({ ...prev, status }))}
|
||||
isSelected={v => v === searchValue.status}
|
||||
closeOnSelect={true}
|
||||
placeholder="Filter by Type"
|
||||
/>
|
||||
<SearchableSelect
|
||||
options={PluginTags.map(tag => ({ label: tag, value: tag }))}
|
||||
value={searchValue.tags}
|
||||
onChange={tags => setSearchValue(prev => ({ ...prev, tags }))}
|
||||
closeOnSelect={false}
|
||||
placeholder="Filter by Tags"
|
||||
multi
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
|
||||
<HeadingTertiary className={Margins.top20}>Plugins</HeadingTertiary>
|
||||
|
||||
|
|
@ -457,6 +463,7 @@ export default function PluginSettings() {
|
|||
<HeadingTertiary className={classes(Margins.top20, Margins.bottom8)}>
|
||||
Required Plugins
|
||||
</HeadingTertiary>
|
||||
|
||||
<div className={cl("grid")}>
|
||||
{requiredPlugins.length
|
||||
? requiredPlugins
|
||||
|
|
|
|||
|
|
@ -45,7 +45,12 @@
|
|||
display: grid;
|
||||
height: 40px;
|
||||
gap: 10px;
|
||||
grid-template-columns: 1fr 150px;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.vc-plugins-filter-control {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.vc-plugins-dep-name {
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ function CloudTab() {
|
|||
<div style={{ flex: 1 }}>
|
||||
<CheckedTextInput
|
||||
key={`backendUrl-${inputKey}`}
|
||||
value={cloud.url}
|
||||
initialValue={cloud.url}
|
||||
onChange={async v => {
|
||||
cloud.url = v;
|
||||
cloud.authenticated = false;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { Paragraph } from "@components/Paragraph";
|
|||
import { openContributorModal, openPluginModal, SettingsTab, wrapTab } from "@components/settings";
|
||||
import { QuickAction, QuickActionCard } from "@components/settings/QuickAction";
|
||||
import { SpecialCard } from "@components/settings/SpecialCard";
|
||||
import BadgeAPI from "@plugins/_api/badges";
|
||||
import { gitRemote } from "@shared/vencordUserAgent";
|
||||
import { DONOR_ROLE_ID, GUILD_ID, IS_MAC, IS_WINDOWS, VC_DONOR_ROLE_ID, VC_GUILD_ID } from "@utils/constants";
|
||||
import { classNameFactory } from "@utils/css";
|
||||
|
|
@ -27,8 +28,7 @@ import { Margins } from "@utils/margins";
|
|||
import { identity, isAnyPluginDev } from "@utils/misc";
|
||||
import { relaunch } from "@utils/native";
|
||||
import { t } from "@utils/translation";
|
||||
import { GuildMemberStore, React, Select, UserStore } from "@webpack/common";
|
||||
import BadgeAPI from "plugins/_api/badges";
|
||||
import { Alerts, GuildMemberStore, React, Select, UserStore } from "@webpack/common";
|
||||
|
||||
import { DonateButtonComponent } from "./DonateButton";
|
||||
import { openNotificationSettingsModal } from "./NotificationSettings";
|
||||
|
|
@ -68,28 +68,28 @@ function EquicordSettings() {
|
|||
warning: { enabled: boolean; message?: string; };
|
||||
}
|
||||
> = [
|
||||
{
|
||||
key: "useQuickCss",
|
||||
title: t("vencord.settings.useQuickCss.title"),
|
||||
description: t("vencord.settings.useQuickCss.description"),
|
||||
restartRequired: true,
|
||||
warning: { enabled: false },
|
||||
},
|
||||
!IS_WEB && {
|
||||
key: "enableReactDevtools",
|
||||
title: t("vencord.settings.enableReactDevtools.title"),
|
||||
description: t("vencord.settings.enableReactDevtools.description"),
|
||||
restartRequired: true,
|
||||
warning: { enabled: false },
|
||||
},
|
||||
(!IS_WEB && !IS_DISCORD_DESKTOP || !IS_WINDOWS) && {
|
||||
key: "mainWindowFrameless",
|
||||
title: t("equicord.mainWindowFrameless.title"),
|
||||
description: t("equicord.mainWindowFrameless.description"),
|
||||
restartRequired: true,
|
||||
warning: { enabled: false },
|
||||
},
|
||||
!IS_WEB &&
|
||||
{
|
||||
key: "useQuickCss",
|
||||
title: t("vencord.settings.useQuickCss.title"),
|
||||
description: t("vencord.settings.useQuickCss.description"),
|
||||
restartRequired: true,
|
||||
warning: { enabled: false },
|
||||
},
|
||||
!IS_WEB && {
|
||||
key: "enableReactDevtools",
|
||||
title: t("vencord.settings.enableReactDevtools.title"),
|
||||
description: t("vencord.settings.enableReactDevtools.description"),
|
||||
restartRequired: true,
|
||||
warning: { enabled: false },
|
||||
},
|
||||
(!IS_WEB && !IS_DISCORD_DESKTOP || !IS_WINDOWS) && {
|
||||
key: "mainWindowFrameless",
|
||||
title: t("equicord.mainWindowFrameless.title"),
|
||||
description: t("equicord.mainWindowFrameless.description"),
|
||||
restartRequired: true,
|
||||
warning: { enabled: false },
|
||||
},
|
||||
!IS_WEB &&
|
||||
(!IS_DISCORD_DESKTOP || !IS_WINDOWS
|
||||
? {
|
||||
key: "frameless",
|
||||
|
|
@ -106,34 +106,34 @@ function EquicordSettings() {
|
|||
warning: { enabled: false },
|
||||
}
|
||||
),
|
||||
!IS_WEB && {
|
||||
key: "transparent",
|
||||
title: t("vencord.settings.transparent.title"),
|
||||
description: t("vencord.settings.transparent.description"),
|
||||
restartRequired: true,
|
||||
warning: {
|
||||
enabled: true,
|
||||
message: IS_WINDOWS
|
||||
? t("vencord.settings.transparent.noteWindows")
|
||||
: t("vencord.settings.transparent.note"),
|
||||
},
|
||||
!IS_WEB && {
|
||||
key: "transparent",
|
||||
title: t("vencord.settings.transparent.title"),
|
||||
description: t("vencord.settings.transparent.description"),
|
||||
restartRequired: true,
|
||||
warning: {
|
||||
enabled: true,
|
||||
message: IS_WINDOWS
|
||||
? t("vencord.settings.transparent.noteWindows")
|
||||
: t("vencord.settings.transparent.note"),
|
||||
},
|
||||
IS_DISCORD_DESKTOP && {
|
||||
key: "disableMinSize",
|
||||
title: t("vencord.settings.disableMinSize.title"),
|
||||
description: t("vencord.settings.disableMinSize.description"),
|
||||
restartRequired: true,
|
||||
warning: { enabled: false },
|
||||
},
|
||||
!IS_WEB &&
|
||||
},
|
||||
IS_DISCORD_DESKTOP && {
|
||||
key: "disableMinSize",
|
||||
title: t("vencord.settings.disableMinSize.title"),
|
||||
description: t("vencord.settings.disableMinSize.description"),
|
||||
restartRequired: true,
|
||||
warning: { enabled: false },
|
||||
},
|
||||
!IS_WEB &&
|
||||
IS_WINDOWS && {
|
||||
key: "winCtrlQ",
|
||||
title: t("vencord.settings.winCtrlQ.title"),
|
||||
description: t("vencord.settings.winCtrlQ.description"),
|
||||
restartRequired: true,
|
||||
warning: { enabled: false },
|
||||
},
|
||||
];
|
||||
key: "winCtrlQ",
|
||||
title: t("vencord.settings.winCtrlQ.title"),
|
||||
description: t("vencord.settings.winCtrlQ.description"),
|
||||
restartRequired: true,
|
||||
warning: { enabled: false },
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<SettingsTab>
|
||||
|
|
@ -250,7 +250,19 @@ function EquicordSettings() {
|
|||
<FormSwitch
|
||||
key={s.key}
|
||||
value={settings[s.key]}
|
||||
onChange={v => (settings[s.key] = v)}
|
||||
onChange={v => {
|
||||
settings[s.key] = v;
|
||||
|
||||
if (s.restartRequired) {
|
||||
Alerts.show({
|
||||
title: "Restart Required",
|
||||
body: "A restart is required to apply this change",
|
||||
confirmText: "Restart now",
|
||||
cancelText: "Later!",
|
||||
onConfirm: relaunch
|
||||
});
|
||||
}
|
||||
}}
|
||||
title={s.title}
|
||||
description={
|
||||
s.warning.enabled ? (
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import { canonicalizeMatch } from "@utils/patches";
|
|||
import { ModuleFactory } from "@vencord/discord-types/webpack";
|
||||
import * as Webpack from "@webpack";
|
||||
import { wreq } from "@webpack";
|
||||
import { AnyModuleFactory } from "@webpack/types";
|
||||
import pLimit from "p-limit";
|
||||
import { AnyModuleFactory } from "webpack";
|
||||
|
||||
function getWebpackChunkMap() {
|
||||
const sym = Symbol();
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export interface ReporterData {
|
|||
};
|
||||
failedWebpack: Record<TypeWebpackSearchHistory, string[][]>;
|
||||
}
|
||||
|
||||
export const reporterData: ReporterData = {
|
||||
failedPatches: {
|
||||
foundNoModule: [],
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ export default definePlugin({
|
|||
find: ".DISPLAY_NAME_STYLES_COACHMARK)",
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=className:(\i)\.\i,style:\i,)children:\[/,
|
||||
replace: "children:[...$self.renderButtons(arguments[0],$1),"
|
||||
match: /children:\[(?=.{0,50}accountContainerRef:\i)/,
|
||||
replace: "children:[...$self.renderButtons(arguments[0]),"
|
||||
},
|
||||
// fix discord weird shrink with extra buttons
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import definePlugin from "@utils/types";
|
|||
export default definePlugin({
|
||||
name: "AltKrispSwitch",
|
||||
description: t("equicord.altKrispSwitch.description"),
|
||||
tags: ["Customisation", "Voice"],
|
||||
authors: [Devs.newwares],
|
||||
patches: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import definePlugin from "@utils/types";
|
|||
export default definePlugin({
|
||||
name: "AlwaysExpandProfiles",
|
||||
description: t("equicord.alwaysExpandProfiles.description"),
|
||||
tags: ["Appearance", "Utility"],
|
||||
authors: [Devs.thororen],
|
||||
patches: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -194,6 +194,7 @@ async function playSound(buffer: AudioBuffer, volume: number) {
|
|||
export default definePlugin({
|
||||
name: "Animalese",
|
||||
description: t("equicord.animalese.description"),
|
||||
tags: ["Customisation", "Fun"],
|
||||
authors: [EquicordDevs.ryanamay, EquicordDevs.Mocha],
|
||||
settings,
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export default definePlugin({
|
|||
name: "AtSomeone",
|
||||
authors: [Devs.Joona],
|
||||
description: t("equicord.atSomeone.description"),
|
||||
tags: ["Chat", "Fun"],
|
||||
patches: [
|
||||
{
|
||||
find: ".LAUNCHABLE_APPLICATIONS;",
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@ function handlePaste(event: ClipboardEvent) {
|
|||
export default definePlugin({
|
||||
name: "AutoZipper",
|
||||
description: t("equicord.autoZipper.description"),
|
||||
tags: ["Chat", "Organisation"],
|
||||
authors: [EquicordDevs.SSnowly],
|
||||
settings,
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ const DATASTORE_KEY = "bannersEverywhere";
|
|||
export default definePlugin({
|
||||
name: "BannersEverywhere",
|
||||
description: t("equicord.bannersEverywhere.description"),
|
||||
tags: ["Appearance", "Customisation"],
|
||||
authors: [Devs.ImLvna, Devs.AutumnVN],
|
||||
settings,
|
||||
patches: [
|
||||
|
|
|
|||
|
|
@ -108,6 +108,8 @@ const settings = definePluginSettings({
|
|||
export default definePlugin({
|
||||
name: "DecodeBase64",
|
||||
description: t("equicord.baseDecoder.description"),
|
||||
dependencies: ["MessagePopoverAPI"],
|
||||
tags: ["Appearance", "Customisation", "Chat"],
|
||||
authors: [EquicordDevs.ThePirateStoner],
|
||||
settings,
|
||||
messagePopoverButton: {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export default definePlugin({
|
|||
name: "BetterActivities",
|
||||
description: t("equicord.betterActivities.description"),
|
||||
authors: [Devs.D3SOX, Devs.Arjix, Devs.AutumnVN, Devs.thororen],
|
||||
tags: ["activity"],
|
||||
tags: ["Activity"],
|
||||
settings,
|
||||
patchActivityList,
|
||||
showAllActivitiesComponent,
|
||||
|
|
@ -46,7 +46,7 @@ export default definePlugin({
|
|||
// Show all activities in the user popout/sidebar
|
||||
find: /onOpenUserProfileModal:\i,onClose:\i\}\),null/,
|
||||
replacement: {
|
||||
match: /((\i)=.{0,10}(\i)\.id\).*?,onInteraction:\i\}\),).{0,250}onClose:\i\}\)/,
|
||||
match: /((\i)=.{0,10}(\i)\.id\).*?,onOpenUserProfileModal:\i\}\),).{0,250}onClose:\i\}\)/,
|
||||
replace: "$1$self.showAllActivitiesComponent({ activity: $2, user: $3 })"
|
||||
},
|
||||
predicate: () => settings.store.userPopout
|
||||
|
|
|
|||
|
|
@ -275,6 +275,7 @@ const settings = definePluginSettings({
|
|||
export default definePlugin({
|
||||
name: "BetterAudioPlayer",
|
||||
description: t("equicord.betterAudioPlayer.description"),
|
||||
tags: ["Appearance", "Media", "Voice"],
|
||||
authors: [EquicordDevs.creations],
|
||||
settings,
|
||||
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ const settings = definePluginSettings({
|
|||
export default definePlugin({
|
||||
name: "BetterBanReasons",
|
||||
description: t("equicord.betterBanReasons.description"),
|
||||
tags: ["Appearance", "Customisation"],
|
||||
authors: [Devs.Inbestigator],
|
||||
patches: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ let updateFunc = (v: any) => { };
|
|||
export default definePlugin({
|
||||
name: "BetterBlockedUsers",
|
||||
description: t("equicord.betterBlockedUsers.description"),
|
||||
tags: ["Appearance", "Shortcuts"],
|
||||
authors: [EquicordDevs.TheArmagan],
|
||||
patches: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ function fetchIndex(target: object) {
|
|||
export default definePlugin({
|
||||
name: "BetterCommands",
|
||||
description: t("equicord.betterCommands.description"),
|
||||
dependencies: ["CommandsAPI"],
|
||||
tags: ["Appearance", "Commands", "Shortcuts"],
|
||||
authors: [Devs.thororen],
|
||||
settings,
|
||||
patches: [
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ function lurk(id: string) {
|
|||
export default definePlugin({
|
||||
name: "BetterInvites",
|
||||
description: t("equicord.betterInvites.description"),
|
||||
tags: ["Appearance", "Customisation", "Chat", "Servers"],
|
||||
authors: [EquicordDevs.iamme, Devs.thororen],
|
||||
patches: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export default definePlugin({
|
|||
name: "BetterPlusReacts",
|
||||
authors: [Devs.Joona],
|
||||
description: t("equicord.betterPlusReacts.description"),
|
||||
tags: ["Chat", "Emotes"],
|
||||
patches: [
|
||||
{
|
||||
find: ".SLASH_COMMAND_USED,",
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ export function containsBlockedKeywords(message: Message) {
|
|||
export default definePlugin({
|
||||
name: "BlockKeywords",
|
||||
description: t("equicord.blockKeywords.description"),
|
||||
tags: ["Appearance", "Customisation", "Privacy"],
|
||||
authors: [EquicordDevs.catcraft],
|
||||
patches: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import definePlugin from "@utils/types";
|
|||
export default definePlugin({
|
||||
name: "BlockKrisp",
|
||||
description: t("equicord.blockKrisp.description"),
|
||||
tags: ["Privacy", "Utility", "Voice"],
|
||||
authors: [Devs.D3SOX],
|
||||
patches: [
|
||||
// Block loading modules on Desktop
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import definePlugin from "@utils/types";
|
|||
export default definePlugin({
|
||||
name: "BypassPinPrompt",
|
||||
description: t("equicord.bypassPinPrompt.description"),
|
||||
tags: ["Shortcuts"],
|
||||
authors: [Devs.thororen],
|
||||
patches: [
|
||||
...[
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@ const settings = definePluginSettings({
|
|||
export default definePlugin({
|
||||
name: "BypassStatus",
|
||||
description: t("equicord.bypassStatus.description"),
|
||||
tags: ["Activity", "Customisation", "Notifications", "Servers"],
|
||||
authors: [Devs.Inbestigator],
|
||||
dependencies: ["AudioPlayerAPI"],
|
||||
flux: {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ function renderBadge(id: number, title: string) {
|
|||
export default definePlugin({
|
||||
name: "ChannelBadges",
|
||||
description: t("equicord.channelBadges.description"),
|
||||
tags: ["Appearance", "Customisation", "Servers"],
|
||||
authors: [EquicordDevs.creations, Devs.thororen],
|
||||
settings,
|
||||
patches: [
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ import { ChannelStore, Menu } from "@webpack/common";
|
|||
import { JSX } from "react";
|
||||
|
||||
import ChannelsTabsContainer from "./components/ChannelTabsContainer";
|
||||
import { BasicChannelTabsProps, createTab, handleChannelSwitch, settings } from "./util";
|
||||
import * as ChannelTabsUtils from "./util";
|
||||
import { BasicChannelTabsProps, createTab, handleChannelSwitch, settings } from "./util";
|
||||
|
||||
const contextMenuPatch: NavContextMenuPatchCallback = (children, props: { channel: Channel, messageId?: string; }) => {
|
||||
const { channel, messageId } = props;
|
||||
|
|
@ -48,6 +48,7 @@ const contextMenuPatch: NavContextMenuPatchCallback = (children, props: { channe
|
|||
export default definePlugin({
|
||||
name: "ChannelTabs",
|
||||
description: t("equicord.channelTabs.description"),
|
||||
tags: ["Appearance", "Customisation", "Organisation", "Servers"],
|
||||
authors: [Devs.TheSun, Devs.TheKodeToad, EquicordDevs.keifufu, Devs.Nickyux, EquicordDevs.DiabeloDEV, EquicordDevs.justjxke],
|
||||
dependencies: ["ContextMenuAPI"],
|
||||
contextMenus: {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export default definePlugin({
|
|||
name: "CleanChannelName",
|
||||
authors: [Devs.AutumnVN],
|
||||
description: t("equicord.cleanChannelName.description"),
|
||||
tags: ["Appearance", "Customisation", "Chat", "Emotes", "Servers"],
|
||||
patches: [
|
||||
{
|
||||
find: "loadAllGuildAndPrivateChannelsFromDisk(){",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import definePlugin from "@utils/types";
|
|||
export default definePlugin({
|
||||
name: "CleanerChannelGroups",
|
||||
description: t("equicord.cleanerChannelGroups.description"),
|
||||
tags: ["Appearance", "Customisation", "Chat", "Organisation", "Servers"],
|
||||
authors: [EquicordDevs.justjxke],
|
||||
patches: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -159,6 +159,7 @@ const WrappedClickableRole = ErrorBoundary.wrap(ClickableRole, { noop: true });
|
|||
export default definePlugin({
|
||||
name: "ClickableRoles",
|
||||
description: t("equicord.clickableRoles.description"),
|
||||
tags: ["Appearance", "Roles"],
|
||||
authors: [Devs.prism],
|
||||
|
||||
patches: [
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ function activeNowView(cards) {
|
|||
export default definePlugin({
|
||||
name: "ClientSideBlock",
|
||||
description: t("equicord.clientSideBlock.description"),
|
||||
tags: ["blocked", "block", "hide", "hidden", "noblockedmessages"],
|
||||
tags: ["Utility"],
|
||||
authors: [Devs.Samwich, EquicordDevs.KamiRu],
|
||||
settings,
|
||||
activeNowView,
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ migratePluginSettings("ClipsEnhancements", "TimelessClips");
|
|||
export default definePlugin({
|
||||
name: "ClipsEnhancements",
|
||||
description: t("equicord.clipsEnhancements.description"),
|
||||
tags: ["Activity", "Media", "Utility"],
|
||||
authors: [Devs.niko, Devs.Joona, EquicordDevs.keircn],
|
||||
settings,
|
||||
patches: [
|
||||
|
|
|
|||
150
src/equicordplugins/collapsibleUi/index.tsx
Normal file
150
src/equicordplugins/collapsibleUi/index.tsx
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2026 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { ChannelToolbarButton } from "@api/HeaderBar";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import { classNameFactory } from "@utils/css";
|
||||
import { classes } from "@utils/misc";
|
||||
import definePlugin from "@utils/types";
|
||||
import { ContextMenuApi, Menu } from "@webpack/common";
|
||||
import type { ReactNode, SVGProps } from "react";
|
||||
|
||||
import { PanelId, panelRegistry, settings, toolbarPanelOrder } from "./settings";
|
||||
import managedStyle from "./style.css?managed";
|
||||
|
||||
const cl = classNameFactory("vc-collapsible-ui-");
|
||||
|
||||
type StateStore = ReturnType<typeof settings.use>;
|
||||
|
||||
function PanelsIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" {...props}>
|
||||
<path fill="currentColor" d="M3 5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v3H3V5Zm0 5h6v11H5a2 2 0 0 1-2-2V10Zm8 0h10v9a2 2 0 0 1-2 2H11V10Zm2-5h8v3h-8V5Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function getStateClasses(store: StateStore) {
|
||||
const classNames = [cl("state"), cl(`transition-${store.transitionMs}`)];
|
||||
|
||||
for (const panelId of toolbarPanelOrder) {
|
||||
const panel = panelRegistry[panelId];
|
||||
if (store[panel.collapsedKey]) classNames.push(cl(`${panel.classId}-collapsed`));
|
||||
}
|
||||
|
||||
return classes(...classNames);
|
||||
}
|
||||
|
||||
function togglePanel(panelId: PanelId) {
|
||||
const key = panelRegistry[panelId].collapsedKey;
|
||||
settings.store[key] = !settings.store[key];
|
||||
}
|
||||
|
||||
const ToolbarMenu = ErrorBoundary.wrap(({ onClose }: { onClose(): void; }) => {
|
||||
const store = settings.use();
|
||||
|
||||
return (
|
||||
<Menu.Menu navId="vc-collapsible-ui-toolbar-menu" onClose={onClose} aria-label="Collapsible UI">
|
||||
{toolbarPanelOrder.map(panelId => {
|
||||
const panel = panelRegistry[panelId];
|
||||
const collapsed = store[panel.collapsedKey];
|
||||
|
||||
return (
|
||||
<Menu.MenuCheckboxItem
|
||||
key={panelId}
|
||||
id={`vc-collapsible-ui-${panel.classId}`}
|
||||
label={panel.label}
|
||||
checked={!collapsed}
|
||||
action={() => togglePanel(panelId)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Menu.Menu>
|
||||
);
|
||||
}, { noop: true });
|
||||
|
||||
const ToolbarButtons = ErrorBoundary.wrap(() => {
|
||||
const store = settings.use();
|
||||
const anyCollapsed = toolbarPanelOrder.some(panelId => store[panelRegistry[panelId].collapsedKey]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<span aria-hidden className={getStateClasses(store)} style={{ display: "none" }} />
|
||||
<ChannelToolbarButton
|
||||
icon={PanelsIcon}
|
||||
tooltip="Collapsible UI"
|
||||
aria-label="Collapsible UI"
|
||||
selected={anyCollapsed}
|
||||
onClick={event => ContextMenuApi.openContextMenu(event, () => <ToolbarMenu onClose={ContextMenuApi.closeContextMenu} />)}
|
||||
onContextMenu={event => ContextMenuApi.openContextMenu(event, () => <ToolbarMenu onClose={ContextMenuApi.closeContextMenu} />)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}, { noop: true });
|
||||
|
||||
const ChatButtonsRow = ErrorBoundary.wrap(({ buttons }: { buttons: ReactNode[]; }) => {
|
||||
const { chatButtonsCollapsed } = settings.use();
|
||||
|
||||
if (buttons.length === 0) return <>{buttons}</>;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes(
|
||||
cl("chat-buttons"),
|
||||
chatButtonsCollapsed && cl("chat-buttons-collapsed")
|
||||
)}
|
||||
>
|
||||
<div className={cl("chat-buttons-items")}>
|
||||
{buttons}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}, { noop: true });
|
||||
|
||||
export default definePlugin({
|
||||
name: "CollapsibleUI",
|
||||
description: "Native collapsible channel, member, chat button, and user area surfaces.",
|
||||
tags: ["Appearance", "Customisation", "Chat", "Servers"],
|
||||
dependencies: ["HeaderBarAPI"],
|
||||
authors: [EquicordDevs.benjii],
|
||||
searchTerms: ["ui", "sidebar", "layout"],
|
||||
managedStyle,
|
||||
settings,
|
||||
|
||||
headerBarButton: {
|
||||
icon: PanelsIcon,
|
||||
location: "channeltoolbar",
|
||||
priority: 25,
|
||||
render: () => <ToolbarButtons />,
|
||||
},
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: ".DISPLAY_NAME_STYLES_COACHMARK)",
|
||||
replacement: {
|
||||
match: /className:(\i\.\i),(?=style:(\i),children:\[)/,
|
||||
replace: "className:$self.userAreaControlsClass($1),"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: '"sticker")',
|
||||
replacement: {
|
||||
match: /(?<="div",\{.{0,15}children:)(.+?)\}/,
|
||||
replace: "$self.wrapChatButtons($1)}"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
wrapChatButtons(buttons: ReactNode[]) {
|
||||
if (!Array.isArray(buttons) || buttons.length === 0) return buttons;
|
||||
return <ChatButtonsRow buttons={buttons} />;
|
||||
},
|
||||
|
||||
userAreaControlsClass(className: string) {
|
||||
return classes(className, cl("user-area-controls"));
|
||||
},
|
||||
});
|
||||
70
src/equicordplugins/collapsibleUi/settings.ts
Normal file
70
src/equicordplugins/collapsibleUi/settings.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2026 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { OptionType } from "@utils/types";
|
||||
|
||||
export const transitionMarkers = [0, 80, 120, 160, 200, 260, 320];
|
||||
|
||||
export const panelRegistry = {
|
||||
channelList: {
|
||||
id: "channelList",
|
||||
classId: "channel-list",
|
||||
label: "Channel List",
|
||||
collapsedKey: "channelListCollapsed",
|
||||
},
|
||||
membersList: {
|
||||
id: "membersList",
|
||||
classId: "members-list",
|
||||
label: "Members List",
|
||||
collapsedKey: "membersListCollapsed",
|
||||
},
|
||||
chatButtons: {
|
||||
id: "chatButtons",
|
||||
classId: "chat-buttons",
|
||||
label: "Message Buttons",
|
||||
collapsedKey: "chatButtonsCollapsed",
|
||||
},
|
||||
userArea: {
|
||||
id: "userArea",
|
||||
classId: "user-area",
|
||||
label: "User Area",
|
||||
collapsedKey: "userAreaCollapsed",
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type PanelId = keyof typeof panelRegistry;
|
||||
|
||||
export const toolbarPanelOrder = ["channelList", "membersList", "chatButtons", "userArea"] as const satisfies readonly PanelId[];
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
transitionMs: {
|
||||
type: OptionType.SLIDER,
|
||||
description: "Panel transition speed in milliseconds.",
|
||||
default: 160,
|
||||
markers: transitionMarkers,
|
||||
},
|
||||
channelListCollapsed: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Persist the channel list as collapsed.",
|
||||
default: false,
|
||||
},
|
||||
membersListCollapsed: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Persist the members list as collapsed.",
|
||||
default: false,
|
||||
},
|
||||
chatButtonsCollapsed: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Persist the message button row as collapsed.",
|
||||
default: false,
|
||||
},
|
||||
userAreaCollapsed: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Persist the user area as collapsed.",
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
213
src/equicordplugins/collapsibleUi/style.css
Normal file
213
src/equicordplugins/collapsibleUi/style.css
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
body:has(.vc-collapsible-ui-state) {
|
||||
--vc-cui-chat-buttons-width: min(320px, 36vw);
|
||||
--vc-cui-collapsed-size: 12px;
|
||||
--vc-cui-hover-peek: 18px;
|
||||
--vc-cui-hover-delay: 120ms;
|
||||
--vc-cui-user-area-collapsed-width: 52px;
|
||||
--vc-cui-transition: 160ms;
|
||||
--vc-cui-ease: cubic-bezier(.2, 0, 0, 1);
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-transition-0) {
|
||||
--vc-cui-transition: 0ms;
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-transition-80) {
|
||||
--vc-cui-transition: 80ms;
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-transition-120) {
|
||||
--vc-cui-transition: 120ms;
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-transition-160) {
|
||||
--vc-cui-transition: 160ms;
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-transition-200) {
|
||||
--vc-cui-transition: 200ms;
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-transition-260) {
|
||||
--vc-cui-transition: 260ms;
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-transition-320) {
|
||||
--vc-cui-transition: 320ms;
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-channel-list-collapsed):has([class*="sidebarList_"]:hover) [class*="sidebarList_"] {
|
||||
transition-delay: var(--vc-cui-hover-delay);
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-channel-list-collapsed):has([class*="sidebarList_"]:hover) [class*="sidebarList_"] > * {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
transition: opacity var(--vc-cui-transition) var(--vc-cui-ease);
|
||||
transition-delay: var(--vc-cui-hover-delay);
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-channel-list-collapsed):not(:has([class*="sidebarList_"]:hover), :has([class*="sidebar_"] > [class*="panels_"]:hover)) [class*="sidebarList_"] {
|
||||
width: var(--vc-cui-collapsed-size) !important;
|
||||
inline-size: var(--vc-cui-collapsed-size) !important;
|
||||
min-width: var(--vc-cui-collapsed-size) !important;
|
||||
min-inline-size: var(--vc-cui-collapsed-size) !important;
|
||||
max-width: var(--vc-cui-collapsed-size) !important;
|
||||
max-inline-size: var(--vc-cui-collapsed-size) !important;
|
||||
flex: 0 0 var(--vc-cui-collapsed-size) !important;
|
||||
overflow: hidden !important;
|
||||
transition:
|
||||
width var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
inline-size var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
min-width var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
min-inline-size var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
max-width var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
max-inline-size var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
flex-basis var(--vc-cui-transition) var(--vc-cui-ease) !important;
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-channel-list-collapsed):not(:has([class*="sidebarList_"]:hover), :has([class*="sidebar_"] > [class*="panels_"]:hover)) [class*="sidebarList_"] > * {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity var(--vc-cui-transition) var(--vc-cui-ease);
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-channel-list-collapsed):not(:has([class*="sidebar_"] > [class*="panels_"]:hover)) [class*="sidebar_"] > [class*="panels_"] {
|
||||
width: var(--vc-cui-user-area-collapsed-width) !important;
|
||||
inline-size: var(--vc-cui-user-area-collapsed-width) !important;
|
||||
min-width: var(--vc-cui-user-area-collapsed-width) !important;
|
||||
min-inline-size: var(--vc-cui-user-area-collapsed-width) !important;
|
||||
max-width: var(--vc-cui-user-area-collapsed-width) !important;
|
||||
max-inline-size: var(--vc-cui-user-area-collapsed-width) !important;
|
||||
overflow: hidden !important;
|
||||
transition:
|
||||
width var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
inline-size var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
min-width var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
min-inline-size var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
max-width var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
max-inline-size var(--vc-cui-transition) var(--vc-cui-ease) !important;
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-channel-list-collapsed):not(:has([class*="sidebar_"] > [class*="panels_"]:hover)) .vc-collapsible-ui-user-area-controls {
|
||||
max-width: 0 !important;
|
||||
min-width: 0 !important;
|
||||
overflow: hidden !important;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition:
|
||||
max-width var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
opacity var(--vc-cui-transition) var(--vc-cui-ease) !important;
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-channel-list-collapsed):has([class*="sidebar_"] > [class*="panels_"]:hover) .vc-collapsible-ui-user-area-controls {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-members-list-collapsed) [class*="membersWrap"] {
|
||||
overflow: hidden !important;
|
||||
transition:
|
||||
width var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
min-width var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
max-width var(--vc-cui-transition) var(--vc-cui-ease) !important;
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-members-list-collapsed):has([class*="membersWrap"]:hover) [class*="membersWrap"],
|
||||
body:has(.vc-collapsible-ui-members-list-collapsed):has([class*="membersWrap"]:focus-within) [class*="membersWrap"] {
|
||||
transition-delay: var(--vc-cui-hover-delay);
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-members-list-collapsed):has([class*="membersWrap"]:hover) [class*="membersWrap"] > :where(*),
|
||||
body:has(.vc-collapsible-ui-members-list-collapsed):has([class*="membersWrap"]:focus-within) [class*="membersWrap"] > :where(*) {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
transition: opacity var(--vc-cui-transition) var(--vc-cui-ease);
|
||||
transition-delay: var(--vc-cui-hover-delay);
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-members-list-collapsed):not(:has([class*="membersWrap"]:hover), :has([class*="membersWrap"]:focus-within)) [class*="membersWrap"] {
|
||||
width: var(--vc-cui-collapsed-size) !important;
|
||||
min-width: var(--vc-cui-collapsed-size) !important;
|
||||
max-width: var(--vc-cui-collapsed-size) !important;
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-members-list-collapsed):not(:has([class*="membersWrap"]:hover), :has([class*="membersWrap"]:focus-within)) [class*="membersWrap"] > :where(*) {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity var(--vc-cui-transition) var(--vc-cui-ease);
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-user-area-collapsed):has([class*="sidebar_"] > [class*="panels_"]:hover) [class*="sidebar_"] > [class*="panels_"] > :where(*),
|
||||
body:has(.vc-collapsible-ui-user-area-collapsed):has([class*="sidebar_"] > [class*="panels_"]:focus-within) [class*="sidebar_"] > [class*="panels_"] > :where(*) {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
transition: opacity var(--vc-cui-transition) var(--vc-cui-ease);
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-user-area-collapsed):not(:has([class*="sidebar_"] > [class*="panels_"]:hover), :has([class*="sidebar_"] > [class*="panels_"]:focus-within)) [class*="sidebar_"] > [class*="panels_"] {
|
||||
height: var(--vc-cui-hover-peek) !important;
|
||||
block-size: var(--vc-cui-hover-peek) !important;
|
||||
min-height: var(--vc-cui-hover-peek) !important;
|
||||
min-block-size: var(--vc-cui-hover-peek) !important;
|
||||
max-height: var(--vc-cui-hover-peek) !important;
|
||||
max-block-size: var(--vc-cui-hover-peek) !important;
|
||||
overflow: hidden !important;
|
||||
border-top-width: 0 !important;
|
||||
padding-block: 0 !important;
|
||||
transition:
|
||||
height var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
block-size var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
min-height var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
min-block-size var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
max-height var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
max-block-size var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
padding var(--vc-cui-transition) var(--vc-cui-ease) !important;
|
||||
}
|
||||
|
||||
body:has(.vc-collapsible-ui-user-area-collapsed):not(:has([class*="sidebar_"] > [class*="panels_"]:hover), :has([class*="sidebar_"] > [class*="panels_"]:focus-within)) [class*="sidebar_"] > [class*="panels_"] > :where(*) {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity var(--vc-cui-transition) var(--vc-cui-ease);
|
||||
}
|
||||
|
||||
.vc-collapsible-ui-chat-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-inline-size: 0;
|
||||
}
|
||||
|
||||
.vc-collapsible-ui-chat-buttons-collapsed {
|
||||
min-inline-size: var(--vc-cui-hover-peek);
|
||||
}
|
||||
|
||||
.vc-collapsible-ui-chat-buttons-items {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-inline-size: 0;
|
||||
max-inline-size: var(--vc-cui-chat-buttons-width);
|
||||
overflow: hidden;
|
||||
transition:
|
||||
max-inline-size var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
opacity var(--vc-cui-transition) var(--vc-cui-ease),
|
||||
margin var(--vc-cui-transition) var(--vc-cui-ease);
|
||||
}
|
||||
|
||||
.vc-collapsible-ui-chat-buttons-collapsed .vc-collapsible-ui-chat-buttons-items {
|
||||
max-inline-size: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.vc-collapsible-ui-chat-buttons-collapsed:hover .vc-collapsible-ui-chat-buttons-items {
|
||||
max-inline-size: var(--vc-cui-chat-buttons-width);
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
transition-delay: var(--vc-cui-hover-delay);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
body:has(.vc-collapsible-ui-state) {
|
||||
--vc-cui-transition: 0ms;
|
||||
}
|
||||
}
|
||||
|
|
@ -203,6 +203,7 @@ export default definePlugin({
|
|||
name: "CommandPalette",
|
||||
description: t("equicord.commandPalette.description"),
|
||||
authors: [EquicordDevs.justjxke, EquicordDevs.Ethan],
|
||||
tags: ["Appearance", "Customisation", "Commands", "Shortcuts"],
|
||||
settings,
|
||||
patches: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { DataStore } from "@api/index";
|
||||
import { isPluginEnabled, plugins, startPlugin, stopPlugin } from "@api/PluginManager";
|
||||
import { SettingsStore } from "@api/Settings";
|
||||
import { Settings, SettingsStore } from "@api/Settings";
|
||||
import { getUserSettingLazy } from "@api/UserSettings";
|
||||
import { openPluginModal } from "@components/settings/tabs";
|
||||
import { toggleEnabled } from "@equicordplugins/equicordHelper/utils";
|
||||
|
|
@ -18,7 +18,6 @@ import { Guild } from "@vencord/discord-types";
|
|||
import { findByPropsLazy, findExportedComponentLazy, findStoreLazy } from "@webpack";
|
||||
import { Alerts, ChannelActionCreators, ChannelRouter, ChannelStore, ComponentDispatch, FluxDispatcher, GuildStore, IconUtils, MediaEngineStore, MessageStore, NavigationRouter, React, ReadStateStore, ReadStateUtils, SelectedChannelStore, SelectedGuildStore, SettingsRouter, StreamerModeStore, Toasts, useEffect, UserStore, VoiceActions } from "@webpack/common";
|
||||
import type { FC, ReactElement, ReactNode } from "react";
|
||||
import { Settings } from "Vencord";
|
||||
|
||||
import commandPalette from ".";
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import ComponentsTab from "./components/ComponentsTab";
|
|||
export default definePlugin({
|
||||
name: "Components",
|
||||
description: t("equicord.components.description"),
|
||||
tags: ["Appearance", "Customisation", "Console", "Developers", "Organisation"],
|
||||
authors: [Devs.prism],
|
||||
dependencies: ["Settings"],
|
||||
startAt: StartAt.WebpackReady,
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ export default definePlugin({
|
|||
name: "ContentWarning",
|
||||
authors: [EquicordDevs.camila314],
|
||||
description: t("equicord.contentWarning.description"),
|
||||
tags: ["Appearance", "Utility"],
|
||||
settings,
|
||||
patches: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ const userContextMenuPatch: NavContextMenuPatchCallback = (children, { user }) =
|
|||
export default definePlugin({
|
||||
name: "CopyProfileColors",
|
||||
description: t("equicord.copyProfileColors.description"),
|
||||
tags: ["Appearance", "Customisation"],
|
||||
authors: [EquicordDevs.Crxa, EquicordDevs.Cortex],
|
||||
contextMenus: {
|
||||
"user-context": userContextMenuPatch,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ const getMetadataFromApi: (activity: any, userId: string) => Promise<any> = find
|
|||
export default definePlugin({
|
||||
name: "CopyStatusUrls",
|
||||
description: t("equicord.copyStatusUrls.description"),
|
||||
tags: ["Activity", "Utility"],
|
||||
authors: [Devs.sadan],
|
||||
|
||||
patches: [
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ export default definePlugin({
|
|||
name: "CopyUserMention",
|
||||
authors: [EquicordDevs.Cortex, Devs.castdrian],
|
||||
description: t("equicord.copyUserMention.description"),
|
||||
tags: ["Chat", "Utility"],
|
||||
contextMenus: {
|
||||
"user-context": UserContextMenuPatch
|
||||
}
|
||||
|
|
|
|||
|
|
@ -236,8 +236,9 @@ migratePluginSettings("CursorBuddy", "Oneko", "oneko");
|
|||
export default definePlugin({
|
||||
name: "CursorBuddy",
|
||||
description: t("equicord.cursorBuddy.description"),
|
||||
tags: ["Appearance", "Customisation", "Fun"],
|
||||
authors: [Devs.Ven, Devs.adryd, EquicordDevs.nexpid, EquicordDevs.ZcraftElite],
|
||||
tags: ["Oneko", "FatassHorse", "Pet"],
|
||||
searchTerms: ["Oneko", "FatassHorse", "Pet"],
|
||||
settings,
|
||||
isModified: true,
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { folderProp, int2rgba } from "./util";
|
|||
export default definePlugin({
|
||||
name: "CustomFolderIcons",
|
||||
description: t("equicord.customFolderIcons.description"),
|
||||
tags: ["Appearance", "Customisation", "Organisation"],
|
||||
authors: [EquicordDevs.sadan],
|
||||
settings,
|
||||
patches: [
|
||||
|
|
|
|||
|
|
@ -394,6 +394,8 @@ export function findOverride(id: string): SoundOverride | null {
|
|||
export default definePlugin({
|
||||
name: "CustomSounds",
|
||||
description: t("equicord.customSounds.description"),
|
||||
dependencies: ["AudioPlayerAPI"],
|
||||
tags: ["Customisation", "Notifications", "Voice"],
|
||||
authors: [Devs.ScattrdBlade, Devs.TheKodeToad],
|
||||
settings,
|
||||
startAt: StartAt.Init,
|
||||
|
|
|
|||
116
src/equicordplugins/customStatusTimeouts/index.tsx
Normal file
116
src/equicordplugins/customStatusTimeouts/index.tsx
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2026 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
const Millis = {
|
||||
HALF_SECOND: 500,
|
||||
SECOND: 1e3,
|
||||
MINUTE: 6e4,
|
||||
HOUR: 36e5,
|
||||
DAY: 864e5,
|
||||
WEEK: 6048e5,
|
||||
DAYS_30: 2592e6
|
||||
};
|
||||
|
||||
const settings = definePluginSettings({
|
||||
showForeverOnTop: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show the Forever option at the top of the list instead of the bottom.",
|
||||
restartNeeded: true,
|
||||
default: true
|
||||
},
|
||||
extraSeconds: {
|
||||
type: OptionType.STRING,
|
||||
description: "Extra seconds to add, separated by a comma (e.g. 5, 10, 30)",
|
||||
restartNeeded: true,
|
||||
default: "15, 30, 45"
|
||||
},
|
||||
extraMinutes: {
|
||||
type: OptionType.STRING,
|
||||
description: "Extra minutes to add, separated by a comma (e.g. 5, 10, 30)",
|
||||
restartNeeded: true,
|
||||
default: "5, 10, 30"
|
||||
},
|
||||
extraHours: {
|
||||
type: OptionType.STRING,
|
||||
description: "Extra hours to add, separated by a comma (e.g. 2, 4, 6, 12)",
|
||||
restartNeeded: true,
|
||||
default: "2, 4, 6, 12"
|
||||
},
|
||||
extraDays: {
|
||||
type: OptionType.STRING,
|
||||
description: "Extra days to add, separated by a comma (e.g. 1, 2)",
|
||||
restartNeeded: true,
|
||||
default: "1, 2"
|
||||
},
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "CustomStatusTimeouts",
|
||||
description: "Adds configurable timeout presets to the status (presence) menu.",
|
||||
tags: ["Activity", "Utility"],
|
||||
authors: [EquicordDevs.Kiri, EquicordDevs.thororen],
|
||||
settings,
|
||||
patches: [
|
||||
{
|
||||
find: "#{intl::DURATION_FOREVER}",
|
||||
replacement: {
|
||||
match: /\[\{duration.*?#{intl::DURATION_FOREVER}\)\}\]/,
|
||||
replace: "$self.buildTimeouts($&)"
|
||||
}
|
||||
}
|
||||
],
|
||||
buildTimeouts(existing) {
|
||||
const parse = (str: string) => str.split(",").map(s => Number(s.trim())).filter(Boolean);
|
||||
|
||||
const seconds = parse(settings.store.extraSeconds);
|
||||
const minutes = parse(settings.store.extraMinutes);
|
||||
const hours = parse(settings.store.extraHours);
|
||||
const days = parse(settings.store.extraDays);
|
||||
// I was testing weeks and months and for some reason random ones didnt apply look below for the working ones
|
||||
// tested weeks between 1-12 working weeks were 1, 2, 3, 8, 9, 10 the rest didnt work
|
||||
// tested months between 1-12 working months were 2, 4, 10, 12 the rest didnt work
|
||||
// for months I only tested evens except for 1 so I hardcoded them to avoid user complaints
|
||||
const weeks = [1, 2, 3];
|
||||
const months = [2, 4];
|
||||
|
||||
const extra = [
|
||||
...seconds.map(s => ({
|
||||
duration: s * Millis.SECOND,
|
||||
label: () => `For ${s} ${s === 1 ? "Second" : "Seconds"}`
|
||||
})),
|
||||
...minutes.map(m => ({
|
||||
duration: m * Millis.MINUTE,
|
||||
label: () => `For ${m} ${m === 1 ? "Minute" : "Minutes"}`
|
||||
})),
|
||||
...hours.map(h => ({
|
||||
duration: h * Millis.HOUR,
|
||||
label: () => `For ${h} ${h === 1 ? "Hour" : "Hours"}`
|
||||
})),
|
||||
...days.map(d => ({
|
||||
duration: d * Millis.DAY,
|
||||
label: () => `For ${d} ${d === 1 ? "Day" : "Days"}`
|
||||
})),
|
||||
...weeks.map(w => ({
|
||||
duration: w * Millis.WEEK,
|
||||
label: () => `For ${w} ${w === 1 ? "Week" : "Weeks"}`
|
||||
})),
|
||||
...months.map(m => ({
|
||||
duration: m * Millis.DAYS_30,
|
||||
label: () => `For ${m} ${m === 1 ? "Month" : "Months"}`
|
||||
})),
|
||||
];
|
||||
|
||||
return [...existing, ...extra].sort((a, b) => {
|
||||
if (a.duration === undefined) return settings.store.showForeverOnTop ? -1 : 1;
|
||||
if (b.duration === undefined) return settings.store.showForeverOnTop ? 1 : -1;
|
||||
return a.duration - b.duration;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -126,6 +126,7 @@ const settings = definePluginSettings({
|
|||
export default definePlugin({
|
||||
name: "CustomTimestamps",
|
||||
description: t("equicord.customTimestamps.description"),
|
||||
tags: ["Appearance", "Customisation"],
|
||||
authors: [Devs.Rini, EquicordDevs.nvhhr, EquicordDevs.Suffocate, Devs.Obsidian],
|
||||
settings,
|
||||
settingsAboutComponent: () => (
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ const settings = definePluginSettings({
|
|||
export default definePlugin({
|
||||
name: "CustomUserColors",
|
||||
description: t("equicord.customUserColors.description"),
|
||||
tags: ["Appearance", "Customisation", "Chat"],
|
||||
authors: [EquicordDevs.mochienya],
|
||||
contextMenus: {
|
||||
"user-context": userContextMenuPatch,
|
||||
|
|
|
|||
|
|
@ -144,15 +144,16 @@ function SectionSeparator(title: string) {
|
|||
export default definePlugin({
|
||||
name: "Declutter",
|
||||
description: t("equicord.declutter.description"),
|
||||
tags: ["Appearance", "Customisation"],
|
||||
authors: [EquicordDevs.Leon135, Devs.prism, Devs.Kyuuhachi],
|
||||
settings,
|
||||
patches: [
|
||||
{
|
||||
// Nameplate
|
||||
find: "#{intl::AVATAR_MALLOW}",
|
||||
find: ".MINI_PREVIEW,[",
|
||||
replacement: {
|
||||
match: /function \i\(\i\)\{(?=.{0,25}skuId:)/,
|
||||
replace: "$&return null;"
|
||||
match: /function \i\((\i)\)\{(?=let\{nameplate:\i,hovered:\i,selected:\i,content:\i,placement:\i\}=)/,
|
||||
replace: '$&if($1.placement!=="preview"&&$1.placement!=="mini_preview")return null;'
|
||||
},
|
||||
predicate: () => settings.store.removeNameplate,
|
||||
},
|
||||
|
|
@ -160,8 +161,8 @@ export default definePlugin({
|
|||
// Profile banner animation effect
|
||||
find: "bannerAdjustment,isHovering",
|
||||
replacement: {
|
||||
match: /\i=\i=>\{(?=.{0,50}\.useReducedMotion\))/,
|
||||
replace: "$&return null;"
|
||||
match: /\i=(\i)=>\{(?=.{0,50}\.useReducedMotion\))/,
|
||||
replace: "$&if(!$1.shopPreview)return null;"
|
||||
},
|
||||
predicate: () => settings.store.removeProfileEffect,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import definePlugin from "@utils/types";
|
|||
export default definePlugin({
|
||||
name: "DisableCameras",
|
||||
description: t("equicord.disableCameras.description"),
|
||||
tags: ["Appearance", "Customisation", "Media", "Privacy"],
|
||||
authors: [Devs.Joona],
|
||||
patches: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import SettingsPlugin from "@plugins/_core/settings";
|
||||
import { detectClient } from "@plugins/_core/supportHelper";
|
||||
import { gitHashShort } from "@shared/vencordUserAgent";
|
||||
import { React } from "@webpack/common";
|
||||
import SettingsPlugin from "plugins/_core/settings";
|
||||
import { JSX } from "react";
|
||||
|
||||
import { ChromiumIcon, ClientIcon, DevBannerIcon, DiscordIcon, ElectronIcon, EquicordIcon, names, settings } from ".";
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { makeDevBanner, settings } from "./components";
|
|||
export default definePlugin({
|
||||
name: "DiscordDevBanner",
|
||||
description: t("equicord.discordDevBanner.description"),
|
||||
tags: ["Appearance", "Console", "Developers"],
|
||||
authors: [EquicordDevs.KrystalSkull, Devs.thororen],
|
||||
settings,
|
||||
patches: [
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export default definePlugin({
|
|||
name: "DragFavoriteEmotes",
|
||||
authors: [EquicordDevs.PWall],
|
||||
description: t("equicord.dragFavoriteEmotes.description"),
|
||||
tags: ["Customisation", "Chat", "Emotes"],
|
||||
patches: [
|
||||
{
|
||||
find: "#{intl::EMOJI_FAVORITE_TOOLTIP}",
|
||||
|
|
|
|||
|
|
@ -150,13 +150,14 @@ const settings = definePluginSettings({
|
|||
export default definePlugin({
|
||||
name: "Dragify",
|
||||
description: t("equicord.dragify.description"),
|
||||
tags: ["Chat", "Servers", "Utility", "Voice"],
|
||||
authors: [EquicordDevs.justjxke],
|
||||
settings,
|
||||
|
||||
patches: [
|
||||
// Voice user rows (voice channel sidebar list)
|
||||
{
|
||||
find: '"system:click_outside","user:escape"',
|
||||
find: ".VOICE_USER],shouldShow:",
|
||||
replacement: {
|
||||
match: /(?<=GuildChannelUserContextMenu.{0,150})"data-dnd-name":\i/,
|
||||
replace: "\"data-dragify-user\":!0,\"data-user-id\":arguments[0].user?.id,draggable:!0,onDragStart:e=>$self.onUserDragStart(e,arguments[0].user),$&"
|
||||
|
|
|
|||
|
|
@ -450,6 +450,7 @@ function onToggle(e: KeyboardEvent) {
|
|||
export default definePlugin({
|
||||
name: "ElementHighlighter",
|
||||
description: t("equicord.elementHighlighter.description"),
|
||||
tags: ["Developers"],
|
||||
authors: [Devs.prism],
|
||||
settings,
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ const settings = definePluginSettings({
|
|||
export default definePlugin({
|
||||
name: "EquibopStreamFixes",
|
||||
description: "Tries to fix stream quality on Equibop by patching Discord's encoder and quality restrictions.",
|
||||
tags: ["Voice"],
|
||||
authors: [EquicordDevs.creations],
|
||||
settings,
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { ApplicationCommandInputType, sendBotMessage } from "@api/Commands";
|
|||
import { HeaderBarButton } from "@api/HeaderBar";
|
||||
import { addMessagePreSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { isPluginEnabled } from "@api/PluginManager";
|
||||
import { definePluginSettings, migratePluginToSettings } from "@api/Settings";
|
||||
import { definePluginSettings, migratePluginToSettings, Settings } from "@api/Settings";
|
||||
import customRPC from "@plugins/customRPC";
|
||||
import { Devs, EquicordDevs, GUILD_ID, SUPPORT_CHANNEL_ID, SUPPORT_CHANNEL_IDS, VC_SUPPORT_CHANNEL_IDS } from "@utils/constants";
|
||||
import { isAnyPluginDev } from "@utils/misc";
|
||||
|
|
@ -126,14 +126,22 @@ const settings = definePluginSettings({
|
|||
disableAdoptTagPrompt: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Disable the prompt to adopt tags",
|
||||
default: true,
|
||||
restartNeeded: true
|
||||
restartNeeded: true,
|
||||
default: false,
|
||||
},
|
||||
jsonGateway: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Forces JSON on gateway reconnect",
|
||||
restartNeeded: true,
|
||||
default: false,
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "EquicordHelper",
|
||||
description: t("equicord.equicordHelper.description"),
|
||||
dependencies: ["CommandsAPI", "HeaderBarAPI", "MessageAccessoriesAPI"],
|
||||
tags: ["Appearance", "Commands", "Utility"],
|
||||
authors: [
|
||||
Devs.thororen,
|
||||
EquicordDevs.nyx,
|
||||
|
|
@ -273,6 +281,36 @@ export default definePlugin({
|
|||
replace: "return null;$&"
|
||||
},
|
||||
predicate: () => settings.store.disableAdoptTagPrompt
|
||||
},
|
||||
{
|
||||
find: "JSONEncoding",
|
||||
replacement: {
|
||||
match: /void 0!==\i\?\i:/,
|
||||
replace: ""
|
||||
},
|
||||
predicate: () => settings.store.jsonGateway
|
||||
},
|
||||
{
|
||||
find: ".USE_OSX_NATIVE_TRAFFIC_LIGHTS",
|
||||
predicate: () => Settings.winNativeTitleBar,
|
||||
replacement: {
|
||||
match: /case \i\.\i\.WINDOWS:/,
|
||||
replace: 'case "WEB":'
|
||||
},
|
||||
},
|
||||
{
|
||||
find: '"refresh-title-bar-small"',
|
||||
predicate: () => Settings.winNativeTitleBar,
|
||||
replacement: [
|
||||
{
|
||||
match: /\i===\i\.PlatformTypes\.WINDOWS/g,
|
||||
replace: "false"
|
||||
},
|
||||
{
|
||||
match: /\i===\i\.PlatformTypes\.WEB/g,
|
||||
replace: "true"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
renderMessageAccessory(props) {
|
||||
|
|
|
|||
|
|
@ -78,11 +78,10 @@ migratePluginSettings("EquicordToolbox", "VencordToolbox");
|
|||
export default definePlugin({
|
||||
name: "EquicordToolbox",
|
||||
description: t("equicord.equicordToolbox.description"),
|
||||
tags: ["Voice", "Accessibility"],
|
||||
authors: [Devs.Ven, Devs.AutumnVN],
|
||||
dependencies: ["HeaderBarAPI"],
|
||||
|
||||
settings,
|
||||
|
||||
headerBarButton: {
|
||||
icon: Icon,
|
||||
render: VencordPopoutButton,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ const settings = definePluginSettings({
|
|||
export default definePlugin({
|
||||
name: "Equissant",
|
||||
description: t("equicord.equissant.description"),
|
||||
tags: ["Fun"],
|
||||
authors: [EquicordDevs.SomeAspy, Devs.thororen],
|
||||
dependencies: ["AudioPlayerAPI"],
|
||||
settings,
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ let original: typeof ChannelActions.selectVoiceChannel;
|
|||
export default definePlugin({
|
||||
name: "ExitSounds",
|
||||
description: t("equicord.exitSounds.description"),
|
||||
tags: ["Fun", "Voice"],
|
||||
authors: [Devs.prism],
|
||||
dependencies: ["AudioPlayerAPI"],
|
||||
settings,
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ function getUsernames(contacts: ContactsList[], type: number): string[] {
|
|||
export default definePlugin({
|
||||
name: "ExportMessages",
|
||||
description: t("equicord.exportMessages.description"),
|
||||
tags: ["Chat", "Utility"],
|
||||
authors: [EquicordDevs.veygax, EquicordDevs.dat_insanity],
|
||||
settings,
|
||||
contextMenus: {
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ const settings = definePluginSettings({
|
|||
export default definePlugin({
|
||||
name: "FastDeleteChannels",
|
||||
description: t("equicord.fastDeleteChannels.description"),
|
||||
tags: ["Servers", "Utility"],
|
||||
authors: [Devs.thororen],
|
||||
settings,
|
||||
patches: [
|
||||
|
|
|
|||
|
|
@ -26,8 +26,9 @@ export const AttachmentContext = proxyLazyWebpack(() => React.createContext<null
|
|||
export default definePlugin({
|
||||
name: "FavouriteAnything",
|
||||
description: t("equicord.favouriteAnything.description"),
|
||||
tags: ["Chat", "Media"],
|
||||
authors: [Devs.nin0dev, EquicordDevs.davri],
|
||||
tags: ["favorite"],
|
||||
searchTerms: ["favorite"],
|
||||
managedStyle,
|
||||
patches: [
|
||||
// EMBEDS
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ interface Uint8ArrayConstructorExtended extends Uint8ArrayConstructor {
|
|||
): Uint8ArrayExtended<ArrayBuffer>;
|
||||
}
|
||||
|
||||
function supportsToBase64(array: Uint8Array<ArrayBufferLike>): array is Uint8ArrayExtended<ArrayBufferLike> {
|
||||
function supportsToBase64(array: Uint8Array<ArrayBufferLike>): boolean {
|
||||
return "toBase64" in array && typeof array.toBase64 === "function";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -217,6 +217,7 @@ const channelAttachMenuPatch: NavContextMenuPatchCallback = (children, props) =>
|
|||
export default definePlugin({
|
||||
name: "FileUpload",
|
||||
description: t("equicord.fileUpload.description"),
|
||||
tags: ["Media"],
|
||||
authors: [EquicordDevs.creations, EquicordDevs.keircn, Devs.ScattrdBlade],
|
||||
settings,
|
||||
patches: [
|
||||
|
|
|
|||
|
|
@ -209,6 +209,12 @@ export const settings = definePluginSettings({
|
|||
default: false,
|
||||
hidden: true
|
||||
},
|
||||
preserveOriginalFilename: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Preserve the original filename when uploading.",
|
||||
default: true,
|
||||
hidden: true
|
||||
},
|
||||
autoCopy: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Auto copy upload URL",
|
||||
|
|
@ -508,6 +514,13 @@ export function SettingsComponent() {
|
|||
/>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection tag="label" name="Preserve Original Filename" description="Use the original filename instead of naming uploads as upload.ext" inlineSetting>
|
||||
<Switch
|
||||
checked={Boolean((store as { preserveOriginalFilename?: boolean; }).preserveOriginalFilename)}
|
||||
onChange={v => (store as { preserveOriginalFilename?: boolean; }).preserveOriginalFilename = v}
|
||||
/>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection tag="label" name="Auto Copy URL" description="Automatically copy the uploaded file URL to clipboard" inlineSetting>
|
||||
<Switch
|
||||
checked={store.autoCopy}
|
||||
|
|
|
|||
|
|
@ -880,9 +880,24 @@ async function normalizeUploadBlob(blob: Blob, sourceUrl?: string): Promise<{ bl
|
|||
}
|
||||
|
||||
const mimeType = getMimeFromExtension(ext);
|
||||
const { preserveOriginalFilename } = settings.store;
|
||||
let sourceFileName = "";
|
||||
if (blob instanceof File && blob.name) {
|
||||
sourceFileName = blob.name;
|
||||
} else if (sourceUrl && URL.canParse(sourceUrl)) {
|
||||
const segment = new URL(sourceUrl).pathname.split("/").pop();
|
||||
if (segment) sourceFileName = decodeURIComponent(segment);
|
||||
}
|
||||
|
||||
let filename = `upload.${ext}`;
|
||||
if (preserveOriginalFilename && sourceFileName) {
|
||||
const dotIndex = sourceFileName.lastIndexOf(".");
|
||||
filename = `${sourceFileName.slice(0, dotIndex < 0 ? undefined : dotIndex)}.${ext}`;
|
||||
}
|
||||
|
||||
return {
|
||||
blob: new Blob([blob], { type: mimeType }),
|
||||
filename: `upload.${ext}`
|
||||
filename
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -90,6 +90,8 @@ const settings = definePluginSettings({
|
|||
export default definePlugin({
|
||||
name: "FindReply",
|
||||
description: t("equicord.findReply.description"),
|
||||
dependencies: ["MessagePopoverAPI"],
|
||||
tags: ["Chat", "Shortcuts"],
|
||||
authors: [Devs.newwares],
|
||||
settings,
|
||||
messagePopoverButton: {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export default definePlugin({
|
|||
name: "FixFileExtensions",
|
||||
authors: [Devs.thororen],
|
||||
description: t("equicord.fixFileExtensions.description"),
|
||||
tags: ["Media", "Utility"],
|
||||
reporterTestable: ReporterTestable.None,
|
||||
patches: [
|
||||
// Taken from AnonymiseFileNames
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { channel,
|
|||
export default definePlugin({
|
||||
name: "FollowVoiceUser",
|
||||
description: t("equicord.followVoiceUser.description"),
|
||||
tags: ["Voice"],
|
||||
authors: [EquicordDevs.TheArmagan],
|
||||
settings,
|
||||
settingsAboutComponent: () => (
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@ const settings = definePluginSettings({
|
|||
export default definePlugin({
|
||||
name: "FontLoader",
|
||||
description: t("equicord.fontLoader.description"),
|
||||
tags: ["Appearance", "Customisation"],
|
||||
authors: [EquicordDevs.vmohammad],
|
||||
settings,
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ const settings = definePluginSettings({
|
|||
export default definePlugin({
|
||||
name: "ForwardAnywhere",
|
||||
description: t("equicord.forwardAnywhere.description"),
|
||||
tags: ["Chat", "Utility"],
|
||||
authors: [Devs.thororen],
|
||||
settings,
|
||||
patches: [
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ function generateSearchResults(query) {
|
|||
export default definePlugin({
|
||||
name: "FrequentQuickSwitcher",
|
||||
description: t("equicord.frequentQuickSwitcher.description"),
|
||||
tags: ["Shortcuts", "Servers"],
|
||||
authors: [Devs.Samwich],
|
||||
generateSearchResults: generateSearchResults,
|
||||
patches: [
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import FriendCodesPanel from "./FriendCodesPanel";
|
|||
export default definePlugin({
|
||||
name: "FriendCodes",
|
||||
description: "Generate FriendCodes to easily add friends",
|
||||
tags: ["Friends", "Utility"],
|
||||
authors: [Devs.HypedDomi],
|
||||
patches: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@ const userPatch: NavContextMenuPatchCallback = (children, { user }) => {
|
|||
export default definePlugin({
|
||||
name: "FriendTags",
|
||||
description: t("equicord.friendTags.description"),
|
||||
tags: ["Shortcuts"],
|
||||
authors: [Devs.Samwich],
|
||||
settings,
|
||||
contextMenus: {
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ function getBadgesToApply() {
|
|||
export default definePlugin({
|
||||
name: "FriendshipRanks",
|
||||
description: t("equicord.friendshipRanks.description"),
|
||||
tags: ["Friends"],
|
||||
authors: [Devs.Samwich],
|
||||
start() {
|
||||
getBadgesToApply().forEach(b => Badges.addProfileBadge(b));
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import style from "./style.css?managed";
|
|||
export default definePlugin({
|
||||
name: "FullVCPFP",
|
||||
description: t("equicord.fullVcPfp.description"),
|
||||
tags: ["Appearance", "Voice"],
|
||||
authors: [EquicordDevs.mochienya],
|
||||
patches: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -148,6 +148,7 @@ function makeContextItem(props) {
|
|||
export default definePlugin({
|
||||
name: "Ghosted",
|
||||
description: t("equicord.ghosted.description"),
|
||||
tags: ["Chat", "Utility"],
|
||||
authors: [EquicordDevs.vei, Devs.sadan, EquicordDevs.justjxke, EquicordDevs.iamme],
|
||||
settings,
|
||||
dependencies: ["AudioPlayerAPI", "ServerListAPI"],
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
import "./style.css";
|
||||
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import { t } from "@utils/translation";
|
||||
import definePlugin from "@utils/types";
|
||||
import { FluxDispatcher } from "@webpack/common";
|
||||
|
||||
|
|
@ -17,7 +18,6 @@ import { cache_collections, refreshCacheCollection, updateGif } from "./utils/co
|
|||
import { getFormat } from "./utils/getFormat";
|
||||
import { logger, stripPrefix } from "./utils/misc";
|
||||
import { batchRefreshAttachmentUrls, isCdnUrlExpired } from "./utils/refreshUrl";
|
||||
import { t } from "@utils/translation";
|
||||
|
||||
let GIF_COLLECTION_PREFIX: string;
|
||||
let GIF_ITEM_PREFIX: string;
|
||||
|
|
@ -28,6 +28,7 @@ export default definePlugin({
|
|||
name: "GifCollections",
|
||||
description: t("equicord.gifCollections.description"),
|
||||
authors: [Devs.Aria, EquicordDevs.creations],
|
||||
tags: ["Chat", "Emotes"],
|
||||
settings,
|
||||
contextMenus: {
|
||||
"message": addCollectionContextMenuPatch,
|
||||
|
|
@ -158,7 +159,7 @@ export default definePlugin({
|
|||
oldTrendingCat = instance.props.trendingCategories;
|
||||
}
|
||||
if (settings.store.onlyShowCollections) {
|
||||
instance.props.trendingCategories = [...this.sortedCollections(), ...instance.props.favorites];
|
||||
instance.props.trendingCategories = this.sortedCollections();
|
||||
} else if (oldTrendingCat != null) {
|
||||
instance.props.trendingCategories = [...this.sortedCollections(), ...oldTrendingCat];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ export const settings = definePluginSettings({
|
|||
export default definePlugin({
|
||||
name: "GitHubRepos",
|
||||
description: t("equicord.githubRepos.description"),
|
||||
dependencies: ["ProfileCollectionsAPI"],
|
||||
tags: ["Appearance"],
|
||||
authors: [EquicordDevs.talhakf, EquicordDevs.Panniku, EquicordDevs.benjii],
|
||||
settings,
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ migratePluginSetting("GlobalBadges", "showRaincord", "showRa1ncord");
|
|||
export default definePlugin({
|
||||
name: "GlobalBadges",
|
||||
description: t("equicord.globalBadges.description"),
|
||||
tags: ["Appearance"],
|
||||
authors: [Devs.HypedDomi, EquicordDevs.Wolfie, Devs.thororen],
|
||||
settings,
|
||||
settingsAboutComponent: () => (
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue