Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
thororen1234 2025-11-18 23:48:00 -05:00
commit 148b7a75a1
No known key found for this signature in database
130 changed files with 2217 additions and 3123 deletions

View file

@ -19,13 +19,15 @@
/// <reference path="../src/modules.d.ts" /> /// <reference path="../src/modules.d.ts" />
/// <reference path="../src/globals.d.ts" /> /// <reference path="../src/globals.d.ts" />
// Be very careful with imports in this file to avoid circular dependency issues.
// Only import pure modules that don't import other parts of Vencord.
import monacoHtmlLocal from "file://monacoWin.html?minify"; import monacoHtmlLocal from "file://monacoWin.html?minify";
import * as DataStore from "../src/api/DataStore"; import * as DataStore from "@api/DataStore";
import { debounce, localStorage } from "../src/utils"; import type { Settings } from "@api/Settings";
import { EXTENSION_BASE_URL } from "../src/utils/web-metadata"; import { debounce } from "@shared/debounce";
import { getTheme, Theme } from "../src/utils/discord"; import { localStorage } from "@utils/localStorage";
import { Settings } from "../src/Vencord";
import { getStylusWebStoreUrl } from "@utils/web"; import { getStylusWebStoreUrl } from "@utils/web";
import { EXTENSION_BASE_URL } from "@utils/web-metadata";
// listeners for ipc.on // listeners for ipc.on
const cssListeners = new Set<(css: string) => void>(); const cssListeners = new Set<(css: string) => void>();
@ -90,6 +92,8 @@ window.VencordNative = {
return; return;
} }
const { getTheme, Theme } = require("@utils/discord");
win.baseUrl = EXTENSION_BASE_URL; win.baseUrl = EXTENSION_BASE_URL;
win.setCss = setCssDebounced; win.setCss = setCssDebounced;
win.getCurrentCss = () => VencordNative.quickCss.get(); win.getCurrentCss = () => VencordNative.quickCss.get();

View file

@ -88,7 +88,7 @@ export default tseslint.config(
"@stylistic/no-extra-semi": "error", "@stylistic/no-extra-semi": "error",
// TS Rules // TS Rules
"@stylistic/func-call-spacing": ["error", "never"], "@stylistic/function-call-spacing": ["error", "never"],
// ESLint Rules // ESLint Rules
"yoda": "error", "yoda": "error",
@ -113,7 +113,7 @@ export default tseslint.config(
"no-useless-escape": [ "no-useless-escape": [
"error", "error",
{ {
"extra": "i" "allowRegexCharacters": ["i"]
} }
], ],
"no-fallthrough": "error", "no-fallthrough": "error",

View file

@ -51,51 +51,51 @@
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3", "gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
"idb": "8.0.3", "idb": "8.0.3",
"jsqr": "1.4.0", "jsqr": "1.4.0",
"monaco-editor": "^0.52.2", "monaco-editor": "^0.54.0",
"nanoid": "^5.1.5", "nanoid": "^5.1.6",
"usercss-meta": "^0.12.0", "usercss-meta": "^0.12.0",
"virtual-merge": "^1.0.1" "virtual-merge": "^1.0.1"
}, },
"devDependencies": { "devDependencies": {
"@electron/asar": "^4.0.1", "@electron/asar": "^4.0.1",
"@stylistic/eslint-plugin": "^4.2.0", "@stylistic/eslint-plugin": "^5.6.0",
"@types/adm-zip": "^0.5.7", "@types/adm-zip": "^0.5.7",
"@types/chrome": "^0.0.312", "@types/chrome": "^0.1.30",
"@types/diff": "^7.0.2", "@types/diff": "^7.0.2",
"@types/lodash": "^4.17.14", "@types/lodash": "^4.17.20",
"@types/node": "^22.13.13", "@types/node": "^24.10.1",
"@types/react": "^19.0.10", "@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4", "@types/react-dom": "^19.0.4",
"@types/yazl": "^2.4.5", "@types/yazl": "^3.3.0",
"@vencord/discord-types": "link:packages/discord-types", "@vencord/discord-types": "link:packages/discord-types",
"adm-zip": "^0.5.16", "adm-zip": "^0.5.16",
"diff": "^7.0.0", "diff": "^8.0.2",
"esbuild": "^0.25.1", "esbuild": "^0.27.0",
"eslint": "9.20.1", "eslint": "9.39.1",
"eslint-import-resolver-alias": "^1.1.2", "eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-react": "^7.37.3", "eslint-plugin-react": "^7.37.5",
"eslint-plugin-simple-header": "^1.2.1", "eslint-plugin-simple-header": "^1.2.1",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "^4.1.4", "eslint-plugin-unused-imports": "^4.3.0",
"highlight.js": "11.11.1", "highlight.js": "11.11.1",
"html-minifier-terser": "^7.2.0", "html-minifier-terser": "^7.2.0",
"moment": "^2.22.2", "moment": "^2.22.2",
"puppeteer-core": "^24.19.0", "puppeteer-core": "^24.30.0",
"standalone-electron-types": "^34.2.0", "standalone-electron-types": "^34.2.0",
"stylelint": "^16.17.0", "stylelint": "^16.25.0",
"stylelint-config-standard": "^37.0.0", "stylelint-config-standard": "^39.0.1",
"ts-patch": "^3.3.0", "ts-patch": "^3.3.0",
"ts-pattern": "^5.6.0", "ts-pattern": "^5.6.0",
"tsx": "^4.19.3", "tsx": "^4.20.6",
"type-fest": "^4.38.0", "type-fest": "^5.2.0",
"typescript": "^5.8.2", "typescript": "^5.9.3",
"typescript-eslint": "^8.28.0", "typescript-eslint": "^8.47.0",
"typescript-transform-paths": "^3.5.5" "typescript-transform-paths": "^3.5.5",
"zip-local": "^0.3.5"
}, },
"packageManager": "pnpm@10.17.1", "packageManager": "pnpm@10.22.0",
"pnpm": { "pnpm": {
"patchedDependencies": { "patchedDependencies": {
"eslint@9.20.1": "patches/eslint@9.20.1.patch",
"@types/less@3.0.8": "patches/@types__less@3.0.8.patch" "@types/less@3.0.8": "patches/@types__less@3.0.8.patch"
}, },
"peerDependencyRules": { "peerDependencyRules": {

View file

@ -47,3 +47,5 @@ export interface Activity {
size?: [number, number]; size?: [number, number];
}; };
} }
export type OnlineStatus = "online" | "idle" | "dnd" | "invisible" | "offline" | "unknown" | "streaming";

View file

@ -0,0 +1,26 @@
import { Activity, OnlineStatus } from "../common";
import { FluxStore } from "./FluxStore";
export interface UserAndActivity {
userId: string;
activity: Activity;
}
export type DiscordPlatform = "desktop" | "mobile" | "web" | "embedded";
export class PresenceStore extends FluxStore {
findActivity(userId: string, predicate: (activity: Activity) => boolean, guildId?: string): Activity | undefined;
getActivities(userId: string, guildId?: string): Activity[];
getActivityMetadata(userId: string): any;
getAllApplicationActivities(applicationId: string): UserAndActivity[];
getApplicationActivity(userId: string, applicationId: string, guildId?: string): Activity | null;
getClientStatus(userId: string): Record<DiscordPlatform, OnlineStatus>;
getHiddenActivities(): any;
/** literally just getActivities(...)[0] */
getPrimaryActivity(userId: string, guildId?: string): Activity | null;
getState(): any;
getStatus(userId: string, guildId?: string | null, defaultStatus?: OnlineStatus): OnlineStatus;
getUnfilteredActivities(userId: string, guildId?: string): Activity[];
getUserIds(): string[];
isMobileOnline(userId: string): boolean;
}

View file

@ -8,6 +8,7 @@ export * from "./GuildMemberStore";
export * from "./GuildRoleStore"; export * from "./GuildRoleStore";
export * from "./GuildStore"; export * from "./GuildStore";
export * from "./MessageStore"; export * from "./MessageStore";
export * from "./PresenceStore";
export * from "./RelationshipStore"; export * from "./RelationshipStore";
export * from "./MediaEngineStore"; export * from "./MediaEngineStore";
export * from "./StreamerModeStore"; export * from "./StreamerModeStore";

View file

@ -1,49 +0,0 @@
diff --git a/lib/rules/no-useless-escape.js b/lib/rules/no-useless-escape.js
index 0e0f6f09f2c35f3276173c08f832cde9f2cf56a0..7dc22851715f3574d935f513c1b5e35552985711 100644
--- a/lib/rules/no-useless-escape.js
+++ b/lib/rules/no-useless-escape.js
@@ -65,13 +65,31 @@ module.exports = {
escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character."
},
- schema: []
+ schema: [{
+ type: "object",
+ properties: {
+ extra: {
+ type: "string",
+ default: ""
+ },
+ extraCharClass: {
+ type: "string",
+ default: ""
+ },
+ },
+ additionalProperties: false
+ }]
},
create(context) {
+ const options = context.options[0] || {};
+ const { extra, extraCharClass } = options;
const sourceCode = context.sourceCode;
const parser = new RegExpParser();
+ const NON_CHARCLASS_ESCAPES = union(REGEX_NON_CHARCLASS_ESCAPES, new Set(extra));
+ const CHARCLASS_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set(extraCharClass));
+
/**
* Reports a node
* @param {ASTNode} node The node to report
@@ -200,9 +218,9 @@ module.exports = {
let allowedEscapes;
if (characterClassStack.length) {
- allowedEscapes = unicodeSets ? REGEX_CLASSSET_CHARACTER_ESCAPES : REGEX_GENERAL_ESCAPES;
+ allowedEscapes = unicodeSets ? REGEX_CLASSSET_CHARACTER_ESCAPES : CHARCLASS_ESCAPES;
} else {
- allowedEscapes = REGEX_NON_CHARCLASS_ESCAPES;
+ allowedEscapes = NON_CHARCLASS_ESCAPES;
}
if (allowedEscapes.has(escapedChar)) {
return;

1392
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -340,7 +340,7 @@ page.on("console", async e => {
}); });
page.on("error", e => logStderr("[Error]", e.message)); page.on("error", e => logStderr("[Error]", e.message));
page.on("pageerror", e => { page.on("pageerror", (e: any) => {
if (e.message.includes("Sentry successfully disabled")) return; if (e.message.includes("Sentry successfully disabled")) return;
if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module") && !/^.{1,2}$/.test(e.message)) { if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module") && !/^.{1,2}$/.test(e.message)) {

View file

@ -20,34 +20,32 @@
import "~plugins"; import "~plugins";
export * as Api from "./api"; export * as Api from "./api";
export * as Plugins from "./api/PluginManager";
export * as Components from "./components"; export * as Components from "./components";
export * as Plugins from "./plugins";
export * as Util from "./utils"; export * as Util from "./utils";
export * as QuickCss from "./utils/quickCss";
export * as Updater from "./utils/updater"; export * as Updater from "./utils/updater";
export * as Webpack from "./webpack"; export * as Webpack from "./webpack";
export * as WebpackPatcher from "./webpack/patchWebpack"; export * as WebpackPatcher from "./webpack/patchWebpack";
export { PlainSettings, Settings }; export { PlainSettings, Settings };
import "./utils/quickCss";
import "./webpack/patchWebpack";
import { addVencordUiStyles } from "@components/css"; import { addVencordUiStyles } from "@components/css";
import { openUpdaterModal } from "@components/settings/tabs/updater"; import { openUpdaterModal } from "@components/settings/tabs/updater";
import { debounce } from "@shared/debounce";
import { IS_WINDOWS } from "@utils/constants"; import { IS_WINDOWS } from "@utils/constants";
import { createAndAppendStyle } from "@utils/css"; import { createAndAppendStyle } from "@utils/css";
import { StartAt } from "@utils/types"; import { StartAt } from "@utils/types";
import { get as dsGet } from "./api/DataStore"; import { get as dsGet } from "./api/DataStore";
import { NotificationData, showNotification } from "./api/Notifications"; import { NotificationData, showNotification } from "./api/Notifications";
import { PlainSettings, Settings } from "./api/Settings"; import { initPluginManager, PMLogger, startAllPlugins } from "./api/PluginManager";
import { patches, PMLogger, startAllPlugins } from "./plugins"; import { PlainSettings, Settings, SettingsStore } from "./api/Settings";
import { getCloudSettings, putCloudSettings } from "./api/SettingsSync/cloudSync";
import { localStorage } from "./utils/localStorage"; import { localStorage } from "./utils/localStorage";
import { relaunch } from "./utils/native"; import { relaunch } from "./utils/native";
import { getCloudSettings, putCloudSettings } from "./utils/settingsSync";
import { checkForUpdates, update, UpdateLogger } from "./utils/updater"; import { checkForUpdates, update, UpdateLogger } from "./utils/updater";
import { onceReady } from "./webpack"; import { onceReady } from "./webpack";
import { SettingsRouter } from "./webpack/common"; import { SettingsRouter } from "./webpack/common";
import { patches } from "./webpack/patchWebpack";
if (IS_REPORTER) { if (IS_REPORTER) {
require("./debug/runReporter"); require("./debug/runReporter");
@ -108,6 +106,18 @@ async function syncSettings() {
}); });
} }
} }
const saveSettingsOnFrequentAction = debounce(async () => {
if (Settings.cloud.settingsSync && Settings.cloud.authenticated) {
await putCloudSettings();
delete localStorage.Vencord_settingsDirty;
}
}, 60_000);
SettingsStore.addGlobalChangeListener(() => {
localStorage.Vencord_settingsDirty = true;
saveSettingsOnFrequentAction();
});
} }
let notifiedForUpdatesThisSession = false; let notifiedForUpdatesThisSession = false;
@ -179,6 +189,7 @@ async function init() {
} }
} }
initPluginManager();
startAllPlugins(StartAt.Init); startAllPlugins(StartAt.Init);
init(); init();

View file

@ -17,7 +17,7 @@
*/ */
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import BadgeAPIPlugin from "plugins/_api/badges"; import BadgeAPIPlugin from "@plugins/_api/badges";
import { ComponentType, HTMLProps } from "react"; import { ComponentType, HTMLProps } from "react";
export const enum BadgePosition { export const enum BadgePosition {

View file

@ -28,8 +28,8 @@ import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/Messag
import { addNicknameIcon, removeNicknameIcon } from "@api/NicknameIcons"; import { addNicknameIcon, removeNicknameIcon } from "@api/NicknameIcons";
import { Settings, SettingsStore } from "@api/Settings"; import { Settings, SettingsStore } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles"; import { disableStyle, enableStyle } from "@api/Styles";
import { makeLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { onlyOnce } from "@utils/onlyOnce";
import { canonicalizeFind, canonicalizeReplacement } from "@utils/patches"; import { canonicalizeFind, canonicalizeReplacement } from "@utils/patches";
import { Patch, Plugin, PluginDef, ReporterTestable, StartAt } from "@utils/types"; import { Patch, Plugin, PluginDef, ReporterTestable, StartAt } from "@utils/types";
import { FluxEvents } from "@vencord/discord-types"; import { FluxEvents } from "@vencord/discord-types";
@ -37,53 +37,29 @@ import { FluxDispatcher } from "@webpack/common";
import { patches } from "@webpack/patcher"; import { patches } from "@webpack/patcher";
import Plugins from "~plugins"; import Plugins from "~plugins";
export { Plugins as plugins };
import { traceFunction } from "../debug/Tracer"; import { traceFunction } from "../debug/Tracer";
const logger = new Logger("PluginManager", "#a6d189"); const logger = new Logger("PluginManager", "#a6d189");
export const PMLogger = logger; export const PMLogger = logger;
export const plugins = Plugins;
export { patches };
/** Whether we have subscribed to flux events of all the enabled plugins when FluxDispatcher was ready */ /** Whether we have subscribed to flux events of all the enabled plugins when FluxDispatcher was ready */
let enabledPluginsSubscribedFlux = false; let enabledPluginsSubscribedFlux = false;
const subscribedFluxEventsPlugins = new Set<string>(); const subscribedFluxEventsPlugins = new Set<string>();
const pluginsValues = Object.values(Plugins);
const settings = Settings.plugins;
/**
* Whether a plugin is required (or dependency of another enabled plugin)
*/
export function isPluginRequired(name: string) {
const p = Plugins[name];
if (!p) return false;
return p.required || p.isDependency;
}
/**
* A map of plugin names to the plugins that depend on them
*/
export const calculatePluginDependencyMap = makeLazy(() => {
const dependencies: Record<string, string[]> = {};
for (const plugin in Plugins) {
const deps = Plugins[plugin].dependencies;
if (deps) {
for (const dep of deps) {
dependencies[dep] ??= [];
dependencies[dep].push(plugin);
}
}
}
return dependencies;
});
export function isPluginEnabled(p: string) { export function isPluginEnabled(p: string) {
return ( return (
isPluginRequired(p) || Plugins[p]?.required ||
settings[p]?.enabled Plugins[p]?.isDependency ||
Settings.plugins[p]?.enabled
) ?? false;
}
export function isPluginRequired(p: string) {
return (
Plugins[p]?.required
) ?? false; ) ?? false;
} }
@ -122,94 +98,6 @@ function isReporterTestable(p: Plugin, part: ReporterTestable) {
: (p.reporterTestable & part) === part; : (p.reporterTestable & part) === part;
} }
const pluginKeysToBind: Array<keyof PluginDef & `${"on" | "render"}${string}`> = [
"onBeforeMessageEdit", "onBeforeMessageSend", "onMessageClick",
"renderChatBarButton", "renderMemberListDecorator", "renderNicknameIcon", "renderMessageAccessory", "renderMessageDecoration", "renderMessagePopoverButton"
];
const neededApiPlugins = new Set<string>();
// First round-trip to mark and force enable dependencies
//
// FIXME: might need to revisit this if there's ever nested (dependencies of dependencies) dependencies since this only
// goes for the top level and their children, but for now this works okay with the current API plugins
for (const p of pluginsValues) if (isPluginEnabled(p.name)) {
p.dependencies?.forEach(d => {
const dep = Plugins[d];
if (!dep) {
const error = new Error(`Plugin ${p.name} has unresolved dependency ${d}`);
if (IS_DEV) {
throw error;
}
logger.warn(error);
return;
}
settings[d].enabled = true;
dep.isDependency = true;
});
if (p.userProfileBadge) {
if (!p.userProfileBadges) {
p.userProfileBadges = [p.userProfileBadge];
} else {
p.userProfileBadges = [p.userProfileBadge, ...p.userProfileBadges];
}
}
if (p.commands?.length) neededApiPlugins.add("CommandsAPI");
if (p.onBeforeMessageEdit || p.onBeforeMessageSend || p.onMessageClick) neededApiPlugins.add("MessageEventsAPI");
if (p.renderChatBarButton) neededApiPlugins.add("ChatInputButtonAPI");
if (p.renderMemberListDecorator) neededApiPlugins.add("MemberListDecoratorsAPI");
if (p.renderNicknameIcon) neededApiPlugins.add("NicknameIconsAPI");
if (p.renderMessageAccessory) neededApiPlugins.add("MessageAccessoriesAPI");
if (p.renderMessageDecoration) neededApiPlugins.add("MessageDecorationsAPI");
if (p.renderMessagePopoverButton) neededApiPlugins.add("MessagePopoverAPI");
if (p.userProfileBadges) neededApiPlugins.add("BadgeAPI");
for (const key of pluginKeysToBind) {
p[key] &&= p[key].bind(p) as any;
}
}
for (const p of neededApiPlugins) {
Plugins[p].isDependency = true;
settings[p].enabled = true;
}
for (const p of pluginsValues) {
if (p.settings) {
p.options ??= {};
p.settings.pluginName = p.name;
for (const name in p.settings.def) {
const def = p.settings.def[name];
const checks = p.settings.checks?.[name];
p.options[name] = { ...def, ...checks };
}
}
if (p.options) {
for (const name in p.options) {
const opt = p.options[name];
if (opt.onChange != null) {
SettingsStore.addChangeListener(`plugins.${p.name}.${name}`, opt.onChange);
}
}
}
if (p.patches && isPluginEnabled(p.name)) {
if (!IS_REPORTER || isReporterTestable(p, ReporterTestable.Patches)) {
for (const patch of p.patches) {
addPatch(patch, p.name);
}
}
}
}
export const startAllPlugins = traceFunction("startAllPlugins", function startAllPlugins(target: StartAt) { export const startAllPlugins = traceFunction("startAllPlugins", function startAllPlugins(target: StartAt) {
logger.info(`Starting plugins (stage ${target})`); logger.info(`Starting plugins (stage ${target})`);
for (const name in Plugins) { for (const name in Plugins) {
@ -225,6 +113,7 @@ export const startAllPlugins = traceFunction("startAllPlugins", function startAl
}); });
export function startDependenciesRecursive(p: Plugin) { export function startDependenciesRecursive(p: Plugin) {
const settings = Settings.plugins;
let restartNeeded = false; let restartNeeded = false;
const failures: string[] = []; const failures: string[] = [];
@ -419,3 +308,88 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
return true; return true;
}, p => `stopPlugin ${p.name}`); }, p => `stopPlugin ${p.name}`);
export const initPluginManager = onlyOnce(function init() {
const pluginsValues = Object.values(Plugins);
const settings = Settings.plugins;
const pluginKeysToBind: Array<keyof PluginDef & `${"on" | "render"}${string}`> = [
"onBeforeMessageEdit", "onBeforeMessageSend", "onMessageClick",
"renderChatBarButton", "renderMemberListDecorator", "renderMessageAccessory", "renderMessageDecoration", "renderMessagePopoverButton"
];
const neededApiPlugins = new Set<string>();
// First round-trip to mark and force enable dependencies
//
// FIXME: might need to revisit this if there's ever nested (dependencies of dependencies) dependencies since this only
// goes for the top level and their children, but for now this works okay with the current API plugins
for (const p of pluginsValues) if (isPluginEnabled(p.name)) {
p.dependencies?.forEach(d => {
const dep = Plugins[d];
if (!dep) {
const error = new Error(`Plugin ${p.name} has unresolved dependency ${d}`);
if (IS_DEV) {
throw error;
}
logger.warn(error);
return;
}
settings[d].enabled = true;
dep.isDependency = true;
});
if (p.commands?.length) neededApiPlugins.add("CommandsAPI");
if (p.onBeforeMessageEdit || p.onBeforeMessageSend || p.onMessageClick) neededApiPlugins.add("MessageEventsAPI");
if (p.renderChatBarButton) neededApiPlugins.add("ChatInputButtonAPI");
if (p.renderMemberListDecorator) neededApiPlugins.add("MemberListDecoratorsAPI");
if (p.renderNicknameIcon) neededApiPlugins.add("NicknameIconsAPI");
if (p.renderMessageAccessory) neededApiPlugins.add("MessageAccessoriesAPI");
if (p.renderMessageDecoration) neededApiPlugins.add("MessageDecorationsAPI");
if (p.renderMessagePopoverButton) neededApiPlugins.add("MessagePopoverAPI");
if (p.userProfileBadge) neededApiPlugins.add("BadgeAPI");
for (const key of pluginKeysToBind) {
p[key] &&= p[key].bind(p) as any;
}
}
for (const p of neededApiPlugins) {
Plugins[p].isDependency = true;
settings[p].enabled = true;
}
for (const p of pluginsValues) {
if (p.settings) {
p.options ??= {};
p.settings.pluginName = p.name;
for (const name in p.settings.def) {
const def = p.settings.def[name];
const checks = p.settings.checks?.[name];
p.options[name] = { ...def, ...checks };
}
}
if (p.options) {
for (const name in p.options) {
const opt = p.options[name];
if (opt.onChange != null) {
SettingsStore.addChangeListener(`plugins.${p.name}.${name}`, opt.onChange);
}
}
}
if (p.patches && isPluginEnabled(p.name)) {
if (!IS_REPORTER || isReporterTestable(p, ReporterTestable.Patches)) {
for (const patch of p.patches) {
addPatch(patch, p.name);
}
}
}
}
});

View file

@ -16,12 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { debounce } from "@shared/debounce";
import { SettingsStore as SettingsStoreClass } from "@shared/SettingsStore"; import { SettingsStore as SettingsStoreClass } from "@shared/SettingsStore";
import { localStorage } from "@utils/localStorage";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { mergeDefaults } from "@utils/mergeDefaults"; import { mergeDefaults } from "@utils/mergeDefaults";
import { putCloudSettings } from "@utils/settingsSync";
import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types"; import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types";
import { React, useEffect } from "@webpack/common"; import { React, useEffect } from "@webpack/common";
@ -129,14 +126,6 @@ const DefaultSettings: Settings = {
const settings = !IS_REPORTER ? VencordNative.settings.get() : {} as Settings; const settings = !IS_REPORTER ? VencordNative.settings.get() : {} as Settings;
mergeDefaults(settings, DefaultSettings); mergeDefaults(settings, DefaultSettings);
const saveSettingsOnFrequentAction = debounce(async () => {
if (Settings.cloud.settingsSync && Settings.cloud.authenticated) {
await putCloudSettings();
delete localStorage.Vencord_settingsDirty;
}
}, 60_000);
export const SettingsStore = new SettingsStoreClass(settings, { export const SettingsStore = new SettingsStoreClass(settings, {
readOnly: true, readOnly: true,
getDefaultValue({ getDefaultValue({
@ -179,8 +168,6 @@ export const SettingsStore = new SettingsStoreClass(settings, {
if (!IS_REPORTER) { if (!IS_REPORTER) {
SettingsStore.addGlobalChangeListener((_, path) => { SettingsStore.addGlobalChangeListener((_, path) => {
SettingsStore.plain.cloud.settingsSyncVersion = Date.now(); SettingsStore.plain.cloud.settingsSyncVersion = Date.now();
localStorage.Vencord_settingsDirty = true;
saveSettingsOnFrequentAction();
VencordNative.settings.set(SettingsStore.plain, path); VencordNative.settings.set(SettingsStore.plain, path);
}); });
} }

View file

@ -1,30 +1,17 @@
/* /*
* Vencord, a modification for Discord's desktop app * Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors * Copyright (c) 2025 Vendicated and contributors
* * SPDX-License-Identifier: GPL-3.0-or-later
* This program is free software: you can redistribute it and/or modify */
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import * as DataStore from "@api/DataStore"; import * as DataStore from "@api/DataStore";
import { showNotification } from "@api/Notifications"; import { showNotification } from "@api/Notifications";
import { Settings } from "@api/Settings"; import { Settings } from "@api/Settings";
import { Logger } from "@utils/Logger";
import { openModal } from "@utils/modal";
import { OAuth2AuthorizeModal, UserStore } from "@webpack/common"; import { OAuth2AuthorizeModal, UserStore } from "@webpack/common";
import { Logger } from "./Logger"; export const logger = new Logger("SettingsSync:CloudSetup", "#39b7e0");
import { openModal } from "./modal";
export const cloudLogger = new Logger("Cloud", "#39b7e0");
export const getCloudUrl = () => new URL(Settings.cloud.url); export const getCloudUrl = () => new URL(Settings.cloud.url);
const getCloudUrlOrigin = () => getCloudUrl().origin; const getCloudUrlOrigin = () => getCloudUrl().origin;
@ -111,7 +98,7 @@ export async function authorizeCloud() {
}); });
const { secret } = await res.json(); const { secret } = await res.json();
if (secret) { if (secret) {
cloudLogger.info("Authorized with secret"); logger.info("Authorized with secret");
await setAuthorization(secret); await setAuthorization(secret);
showNotification({ showNotification({
title: "Cloud Integration", title: "Cloud Integration",
@ -126,7 +113,7 @@ export async function authorizeCloud() {
Settings.cloud.authenticated = false; Settings.cloud.authenticated = false;
} }
} catch (e: any) { } catch (e: any) {
cloudLogger.error("Failed to authorize", e); logger.error("Failed to authorize", e);
showNotification({ showNotification({
title: "Cloud Integration", title: "Cloud Integration",
body: `Setup failed (${e.toString()}).` body: `Setup failed (${e.toString()}).`

View file

@ -0,0 +1,224 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { showNotification } from "@api/Notifications";
import { PlainSettings, Settings } from "@api/Settings";
import { Logger } from "@utils/Logger";
import { relaunch } from "@utils/native";
import { SettingsRouter } from "@webpack/common";
import { deflateSync, inflateSync } from "fflate";
import { deauthorizeCloud, getCloudAuth, getCloudUrl } from "./cloudSetup";
import { exportSettings, importSettings } from "./offline";
const logger = new Logger("SettingsSync:Cloud", "#39b7e0");
export async function putCloudSettings(manual?: boolean) {
const settings = await exportSettings({ minify: true });
try {
const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
method: "PUT",
headers: {
Authorization: await getCloudAuth(),
"Content-Type": "application/octet-stream"
},
body: deflateSync(new TextEncoder().encode(settings)) as Uint8Array<ArrayBuffer>
});
if (!res.ok) {
logger.error(`Failed to sync up, API returned ${res.status}`);
showNotification({
title: "Cloud Settings",
body: `Could not synchronize settings to cloud (API returned ${res.status}).`,
color: "var(--red-360)"
});
return;
}
const { written } = await res.json();
PlainSettings.cloud.settingsSyncVersion = written;
VencordNative.settings.set(PlainSettings);
logger.info("Settings uploaded to cloud successfully");
if (manual) {
showNotification({
title: "Cloud Settings",
body: "Synchronized settings to the cloud!",
noPersist: true,
});
}
} catch (e: any) {
logger.error("Failed to sync up", e);
showNotification({
title: "Cloud Settings",
body: `Could not synchronize settings to the cloud (${e.toString()}).`,
color: "var(--red-360)"
});
}
}
export async function getCloudSettings(shouldNotify = true, force = false) {
try {
const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
method: "GET",
headers: {
Authorization: await getCloudAuth(),
Accept: "application/octet-stream",
"If-None-Match": Settings.cloud.settingsSyncVersion.toString()
},
});
if (res.status === 401) {
// User switched to an account that isn't connected to cloud
showNotification({
title: "Cloud Settings",
body: "Cloud sync was disabled because this account isn't connected to the cloud App. You can enable it again by connecting this account in Cloud Settings. (note: it will store your preferences separately)",
color: "var(--yellow-360)",
onClick: () => SettingsRouter.open("VencordCloud")
});
// Disable cloud sync globally
Settings.cloud.authenticated = false;
return false;
}
if (res.status === 404) {
logger.info("No settings on the cloud");
if (shouldNotify)
showNotification({
title: "Cloud Settings",
body: "There are no settings in the cloud.",
noPersist: true
});
return false;
}
if (res.status === 304) {
logger.info("Settings up to date");
if (shouldNotify)
showNotification({
title: "Cloud Settings",
body: "Your settings are up to date.",
noPersist: true
});
return false;
}
if (!res.ok) {
logger.error(`Failed to sync down, API returned ${res.status}`);
showNotification({
title: "Cloud Settings",
body: `Could not synchronize settings from the cloud (API returned ${res.status}).`,
color: "var(--red-360)"
});
return false;
}
const written = Number(res.headers.get("etag")!);
const localWritten = Settings.cloud.settingsSyncVersion;
// don't need to check for written > localWritten because the server will return 304 due to if-none-match
if (!force && written < localWritten) {
if (shouldNotify)
showNotification({
title: "Cloud Settings",
body: "Your local settings are newer than the cloud ones.",
noPersist: true,
});
return;
}
const data = await res.arrayBuffer();
const settings = new TextDecoder().decode(inflateSync(new Uint8Array(data)));
await importSettings(settings);
// sync with server timestamp instead of local one
PlainSettings.cloud.settingsSyncVersion = written;
VencordNative.settings.set(PlainSettings);
logger.info("Settings loaded from cloud successfully");
if (shouldNotify)
showNotification({
title: "Cloud Settings",
body: "Your settings have been updated! Click here to restart to fully apply changes!",
color: "var(--green-360)",
onClick: IS_WEB ? () => location.reload() : relaunch,
noPersist: true
});
return true;
} catch (e: any) {
logger.error("Failed to sync down", e);
showNotification({
title: "Cloud Settings",
body: `Could not synchronize settings from the cloud (${e.toString()}).`,
color: "var(--red-360)"
});
return false;
}
}
export async function deleteCloudSettings() {
try {
const res = await fetch(new URL("/v1/settings", getCloudUrl()), {
method: "DELETE",
headers: { Authorization: await getCloudAuth() },
});
if (!res.ok) {
logger.error(`Failed to delete, API returned ${res.status}`);
showNotification({
title: "Cloud Settings",
body: `Could not delete settings (API returned ${res.status}).`,
color: "var(--red-360)"
});
return;
}
logger.info("Settings deleted from cloud successfully");
showNotification({
title: "Cloud Settings",
body: "Settings deleted from cloud!",
color: "var(--green-360)"
});
} catch (e: any) {
logger.error("Failed to delete", e);
showNotification({
title: "Cloud Settings",
body: `Could not delete settings (${e.toString()}).`,
color: "var(--red-360)"
});
}
}
export async function eraseAllCloudData() {
const res = await fetch(new URL("/v1/", getCloudUrl()), {
method: "DELETE",
headers: { Authorization: await getCloudAuth() }
});
if (!res.ok) {
logger.error(`Failed to erase data, API returned ${res.status}`);
showNotification({
title: "Cloud Integrations",
body: `Could not erase all data (API returned ${res.status}), please contact support.`,
color: "var(--red-360)"
});
return;
}
Settings.cloud.authenticated = false;
await deauthorizeCloud();
showNotification({
title: "Cloud Integrations",
body: "Successfully erased all data.",
color: "var(--green-360)"
});
}

View file

@ -0,0 +1,95 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { PlainSettings } from "@api/Settings";
import { Logger } from "@utils/Logger";
import { chooseFile, saveFile } from "@utils/web";
import { moment, Toasts } from "@webpack/common";
const toast = (type: string, message: string) =>
Toasts.show({
type,
message,
id: Toasts.genId()
});
const toastSuccess = () =>
toast(Toasts.Type.SUCCESS, "Settings successfully imported. Restart to apply changes!");
const toastFailure = (err: any) =>
toast(Toasts.Type.FAILURE, `Failed to import settings: ${String(err)}`);
const logger = new Logger("SettingsSync:Offline", "#39b7e0");
export async function importSettings(data: string) {
try {
var parsed = JSON.parse(data);
} catch (err) {
console.log(data);
throw new Error("Failed to parse JSON: " + String(err));
}
if ("settings" in parsed && "quickCss" in parsed) {
Object.assign(PlainSettings, parsed.settings);
await VencordNative.settings.set(parsed.settings);
await VencordNative.quickCss.set(parsed.quickCss);
} else
throw new Error("Invalid Settings. Is this even a Vencord Settings file?");
}
export async function exportSettings({ minify }: { minify?: boolean; } = {}) {
const settings = VencordNative.settings.get();
const quickCss = await VencordNative.quickCss.get();
return JSON.stringify({ settings, quickCss }, null, minify ? undefined : 4);
}
export async function downloadSettingsBackup() {
const filename = `vencord-settings-backup-${moment().format("YYYY-MM-DD")}.json`;
const backup = await exportSettings();
const data = new TextEncoder().encode(backup);
if (IS_DISCORD_DESKTOP) {
DiscordNative.fileManager.saveWithDialog(data, filename);
} else {
saveFile(new File([data], filename, { type: "application/json" }));
}
}
export async function uploadSettingsBackup(showToast = true): Promise<void> {
if (IS_DISCORD_DESKTOP) {
const [file] = await DiscordNative.fileManager.openFiles({
filters: [
{ name: "Vencord Settings Backup", extensions: ["json"] },
{ name: "all", extensions: ["*"] }
]
});
if (file) {
try {
await importSettings(new TextDecoder().decode(file.data));
if (showToast) toastSuccess();
} catch (err) {
logger.error(err);
if (showToast) toastFailure(err);
}
}
} else {
const file = await chooseFile("application/json");
if (!file) return;
const reader = new FileReader();
reader.onload = async () => {
try {
await importSettings(reader.result as string);
if (showToast) toastSuccess();
} catch (err) {
logger.error(err);
if (showToast) toastFailure(err);
}
};
reader.readAsText(file);
}
}

View file

@ -16,9 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import type { MapValue } from "type-fest/source/entry"; export interface Style {
name: string;
export type Style = MapValue<typeof VencordStyles>; source: string;
classNames: Record<string, string>;
dom: HTMLStyleElement | null;
}
export const styleMap = window.VencordStyles ??= new Map(); export const styleMap = window.VencordStyles ??= new Map();

View file

@ -17,9 +17,8 @@
*/ */
import { Settings, SettingsStore } from "@api/Settings"; import { Settings, SettingsStore } from "@api/Settings";
import { ThemeStore } from "@webpack/common"; import { createAndAppendStyle } from "@utils/css";
import { ThemeStore } from "@vencord/discord-types";
import { createAndAppendStyle } from "./css";
let style: HTMLStyleElement; let style: HTMLStyleElement;
let themesStyle: HTMLStyleElement; let themesStyle: HTMLStyleElement;
@ -54,14 +53,15 @@ async function initThemes() {
const { enabledThemeLinks, enabledThemes } = Settings; const { enabledThemeLinks, enabledThemes } = Settings;
const enabledlinks: string[] = [...enabledThemeLinks]; const { ThemeStore } = require("@webpack/common/stores") as typeof import("@webpack/common/stores");
// "darker" and "midnight" both count as dark // "darker" and "midnight" both count as dark
// This function is first called on DOMContentLoaded, so ThemeStore may not have been loaded yet // This function is first called on DOMContentLoaded, so ThemeStore may not have been loaded yet
const activeTheme = ThemeStore == null const activeTheme = ThemeStore == null
? undefined ? undefined
: ThemeStore.theme === "light" ? "light" : "dark"; : ThemeStore.theme === "light" ? "light" : "dark";
const links = enabledlinks const links = enabledThemeLinks
.map(rawLink => { .map(rawLink => {
const match = /^@(light|dark) (.*)/.exec(rawLink); const match = /^@(light|dark) (.*)/.exec(rawLink);
if (!match) return rawLink; if (!match) return rawLink;
@ -103,16 +103,16 @@ document.addEventListener("DOMContentLoaded", () => {
} }
}, { once: true }); }, { once: true });
export function initQuickCssThemeStore() { export function initQuickCssThemeStore(themeStore: ThemeStore) {
if (IS_USERSCRIPT) return; if (IS_USERSCRIPT) return;
initThemes(); initThemes();
let currentTheme = ThemeStore.theme; let currentTheme = themeStore.theme;
ThemeStore.addChangeListener(() => { themeStore.addChangeListener(() => {
if (currentTheme === ThemeStore.theme) return; if (currentTheme === themeStore.theme) return;
currentTheme = ThemeStore.theme; currentTheme = themeStore.theme;
initThemes(); initThemes();
}); });
} }

View file

@ -20,6 +20,8 @@ import { proxyLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { findModuleId, proxyLazyWebpack, wreq } from "@webpack"; import { findModuleId, proxyLazyWebpack, wreq } from "@webpack";
import { isPluginEnabled } from "./PluginManager";
interface UserSettingDefinition<T> { interface UserSettingDefinition<T> {
/** /**
* Get the setting value * Get the setting value
@ -45,7 +47,7 @@ interface UserSettingDefinition<T> {
export const UserSettings: Record<PropertyKey, UserSettingDefinition<any>> | undefined = proxyLazyWebpack(() => { export const UserSettings: Record<PropertyKey, UserSettingDefinition<any>> | undefined = proxyLazyWebpack(() => {
const modId = findModuleId('"textAndImages","renderSpoilers"'); const modId = findModuleId('"textAndImages","renderSpoilers"');
if (modId == null) return new Logger("UserSettingsAPI ").error("Didn't find settings module."); if (modId == null) return new Logger("UserSettingsAPI").error("Didn't find settings module.");
return wreq(modId as any); return wreq(modId as any);
}); });
@ -57,7 +59,7 @@ export const UserSettings: Record<PropertyKey, UserSettingDefinition<any>> | und
* @param name The name of the setting * @param name The name of the setting
*/ */
export function getUserSetting<T = any>(group: string, name: string): UserSettingDefinition<T> | undefined { export function getUserSetting<T = any>(group: string, name: string): UserSettingDefinition<T> | undefined {
if (!Vencord.Plugins.isPluginEnabled("UserSettingsAPI")) throw new Error("Cannot use UserSettingsAPI without setting as dependency."); if (!isPluginEnabled("UserSettingsAPI")) throw new Error("Cannot use UserSettingsAPI without setting it as a dependency.");
for (const key in UserSettings) { for (const key in UserSettings) {
const userSetting = UserSettings[key]; const userSetting = UserSettings[key];

View file

@ -31,9 +31,11 @@ import * as $MessageUpdater from "./MessageUpdater";
import * as $NicknameIcons from "./NicknameIcons"; import * as $NicknameIcons from "./NicknameIcons";
import * as $Notices from "./Notices"; import * as $Notices from "./Notices";
import * as $Notifications from "./Notifications"; import * as $Notifications from "./Notifications";
export * as PluginManager from "./PluginManager";
import * as $ServerList from "./ServerList"; import * as $ServerList from "./ServerList";
import * as $Settings from "./Settings"; import * as $Settings from "./Settings";
import * as $Styles from "./Styles"; import * as $Styles from "./Styles";
import * as $Themes from "./Themes";
import * as $UserSettings from "./UserSettings"; import * as $UserSettings from "./UserSettings";
/** /**
@ -137,6 +139,11 @@ export const MessageUpdater = $MessageUpdater;
*/ */
export const UserSettings = $UserSettings; export const UserSettings = $UserSettings;
/**
* Don't use this
*/
export const Themes = $Themes;
/** /**
* An API allowing you to add icons to the nickname, in profiles. * An API allowing you to add icons to the nickname, in profiles.
*/ */

View file

@ -12,6 +12,7 @@ import type { Button as DiscordButton } from "@vencord/discord-types";
import type { ComponentPropsWithRef } from "react"; import type { ComponentPropsWithRef } from "react";
import { OpenExternalIcon } from "./Icons"; import { OpenExternalIcon } from "./Icons";
import { Link } from "./Link";
const btnCls = classNameFactory("vc-btn-"); const btnCls = classNameFactory("vc-btn-");
const textBtnCls = classNameFactory("vc-text-btn-"); const textBtnCls = classNameFactory("vc-text-btn-");
@ -25,6 +26,11 @@ export type ButtonProps = ComponentPropsWithRef<"button"> & {
size?: ButtonSize; size?: ButtonSize;
}; };
export type LinkButtonProps = ComponentPropsWithRef<"a"> & {
size?: ButtonSize;
variant?: ButtonVariant;
};
export function Button({ variant = "primary", size = "medium", children, className, ...restProps }: ButtonProps) { export function Button({ variant = "primary", size = "medium", children, className, ...restProps }: ButtonProps) {
return ( return (
<button data-mana-component="button" className={classes(btnCls("base", variant, size), className)} {...restProps}> <button data-mana-component="button" className={classes(btnCls("base", variant, size), className)} {...restProps}>
@ -34,6 +40,15 @@ export function Button({ variant = "primary", size = "medium", children, classNa
); );
} }
export function LinkButton({ variant = "link", size = "medium", className, children, ...restProps }: LinkButtonProps) {
return (
<Link data-mana-component="button" className={classes(btnCls("base", variant, size), className)} {...restProps}>
{children}
<OpenExternalIcon className={btnCls("link-icon")} />
</Link>
);
}
export type TextButtonVariant = "primary" | "secondary" | "danger" | "link"; export type TextButtonVariant = "primary" | "secondary" | "danger" | "link";
export type TextButtonProps = ComponentPropsWithRef<"button"> & { export type TextButtonProps = ComponentPropsWithRef<"button"> & {

20
src/components/Card.css Normal file
View file

@ -0,0 +1,20 @@
.vc-card-base {
background: var(--card-primary-bg);
border-radius: var(--radius-sm, 8px)
}
.vc-card-normal {
border: 1px solid var(--border-subtle);
}
.vc-card-warning {
border: 1px solid var(--info-warning-foreground);
}
.vc-card-danger {
border: 1px solid var(--info-danger-foreground);
}
.vc-card-defaultPadding {
padding: 1em;
}

31
src/components/Card.tsx Normal file
View file

@ -0,0 +1,31 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./Card.css";
import { classNameFactory } from "@api/Styles";
import { classes } from "@utils/misc";
import { ComponentPropsWithRef } from "react";
const cl = classNameFactory("vc-card-");
export interface CardProps extends ComponentPropsWithRef<"div"> {
variant?: "normal" | "warning" | "danger";
/** Add a default padding of 1em to the card. This is implied if no className prop is passed */
defaultPadding?: boolean;
}
export function Card({ variant = "normal", defaultPadding, children, className, ...restProps }: CardProps) {
const addDefaultPadding = defaultPadding != null
? defaultPadding
: !className;
return (
<div className={classes(cl("base", variant, addDefaultPadding && "defaultPadding"), className)} {...restProps}>
{children}
</div>
);
}

View file

@ -16,22 +16,28 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import type { React } from "@webpack/common"; import type { CSSProperties, HTMLAttributes } from "react";
export interface FlexProps extends HTMLAttributes<HTMLDivElement> {
flexDirection?: CSSProperties["flexDirection"];
gap?: CSSProperties["gap"];
justifyContent?: CSSProperties["justifyContent"];
alignItems?: CSSProperties["alignItems"];
}
export function Flex({ flexDirection, gap = "1em", justifyContent, alignItems, children, style, ...restProps }: FlexProps) {
style ??= {};
Object.assign(style, {
display: "flex",
flexDirection,
gap,
justifyContent,
alignItems,
});
export function Flex(props: React.PropsWithChildren<{
flexDirection?: React.CSSProperties["flexDirection"];
style?: React.CSSProperties;
className?: string;
} & React.HTMLProps<HTMLDivElement>>) {
props.style ??= {};
props.style.display = "flex";
// TODO(ven): Remove me, what was I thinking??
props.style.gap ??= "1em";
props.style.flexDirection ||= props.flexDirection;
delete props.flexDirection;
return ( return (
<div {...props}> <div style={style} {...restProps}>
{props.children} {children}
</div> </div>
); );
} }

View file

@ -1,6 +1,6 @@
.vc-settings-theme-grid { .vc-settings-theme-grid {
display: grid; display: grid;
grid-gap: 16px; gap: 16px;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
} }
@ -37,13 +37,30 @@
margin-bottom: 16px; margin-bottom: 16px;
} }
.vc-settings-usercss-ie-buttons>div { .vc-settings-usercss-ie-buttons > div {
color: var(--interactive-normal); color: var(--interactive-normal);
opacity: .5; opacity: .5;
padding: 4px; padding: 4px;
} }
.vc-settings-usercss-ie-buttons>div:hover { .vc-settings-usercss-ie-buttons > div:hover {
color: var(--interactive-hover); color: var(--interactive-hover);
opacity: 1; opacity: 1;
} }
.vc-settings-csp-row {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
& a {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
line-height: 1.2em;
}
--custom-button-button-md-height: 26px;
}

View file

@ -5,6 +5,8 @@
*/ */
export * from "./BaseText"; export * from "./BaseText";
export * from "./Button";
export * from "./Card";
export * from "./CheckedTextInput"; export * from "./CheckedTextInput";
export * from "./CodeBlock"; export * from "./CodeBlock";
export * from "./Divider"; export * from "./Divider";

View file

@ -7,7 +7,7 @@
import "./QuickAction.css"; import "./QuickAction.css";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { Card } from "@webpack/common"; import { Card } from "@components/Card";
import type { ComponentType, PropsWithChildren, ReactNode } from "react"; import type { ComponentType, PropsWithChildren, ReactNode } from "react";
const cl = classNameFactory("vc-settings-quickActions-"); const cl = classNameFactory("vc-settings-quickActions-");

View file

@ -19,10 +19,11 @@
import "./SpecialCard.css"; import "./SpecialCard.css";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { Card } from "@components/Card";
import { Divider } from "@components/Divider"; import { Divider } from "@components/Divider";
import { Heading } from "@components/Heading"; import { Heading } from "@components/Heading";
import { Paragraph } from "@components/Paragraph"; import { Paragraph } from "@components/Paragraph";
import { Card, Clickable, React } from "@webpack/common"; import { Clickable } from "@webpack/common";
import type { PropsWithChildren } from "react"; import type { PropsWithChildren } from "react";
const cl = classNameFactory("vc-special-"); const cl = classNameFactory("vc-special-");

View file

@ -22,8 +22,8 @@ import { Flex } from "@components/Flex";
import { HeadingTertiary } from "@components/Heading"; import { HeadingTertiary } from "@components/Heading";
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
import { debounce } from "@shared/debounce"; import { debounce } from "@shared/debounce";
import { copyWithToast } from "@utils/discord";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { copyWithToast } from "@utils/misc";
import { stripIndent } from "@utils/text"; import { stripIndent } from "@utils/text";
import { ReplaceFn } from "@utils/types"; import { ReplaceFn } from "@utils/types";
import { search } from "@webpack"; import { search } from "@webpack";

View file

@ -5,10 +5,10 @@
*/ */
import { showNotice } from "@api/Notices"; import { showNotice } from "@api/Notices";
import { isPluginEnabled, startDependenciesRecursive, startPlugin, stopPlugin } from "@api/PluginManager";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { CogWheel, InfoIcon } from "@components/Icons"; import { CogWheel, InfoIcon } from "@components/Icons";
import { AddonCard } from "@components/settings/AddonCard"; import { AddonCard } from "@components/settings/AddonCard";
import { proxyLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { isObjectEmpty } from "@utils/misc"; import { isObjectEmpty } from "@utils/misc";
import { Plugin } from "@utils/types"; import { Plugin } from "@utils/types";
@ -21,10 +21,6 @@ import { openPluginModal } from "./PluginModal";
const logger = new Logger("PluginCard"); const logger = new Logger("PluginCard");
const cl = classNameFactory("vc-plugins-"); const cl = classNameFactory("vc-plugins-");
// Avoid circular dependency
const { startDependenciesRecursive, startPlugin, stopPlugin, isPluginEnabled } = proxyLazy(() => require("plugins") as typeof import("plugins"));
interface PluginCardProps extends React.HTMLProps<HTMLDivElement> { interface PluginCardProps extends React.HTMLProps<HTMLDivElement> {
plugin: Plugin; plugin: Plugin;
disabled?: boolean; disabled?: boolean;

View file

@ -19,8 +19,10 @@
import "./styles.css"; import "./styles.css";
import * as DataStore from "@api/DataStore"; import * as DataStore from "@api/DataStore";
import { isPluginEnabled, stopPlugin } from "@api/PluginManager";
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { Card } from "@components/Card";
import { Divider } from "@components/Divider"; import { Divider } from "@components/Divider";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { HeadingTertiary } from "@components/Heading"; import { HeadingTertiary } from "@components/Heading";
@ -28,12 +30,12 @@ import { Paragraph } from "@components/Paragraph";
import { SettingsTab } from "@components/settings"; import { SettingsTab } from "@components/settings";
import { debounce } from "@shared/debounce"; import { debounce } from "@shared/debounce";
import { ChangeList } from "@utils/ChangeList"; import { ChangeList } from "@utils/ChangeList";
import { proxyLazy } from "@utils/lazy"; import { isTruthy } from "@utils/guards";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { useAwaiter, useIntersection } from "@utils/react"; import { useAwaiter, useIntersection } from "@utils/react";
import { Alerts, Button, Card, lodash, Parser, React, Select, TextInput, Toasts, Tooltip, useMemo } from "@webpack/common"; import { Alerts, Button, lodash, Parser, React, Select, TextInput, Toasts, Tooltip, useMemo, useState } from "@webpack/common";
import { JSX } from "react"; import { JSX } from "react";
import Plugins, { ExcludedPlugins, PluginMeta } from "~plugins"; import Plugins, { ExcludedPlugins, PluginMeta } from "~plugins";
@ -42,9 +44,6 @@ import { PluginCard } from "./PluginCard";
import { openWarningModal } from "./PluginModal"; import { openWarningModal } from "./PluginModal";
import { StockPluginsCard, UserPluginsCard } from "./PluginStatCards"; import { StockPluginsCard, UserPluginsCard } from "./PluginStatCards";
// Avoid circular dependency
const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() => require("plugins"));
export const cl = classNameFactory("vc-plugins-"); export const cl = classNameFactory("vc-plugins-");
export const logger = new Logger("PluginSettings", "#a6d189"); export const logger = new Logger("PluginSettings", "#a6d189");
@ -100,8 +99,9 @@ const enum SearchStatus {
DISABLED, DISABLED,
EQUICORD, EQUICORD,
VENCORD, VENCORD,
CUSTOM,
NEW, NEW,
USER_PLUGINS,
API_PLUGINS
} }
export const ExcludedReasons: Record<"web" | "discordDesktop" | "vesktop" | "equibop" | "desktop" | "dev", string> = { export const ExcludedReasons: Record<"web" | "discordDesktop" | "vesktop" | "equibop" | "desktop" | "dev", string> = {
@ -114,8 +114,10 @@ export const ExcludedReasons: Record<"web" | "discordDesktop" | "vesktop" | "equ
}; };
function ExcludedPluginsList({ search }: { search: string; }) { function ExcludedPluginsList({ search }: { search: string; }) {
const matchingExcludedPlugins = Object.entries(ExcludedPlugins) const matchingExcludedPlugins = search
.filter(([name]) => name.toLowerCase().includes(search)); ? Object.entries(ExcludedPlugins)
.filter(([name]) => name.toLowerCase().includes(search))
: [];
return ( return (
<Paragraph className={Margins.top16}> <Paragraph className={Margins.top16}>
@ -169,12 +171,26 @@ export default function PluginSettings() {
}; };
}, []); }, []);
const depMap = Vencord.Plugins.calculatePluginDependencyMap(); const depMap = useMemo(() => {
const o = {} as Record<string, string[]>;
for (const plugin in Plugins) {
const deps = Plugins[plugin].dependencies;
if (deps) {
for (const dep of deps) {
o[dep] ??= [];
o[dep].push(plugin);
}
}
}
return o;
}, []);
const sortedPlugins = useMemo(() => Object.values(Plugins) const sortedPlugins = useMemo(() => Object.values(Plugins)
.sort((a, b) => a.name.localeCompare(b.name)), []); .sort((a, b) => a.name.localeCompare(b.name)), []);
const [searchValue, setSearchValue] = React.useState({ value: "", status: SearchStatus.ALL }); const hasUserPlugins = useMemo(() => !IS_STANDALONE && Object.values(PluginMeta).some(m => m.userPlugin), []);
const [searchValue, setSearchValue] = useState({ value: "", status: SearchStatus.ALL });
const search = searchValue.value.toLowerCase(); const search = searchValue.value.toLowerCase();
const onSearch = (query: string) => { const onSearch = (query: string) => {
@ -186,17 +202,32 @@ export default function PluginSettings() {
const pluginFilter = (plugin: typeof Plugins[keyof typeof Plugins]) => { const pluginFilter = (plugin: typeof Plugins[keyof typeof Plugins]) => {
const { status } = searchValue; const { status } = searchValue;
const enabled = Vencord.Plugins.isPluginEnabled(plugin.name); const enabled = isPluginEnabled(plugin.name);
const pluginMeta = PluginMeta[plugin.name];
const isEquicordPlugin = pluginMeta.folderName.startsWith("src/equicordplugins/") ?? false; switch (status) {
const isUserplugin = pluginMeta.userPlugin ?? false; case SearchStatus.DISABLED:
if (enabled) return false;
break;
case SearchStatus.ENABLED:
if (!enabled) return false;
break;
case SearchStatus.EQUICORD:
if (!PluginMeta[plugin.name].folderName.startsWith("src/equicordplugins/")) return false;
break;
case SearchStatus.VENCORD:
if (!PluginMeta[plugin.name].folderName.startsWith("src/plugins/")) return false;
break;
case SearchStatus.NEW:
if (!newPlugins?.includes(plugin.name)) return false;
break;
case SearchStatus.USER_PLUGINS:
if (!PluginMeta[plugin.name]?.userPlugin) return false;
break;
case SearchStatus.API_PLUGINS:
if (!plugin.name.endsWith("API")) return false;
break;
}
if (enabled && status === SearchStatus.DISABLED) return false;
if (!enabled && status === SearchStatus.ENABLED) return false;
if (status === SearchStatus.NEW && !newPlugins?.includes(plugin.name)) return false;
if (status === SearchStatus.EQUICORD && !isEquicordPlugin) return false;
if (status === SearchStatus.VENCORD && isEquicordPlugin) return false;
if (status === SearchStatus.CUSTOM && !isUserplugin) return false;
if (!search.length) return true; if (!search.length) return true;
return ( return (
@ -226,7 +257,7 @@ export default function PluginSettings() {
const plugins = [] as JSX.Element[]; const plugins = [] as JSX.Element[];
const requiredPlugins = [] as JSX.Element[]; const requiredPlugins = [] as JSX.Element[];
const showApi = searchValue.value.includes("API"); const showApi = searchValue.status === SearchStatus.API_PLUGINS;
for (const p of sortedPlugins) { for (const p of sortedPlugins) {
if (p.hidden || (!p.options && p.name.endsWith("API") && !showApi)) if (p.hidden || (!p.options && p.name.endsWith("API") && !showApi))
continue; continue;
@ -374,9 +405,10 @@ export default function PluginSettings() {
{ label: "Show Disabled", value: SearchStatus.DISABLED }, { label: "Show Disabled", value: SearchStatus.DISABLED },
{ label: "Show Equicord", value: SearchStatus.EQUICORD }, { label: "Show Equicord", value: SearchStatus.EQUICORD },
{ label: "Show Vencord", value: SearchStatus.VENCORD }, { label: "Show Vencord", value: SearchStatus.VENCORD },
...(totalUserPlugins > 0 ? [{ label: "Show Custom", value: SearchStatus.CUSTOM }] : []),
{ label: "Show New", value: SearchStatus.NEW }, { 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} serialize={String}
select={onStatusChange} select={onStatusChange}
isSelected={v => v === searchValue.status} isSelected={v => v === searchValue.status}

View file

@ -19,7 +19,7 @@
.vc-plugins-grid { .vc-plugins-grid {
margin-top: 16px; margin-top: 16px;
display: grid; display: grid;
grid-gap: 16px; gap: 16px;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
} }

View file

@ -71,7 +71,7 @@
.vc-cloud-settings-sync-grid { .vc-cloud-settings-sync-grid {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
grid-gap: 1em; gap: 1em;
} }
.vc-cloud-erase-data-danger-btn { .vc-cloud-erase-data-danger-btn {

View file

@ -16,70 +16,73 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { BaseText } from "@components/BaseText"; import { downloadSettingsBackup, uploadSettingsBackup } from "@api/SettingsSync/offline";
import { Card } from "@components/Card";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { Heading } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes } from "@utils/misc"; import { Button, Text } from "@webpack/common";
import { downloadSettingsBackup, uploadSettingsBackup } from "@utils/settingsSync";
import { Button, Card } from "@webpack/common";
function BackupAndRestoreTab() { function BackupAndRestoreTab() {
return ( return (
<SettingsTab title="Backup & Restore"> <SettingsTab title="Backup & Restore">
<Card className={classes("vc-settings-card", "vc-backup-restore-card")}> <Flex flexDirection="column" gap="0.5em">
<Flex flexDirection="column"> <Card variant="warning">
<strong>Warning</strong> <Heading tag="h4">Warning</Heading>
<span>Importing a settings file will overwrite your current settings.</span> <Paragraph>Importing a settings file will overwrite your current settings.</Paragraph>
</Card>
<Text variant="text-md/normal" className={Margins.bottom8}>
You can import and export your Vencord settings as a JSON file.
This allows you to easily transfer your settings to another device,
or recover your settings after reinstalling Vencord or Discord.
</Text>
<Heading tag="h4">Settings Export contains:</Heading>
<Text variant="text-md/normal" className={Margins.bottom8}>
<ul>
<li>&mdash; Custom QuickCSS</li>
<li>&mdash; Theme Links</li>
<li>&mdash; Plugin Settings</li>
</ul>
</Text>
<Flex>
<Button
onClick={() => uploadSettingsBackup()}
size={Button.Sizes.SMALL}
>
Import Settings
</Button>
<Button
onClick={() => downloadSettingsBackup("settings")}
size={Button.Sizes.SMALL}
>
Export Settings
</Button>
<Button
onClick={() => downloadSettingsBackup("plugins")}
size={Button.Sizes.SMALL}
>
Export Plugins
</Button>
<Button
onClick={() => downloadSettingsBackup("css")}
size={Button.Sizes.SMALL}
>
Export CSS
</Button>
<Button
onClick={() => downloadSettingsBackup("datastore")}
size={Button.Sizes.SMALL}
>
Export DataStores
</Button>
</Flex> </Flex>
</Card>
<BaseText size="md" className={Margins.bottom8}>
You can import and export your Equicord settings as a JSON file.
This allows you to easily transfer your settings to another device,
or recover your settings after reinstalling Equicord or Discord.
</BaseText>
<BaseText size="md" className={Margins.bottom8}>
Settings Export contains:
<ul>
<li>&mdash; Custom QuickCSS</li>
<li>&mdash; Theme Links</li>
<li>&mdash; Plugin Settings</li>
<li>&mdash; Plugin DataStores (e.g. Timezones or IRememberYou)</li>
</ul>
</BaseText>
<Flex>
<Button
onClick={() => uploadSettingsBackup()}
size={Button.Sizes.SMALL}
>
Import Settings
</Button>
<Button
onClick={() => downloadSettingsBackup("settings")}
size={Button.Sizes.SMALL}
>
Export Settings
</Button>
<Button
onClick={() => downloadSettingsBackup("plugins")}
size={Button.Sizes.SMALL}
>
Export Plugins
</Button>
<Button
onClick={() => downloadSettingsBackup("css")}
size={Button.Sizes.SMALL}
>
Export CSS
</Button>
<Button
onClick={() => downloadSettingsBackup("datastore")}
size={Button.Sizes.SMALL}
>
Export DataStores
</Button>
</Flex> </Flex>
</SettingsTab> </SettingsTab >
); );
} }

View file

@ -16,8 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { showNotification } from "@api/Notifications"; import { useSettings } from "@api/Settings";
import { Settings, useSettings } from "@api/Settings"; import { authorizeCloud, deauthorizeCloud } from "@api/SettingsSync/cloudSetup";
import { deleteCloudSettings, eraseAllCloudData, getCloudSettings, putCloudSettings } from "@api/SettingsSync/cloudSync";
import { CheckedTextInput } from "@components/CheckedTextInput"; import { CheckedTextInput } from "@components/CheckedTextInput";
import { Divider } from "@components/Divider"; import { Divider } from "@components/Divider";
import { FormSwitch } from "@components/FormSwitch"; import { FormSwitch } from "@components/FormSwitch";
@ -26,9 +27,7 @@ import { Heading } from "@components/Heading";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { Paragraph } from "@components/Paragraph"; import { Paragraph } from "@components/Paragraph";
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
import { authorizeCloud, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { deleteCloudSettings, getCloudSettings, putCloudSettings } from "@utils/settingsSync";
import { Alerts, Button, Tooltip, useState } from "@webpack/common"; import { Alerts, Button, Tooltip, useState } from "@webpack/common";
function validateUrl(url: string) { function validateUrl(url: string) {
@ -40,32 +39,6 @@ function validateUrl(url: string) {
} }
} }
async function eraseAllData() {
const res = await fetch(new URL("/v1/", getCloudUrl()), {
method: "DELETE",
headers: { Authorization: await getCloudAuth() }
});
if (!res.ok) {
cloudLogger.error(`Failed to erase data, API returned ${res.status}`);
showNotification({
title: "Cloud Integrations",
body: `Could not erase all data (API returned ${res.status}), please contact support.`,
color: "var(--red-360)"
});
return;
}
Settings.cloud.authenticated = false;
await deauthorizeCloud();
showNotification({
title: "Cloud Integrations",
body: "Successfully erased all data.",
color: "var(--green-360)"
});
}
function SettingsSyncSection() { function SettingsSyncSection() {
const { cloud } = useSettings(["cloud.authenticated", "cloud.settingsSync"]); const { cloud } = useSettings(["cloud.authenticated", "cloud.settingsSync"]);
const sectionEnabled = cloud.authenticated && cloud.settingsSync; const sectionEnabled = cloud.authenticated && cloud.settingsSync;
@ -196,7 +169,7 @@ function CloudTab() {
onClick={() => Alerts.show({ onClick={() => Alerts.show({
title: "Are you sure?", title: "Are you sure?",
body: "Once your data is erased, we cannot recover it. There's no going back!", body: "Once your data is erased, we cannot recover it. There's no going back!",
onConfirm: eraseAllData, onConfirm: eraseAllCloudData,
confirmText: "Erase it!", confirmText: "Erase it!",
confirmColor: "vc-cloud-erase-data-danger-btn", confirmColor: "vc-cloud-erase-data-danger-btn",
cancelText: "Nevermind" cancelText: "Nevermind"

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { Card } from "@components/Card";
import { ErrorCard } from "@components/ErrorCard"; import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
@ -12,7 +13,7 @@ import { Margins } from "@utils/margins";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { relaunch } from "@utils/native"; import { relaunch } from "@utils/native";
import { changes, checkForUpdates, update, updateError } from "@utils/updater"; import { changes, checkForUpdates, update, updateError } from "@utils/updater";
import { Alerts, Button, Card, React, Toasts, useState } from "@webpack/common"; import { Alerts, Button, React, Toasts, useState } from "@webpack/common";
import { runWithDispatch } from "./runWithDispatch"; import { runWithDispatch } from "./runWithDispatch";
@ -31,7 +32,7 @@ export function HashLink({ repo, hash, disabled = false }: { repo: string, hash:
export function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) { export function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) {
return ( return (
<Card style={{ padding: "0 0.5em" }}> <Card style={{ padding: "0 0.5em" }} defaultPadding={false}>
{updates.map(({ hash, author, message }) => ( {updates.map(({ hash, author, message }) => (
<div <div
key={hash} key={hash}

View file

@ -4,10 +4,10 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { DonateButton } from "@components/settings"; import { DonateButton } from "@components/settings/DonateButton";
import BadgeAPI from "@plugins/_api/badges";
import { DONOR_ROLE_ID, VC_GUILD_ID } from "@utils/constants"; import { DONOR_ROLE_ID, VC_GUILD_ID } from "@utils/constants";
import { Button, GuildMemberStore } from "@webpack/common"; import { Button, GuildMemberStore } from "@webpack/common";
import BadgeAPI from "plugins/_api/badges";
export const isDonor = (userId: string) => !!( export const isDonor = (userId: string) => !!(
BadgeAPI.getDonorBadges(userId)?.length > 0 BadgeAPI.getDonorBadges(userId)?.length > 0

View file

@ -9,12 +9,12 @@ import "./VencordTab.css";
import { openNotificationLogModal } from "@api/Notifications/notificationLog"; import { openNotificationLogModal } from "@api/Notifications/notificationLog";
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { Divider } from "@components/Divider"; import { Divider } from "@components/Divider";
import { Flex } from "@components/Flex";
import { FormSwitch } from "@components/FormSwitch"; import { FormSwitch } from "@components/FormSwitch";
import { Heading } from "@components/Heading"; import { Heading } from "@components/Heading";
import { FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from "@components/Icons"; import { FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from "@components/Icons";
import { Paragraph } from "@components/Paragraph"; import { Paragraph } from "@components/Paragraph";
import { openContributorModal, openPluginModal, SettingsTab, wrapTab } from "@components/settings"; import { DonateButton, InviteButton, openContributorModal, openPluginModal, SettingsTab, wrapTab } from "@components/settings";
import { DonateButton, InviteButton } from "@components/settings/DonateButton";
import { QuickAction, QuickActionCard } from "@components/settings/QuickAction"; import { QuickAction, QuickActionCard } from "@components/settings/QuickAction";
import { SpecialCard } from "@components/settings/SpecialCard"; import { SpecialCard } from "@components/settings/SpecialCard";
import { gitRemote } from "@shared/vencordUserAgent"; import { gitRemote } from "@shared/vencordUserAgent";
@ -22,7 +22,7 @@ import { DONOR_ROLE_ID, GUILD_ID, VC_DONOR_ROLE_ID, VC_GUILD_ID } from "@utils/c
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { identity, isAnyPluginDev } from "@utils/misc"; import { identity, isAnyPluginDev } from "@utils/misc";
import { relaunch } from "@utils/native"; import { relaunch } from "@utils/native";
import { Button, Flex, GuildMemberStore, React, Select, UserStore } from "@webpack/common"; import { Button, GuildMemberStore, React, Select, UserStore } from "@webpack/common";
import BadgeAPI from "plugins/_api/badges"; import BadgeAPI from "plugins/_api/badges";
import { openNotificationSettingsModal } from "./NotificationSettings"; import { openNotificationSettingsModal } from "./NotificationSettings";

View file

@ -4,12 +4,12 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { addPatch } from "@api/PluginManager";
import { initWs } from "@plugins/devCompanion.dev/initWs"; import { initWs } from "@plugins/devCompanion.dev/initWs";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import * as Webpack from "@webpack"; import * as Webpack from "@webpack";
import { getBuildNumber, patchTimings } from "webpack/patchWebpack"; import { getBuildNumber, patches, patchTimings } from "@webpack/patcher";
import { addPatch, patches } from "../plugins";
import { loadLazyChunks } from "./loadLazyChunks"; import { loadLazyChunks } from "./loadLazyChunks";
import { reporterData } from "./reporterData"; import { reporterData } from "./reporterData";

View file

@ -16,9 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import "./style.css"; import "./styles.css";
import { openNotificationLogModal } from "@api/Notifications/notificationLog"; import { openNotificationLogModal } from "@api/Notifications/notificationLog";
import { isPluginEnabled, plugins } from "@api/PluginManager";
import { migratePluginSettings, Settings, useSettings } from "@api/Settings"; import { migratePluginSettings, Settings, useSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
@ -34,8 +35,8 @@ function VencordPopout(onClose: () => void) {
const pluginEntries = [] as ReactNode[]; const pluginEntries = [] as ReactNode[];
for (const plugin of Object.values(Vencord.Plugins.plugins)) { for (const plugin of Object.values(plugins)) {
if (plugin.toolboxActions && Vencord.Plugins.isPluginEnabled(plugin.name)) { if (plugin.toolboxActions && isPluginEnabled(plugin.name)) {
pluginEntries.push( pluginEntries.push(
<Menu.MenuGroup <Menu.MenuGroup
label={plugin.name} label={plugin.name}

View file

@ -0,0 +1,16 @@
.vc-toolbox-btn,
.vc-toolbox-icon {
-webkit-app-region: no-drag;
}
.vc-toolbox-icon {
color: var(--interactive-normal);
}
.vc-toolbox-btn[class*="selected"] .vc-toolbox-icon {
color: var(--interactive-active);
}
.vc-toolbox-btn:hover .vc-toolbox-icon {
color: var(--interactive-hover);
}

View file

@ -25,8 +25,8 @@ import { CopyIcon, ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Ico
import { Paragraph } from "@components/Paragraph"; import { Paragraph } from "@components/Paragraph";
import { Span } from "@components/Span"; import { Span } from "@components/Span";
import { debounce } from "@shared/debounce"; import { debounce } from "@shared/debounce";
import { openImageModal } from "@utils/discord"; import { copyWithToast, openImageModal } from "@utils/discord";
import { classes, copyWithToast } from "@utils/misc"; import { classes } from "@utils/misc";
import { ContextMenuApi, FluxDispatcher, Menu, React, useEffect, useState, useStateFromStores } from "@webpack/common"; import { ContextMenuApi, FluxDispatcher, Menu, React, useEffect, useState, useStateFromStores } from "@webpack/common";
import { SeekBar } from "./SeekBar"; import { SeekBar } from "./SeekBar";

View file

@ -16,7 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { isPluginEnabled } from "@api/PluginManager";
import { Settings } from "@api/Settings"; import { Settings } from "@api/Settings";
import OpenInAppPlugin from "@plugins/openInApp";
import { findByProps, findByPropsLazy, proxyLazyWebpack } from "@webpack"; import { findByProps, findByPropsLazy, proxyLazyWebpack } from "@webpack";
import { Flux, FluxDispatcher } from "@webpack/common"; import { Flux, FluxDispatcher } from "@webpack/common";
@ -89,7 +91,7 @@ export const SpotifyStore = proxyLazyWebpack(() => {
public isSettingPosition = false; public isSettingPosition = false;
public openExternal(path: string) { public openExternal(path: string) {
const url = Settings.plugins.MusicControls.useSpotifyUris || Vencord.Plugins.isPluginEnabled("OpenInApp") const url = Settings.plugins.SpotifyControls.useSpotifyUris || isPluginEnabled(OpenInAppPlugin.name)
? "spotify:" + path.replaceAll("/", (_, idx) => idx === 0 ? "" : ":") ? "spotify:" + path.replaceAll("/", (_, idx) => idx === 0 ? "" : ":")
: "https://open.spotify.com" + path; : "https://open.spotify.com" + path;

9
src/globals.d.ts vendored
View file

@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Style } from "@api/Styles";
declare global { declare global {
/** /**
* This exists only at build time, so references to it in patches should insert it * This exists only at build time, so references to it in patches should insert it
@ -47,12 +49,7 @@ declare global {
export var VencordNative: typeof import("./VencordNative").default; export var VencordNative: typeof import("./VencordNative").default;
export var Vencord: typeof import("./Vencord"); export var Vencord: typeof import("./Vencord");
export var VencordStyles: Map<string, { export var VencordStyles: Map<string, Style>;
name: string;
source: string;
classNames: Record<string, string>;
dom: HTMLStyleElement | null;
}>;
export var appSettings: { export var appSettings: {
set(setting: string, v: any): void; set(setting: string, v: any): void;
}; };

View file

@ -22,8 +22,9 @@ import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Bad
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { openContributorModal } from "@components/settings/tabs"; import { openContributorModal } from "@components/settings/tabs";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { copyWithToast } from "@utils/discord";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { copyWithToast, shouldShowContributorBadge, shouldShowEquicordContributorBadge } from "@utils/misc"; import { shouldShowContributorBadge, shouldShowEquicordContributorBadge } from "@utils/misc";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { User } from "@vencord/discord-types"; import { User } from "@vencord/discord-types";
import { ContextMenuApi, Menu, Toasts, UserStore } from "@webpack/common"; import { ContextMenuApi, Menu, Toasts, UserStore } from "@webpack/common";
@ -63,10 +64,11 @@ const UserPluginContributorBadge: ProfileBadge = {
iconSrc: USERPLUGIN_CONTRIBUTOR_BADGE, iconSrc: USERPLUGIN_CONTRIBUTOR_BADGE,
position: BadgePosition.START, position: BadgePosition.START,
shouldShow: ({ userId }) => { shouldShow: ({ userId }) => {
if (!IS_DEV) return false;
const allPlugins = Object.values(Plugins); const allPlugins = Object.values(Plugins);
return allPlugins.some(p => { return allPlugins.some(p => {
const pluginMeta = PluginMeta[p.name]; const pluginMeta = PluginMeta[p.name];
return pluginMeta?.userPlugin && p.authors.some(a => a.id.toString() === userId) && IS_DEV; return pluginMeta?.userPlugin && p.authors.some(a => a.id.toString() === userId);
}); });
}, },
onClick: (_, { userId }) => openContributorModal(UserStore.getUser(userId)), onClick: (_, { userId }) => openContributorModal(UserStore.getUser(userId)),

View file

@ -17,14 +17,16 @@
*/ */
import { sendBotMessage } from "@api/Commands"; import { sendBotMessage } from "@api/Commands";
import { isPluginEnabled } from "@api/PluginManager";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { getUserSettingLazy } from "@api/UserSettings"; import { getUserSettingLazy } from "@api/UserSettings";
import { BaseText } from "@components/BaseText"; import { BaseText } from "@components/BaseText";
import { Card } from "@components/Card";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { Paragraph } from "@components/Paragraph"; import { Paragraph } from "@components/Paragraph";
import { openUpdaterModal } from "@components/settings/tabs/updater"; import { openUpdaterModal } from "@components/settings";
import { platformName } from "@equicordplugins/equicordHelper/utils"; import { platformName } from "@equicordplugins/equicordHelper/utils";
import { gitHashShort } from "@shared/vencordUserAgent"; import { gitHashShort } from "@shared/vencordUserAgent";
import { CONTRIB_ROLE_ID, Devs, DONOR_ROLE_ID, EQUIBOP_CONTRIB_ROLE_ID, EQUICORD_TEAM, GUILD_ID, SUPPORT_CHANNEL_ID, SUPPORT_CHANNEL_IDS, VC_CONTRIB_ROLE_ID, VC_DONOR_ROLE_ID, VC_GUILD_ID, VC_REGULAR_ROLE_ID, VC_SUPPORT_CHANNEL_IDS, VENCORD_CONTRIB_ROLE_ID } from "@utils/constants"; import { CONTRIB_ROLE_ID, Devs, DONOR_ROLE_ID, EQUIBOP_CONTRIB_ROLE_ID, EQUICORD_TEAM, GUILD_ID, SUPPORT_CHANNEL_ID, SUPPORT_CHANNEL_IDS, VC_CONTRIB_ROLE_ID, VC_DONOR_ROLE_ID, VC_GUILD_ID, VC_REGULAR_ROLE_ID, VC_SUPPORT_CHANNEL_IDS, VENCORD_CONTRIB_ROLE_ID } from "@utils/constants";
@ -37,7 +39,7 @@ import { onlyOnce } from "@utils/onlyOnce";
import { makeCodeblock } from "@utils/text"; import { makeCodeblock } from "@utils/text";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { checkForUpdates, isOutdated, update } from "@utils/updater"; import { checkForUpdates, isOutdated, update } from "@utils/updater";
import { Alerts, Button, Card, ChannelStore, GuildMemberStore, Parser, PermissionsBits, PermissionStore, RelationshipStore, SelectedChannelStore, showToast, Toasts, UserStore } from "@webpack/common"; import { Alerts, Button, ChannelStore, GuildMemberStore, Parser, PermissionsBits, PermissionStore, RelationshipStore, SelectedChannelStore, showToast, Toasts, UserStore } from "@webpack/common";
import { JSX } from "react"; import { JSX } from "react";
import gitHash from "~git-hash"; import gitHash from "~git-hash";
@ -137,7 +139,7 @@ function generatePluginList() {
const isApiPlugin = (plugin: string) => plugin.endsWith("API") || plugins[plugin].required; const isApiPlugin = (plugin: string) => plugin.endsWith("API") || plugins[plugin].required;
const enabledPlugins = Object.keys(plugins) const enabledPlugins = Object.keys(plugins)
.filter(p => Vencord.Plugins.isPluginEnabled(p) && !isApiPlugin(p)).sort(); .filter(p => isPluginEnabled(p) && !isApiPlugin(p));
const enabledStockPlugins = enabledPlugins.filter(p => !PluginMeta[p].userPlugin); const enabledStockPlugins = enabledPlugins.filter(p => !PluginMeta[p].userPlugin);
const enabledUserPlugins = enabledPlugins.filter(p => PluginMeta[p].userPlugin); const enabledUserPlugins = enabledPlugins.filter(p => PluginMeta[p].userPlugin);
@ -408,8 +410,8 @@ export default definePlugin({
if (RelationshipStore.isFriend(userId) || isAnyPluginDev(UserStore.getCurrentUser()?.id)) return null; if (RelationshipStore.isFriend(userId) || isAnyPluginDev(UserStore.getCurrentUser()?.id)) return null;
return ( return (
<Card className={`vc-plugins-restart-card ${Margins.top8}`}> <Card variant="warning" className={Margins.top8} defaultPadding>
Please do not private message plugin developers for support! Please do not private message Vencord plugin developers for support!
<br /> <br />
Instead, use the support channel: {Parser.parse("https://discord.com/channels/1173279886065029291/1297590739911573585")} Instead, use the support channel: {Parser.parse("https://discord.com/channels/1173279886065029291/1297590739911573585")}
{!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"} {!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"}

View file

@ -18,7 +18,7 @@
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { copyWithToast } from "@utils/misc"; import { copyWithToast } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
const settings = definePluginSettings({ const settings = definePluginSettings({

View file

@ -5,9 +5,9 @@
*/ */
import { Button } from "@components/Button"; import { Button } from "@components/Button";
import { SessionInfo } from "@plugins/betterSessions/types";
import { openModal } from "@utils/modal"; import { openModal } from "@utils/modal";
import { SessionInfo } from "../types";
import { RenameModal } from "./RenameModal"; import { RenameModal } from "./RenameModal";
export function RenameButton({ session, state }: { session: SessionInfo["session"], state: [string, React.Dispatch<React.SetStateAction<string>>]; }) { export function RenameButton({ session, state }: { session: SessionInfo["session"], state: [string, React.Dispatch<React.SetStateAction<string>>]; }) {

View file

@ -16,14 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Heading, HeadingTertiary } from "@components/Heading"; import { Heading,HeadingTertiary } from "@components/Heading";
import { SessionInfo } from "@plugins/betterSessions/types";
import { getDefaultName, savedSessionsCache, saveSessionsToDataStore } from "@plugins/betterSessions/utils";
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal"; import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
import { Button, React, TextInput } from "@webpack/common"; import { Button, React, TextInput } from "@webpack/common";
import { KeyboardEvent } from "react"; import { KeyboardEvent } from "react";
import { SessionInfo } from "../types";
import { getDefaultName, savedSessionsCache, saveSessionsToDataStore } from "../utils";
export function RenameModal({ props, session, state }: { props: ModalProps, session: SessionInfo["session"], state: [string, React.Dispatch<React.SetStateAction<string>>]; }) { export function RenameModal({ props, session, state }: { props: ModalProps, session: SessionInfo["session"], state: [string, React.Dispatch<React.SetStateAction<string>>]; }) {
const [title, setTitle] = state; const [title, setTitle] = state;
const [value, setValue] = React.useState(savedSessionsCache.get(session.id_hash)?.name ?? ""); const [value, setValue] = React.useState(savedSessionsCache.get(session.id_hash)?.name ?? "");

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { DataStore } from "@api/index"; import * as DataStore from "@api/DataStore";
import { UserStore } from "@webpack/common"; import { UserStore } from "@webpack/common";
import { ChromeIcon, DiscordIcon, EdgeIcon, FirefoxIcon, IEIcon, MobileIcon, OperaIcon, SafariIcon, UnknownIcon } from "./components/icons"; import { ChromeIcon, DiscordIcon, EdgeIcon, FirefoxIcon, IEIcon, MobileIcon, OperaIcon, SafariIcon, UnknownIcon } from "./components/icons";

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { isPluginEnabled } from "@api/PluginManager";
import { openPluginModal } from "@components/settings/tabs"; import { openPluginModal } from "@components/settings/tabs";
import { getIntlMessage } from "@utils/discord"; import { getIntlMessage } from "@utils/discord";
import { isObjectEmpty } from "@utils/misc"; import { isObjectEmpty } from "@utils/misc";
@ -28,7 +29,7 @@ export default function PluginsSubmenu() {
const search = query.toLowerCase(); const search = query.toLowerCase();
const include = (p: typeof Plugins[keyof typeof Plugins]) => ( const include = (p: typeof Plugins[keyof typeof Plugins]) => (
Vencord.Plugins.isPluginEnabled(p.name) isPluginEnabled(p.name)
&& p.options && !isObjectEmpty(p.options) && p.options && !isObjectEmpty(p.options)
&& ( && (
p.name.toLowerCase().includes(search) p.name.toLowerCase().includes(search)

View file

@ -6,15 +6,15 @@
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { ErrorCard } from "@components/ErrorCard"; import { ErrorCard } from "@components/ErrorCard";
import { HeadingPrimary, HeadingSecondary } from "@components/Heading"; import { HeadingPrimary,HeadingSecondary } from "@components/Heading";
import { Paragraph } from "@components/Paragraph"; import { Paragraph } from "@components/Paragraph";
import { relativeLuminance } from "@plugins/clientTheme/utils/colorUtils";
import { createOrUpdateThemeColorVars } from "@plugins/clientTheme/utils/styleUtils";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { findByCodeLazy, findStoreLazy } from "@webpack"; import { findByCodeLazy, findStoreLazy } from "@webpack";
import { Button, ColorPicker, ThemeStore, useStateFromStores } from "@webpack/common"; import { Button, ColorPicker, ThemeStore, useStateFromStores } from "@webpack/common";
import { settings } from ".."; import { settings } from "..";
import { relativeLuminance } from "../utils/colorUtils";
import { createOrUpdateThemeColorVars } from "../utils/styleUtils";
const saveClientTheme = findByCodeLazy('type:"UNSYNCED_USER_SETTINGS_UPDATE', '"system"==='); const saveClientTheme = findByCodeLazy('type:"UNSYNCED_USER_SETTINGS_UPDATE', '"system"===');
const NitroThemeStore = findStoreLazy("ClientThemesBackgroundStore"); const NitroThemeStore = findStoreLazy("ClientThemesBackgroundStore");

View file

@ -170,7 +170,7 @@ function makeShortcuts() {
openModal: { getter: () => ModalAPI.openModal }, openModal: { getter: () => ModalAPI.openModal },
openModalLazy: { getter: () => ModalAPI.openModalLazy }, openModalLazy: { getter: () => ModalAPI.openModalLazy },
Stores: Webpack.fluxStores, Stores: { getter: () => Object.fromEntries(Webpack.fluxStores) },
// e.g. "2024-05_desktop_visual_refresh", 0 // e.g. "2024-05_desktop_visual_refresh", 0
setExperiment: (id: string, bucket: number) => { setExperiment: (id: string, bucket: number) => {

View file

@ -6,7 +6,7 @@
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { copyWithToast } from "@utils/misc"; import { copyWithToast } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { Menu } from "@webpack/common"; import { Menu } from "@webpack/common";

View file

@ -9,7 +9,7 @@ import "./style.css";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { CopyIcon, NoEntrySignIcon } from "@components/Icons"; import { CopyIcon, NoEntrySignIcon } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { copyWithToast } from "@utils/misc"; import { copyWithToast } from "@utils/discord";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { Tooltip, useState } from "@webpack/common"; import { Tooltip, useState } from "@webpack/common";

View file

@ -17,12 +17,13 @@
*/ */
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { isPluginEnabled } from "@api/PluginManager";
import ExpressionClonerPlugin from "@plugins/expressionCloner";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { copyWithToast } from "@utils/misc"; import { copyWithToast } from "@utils/discord";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { Message, Sticker } from "@vencord/discord-types"; import { Message, Sticker } from "@vencord/discord-types";
import { Menu, React, StickersStore } from "@webpack/common"; import { Menu, React, StickersStore } from "@webpack/common";
import ExpressionClonerPlugin from "plugins/expressionCloner";
const StickerExt = [, "png", "png", "json", "gif"] as const; const StickerExt = [, "png", "png", "json", "gif"] as const;
@ -79,7 +80,7 @@ const expressionPickerPatch: NavContextMenuPatchCallback = (children, props: { t
const sticker = StickersStore.getStickerById(id); const sticker = StickersStore.getStickerById(id);
if (sticker) { if (sticker) {
children.push(buildMenuItem(sticker, Vencord.Plugins.isPluginEnabled(ExpressionClonerPlugin.name))); children.push(buildMenuItem(sticker, isPluginEnabled(ExpressionClonerPlugin.name)));
} }
}; };

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { Notices } from "@api/index"; import { currentNotice, noticesQueue, popNotice, showNotice } from "@api/Notices";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { makeRange, OptionType } from "@utils/types"; import definePlugin, { makeRange, OptionType } from "@utils/types";
@ -62,12 +62,12 @@ export default definePlugin({
const backOnlineMessage = "Welcome back! Click the button to go online. Click the X to stay idle until reload."; const backOnlineMessage = "Welcome back! Click the button to go online. Click the X to stay idle until reload.";
if ( if (
Notices.currentNotice?.[1] === backOnlineMessage || currentNotice?.[1] === backOnlineMessage ||
Notices.noticesQueue.some(([, noticeMessage]) => noticeMessage === backOnlineMessage) noticesQueue.some(([, noticeMessage]) => noticeMessage === backOnlineMessage)
) return; ) return;
Notices.showNotice(backOnlineMessage, "Exit idle", () => { showNotice(backOnlineMessage, "Exit idle", () => {
Notices.popNotice(); popNotice();
FluxDispatcher.dispatch({ FluxDispatcher.dispatch({
type: "IDLE", type: "IDLE",
idle: false idle: false

View file

@ -6,6 +6,7 @@
import "./settings.css"; import "./settings.css";
import { isPluginEnabled } from "@api/PluginManager";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { Divider } from "@components/Divider"; import { Divider } from "@components/Divider";
import { Heading } from "@components/Heading"; import { Heading } from "@components/Heading";
@ -14,7 +15,7 @@ import { debounce } from "@shared/debounce";
import { ActivityType } from "@vencord/discord-types/enums"; import { ActivityType } from "@vencord/discord-types/enums";
import { Select, Text, TextInput, useState } from "@webpack/common"; import { Select, Text, TextInput, useState } from "@webpack/common";
import { setRpc, settings, TimestampMode } from "."; import CustomRPCPlugin, { setRpc, settings, TimestampMode } from ".";
const cl = classNameFactory("vc-customRPC-settings-"); const cl = classNameFactory("vc-customRPC-settings-");
@ -50,7 +51,7 @@ function isAppIdValid(value: string) {
const updateRPC = debounce(() => { const updateRPC = debounce(() => {
setRpc(true); setRpc(true);
if (Vencord.Plugins.isPluginEnabled("CustomRPC")) setRpc(); if (isPluginEnabled(CustomRPCPlugin.name)) setRpc();
}); });
function isStreamLinkDisabled() { function isStreamLinkDisabled() {

View file

@ -4,14 +4,13 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { DataStore } from "@api/index"; import * as DataStore from "@api/DataStore";
import { AUTHORIZE_URL, CLIENT_ID } from "@plugins/decor/lib/constants";
import { proxyLazy } from "@utils/lazy"; import { proxyLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { openModal } from "@utils/modal"; import { openModal } from "@utils/modal";
import { OAuth2AuthorizeModal, showToast, Toasts, UserStore, zustandCreate, zustandPersist } from "@webpack/common"; import { OAuth2AuthorizeModal, showToast, Toasts, UserStore, zustandCreate, zustandPersist } from "@webpack/common";
import { AUTHORIZE_URL, CLIENT_ID } from "../constants";
interface AuthorizationState { interface AuthorizationState {
token: string | null; token: string | null;
tokens: Record<string, string>; tokens: Record<string, string>;

View file

@ -4,11 +4,11 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { Decoration, deleteDecoration, getUserDecoration, getUserDecorations, NewDecoration, setUserDecoration } from "@plugins/decor/lib/api";
import { decorationToAsset } from "@plugins/decor/lib/utils/decoration";
import { proxyLazy } from "@utils/lazy"; import { proxyLazy } from "@utils/lazy";
import { UserStore, zustandCreate } from "@webpack/common"; import { UserStore, zustandCreate } from "@webpack/common";
import { Decoration, deleteDecoration, getUserDecoration, getUserDecorations, NewDecoration, setUserDecoration } from "../api";
import { decorationToAsset } from "../utils/decoration";
import { useUsersDecorationsStore } from "./UsersDecorationsStore"; import { useUsersDecorationsStore } from "./UsersDecorationsStore";
interface UserDecorationsState { interface UserDecorationsState {

View file

@ -4,15 +4,14 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { AvatarDecoration } from "@plugins/decor";
import { getUsersDecorations } from "@plugins/decor/lib/api";
import { DECORATION_FETCH_COOLDOWN, SKU_ID } from "@plugins/decor/lib/constants";
import { debounce } from "@shared/debounce"; import { debounce } from "@shared/debounce";
import { proxyLazy } from "@utils/lazy"; import { proxyLazy } from "@utils/lazy";
import { User } from "@vencord/discord-types"; import { User } from "@vencord/discord-types";
import { useEffect, useState, zustandCreate } from "@webpack/common"; import { useEffect, useState, zustandCreate } from "@webpack/common";
import { AvatarDecoration } from "../../";
import { getUsersDecorations } from "../api";
import { DECORATION_FETCH_COOLDOWN, SKU_ID } from "../constants";
interface UserDecorationData { interface UserDecorationData {
asset: string | null; asset: string | null;
fetchedAt: Date; fetchedAt: Date;

View file

@ -4,9 +4,9 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { AvatarDecoration } from "../../"; import { AvatarDecoration } from "@plugins/decor";
import { Decoration } from "../api"; import { Decoration } from "@plugins/decor/lib/api";
import { SKU_ID } from "../constants"; import { SKU_ID } from "@plugins/decor/lib/constants";
export function decorationToAsset(decoration: Decoration) { export function decorationToAsset(decoration: Decoration) {
return `${decoration.animated ? "a_" : ""}${decoration.hash}`; return `${decoration.animated ? "a_" : ""}${decoration.hash}`;

View file

@ -13,13 +13,14 @@ import { closeAllModals } from "@utils/modal";
import { OptionType } from "@utils/types"; import { OptionType } from "@utils/types";
import { FluxDispatcher } from "@webpack/common"; import { FluxDispatcher } from "@webpack/common";
import DecorPlugin from ".";
import DecorSection from "./ui/components/DecorSection"; import DecorSection from "./ui/components/DecorSection";
export const settings = definePluginSettings({ export const settings = definePluginSettings({
changeDecoration: { changeDecoration: {
type: OptionType.COMPONENT, type: OptionType.COMPONENT,
component() { component() {
if (!Vencord.Plugins.plugins.Decor.started) return <Paragraph> if (!DecorPlugin.started) return <Paragraph>
Enable Decor and restart your client to change your avatar decoration. Enable Decor and restart your client to change your avatar decoration.
</Paragraph>; </Paragraph>;

View file

@ -4,11 +4,11 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { Decoration } from "@plugins/decor/lib/api";
import { decorationToAvatarDecoration } from "@plugins/decor/lib/utils/decoration";
import { ContextMenuApi } from "@webpack/common"; import { ContextMenuApi } from "@webpack/common";
import type { HTMLProps } from "react"; import type { HTMLProps } from "react";
import { Decoration } from "../../lib/api";
import { decorationToAvatarDecoration } from "../../lib/utils/decoration";
import { DecorationGridDecoration } from "."; import { DecorationGridDecoration } from ".";
import DecorationContextMenu from "./DecorationContextMenu"; import DecorationContextMenu from "./DecorationContextMenu";

View file

@ -6,14 +6,13 @@
import { Button } from "@components/Button"; import { Button } from "@components/Button";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { useAuthorizationStore } from "@plugins/decor/lib/stores/AuthorizationStore";
import { useCurrentUserDecorationsStore } from "@plugins/decor/lib/stores/CurrentUserDecorationsStore";
import { cl } from "@plugins/decor/ui";
import { openChangeDecorationModal } from "@plugins/decor/ui/modals/ChangeDecorationModal";
import { findComponentByCodeLazy } from "@webpack"; import { findComponentByCodeLazy } from "@webpack";
import { useEffect } from "@webpack/common"; import { useEffect } from "@webpack/common";
import { useAuthorizationStore } from "../../lib/stores/AuthorizationStore";
import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDecorationsStore";
import { cl } from "../";
import { openChangeDecorationModal } from "../modals/ChangeDecorationModal";
const CustomizationSection = findComponentByCodeLazy(".customizationSectionBackground"); const CustomizationSection = findComponentByCodeLazy(".customizationSectionBackground");
export interface DecorSectionProps { export interface DecorSectionProps {

View file

@ -5,13 +5,12 @@
*/ */
import { CopyIcon, DeleteIcon } from "@components/Icons"; import { CopyIcon, DeleteIcon } from "@components/Icons";
import { Decoration } from "@plugins/decor/lib/api";
import { useCurrentUserDecorationsStore } from "@plugins/decor/lib/stores/CurrentUserDecorationsStore";
import { cl } from "@plugins/decor/ui";
import { copyToClipboard } from "@utils/clipboard"; import { copyToClipboard } from "@utils/clipboard";
import { Alerts, ContextMenuApi, Menu, UserStore } from "@webpack/common"; import { Alerts, ContextMenuApi, Menu, UserStore } from "@webpack/common";
import { Decoration } from "../../lib/api";
import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDecorationsStore";
import { cl } from "../";
export default function DecorationContextMenu({ decoration }: { decoration: Decoration; }) { export default function DecorationContextMenu({ decoration }: { decoration: Decoration; }) {
const { delete: deleteDecoration } = useCurrentUserDecorationsStore(); const { delete: deleteDecoration } = useCurrentUserDecorationsStore();

View file

@ -4,11 +4,10 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { cl } from "@plugins/decor/ui";
import { React } from "@webpack/common"; import { React } from "@webpack/common";
import { JSX } from "react"; import { JSX } from "react";
import { cl } from "../";
export interface GridProps<ItemT> { export interface GridProps<ItemT> {
renderItem: (item: ItemT) => JSX.Element; renderItem: (item: ItemT) => JSX.Element;
getItemKey: (item: ItemT) => string; getItemKey: (item: ItemT) => string;

View file

@ -4,12 +4,12 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { cl } from "@plugins/decor/ui";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { React } from "@webpack/common"; import { React } from "@webpack/common";
import { JSX } from "react"; import { JSX } from "react";
import { cl } from "../";
import Grid, { GridProps } from "./Grid"; import Grid, { GridProps } from "./Grid";
const ScrollerClasses = findByPropsLazy("managedReactiveScroller"); const ScrollerClasses = findByPropsLazy("managedReactiveScroller");

View file

@ -4,12 +4,11 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { AvatarDecoration } from "@plugins/decor";
import { findComponentByCode, LazyComponentWebpack } from "@webpack"; import { findComponentByCode, LazyComponentWebpack } from "@webpack";
import { React } from "@webpack/common"; import { React } from "@webpack/common";
import type { ComponentType, HTMLProps, PropsWithChildren } from "react"; import type { ComponentType, HTMLProps, PropsWithChildren } from "react";
import { AvatarDecoration } from "../..";
type DecorationGridItemComponent = ComponentType<PropsWithChildren<HTMLProps<HTMLDivElement>> & { type DecorationGridItemComponent = ComponentType<PropsWithChildren<HTMLProps<HTMLDivElement>> & {
onSelect: () => void, onSelect: () => void,
isSelected: boolean, isSelected: boolean,

View file

@ -10,26 +10,25 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { Heading } from "@components/Heading"; import { Heading } from "@components/Heading";
import { Paragraph } from "@components/Paragraph"; import { Paragraph } from "@components/Paragraph";
import { openInviteModal } from "@utils/discord"; import { Decoration, getPresets, Preset } from "@plugins/decor/lib/api";
import { GUILD_ID, INVITE_KEY } from "@plugins/decor/lib/constants";
import { useAuthorizationStore } from "@plugins/decor/lib/stores/AuthorizationStore";
import { useCurrentUserDecorationsStore } from "@plugins/decor/lib/stores/CurrentUserDecorationsStore";
import { decorationToAvatarDecoration } from "@plugins/decor/lib/utils/decoration";
import { settings } from "@plugins/decor/settings";
import { cl, DecorationModalStyles, requireAvatarDecorationModal } from "@plugins/decor/ui";
import { AvatarDecorationModalPreview } from "@plugins/decor/ui/components";
import DecorationGridCreate from "@plugins/decor/ui/components/DecorationGridCreate";
import DecorationGridNone from "@plugins/decor/ui/components/DecorationGridNone";
import DecorDecorationGridDecoration from "@plugins/decor/ui/components/DecorDecorationGridDecoration";
import SectionedGridList from "@plugins/decor/ui/components/SectionedGridList";
import { copyWithToast, openInviteModal } from "@utils/discord";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { copyWithToast } from "@utils/misc";
import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { Queue } from "@utils/Queue"; import { Queue } from "@utils/Queue";
import { User } from "@vencord/discord-types"; import { User } from "@vencord/discord-types";
import { Alerts, Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Parser, Tooltip, useEffect, UserStore, UserSummaryItem, UserUtils, useState } from "@webpack/common"; import { Alerts, Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Parser, Tooltip, useEffect, UserStore, UserSummaryItem, UserUtils, useState } from "@webpack/common";
import { Decoration, getPresets, Preset } from "../../lib/api";
import { GUILD_ID, INVITE_KEY } from "../../lib/constants";
import { useAuthorizationStore } from "../../lib/stores/AuthorizationStore";
import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDecorationsStore";
import { decorationToAvatarDecoration } from "../../lib/utils/decoration";
import { settings } from "../../settings";
import { cl, DecorationModalStyles, requireAvatarDecorationModal } from "../";
import { AvatarDecorationModalPreview } from "../components";
import DecorationGridCreate from "../components/DecorationGridCreate";
import DecorationGridNone from "../components/DecorationGridNone";
import DecorDecorationGridDecoration from "../components/DecorDecorationGridDecoration";
import SectionedGridList from "../components/SectionedGridList";
import { openCreateDecorationModal } from "./CreateDecorationModal"; import { openCreateDecorationModal } from "./CreateDecorationModal";
import { openGuidelinesModal } from "./GuidelinesModal"; import { openGuidelinesModal } from "./GuidelinesModal";

View file

@ -9,17 +9,16 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Heading } from "@components/Heading"; import { Heading } from "@components/Heading";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { Paragraph } from "@components/Paragraph"; import { Paragraph } from "@components/Paragraph";
import { GUILD_ID, INVITE_KEY, RAW_SKU_ID } from "@plugins/decor/lib/constants";
import { useCurrentUserDecorationsStore } from "@plugins/decor/lib/stores/CurrentUserDecorationsStore";
import { cl, DecorationModalStyles, requireAvatarDecorationModal, requireCreateStickerModal } from "@plugins/decor/ui";
import { AvatarDecorationModalPreview } from "@plugins/decor/ui/components";
import { openInviteModal } from "@utils/discord"; import { openInviteModal } from "@utils/discord";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { filters, findComponentByCodeLazy, mapMangledModuleLazy } from "@webpack"; import { filters, findComponentByCodeLazy, mapMangledModuleLazy } from "@webpack";
import { Button, FluxDispatcher, GuildStore, NavigationRouter, TextInput, useEffect, useMemo, UserStore, useState } from "@webpack/common"; import { Button, FluxDispatcher, GuildStore, NavigationRouter, TextInput, useEffect, useMemo, UserStore, useState } from "@webpack/common";
import { GUILD_ID, INVITE_KEY, RAW_SKU_ID } from "../../lib/constants";
import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDecorationsStore";
import { cl, DecorationModalStyles, requireAvatarDecorationModal, requireCreateStickerModal } from "../";
import { AvatarDecorationModalPreview } from "../components";
const FileUpload = findComponentByCodeLazy(".fileUpload),"); const FileUpload = findComponentByCodeLazy(".fileUpload),");
const { HelpMessage, HelpMessageTypes } = mapMangledModuleLazy('POSITIVE="positive', { const { HelpMessage, HelpMessageTypes } = mapMangledModuleLazy('POSITIVE="positive', {

View file

@ -8,11 +8,11 @@ import { BaseText } from "@components/BaseText";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { Paragraph } from "@components/Paragraph"; import { Paragraph } from "@components/Paragraph";
import { settings } from "@plugins/decor/settings";
import { cl, DecorationModalStyles, requireAvatarDecorationModal } from "@plugins/decor/ui";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { Button } from "@webpack/common"; import { Button } from "@webpack/common";
import { settings } from "../../settings";
import { cl, DecorationModalStyles, requireAvatarDecorationModal } from "../";
import { openCreateDecorationModal } from "./CreateDecorationModal"; import { openCreateDecorationModal } from "./CreateDecorationModal";
function GuidelinesModal(props: ModalProps) { function GuidelinesModal(props: ModalProps) {

View file

@ -19,8 +19,8 @@
import { addMessagePreEditListener, addMessagePreSendListener, removeMessagePreEditListener, removeMessagePreSendListener } from "@api/MessageEvents"; import { addMessagePreEditListener, addMessagePreSendListener, removeMessagePreEditListener, removeMessagePreSendListener } from "@api/MessageEvents";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Paragraph } from "@components/Paragraph"; import { Paragraph } from "@components/Paragraph";
import { ApngBlendOp, ApngDisposeOp, parseAPNG } from "@utils/apng";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
import { getCurrentGuild, getEmojiURL } from "@utils/discord"; import { getCurrentGuild, getEmojiURL } from "@utils/discord";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import definePlugin, { OptionType, Patch } from "@utils/types"; import definePlugin, { OptionType, Patch } from "@utils/types";
@ -711,9 +711,10 @@ export default definePlugin({
}, },
async sendAnimatedSticker(stickerLink: string, stickerId: string, channelId: string) { async sendAnimatedSticker(stickerLink: string, stickerId: string, channelId: string) {
const { parseURL } = importApngJs();
const { frames, width, height } = await parseURL(stickerLink); const { frames, width, height } = await fetch(stickerLink)
.then(res => res.arrayBuffer())
.then(parseAPNG);
const gif = GIFEncoder(); const gif = GIFEncoder();
const resolution = settings.store.stickerSize; const resolution = settings.store.stickerSize;
@ -762,7 +763,7 @@ export default definePlugin({
gif.finish(); gif.finish();
const file = new File([gif.bytesView()], `${stickerId}.gif`, { type: "image/gif" }); const file = new File([gif.bytesView() as Uint8Array<ArrayBuffer>], `${stickerId}.gif`, { type: "image/gif" });
UploadHandler.promptToUpload([file], ChannelStore.getChannel(channelId), DraftType.ChannelMessage); UploadHandler.promptToUpload([file], ChannelStore.getChannel(channelId), DraftType.ChannelMessage);
}, },

View file

@ -17,23 +17,24 @@
*/ */
// This plugin is a port from Alyxia's Vendetta plugin // This plugin is a port from Alyxia's Vendetta plugin
import "./style.css"; import "./styles.css";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { BaseText } from "@components/BaseText"; import { BaseText } from "@components/BaseText";
import { Divider } from "@components/Divider"; import { Divider } from "@components/Divider";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { HeadingSecondary } from "@components/Heading"; import { HeadingSecondary } from "@components/Heading";
import { Paragraph } from "@components/Paragraph"; import { Paragraph } from "@components/Paragraph";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { fetchUserProfile } from "@utils/discord"; import { copyWithToast, fetchUserProfile } from "@utils/discord";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes, copyWithToast } from "@utils/misc"; import { classes } from "@utils/misc";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { User, UserProfile } from "@vencord/discord-types"; import { User, UserProfile } from "@vencord/discord-types";
import { findComponentByCodeLazy } from "@webpack"; import { findComponentByCodeLazy } from "@webpack";
import { Button, ColorPicker, Flex, React, UserProfileStore, UserStore, useState } from "@webpack/common"; import { Button, ColorPicker, Forms, React, UserProfileStore, UserStore, useState } from "@webpack/common";
import virtualMerge from "virtual-merge"; import virtualMerge from "virtual-merge";
interface Colors { interface Colors {
@ -122,23 +123,19 @@ function SettingsAboutComponent() {
<Paragraph> <Paragraph>
After enabling this plugin, you will see custom colors in After enabling this plugin, you will see custom colors in
the profiles of other people using compatible plugins.{" "} the profiles of other people using compatible plugins.{" "}
<br /> </Paragraph>
To set your own colors: <Paragraph className={Margins.top8}>
<strong>To set your own profile theme colors:</strong>
<ul> <ul>
<li> <li>&mdash; use the color pickers below to choose your colors</li>
• use the color pickers below to choose your colors <li>&mdash; click the "Copy 3y3" button</li>
</li> <li>&mdash; paste the invisible text anywhere in your bio</li>
<li>• click the "Copy 3y3" button</li> </ul>
<li>• paste the invisible text anywhere in your bio</li>
</ul><br />
<Divider <Divider
className={classes(Margins.top8, Margins.bottom8)} className={classes(Margins.top8, Margins.bottom8)}
/> />
<HeadingSecondary>Color pickers</HeadingSecondary> <Forms.FormTitle tag="h3">Color pickers</Forms.FormTitle>
<Flex <Flex gap="1em">
direction={Flex.Direction.HORIZONTAL}
style={{ gap: "1rem" }}
>
<ColorPicker <ColorPicker
color={color1} color={color1}
label={ label={
@ -174,6 +171,7 @@ function SettingsAboutComponent() {
}} }}
color={Button.Colors.PRIMARY} color={Button.Colors.PRIMARY}
size={Button.Sizes.XLARGE} size={Button.Sizes.XLARGE}
style={{ marginBottom: "auto" }}
> >
Copy 3y3 Copy 3y3
</Button> </Button>

View file

@ -0,0 +1,3 @@
.vc-fpt-preview * {
pointer-events: none;
}

View file

@ -18,12 +18,11 @@
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { settings } from "@plugins/imageZoom";
import { ELEMENT_ID } from "@plugins/imageZoom/constants";
import { waitFor } from "@plugins/imageZoom/utils/waitFor";
import { FluxDispatcher, useLayoutEffect, useMemo, useRef, useState } from "@webpack/common"; import { FluxDispatcher, useLayoutEffect, useMemo, useRef, useState } from "@webpack/common";
import { ELEMENT_ID } from "../constants";
import { settings } from "../index";
import { waitFor } from "../utils/waitFor";
interface Vec2 { interface Vec2 {
x: number, x: number,
y: number; y: number;

View file

@ -1,227 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
import { updateMessage } from "@api/MessageUpdater";
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { getStegCloak } from "@utils/dependencies";
import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
import { Message } from "@vencord/discord-types";
import { ChannelStore, Constants, RestAPI, Tooltip } from "@webpack/common";
import { buildDecModal } from "./components/DecryptionModal";
import { buildEncModal } from "./components/EncryptionModal";
let steggo: any;
function PopOverIcon() {
return (
<svg
fill="var(--header-secondary)"
width={24} height={24}
viewBox={"0 0 64 64"}
>
<path d="M 32 9 C 24.832 9 19 14.832 19 22 L 19 27.347656 C 16.670659 28.171862 15 30.388126 15 33 L 15 49 C 15 52.314 17.686 55 21 55 L 43 55 C 46.314 55 49 52.314 49 49 L 49 33 C 49 30.388126 47.329341 28.171862 45 27.347656 L 45 22 C 45 14.832 39.168 9 32 9 z M 32 13 C 36.963 13 41 17.038 41 22 L 41 27 L 23 27 L 23 22 C 23 17.038 27.037 13 32 13 z" />
</svg>
);
}
function Indicator() {
return (
<Tooltip text="This message has a hidden message! (InvisibleChat)">
{({ onMouseEnter, onMouseLeave }) => (
<img
aria-label="Hidden Message Indicator (InvisibleChat)"
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
src="https://github.com/SammCheese/invisible-chat/raw/NewReplugged/src/assets/lock.png"
width={20}
height={20}
style={{ transform: "translateY(4p)", paddingInline: 4 }}
/>
)}
</Tooltip>
);
}
const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
if (!isMainChat) return null;
return (
<ChatBarButton
tooltip="Encrypt Message"
onClick={() => buildEncModal()}
buttonProps={{
"aria-haspopup": "dialog",
}}
>
<svg
aria-hidden
role="img"
width="20"
height="20"
viewBox={"0 0 64 64"}
style={{ scale: "1.39", translate: "0 -1px" }}
>
<path fill="currentColor" d="M 32 9 C 24.832 9 19 14.832 19 22 L 19 27.347656 C 16.670659 28.171862 15 30.388126 15 33 L 15 49 C 15 52.314 17.686 55 21 55 L 43 55 C 46.314 55 49 52.314 49 49 L 49 33 C 49 30.388126 47.329341 28.171862 45 27.347656 L 45 22 C 45 14.832 39.168 9 32 9 z M 32 13 C 36.963 13 41 17.038 41 22 L 41 27 L 23 27 L 23 22 C 23 17.038 27.037 13 32 13 z" />
</svg>
</ChatBarButton>
);
};
const settings = definePluginSettings({
savedPasswords: {
type: OptionType.STRING,
default: "password, Password",
description: "Saved Passwords (Seperated with a , )"
}
});
export default definePlugin({
name: "InvisibleChat",
description: "Encrypt your Messages in a non-suspicious way!",
authors: [Devs.SammCheese],
dependencies: ["MessageUpdaterAPI"],
reporterTestable: ReporterTestable.Patches,
settings,
patches: [
{
// Indicator
find: ".SEND_FAILED,",
replacement: {
match: /let\{className:\i,message:\i[^}]*\}=(\i)/,
replace: "try {$1 && $self.INV_REGEX.test($1.message.content) ? $1.content.push($self.indicator()) : null } catch {};$&"
}
},
],
EMBED_API_URL: "https://embed.sammcheese.net",
INV_REGEX: new RegExp(/( \u200c|\u200d |[\u2060-\u2064])[^\u200b]/),
URL_REGEX: new RegExp(
/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/,
),
async start() {
const { default: StegCloak } = await getStegCloak();
steggo = new StegCloak(true, false);
},
renderMessagePopoverButton(message) {
return this.INV_REGEX.test(message?.content)
? {
label: "Decrypt Message",
icon: this.popOverIcon,
message: message,
channel: ChannelStore.getChannel(message.channel_id),
onClick: async () => {
const res = await iteratePasswords(message);
if (res)
this.buildEmbed(message, res);
else
buildDecModal({ message });
}
}
: null;
},
renderChatBarButton: ChatBarIcon,
colorCodeFromNumber(color: number): string {
return `#${[color >> 16, color >> 8, color]
.map(x => (x & 0xFF).toString(16))
.join("")}`;
},
// Gets the Embed of a Link
async getEmbed(url: URL): Promise<Object | {}> {
const { body } = await RestAPI.post({
url: Constants.Endpoints.UNFURL_EMBED_URLS,
body: {
urls: [url]
}
});
// The endpoint returns the color as a number, but Discord expects a string
body.embeds[0].color = this.colorCodeFromNumber(body.embeds[0].color);
return await body.embeds[0];
},
async buildEmbed(message: any, revealed: string): Promise<void> {
const urlCheck = revealed.match(this.URL_REGEX);
message.embeds.push({
type: "rich",
rawTitle: "Decrypted Message",
color: "#45f5f5",
rawDescription: revealed,
footer: {
text: "Made with ❤️ by c0dine and Sammy!",
},
});
if (urlCheck?.length) {
const embed = await this.getEmbed(new URL(urlCheck[0]));
if (embed)
message.embeds.push(embed);
}
updateMessage(message.channel_id, message.id, { embeds: message.embeds });
},
popOverIcon: () => <PopOverIcon />,
indicator: ErrorBoundary.wrap(Indicator, { noop: true })
});
export function encrypt(secret: string, password: string, cover: string): string {
return steggo.hide(secret + "\u200b", password, cover);
}
export function decrypt(encrypted: string, password: string, removeIndicator: boolean): string {
const decrypted = steggo.reveal(encrypted, password);
return removeIndicator ? decrypted.replace("\u200b", "") : decrypted;
}
export function isCorrectPassword(result: string): boolean {
return result.endsWith("\u200b");
}
export async function iteratePasswords(message: Message): Promise<string | false> {
const passwords = settings.store.savedPasswords.split(",").map(s => s.trim());
if (!message?.content || !passwords?.length) return false;
let { content } = message;
// we use an extra variable so we dont have to edit the message content directly
if (/^\W/.test(message.content)) content = `d ${message.content}d`;
for (let i = 0; i < passwords.length; i++) {
const result = decrypt(content, passwords[i], false);
if (isCorrectPassword(result)) {
return result;
}
}
return false;
}

View file

@ -22,7 +22,7 @@ import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { Activity, ActivityAssets, ActivityButton } from "@vencord/discord-types"; import { Activity, ActivityAssets, ActivityButton } from "@vencord/discord-types";
import { ActivityFlags, ActivityStatusDisplayType, ActivityType } from "@vencord/discord-types/enums"; import { ActivityFlags, ActivityStatusDisplayType, ActivityType } from "@vencord/discord-types/enums";
import { ApplicationAssetUtils, FluxDispatcher, PresenceStore } from "@webpack/common"; import { ApplicationAssetUtils, AuthenticationStore, FluxDispatcher, PresenceStore } from "@webpack/common";
interface TrackData { interface TrackData {
name: string; name: string;
@ -251,13 +251,13 @@ export default definePlugin({
async getActivity(): Promise<Activity | null> { async getActivity(): Promise<Activity | null> {
if (settings.store.hideWithActivity) { if (settings.store.hideWithActivity) {
if (PresenceStore.getActivities().some(a => a.application_id !== DISCORD_APP_ID)) { if (PresenceStore.getActivities(AuthenticationStore.getId()).some(a => a.application_id !== DISCORD_APP_ID)) {
return null; return null;
} }
} }
if (settings.store.hideWithSpotify) { if (settings.store.hideWithSpotify) {
if (PresenceStore.getActivities().some(a => a.type === ActivityType.LISTENING && a.application_id !== DISCORD_APP_ID)) { if (PresenceStore.getActivities(AuthenticationStore.getId()).some(a => a.type === ActivityType.LISTENING && a.application_id !== DISCORD_APP_ID)) {
// there is already music status because of Spotify or richerCider (probably more) // there is already music status because of Spotify or richerCider (probably more)
return null; return null;
} }

View file

@ -16,13 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { isPluginEnabled } from "@api/PluginManager";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import NoReplyMentionPlugin from "@plugins/noReplyMention";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { MessageFlags } from "@vencord/discord-types/enums"; import { MessageFlags } from "@vencord/discord-types/enums";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { FluxDispatcher, MessageTypeSets, PermissionsBits, PermissionStore, UserStore, WindowStore } from "@webpack/common"; import { FluxDispatcher, MessageTypeSets, PermissionsBits, PermissionStore, UserStore, WindowStore } from "@webpack/common";
import NoReplyMentionPlugin from "plugins/noReplyMention";
const MessageActions = findByPropsLazy("deleteMessage", "startEditMessage"); const MessageActions = findByPropsLazy("deleteMessage", "startEditMessage");
const EditStore = findByPropsLazy("isEditing", "isEditingAny"); const EditStore = findByPropsLazy("isEditing", "isEditingAny");
@ -76,10 +77,36 @@ export default definePlugin({
onMessageClick(msg, channel, event) { onMessageClick(msg, channel, event) {
const isMe = msg.author.id === UserStore.getCurrentUser().id; const isMe = msg.author.id === UserStore.getCurrentUser().id;
if (!isDeletePressed) {
if (event.detail < 2) return;
if (settings.store.requireModifier && !event.ctrlKey && !event.shiftKey) return;
if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return;
if (msg.deleted === true) return;
if (isDeletePressed) { if (isMe) {
if (!settings.store.enableDeleteOnClick || !(isMe || PermissionStore.can(PermissionsBits.MANAGE_MESSAGES, channel))) return; if (!settings.store.enableDoubleClickToEdit || EditStore.isEditing(channel.id, msg.id) || msg.state !== "SENT") return;
MessageActions.startEditMessage(channel.id, msg.id, msg.content);
event.preventDefault();
} else {
if (!settings.store.enableDoubleClickToReply) return;
if (!MessageTypeSets.REPLYABLE.has(msg.type) || msg.hasFlag(MessageFlags.EPHEMERAL)) return;
const isShiftPress = event.shiftKey && !settings.store.requireModifier;
const shouldMention = isPluginEnabled(NoReplyMentionPlugin.name)
? NoReplyMentionPlugin.shouldMention(msg, isShiftPress)
: !isShiftPress;
FluxDispatcher.dispatch({
type: "CREATE_PENDING_REPLY",
channel,
message: msg,
shouldMention,
showMentionToggle: channel.guild_id !== null
});
}
} else if (settings.store.enableDeleteOnClick && (isMe || PermissionStore.can(PermissionsBits.MANAGE_MESSAGES, channel))) {
if (msg.deleted) { if (msg.deleted) {
FluxDispatcher.dispatch({ FluxDispatcher.dispatch({
type: "MESSAGE_DELETE", type: "MESSAGE_DELETE",
@ -90,40 +117,7 @@ export default definePlugin({
} else { } else {
MessageActions.deleteMessage(channel.id, msg.id); MessageActions.deleteMessage(channel.id, msg.id);
} }
event.preventDefault(); event.preventDefault();
return;
}
if (event.detail !== 2) return;
if (settings.store.requireModifier && !event.ctrlKey && !event.shiftKey) return;
if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return;
if (msg.deleted === true) return;
if (isMe) {
if (!settings.store.enableDoubleClickToEdit || EditStore.isEditing(channel.id, msg.id) || msg.state !== "SENT") return;
MessageActions.startEditMessage(channel.id, msg.id, msg.content);
event.preventDefault();
return;
} else {
if (!settings.store.enableDoubleClickToReply) return;
if (!MessageTypeSets.REPLYABLE.has(msg.type) || msg.hasFlag(MessageFlags.EPHEMERAL)) return;
const isShiftPress = event.shiftKey && !settings.store.requireModifier;
const shouldMention = Vencord.Plugins.isPluginEnabled(NoReplyMentionPlugin.name)
? NoReplyMentionPlugin.shouldMention(msg, isShiftPress)
: !isShiftPress;
FluxDispatcher.dispatch({
type: "CREATE_PENDING_REPLY",
channel,
message: msg,
shouldMention,
showMentionToggle: channel.guild_id !== null
});
event.preventDefault();
return;
} }
}, },
}); });

View file

@ -1,3 +1,3 @@
.messagelogger-deleted { .messagelogger-deleted {
background-color: hsla(var(--red-430-hsl, 0 85% 61%) / 15%) !important; background-color: hsl(var(--red-430-hsl, 0 85% 61%) / 15%) !important;
} }

View file

@ -20,6 +20,7 @@ import { BaseText } from "@components/BaseText";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { InfoIcon, OwnerCrownIcon } from "@components/Icons"; import { InfoIcon, OwnerCrownIcon } from "@components/Icons";
import { cl, getGuildPermissionSpecMap } from "@plugins/permissionsViewer/utils";
import { copyToClipboard } from "@utils/clipboard"; import { copyToClipboard } from "@utils/clipboard";
import { getIntlMessage, getUniqueUsername } from "@utils/discord"; import { getIntlMessage, getUniqueUsername } from "@utils/discord";
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
@ -28,7 +29,6 @@ import { findByCodeLazy } from "@webpack";
import { ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildRoleStore, i18n, Menu, PermissionsBits, ScrollerThin, Tooltip, useEffect, useMemo, UserStore, useState, useStateFromStores } from "@webpack/common"; import { ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildRoleStore, i18n, Menu, PermissionsBits, ScrollerThin, Tooltip, useEffect, useMemo, UserStore, useState, useStateFromStores } from "@webpack/common";
import { settings } from ".."; import { settings } from "..";
import { cl, getGuildPermissionSpecMap } from "../utils";
import { PermissionAllowedIcon, PermissionDefaultIcon, PermissionDeniedIcon } from "./icons"; import { PermissionAllowedIcon, PermissionDefaultIcon, PermissionDeniedIcon } from "./icons";
export const enum PermissionType { export const enum PermissionType {

View file

@ -19,6 +19,7 @@
import { BaseText } from "@components/BaseText"; import { BaseText } from "@components/BaseText";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { HeadingTertiary } from "@components/Heading"; import { HeadingTertiary } from "@components/Heading";
import { cl, getGuildPermissionSpecMap, getSortedRolesForMember, sortUserRoles } from "@plugins/permissionsViewer/utils";
import { getIntlMessage } from "@utils/discord"; import { getIntlMessage } from "@utils/discord";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import type { Guild, GuildMember } from "@vencord/discord-types"; import type { Guild, GuildMember } from "@vencord/discord-types";
@ -26,7 +27,6 @@ import { filters, findBulk, proxyLazyWebpack } from "@webpack";
import { PermissionsBits, Tooltip, useMemo, UserStore } from "@webpack/common"; import { PermissionsBits, Tooltip, useMemo, UserStore } from "@webpack/common";
import { PermissionsSortOrder, settings } from ".."; import { PermissionsSortOrder, settings } from "..";
import { cl, getGuildPermissionSpecMap, getSortedRolesForMember, sortUserRoles } from "../utils";
import openRolesAndUsersPermissionsModal, { PermissionType, type RoleOrUserPermission } from "./RolesAndUsersPermissions"; import openRolesAndUsersPermissionsModal, { PermissionType, type RoleOrUserPermission } from "./RolesAndUsersPermissions";
interface UserPermission { interface UserPermission {

View file

@ -8,13 +8,12 @@ import { classNameFactory } from "@api/Styles";
import { BaseText } from "@components/BaseText"; import { BaseText } from "@components/BaseText";
import { Divider } from "@components/Divider"; import { Divider } from "@components/Divider";
import { Heading } from "@components/Heading"; import { Heading } from "@components/Heading";
import { DEFAULT_COLOR, SWATCHES } from "@plugins/pinDms/constants";
import { categoryLen, createCategory, getCategory } from "@plugins/pinDms/data";
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModalLazy } from "@utils/modal"; import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModalLazy } from "@utils/modal";
import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack"; import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack";
import { Button, ColorPicker, TextInput, Toasts, useMemo, useState } from "@webpack/common"; import { Button, ColorPicker, TextInput, Toasts, useMemo, useState } from "@webpack/common";
import { DEFAULT_COLOR, SWATCHES } from "../constants";
import { categoryLen, createCategory, getCategory } from "../data";
interface ColorPickerWithSwatchesProps { interface ColorPickerWithSwatchesProps {
defaultColor: number; defaultColor: number;
colors: number[]; colors: number[];

View file

@ -5,10 +5,10 @@
*/ */
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { PinOrder, settings } from "@plugins/pinDms";
import { addChannelToCategory, canMoveChannelInDirection, currentUserCategories, isPinned, moveChannel, removeChannelFromCategory } from "@plugins/pinDms/data";
import { Menu } from "@webpack/common"; import { Menu } from "@webpack/common";
import { addChannelToCategory, canMoveChannelInDirection, currentUserCategories, isPinned, moveChannel, removeChannelFromCategory } from "../data";
import { PinOrder, settings } from "../index";
import { openCategoryModal } from "./CreateCategoryModal"; import { openCategoryModal } from "./CreateCategoryModal";
function createPinMenuItem(channelId: string) { function createPinMenuItem(channelId: string) {

View file

@ -4,11 +4,10 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { PinOrder, PrivateChannelSortStore, settings } from "@plugins/pinDms";
import { useForceUpdater } from "@utils/react"; import { useForceUpdater } from "@utils/react";
import { UserStore } from "@webpack/common"; import { UserStore } from "@webpack/common";
import { PinOrder, PrivateChannelSortStore, settings } from "./index";
export interface Category { export interface Category {
id: string; id: string;
name: string; name: string;

View file

@ -25,7 +25,7 @@ import { definePluginSettings, migratePluginSetting, Settings } from "@api/Setti
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { User } from "@vencord/discord-types"; import { DiscordPlatform, User } from "@vencord/discord-types";
import { filters, findStoreLazy, mapMangledModuleLazy } from "@webpack"; import { filters, findStoreLazy, mapMangledModuleLazy } from "@webpack";
import { AuthenticationStore, PresenceStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common"; import { AuthenticationStore, PresenceStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
@ -84,7 +84,8 @@ const Icons = {
mobile: Icon("M 187 0 L 813 0 C 916.277 0 1000 83.723 1000 187 L 1000 1313 C 1000 1416.277 916.277 1500 813 1500 L 187 1500 C 83.723 1500 0 1416.277 0 1313 L 0 187 C 0 83.723 83.723 0 187 0 Z M 125 1000 L 875 1000 L 875 250 L 125 250 Z M 500 1125 C 430.964 1125 375 1180.964 375 1250 C 375 1319.036 430.964 1375 500 1375 C 569.036 1375 625 1319.036 625 1250 C 625 1180.964 569.036 1125 500 1125 Z", { viewBox: "0 0 1000 1500", height: 17, width: 17 }), mobile: Icon("M 187 0 L 813 0 C 916.277 0 1000 83.723 1000 187 L 1000 1313 C 1000 1416.277 916.277 1500 813 1500 L 187 1500 C 83.723 1500 0 1416.277 0 1313 L 0 187 C 0 83.723 83.723 0 187 0 Z M 125 1000 L 875 1000 L 875 250 L 125 250 Z M 500 1125 C 430.964 1125 375 1180.964 375 1250 C 375 1319.036 430.964 1375 500 1375 C 569.036 1375 625 1319.036 625 1250 C 625 1180.964 569.036 1125 500 1125 Z", { viewBox: "0 0 1000 1500", height: 17, width: 17 }),
embedded: Icon("M3.06 20.4q-1.53 0-2.37-1.065T.06 16.74l1.26-9q.27-1.8 1.605-2.97T6.06 3.6h11.88q1.8 0 3.135 1.17t1.605 2.97l1.26 9q.21 1.53-.63 2.595T20.94 20.4q-.63 0-1.17-.225T18.78 19.5l-2.7-2.7H7.92l-2.7 2.7q-.45.45-.99.675t-1.17.225Zm14.94-7.2q.51 0 .855-.345T19.2 12q0-.51-.345-.855T18 10.8q-.51 0-.855.345T16.8 12q0 .51.345 .855T18 13.2Zm-2.4-3.6q.51 0 .855-.345T16.8 8.4q0-.51-.345-.855T15.6 7.2q-.51 0-.855.345T14.4 8.4q0 .51.345 .855T15.6 9.6ZM6.9 13.2h1.8v-2.1h2.1v-1.8h-2.1v-2.1h-1.8v2.1h-2.1v1.8h2.1v2.1Z", { viewBox: "0 0 24 24", height: 20, width: 20 }), embedded: Icon("M3.06 20.4q-1.53 0-2.37-1.065T.06 16.74l1.26-9q.27-1.8 1.605-2.97T6.06 3.6h11.88q1.8 0 3.135 1.17t1.605 2.97l1.26 9q.21 1.53-.63 2.595T20.94 20.4q-.63 0-1.17-.225T18.78 19.5l-2.7-2.7H7.92l-2.7 2.7q-.45.45-.99.675t-1.17.225Zm14.94-7.2q.51 0 .855-.345T19.2 12q0-.51-.345-.855T18 10.8q-.51 0-.855.345T16.8 12q0 .51.345 .855T18 13.2Zm-2.4-3.6q.51 0 .855-.345T16.8 8.4q0-.51-.345-.855T15.6 7.2q-.51 0-.855.345T14.4 8.4q0 .51.345 .855T15.6 9.6ZM6.9 13.2h1.8v-2.1h2.1v-1.8h-2.1v-2.1h-1.8v2.1h-2.1v1.8h2.1v2.1Z", { viewBox: "0 0 24 24", height: 20, width: 20 }),
suncord: Icon("M7 4a6 6 0 00-6 6v4a6 6 0 006 6h10a6 6 0 006-6v-4a6 6 0 00-6-6H7zm0 11a1 1 0 01-1-1v-1H5a1 1 0 010-2h1v-1a1 1 0 012 0v1h1a1 1 0 010 2H8v1a1 1 0 01-1 1zm10-4a1 1 0 100-2 1 1 0 000 2zm1 3a1 1 0 11-2 0 1 1 0 012 0zm0-2a1 1 0 102 0 1 1 0 00-2 0zm-3 1a1 1 0 110-2 1 1 0 010 2z", { viewBox: "0 0 24 24", height: 24, width: 24 }), suncord: Icon("M7 4a6 6 0 00-6 6v4a6 6 0 006 6h10a6 6 0 006-6v-4a6 6 0 00-6-6H7zm0 11a1 1 0 01-1-1v-1H5a1 1 0 010-2h1v-1a1 1 0 012 0v1h1a1 1 0 010 2H8v1a1 1 0 01-1 1zm10-4a1 1 0 100-2 1 1 0 000 2zm1 3a1 1 0 11-2 0 1 1 0 012 0zm0-2a1 1 0 102 0 1 1 0 00-2 0zm-3 1a1 1 0 110-2 1 1 0 010 2z", { viewBox: "0 0 24 24", height: 24, width: 24 }),
vencord: Icon("M14.8 2.7 9 3.1V47h3.3c1.7 0 6.2.3 10 .7l6.7.6V2l-4.2.2c-2.4.1-6.9.3-10 .5zm1.8 6.4c1 1.7-1.3 3.6-2.7 2.2C12.7 10.1 13.5 8 15 8c.5 0 1.2.5 1.6 1.1zM16 33c0 6-.4 10-1 10s-1-4-1-10 .4-10 1-10 1 4 1 10zm15-8v23.3l3.8-.7c2-.3 4.7-.6 6-.6H43V3h-2.2c-1.3 0-4-.3-6-.6L31 1.7V25z", { viewBox: "0 0 50 50" }), newVencord: Icon("M14.8 2.7 9 3.1V47h3.3c1.7 0 6.2.3 10 .7l6.7.6V2l-4.2.2c-2.4.1-6.9.3-10 .5zm1.8 6.4c1 1.7-1.3 3.6-2.7 2.2C12.7 10.1 13.5 8 15 8c.5 0 1.2.5 1.6 1.1zM16 33c0 6-.4 10-1 10s-1-4-1-10 .4-10 1-10 1 4 1 10zm15-8v23.3l3.8-.7c2-.3 4.7-.6 6-.6H43V3h-2.2c-1.3 0-4-.3-6-.6L31 1.7V25z", { viewBox: "0 0 50 50" }),
oldVencord: Icon("M14.8 2.7 9 3.1V47h3.3c1.7 0 6.2.3 10 .7l6.7.6V2l-4.2.2c-2.4.1-6.9.3-10 .5zm1.8 6.4c1 1.7-1.3 3.6-2.7 2.2C12.7 10.1 13.5 8 15 8c.5 0 1.2.5 1.6 1.1zM16 33c0 6-.4 10-1 10s-1-4-1-10 .4-10 1-10 1 4 1 10zm15-8v23.3l3.8-.7c2-.3 4.7-.6 6-.6H43V3h-2.2c-1.3 0-4-.3-6-.6L31 1.7V25z", { viewBox: "0 0 50 50" }),
}; };
type Platform = keyof typeof Icons; type Platform = keyof typeof Icons;
@ -102,11 +103,27 @@ const PlatformIcon = ({ platform, status, small }: PlatformIconProps) => {
: platform[0].toUpperCase() + platform.slice(1); : platform[0].toUpperCase() + platform.slice(1);
let Icon = Icons[platform] ?? Icons.desktop; let Icon = Icons[platform] ?? Icons.desktop;
const { ConsoleIcon } = settings.store; const { ConsoleIcon } = settings.store;
if (platform === "embedded" && ConsoleIcon === "vencord") { if (platform === "embedded" && ConsoleIcon === "oldVencord") {
Icon = Icons.vencord; Icon = Icons.oldVencord;
} }
if (platform === "embedded" && ConsoleIcon === "suncord") { if (platform === "embedded") {
Icon = Icons.suncord; switch (ConsoleIcon) {
case "equicord":
Icon = Icons.embedded;
break;
case "suncord":
Icon = Icons.suncord;
break;
case "newVencord":
Icon = Icons.newVencord;
break;
case "oldVencord":
Icon = Icons.oldVencord;
break;
default:
Icon = Icons.embedded;
break;
}
} }
return <Icon color={useStatusFillColor(status)} tooltip={tooltip} small={small} />; return <Icon color={useStatusFillColor(status)} tooltip={tooltip} small={small} />;
@ -149,15 +166,13 @@ const PlatformIndicator = ({ user, isProfile, isMessage, isMemberList }: Platfor
if (user == null || (user.bot && !Settings.plugins.PlatformIndicators.showBots)) return null; if (user == null || (user.bot && !Settings.plugins.PlatformIndicators.showBots)) return null;
useEnsureOwnStatus(user); useEnsureOwnStatus(user);
const status: Record<Platform, string> | undefined = useStateFromStores([PresenceStore], () => PresenceStore.getState()?.clientStatuses?.[user.id]); const status = useStateFromStores([PresenceStore], () => PresenceStore.getClientStatus(user.id));
if (status == null) { if (!status) return null;
return null;
}
const icons = Array.from(Object.entries(status), ([platform, status]) => ( const icons = Array.from(Object.entries(status), ([platform, status]) => (
<PlatformIcon <PlatformIcon
key={platform} key={platform}
platform={platform as Platform} platform={platform as DiscordPlatform}
status={status} status={status}
small={isProfile || isMemberList} small={isProfile || isMemberList}
/> />
@ -248,8 +263,12 @@ const settings = definePluginSettings({
value: "suncord", value: "suncord",
}, },
{ {
label: "Vencord", label: "Old Vencord Icon",
value: "vencord", value: "newVencord",
},
{
label: "Old Vencord Icon",
value: "oldVencord",
}, },
], ],
} }

View file

@ -16,14 +16,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { isPluginEnabled } from "@api/PluginManager";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import NoBlockedMessagesPlugin from "@plugins/noBlockedMessages";
import NoReplyMentionPlugin from "@plugins/noReplyMention";
import { Devs, IS_MAC } from "@utils/constants"; import { Devs, IS_MAC } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { Message } from "@vencord/discord-types"; import { Message } from "@vencord/discord-types";
import { MessageFlags } from "@vencord/discord-types/enums"; import { MessageFlags } from "@vencord/discord-types/enums";
import { ChannelStore, ComponentDispatch, FluxDispatcher as Dispatcher, MessageActions, MessageStore, MessageTypeSets, PermissionsBits, PermissionStore, RelationshipStore, SelectedChannelStore, UserStore } from "@webpack/common"; import { ChannelStore, ComponentDispatch, FluxDispatcher as Dispatcher, MessageActions, MessageStore, MessageTypeSets, PermissionsBits, PermissionStore, RelationshipStore, SelectedChannelStore, UserStore } from "@webpack/common";
import NoBlockedMessagesPlugin from "plugins/noBlockedMessages";
import NoReplyMentionPlugin from "plugins/noReplyMention";
let currentlyReplyingId: string | null = null; let currentlyReplyingId: string | null = null;
let currentlyEditingId: string | null = null; let currentlyEditingId: string | null = null;
@ -134,7 +135,7 @@ function getNextMessage(isUp: boolean, isReply: boolean) {
let messages: Message[] = MessageStore.getMessages(SelectedChannelStore.getChannelId())._array; let messages: Message[] = MessageStore.getMessages(SelectedChannelStore.getChannelId())._array;
const meId = UserStore.getCurrentUser().id; const meId = UserStore.getCurrentUser().id;
const hasNoBlockedMessages = Vencord.Plugins.isPluginEnabled(NoBlockedMessagesPlugin.name); const hasNoBlockedMessages = isPluginEnabled(NoBlockedMessagesPlugin.name);
messages = messages.filter(m => { messages = messages.filter(m => {
if (m.deleted) return false; if (m.deleted) return false;
@ -170,7 +171,7 @@ function getNextMessage(isUp: boolean, isReply: boolean) {
function shouldMention(message: Message) { function shouldMention(message: Message) {
switch (settings.store.shouldMention) { switch (settings.store.shouldMention) {
case MentionOptions.NO_REPLY_MENTION_PLUGIN: case MentionOptions.NO_REPLY_MENTION_PLUGIN:
if (!Vencord.Plugins.isPluginEnabled(NoReplyMentionPlugin.name)) return true; if (!isPluginEnabled(NoReplyMentionPlugin.name)) return true;
return NoReplyMentionPlugin.shouldMention(message, false); return NoReplyMentionPlugin.shouldMention(message, false);
case MentionOptions.DISABLED: case MentionOptions.DISABLED:
return false; return false;

View file

@ -16,7 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { DataStore, Notices } from "@api/index"; import * as DataStore from "@api/DataStore";
import { popNotice, showNotice } from "@api/Notices";
import { showNotification } from "@api/Notifications"; import { showNotification } from "@api/Notifications";
import { getUniqueUsername, openUserProfile } from "@utils/discord"; import { getUniqueUsername, openUserProfile } from "@utils/discord";
import { FluxStore } from "@vencord/discord-types"; import { FluxStore } from "@vencord/discord-types";
@ -111,7 +112,7 @@ export async function syncAndRunChecks() {
export function notify(text: string, icon?: string, onClick?: () => void) { export function notify(text: string, icon?: string, onClick?: () => void) {
if (settings.store.notices) if (settings.store.notices)
Notices.showNotice(text, "OK", () => Notices.popNotice()); showNotice(text, "OK", () => popNotice());
showNotification({ showNotification({
title: "Relationship Notifier", title: "Relationship Notifier",

View file

@ -6,9 +6,10 @@
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { definePluginSettings, migratePluginSettings } from "@api/Settings"; import { definePluginSettings, migratePluginSettings } from "@api/Settings";
import { Flex } from "@components/Flex";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { Flex, Menu } from "@webpack/common"; import { Menu } from "@webpack/common";
const DefaultEngines = { const DefaultEngines = {
Google: "https://www.google.com/search?q=", Google: "https://www.google.com/search?q=",
@ -93,7 +94,7 @@ function makeSearchItem(src: string) {
key={key} key={key}
id={key} id={key}
label={ label={
<Flex style={{ alignItems: "center", gap: "0.5em" }}> <Flex gap="0.5em" alignItems="center">
<img <img
style={{ style={{
borderRadius: "50%" borderRadius: "50%"

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { DataStore } from "@api/index"; import * as DataStore from "@api/DataStore";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { openModal } from "@utils/modal"; import { openModal } from "@utils/modal";
import { OAuth2AuthorizeModal, showToast, Toasts, UserStore } from "@webpack/common"; import { OAuth2AuthorizeModal, showToast, Toasts, UserStore } from "@webpack/common";

View file

@ -5,16 +5,15 @@
*/ */
import { Paragraph } from "@components/Paragraph"; import { Paragraph } from "@components/Paragraph";
import { Auth } from "@plugins/reviewDB/auth";
import { ReviewDBUser } from "@plugins/reviewDB/entities";
import { fetchBlocks, unblockUser } from "@plugins/reviewDB/reviewDbApi";
import { cl } from "@plugins/reviewDB/utils";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { ModalCloseButton, ModalContent, ModalHeader, ModalRoot, openModal } from "@utils/modal"; import { ModalCloseButton, ModalContent, ModalHeader, ModalRoot, openModal } from "@utils/modal";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import { Forms, Tooltip, useState } from "@webpack/common"; import { Forms, Tooltip, useState } from "@webpack/common";
import { Auth } from "../auth";
import { ReviewDBUser } from "../entities";
import { fetchBlocks, unblockUser } from "../reviewDbApi";
import { cl } from "../utils";
function UnblockButton(props: { onClick?(): void; }) { function UnblockButton(props: { onClick?(): void; }) {
return ( return (
<Tooltip text="Unblock user"> <Tooltip text="Unblock user">

View file

@ -16,12 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Badge } from "@plugins/reviewDB/entities";
import { cl } from "@plugins/reviewDB/utils";
import { MaskedLink, React, Tooltip } from "@webpack/common"; import { MaskedLink, React, Tooltip } from "@webpack/common";
import { HTMLAttributes } from "react"; import { HTMLAttributes } from "react";
import { Badge } from "../entities";
import { cl } from "../utils";
export default function ReviewBadge(badge: Badge & { onClick?(): void; }) { export default function ReviewBadge(badge: Badge & { onClick?(): void; }) {
const Wrapper = badge.redirectURL const Wrapper = badge.redirectURL
? MaskedLink ? MaskedLink

View file

@ -16,17 +16,17 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Auth, getToken } from "@plugins/reviewDB/auth";
import { Review, ReviewType } from "@plugins/reviewDB/entities";
import { blockUser, deleteReview, reportReview, unblockUser } from "@plugins/reviewDB/reviewDbApi";
import { settings } from "@plugins/reviewDB/settings";
import { canBlockReviewAuthor, canDeleteReview, canReportReview, cl, showToast } from "@plugins/reviewDB/utils";
import { openUserProfile } from "@utils/discord"; import { openUserProfile } from "@utils/discord";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { LazyComponent } from "@utils/react"; import { LazyComponent } from "@utils/react";
import { filters, findBulk } from "@webpack"; import { filters, findBulk } from "@webpack";
import { Alerts, Parser, Timestamp, useState } from "@webpack/common"; import { Alerts, Parser, Timestamp, useState } from "@webpack/common";
import { Auth, getToken } from "../auth";
import { Review, ReviewType } from "../entities";
import { blockUser, deleteReview, reportReview, unblockUser } from "../reviewDbApi";
import { settings } from "../settings";
import { canBlockReviewAuthor, canDeleteReview, canReportReview, cl, showToast } from "../utils";
import { openBlockModal } from "./BlockedUserModal"; import { openBlockModal } from "./BlockedUserModal";
import { BlockButton, DeleteButton, ReportButton } from "./MessageButton"; import { BlockButton, DeleteButton, ReportButton } from "./MessageButton";
import ReviewBadge from "./ReviewBadge"; import ReviewBadge from "./ReviewBadge";

View file

@ -18,14 +18,14 @@
import { BaseText } from "@components/BaseText"; import { BaseText } from "@components/BaseText";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Auth } from "@plugins/reviewDB/auth";
import { ReviewType } from "@plugins/reviewDB/entities";
import { Response, REVIEWS_PER_PAGE } from "@plugins/reviewDB/reviewDbApi";
import { cl } from "@plugins/reviewDB/utils";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { useForceUpdater } from "@utils/react"; import { useForceUpdater } from "@utils/react";
import { Paginator, useRef, useState } from "@webpack/common"; import { Paginator, useRef, useState } from "@webpack/common";
import { Auth } from "../auth";
import { ReviewType } from "../entities";
import { Response, REVIEWS_PER_PAGE } from "../reviewDbApi";
import { cl } from "../utils";
import ReviewComponent from "./ReviewComponent"; import ReviewComponent from "./ReviewComponent";
import ReviewsView, { ReviewsInputComponent } from "./ReviewsView"; import ReviewsView, { ReviewsInputComponent } from "./ReviewsView";

View file

@ -17,15 +17,15 @@
*/ */
import { Paragraph } from "@components/Paragraph"; import { Paragraph } from "@components/Paragraph";
import { Auth, authorize } from "@plugins/reviewDB/auth";
import { Review, ReviewType } from "@plugins/reviewDB/entities";
import { addReview, getReviews, Response, REVIEWS_PER_PAGE } from "@plugins/reviewDB/reviewDbApi";
import { settings } from "@plugins/reviewDB/settings";
import { cl, showToast } from "@plugins/reviewDB/utils";
import { useAwaiter, useForceUpdater } from "@utils/react"; import { useAwaiter, useForceUpdater } from "@utils/react";
import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import { React, RelationshipStore, useRef, UserStore } from "@webpack/common"; import { React, RelationshipStore, useRef, UserStore } from "@webpack/common";
import { Auth, authorize } from "../auth";
import { Review, ReviewType } from "../entities";
import { addReview, getReviews, Response, REVIEWS_PER_PAGE } from "../reviewDbApi";
import { settings } from "../settings";
import { cl, showToast } from "../utils";
import ReviewComponent from "./ReviewComponent"; import ReviewComponent from "./ReviewComponent";
const Transforms = findByPropsLazy("insertNodes", "textToText"); const Transforms = findByPropsLazy("insertNodes", "textToText");

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { DataStore } from "@api/index"; import * as DataStore from "@api/DataStore";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { hasGuildFeature } from "@utils/discord"; import { hasGuildFeature } from "@utils/discord";

View file

@ -16,12 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { dispatchTheme } from "@plugins/shikiCodeblocks.desktop/hooks/useTheme";
import type { ShikiSpec } from "@plugins/shikiCodeblocks.desktop/types";
import { shikiOnigasmSrc, shikiWorkerSrc } from "@utils/dependencies"; import { shikiOnigasmSrc, shikiWorkerSrc } from "@utils/dependencies";
import { WorkerClient } from "@vap/core/ipc"; import { WorkerClient } from "@vap/core/ipc";
import type { IShikiTheme, IThemedToken } from "@vap/shiki"; import type { IShikiTheme, IThemedToken } from "@vap/shiki";
import { dispatchTheme } from "../hooks/useTheme";
import type { ShikiSpec } from "../types";
import { getGrammar, languages, loadLanguages, resolveLang } from "./languages"; import { getGrammar, languages, loadLanguages, resolveLang } from "./languages";
import { themes } from "./themes"; import { themes } from "./themes";

View file

@ -16,7 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { cl } from "../utils/misc"; import { cl } from "@plugins/shikiCodeblocks.desktop/utils/misc";
import { CopyButton } from "./CopyButton"; import { CopyButton } from "./CopyButton";
export interface ButtonRowProps { export interface ButtonRowProps {

View file

@ -16,11 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { cl } from "@plugins/shikiCodeblocks.desktop/utils/misc";
import type { IThemedToken } from "@vap/shiki"; import type { IThemedToken } from "@vap/shiki";
import { hljs } from "@webpack/common"; import { hljs } from "@webpack/common";
import { JSX } from "react"; import { JSX } from "react";
import { cl } from "../utils/misc";
import { ThemeBase } from "./Highlighter"; import { ThemeBase } from "./Highlighter";
export interface CodeProps { export interface CodeProps {

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { useCopyCooldown } from "../hooks/useCopyCooldown"; import { useCopyCooldown } from "@plugins/shikiCodeblocks.desktop/hooks/useCopyCooldown";
export interface CopyButtonProps extends React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> { export interface CopyButtonProps extends React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
content: string; content: string;

Some files were not shown because too many files have changed in this diff Show more