Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
commit
148b7a75a1
130 changed files with 2217 additions and 3123 deletions
|
|
@ -19,13 +19,15 @@
|
|||
/// <reference path="../src/modules.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 * as DataStore from "../src/api/DataStore";
|
||||
import { debounce, localStorage } from "../src/utils";
|
||||
import { EXTENSION_BASE_URL } from "../src/utils/web-metadata";
|
||||
import { getTheme, Theme } from "../src/utils/discord";
|
||||
import { Settings } from "../src/Vencord";
|
||||
import * as DataStore from "@api/DataStore";
|
||||
import type { Settings } from "@api/Settings";
|
||||
import { debounce } from "@shared/debounce";
|
||||
import { localStorage } from "@utils/localStorage";
|
||||
import { getStylusWebStoreUrl } from "@utils/web";
|
||||
import { EXTENSION_BASE_URL } from "@utils/web-metadata";
|
||||
|
||||
// listeners for ipc.on
|
||||
const cssListeners = new Set<(css: string) => void>();
|
||||
|
|
@ -90,6 +92,8 @@ window.VencordNative = {
|
|||
return;
|
||||
}
|
||||
|
||||
const { getTheme, Theme } = require("@utils/discord");
|
||||
|
||||
win.baseUrl = EXTENSION_BASE_URL;
|
||||
win.setCss = setCssDebounced;
|
||||
win.getCurrentCss = () => VencordNative.quickCss.get();
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ export default tseslint.config(
|
|||
"@stylistic/no-extra-semi": "error",
|
||||
|
||||
// TS Rules
|
||||
"@stylistic/func-call-spacing": ["error", "never"],
|
||||
"@stylistic/function-call-spacing": ["error", "never"],
|
||||
|
||||
// ESLint Rules
|
||||
"yoda": "error",
|
||||
|
|
@ -113,7 +113,7 @@ export default tseslint.config(
|
|||
"no-useless-escape": [
|
||||
"error",
|
||||
{
|
||||
"extra": "i"
|
||||
"allowRegexCharacters": ["i"]
|
||||
}
|
||||
],
|
||||
"no-fallthrough": "error",
|
||||
|
|
|
|||
44
package.json
44
package.json
|
|
@ -51,51 +51,51 @@
|
|||
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
|
||||
"idb": "8.0.3",
|
||||
"jsqr": "1.4.0",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"nanoid": "^5.1.5",
|
||||
"monaco-editor": "^0.54.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"usercss-meta": "^0.12.0",
|
||||
"virtual-merge": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron/asar": "^4.0.1",
|
||||
"@stylistic/eslint-plugin": "^4.2.0",
|
||||
"@stylistic/eslint-plugin": "^5.6.0",
|
||||
"@types/adm-zip": "^0.5.7",
|
||||
"@types/chrome": "^0.0.312",
|
||||
"@types/chrome": "^0.1.30",
|
||||
"@types/diff": "^7.0.2",
|
||||
"@types/lodash": "^4.17.14",
|
||||
"@types/node": "^22.13.13",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@types/yazl": "^2.4.5",
|
||||
"@types/yazl": "^3.3.0",
|
||||
"@vencord/discord-types": "link:packages/discord-types",
|
||||
"adm-zip": "^0.5.16",
|
||||
"diff": "^7.0.0",
|
||||
"esbuild": "^0.25.1",
|
||||
"eslint": "9.20.1",
|
||||
"diff": "^8.0.2",
|
||||
"esbuild": "^0.27.0",
|
||||
"eslint": "9.39.1",
|
||||
"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-import-sort": "^12.1.1",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"eslint-plugin-unused-imports": "^4.3.0",
|
||||
"highlight.js": "11.11.1",
|
||||
"html-minifier-terser": "^7.2.0",
|
||||
"moment": "^2.22.2",
|
||||
"puppeteer-core": "^24.19.0",
|
||||
"puppeteer-core": "^24.30.0",
|
||||
"standalone-electron-types": "^34.2.0",
|
||||
"stylelint": "^16.17.0",
|
||||
"stylelint-config-standard": "^37.0.0",
|
||||
"stylelint": "^16.25.0",
|
||||
"stylelint-config-standard": "^39.0.1",
|
||||
"ts-patch": "^3.3.0",
|
||||
"ts-pattern": "^5.6.0",
|
||||
"tsx": "^4.19.3",
|
||||
"type-fest": "^4.38.0",
|
||||
"typescript": "^5.8.2",
|
||||
"typescript-eslint": "^8.28.0",
|
||||
"typescript-transform-paths": "^3.5.5"
|
||||
"tsx": "^4.20.6",
|
||||
"type-fest": "^5.2.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.47.0",
|
||||
"typescript-transform-paths": "^3.5.5",
|
||||
"zip-local": "^0.3.5"
|
||||
},
|
||||
"packageManager": "pnpm@10.17.1",
|
||||
"packageManager": "pnpm@10.22.0",
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"eslint@9.20.1": "patches/eslint@9.20.1.patch",
|
||||
"@types/less@3.0.8": "patches/@types__less@3.0.8.patch"
|
||||
},
|
||||
"peerDependencyRules": {
|
||||
|
|
|
|||
|
|
@ -47,3 +47,5 @@ export interface Activity {
|
|||
size?: [number, number];
|
||||
};
|
||||
}
|
||||
|
||||
export type OnlineStatus = "online" | "idle" | "dnd" | "invisible" | "offline" | "unknown" | "streaming";
|
||||
|
|
|
|||
26
packages/discord-types/src/stores/PresenceStore.d.ts
vendored
Normal file
26
packages/discord-types/src/stores/PresenceStore.d.ts
vendored
Normal 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;
|
||||
}
|
||||
1
packages/discord-types/src/stores/index.d.ts
vendored
1
packages/discord-types/src/stores/index.d.ts
vendored
|
|
@ -8,6 +8,7 @@ export * from "./GuildMemberStore";
|
|||
export * from "./GuildRoleStore";
|
||||
export * from "./GuildStore";
|
||||
export * from "./MessageStore";
|
||||
export * from "./PresenceStore";
|
||||
export * from "./RelationshipStore";
|
||||
export * from "./MediaEngineStore";
|
||||
export * from "./StreamerModeStore";
|
||||
|
|
|
|||
|
|
@ -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
1392
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -340,7 +340,7 @@ page.on("console", async e => {
|
|||
});
|
||||
|
||||
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.startsWith("Object") && !e.message.includes("Cannot find module") && !/^.{1,2}$/.test(e.message)) {
|
||||
|
|
|
|||
|
|
@ -20,34 +20,32 @@
|
|||
import "~plugins";
|
||||
|
||||
export * as Api from "./api";
|
||||
export * as Plugins from "./api/PluginManager";
|
||||
export * as Components from "./components";
|
||||
export * as Plugins from "./plugins";
|
||||
export * as Util from "./utils";
|
||||
export * as QuickCss from "./utils/quickCss";
|
||||
export * as Updater from "./utils/updater";
|
||||
export * as Webpack from "./webpack";
|
||||
export * as WebpackPatcher from "./webpack/patchWebpack";
|
||||
export { PlainSettings, Settings };
|
||||
|
||||
import "./utils/quickCss";
|
||||
import "./webpack/patchWebpack";
|
||||
|
||||
import { addVencordUiStyles } from "@components/css";
|
||||
import { openUpdaterModal } from "@components/settings/tabs/updater";
|
||||
import { debounce } from "@shared/debounce";
|
||||
import { IS_WINDOWS } from "@utils/constants";
|
||||
import { createAndAppendStyle } from "@utils/css";
|
||||
import { StartAt } from "@utils/types";
|
||||
|
||||
import { get as dsGet } from "./api/DataStore";
|
||||
import { NotificationData, showNotification } from "./api/Notifications";
|
||||
import { PlainSettings, Settings } from "./api/Settings";
|
||||
import { patches, PMLogger, startAllPlugins } from "./plugins";
|
||||
import { initPluginManager, PMLogger, startAllPlugins } from "./api/PluginManager";
|
||||
import { PlainSettings, Settings, SettingsStore } from "./api/Settings";
|
||||
import { getCloudSettings, putCloudSettings } from "./api/SettingsSync/cloudSync";
|
||||
import { localStorage } from "./utils/localStorage";
|
||||
import { relaunch } from "./utils/native";
|
||||
import { getCloudSettings, putCloudSettings } from "./utils/settingsSync";
|
||||
import { checkForUpdates, update, UpdateLogger } from "./utils/updater";
|
||||
import { onceReady } from "./webpack";
|
||||
import { SettingsRouter } from "./webpack/common";
|
||||
import { patches } from "./webpack/patchWebpack";
|
||||
|
||||
if (IS_REPORTER) {
|
||||
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;
|
||||
|
|
@ -179,6 +189,7 @@ async function init() {
|
|||
}
|
||||
}
|
||||
|
||||
initPluginManager();
|
||||
startAllPlugins(StartAt.Init);
|
||||
init();
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
*/
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import BadgeAPIPlugin from "plugins/_api/badges";
|
||||
import BadgeAPIPlugin from "@plugins/_api/badges";
|
||||
import { ComponentType, HTMLProps } from "react";
|
||||
|
||||
export const enum BadgePosition {
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/Messag
|
|||
import { addNicknameIcon, removeNicknameIcon } from "@api/NicknameIcons";
|
||||
import { Settings, SettingsStore } from "@api/Settings";
|
||||
import { disableStyle, enableStyle } from "@api/Styles";
|
||||
import { makeLazy } from "@utils/lazy";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { onlyOnce } from "@utils/onlyOnce";
|
||||
import { canonicalizeFind, canonicalizeReplacement } from "@utils/patches";
|
||||
import { Patch, Plugin, PluginDef, ReporterTestable, StartAt } from "@utils/types";
|
||||
import { FluxEvents } from "@vencord/discord-types";
|
||||
|
|
@ -37,53 +37,29 @@ import { FluxDispatcher } from "@webpack/common";
|
|||
import { patches } from "@webpack/patcher";
|
||||
|
||||
import Plugins from "~plugins";
|
||||
export { Plugins as plugins };
|
||||
|
||||
import { traceFunction } from "../debug/Tracer";
|
||||
|
||||
const logger = new Logger("PluginManager", "#a6d189");
|
||||
|
||||
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 */
|
||||
let enabledPluginsSubscribedFlux = false;
|
||||
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) {
|
||||
return (
|
||||
isPluginRequired(p) ||
|
||||
settings[p]?.enabled
|
||||
Plugins[p]?.required ||
|
||||
Plugins[p]?.isDependency ||
|
||||
Settings.plugins[p]?.enabled
|
||||
) ?? false;
|
||||
}
|
||||
|
||||
export function isPluginRequired(p: string) {
|
||||
return (
|
||||
Plugins[p]?.required
|
||||
) ?? false;
|
||||
}
|
||||
|
||||
|
|
@ -122,94 +98,6 @@ function isReporterTestable(p: Plugin, part: ReporterTestable) {
|
|||
: (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) {
|
||||
logger.info(`Starting plugins (stage ${target})`);
|
||||
for (const name in Plugins) {
|
||||
|
|
@ -225,6 +113,7 @@ export const startAllPlugins = traceFunction("startAllPlugins", function startAl
|
|||
});
|
||||
|
||||
export function startDependenciesRecursive(p: Plugin) {
|
||||
const settings = Settings.plugins;
|
||||
let restartNeeded = false;
|
||||
const failures: string[] = [];
|
||||
|
||||
|
|
@ -419,3 +308,88 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
|
|||
|
||||
return true;
|
||||
}, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -16,12 +16,9 @@
|
|||
* 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 { localStorage } from "@utils/localStorage";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { mergeDefaults } from "@utils/mergeDefaults";
|
||||
import { putCloudSettings } from "@utils/settingsSync";
|
||||
import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types";
|
||||
import { React, useEffect } from "@webpack/common";
|
||||
|
||||
|
|
@ -129,14 +126,6 @@ const DefaultSettings: Settings = {
|
|||
const settings = !IS_REPORTER ? VencordNative.settings.get() : {} as Settings;
|
||||
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, {
|
||||
readOnly: true,
|
||||
getDefaultValue({
|
||||
|
|
@ -179,8 +168,6 @@ export const SettingsStore = new SettingsStoreClass(settings, {
|
|||
if (!IS_REPORTER) {
|
||||
SettingsStore.addGlobalChangeListener((_, path) => {
|
||||
SettingsStore.plain.cloud.settingsSyncVersion = Date.now();
|
||||
localStorage.Vencord_settingsDirty = true;
|
||||
saveSettingsOnFrequentAction();
|
||||
VencordNative.settings.set(SettingsStore.plain, path);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,17 @@
|
|||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import * as DataStore from "@api/DataStore";
|
||||
import { showNotification } from "@api/Notifications";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { openModal } from "@utils/modal";
|
||||
import { OAuth2AuthorizeModal, UserStore } from "@webpack/common";
|
||||
|
||||
import { Logger } from "./Logger";
|
||||
import { openModal } from "./modal";
|
||||
|
||||
export const cloudLogger = new Logger("Cloud", "#39b7e0");
|
||||
export const logger = new Logger("SettingsSync:CloudSetup", "#39b7e0");
|
||||
|
||||
export const getCloudUrl = () => new URL(Settings.cloud.url);
|
||||
const getCloudUrlOrigin = () => getCloudUrl().origin;
|
||||
|
|
@ -111,7 +98,7 @@ export async function authorizeCloud() {
|
|||
});
|
||||
const { secret } = await res.json();
|
||||
if (secret) {
|
||||
cloudLogger.info("Authorized with secret");
|
||||
logger.info("Authorized with secret");
|
||||
await setAuthorization(secret);
|
||||
showNotification({
|
||||
title: "Cloud Integration",
|
||||
|
|
@ -126,7 +113,7 @@ export async function authorizeCloud() {
|
|||
Settings.cloud.authenticated = false;
|
||||
}
|
||||
} catch (e: any) {
|
||||
cloudLogger.error("Failed to authorize", e);
|
||||
logger.error("Failed to authorize", e);
|
||||
showNotification({
|
||||
title: "Cloud Integration",
|
||||
body: `Setup failed (${e.toString()}).`
|
||||
224
src/api/SettingsSync/cloudSync.ts
Normal file
224
src/api/SettingsSync/cloudSync.ts
Normal 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)"
|
||||
});
|
||||
}
|
||||
95
src/api/SettingsSync/offline.ts
Normal file
95
src/api/SettingsSync/offline.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -16,9 +16,12 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type { MapValue } from "type-fest/source/entry";
|
||||
|
||||
export type Style = MapValue<typeof VencordStyles>;
|
||||
export interface Style {
|
||||
name: string;
|
||||
source: string;
|
||||
classNames: Record<string, string>;
|
||||
dom: HTMLStyleElement | null;
|
||||
}
|
||||
|
||||
export const styleMap = window.VencordStyles ??= new Map();
|
||||
|
||||
|
|
|
|||
|
|
@ -17,9 +17,8 @@
|
|||
*/
|
||||
|
||||
import { Settings, SettingsStore } from "@api/Settings";
|
||||
import { ThemeStore } from "@webpack/common";
|
||||
|
||||
import { createAndAppendStyle } from "./css";
|
||||
import { createAndAppendStyle } from "@utils/css";
|
||||
import { ThemeStore } from "@vencord/discord-types";
|
||||
|
||||
let style: HTMLStyleElement;
|
||||
let themesStyle: HTMLStyleElement;
|
||||
|
|
@ -54,14 +53,15 @@ async function initThemes() {
|
|||
|
||||
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
|
||||
// This function is first called on DOMContentLoaded, so ThemeStore may not have been loaded yet
|
||||
const activeTheme = ThemeStore == null
|
||||
? undefined
|
||||
: ThemeStore.theme === "light" ? "light" : "dark";
|
||||
|
||||
const links = enabledlinks
|
||||
const links = enabledThemeLinks
|
||||
.map(rawLink => {
|
||||
const match = /^@(light|dark) (.*)/.exec(rawLink);
|
||||
if (!match) return rawLink;
|
||||
|
|
@ -103,16 +103,16 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||
}
|
||||
}, { once: true });
|
||||
|
||||
export function initQuickCssThemeStore() {
|
||||
export function initQuickCssThemeStore(themeStore: ThemeStore) {
|
||||
if (IS_USERSCRIPT) return;
|
||||
|
||||
initThemes();
|
||||
|
||||
let currentTheme = ThemeStore.theme;
|
||||
ThemeStore.addChangeListener(() => {
|
||||
if (currentTheme === ThemeStore.theme) return;
|
||||
let currentTheme = themeStore.theme;
|
||||
themeStore.addChangeListener(() => {
|
||||
if (currentTheme === themeStore.theme) return;
|
||||
|
||||
currentTheme = ThemeStore.theme;
|
||||
currentTheme = themeStore.theme;
|
||||
initThemes();
|
||||
});
|
||||
}
|
||||
|
|
@ -20,6 +20,8 @@ import { proxyLazy } from "@utils/lazy";
|
|||
import { Logger } from "@utils/Logger";
|
||||
import { findModuleId, proxyLazyWebpack, wreq } from "@webpack";
|
||||
|
||||
import { isPluginEnabled } from "./PluginManager";
|
||||
|
||||
interface UserSettingDefinition<T> {
|
||||
/**
|
||||
* Get the setting value
|
||||
|
|
@ -45,7 +47,7 @@ interface UserSettingDefinition<T> {
|
|||
|
||||
export const UserSettings: Record<PropertyKey, UserSettingDefinition<any>> | undefined = proxyLazyWebpack(() => {
|
||||
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);
|
||||
});
|
||||
|
|
@ -57,7 +59,7 @@ export const UserSettings: Record<PropertyKey, UserSettingDefinition<any>> | und
|
|||
* @param name The name of the setting
|
||||
*/
|
||||
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) {
|
||||
const userSetting = UserSettings[key];
|
||||
|
|
|
|||
|
|
@ -31,9 +31,11 @@ import * as $MessageUpdater from "./MessageUpdater";
|
|||
import * as $NicknameIcons from "./NicknameIcons";
|
||||
import * as $Notices from "./Notices";
|
||||
import * as $Notifications from "./Notifications";
|
||||
export * as PluginManager from "./PluginManager";
|
||||
import * as $ServerList from "./ServerList";
|
||||
import * as $Settings from "./Settings";
|
||||
import * as $Styles from "./Styles";
|
||||
import * as $Themes from "./Themes";
|
||||
import * as $UserSettings from "./UserSettings";
|
||||
|
||||
/**
|
||||
|
|
@ -137,6 +139,11 @@ export const MessageUpdater = $MessageUpdater;
|
|||
*/
|
||||
export const UserSettings = $UserSettings;
|
||||
|
||||
/**
|
||||
* Don't use this
|
||||
*/
|
||||
export const Themes = $Themes;
|
||||
|
||||
/**
|
||||
* An API allowing you to add icons to the nickname, in profiles.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import type { Button as DiscordButton } from "@vencord/discord-types";
|
|||
import type { ComponentPropsWithRef } from "react";
|
||||
|
||||
import { OpenExternalIcon } from "./Icons";
|
||||
import { Link } from "./Link";
|
||||
|
||||
const btnCls = classNameFactory("vc-btn-");
|
||||
const textBtnCls = classNameFactory("vc-text-btn-");
|
||||
|
|
@ -25,6 +26,11 @@ export type ButtonProps = ComponentPropsWithRef<"button"> & {
|
|||
size?: ButtonSize;
|
||||
};
|
||||
|
||||
export type LinkButtonProps = ComponentPropsWithRef<"a"> & {
|
||||
size?: ButtonSize;
|
||||
variant?: ButtonVariant;
|
||||
};
|
||||
|
||||
export function Button({ variant = "primary", size = "medium", children, className, ...restProps }: ButtonProps) {
|
||||
return (
|
||||
<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 TextButtonProps = ComponentPropsWithRef<"button"> & {
|
||||
|
|
|
|||
20
src/components/Card.css
Normal file
20
src/components/Card.css
Normal 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
31
src/components/Card.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -16,22 +16,28 @@
|
|||
* 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 (
|
||||
<div {...props}>
|
||||
{props.children}
|
||||
<div style={style} {...restProps}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
.vc-settings-theme-grid {
|
||||
display: grid;
|
||||
grid-gap: 16px;
|
||||
gap: 16px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
}
|
||||
|
||||
|
|
@ -37,13 +37,30 @@
|
|||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.vc-settings-usercss-ie-buttons>div {
|
||||
.vc-settings-usercss-ie-buttons > div {
|
||||
color: var(--interactive-normal);
|
||||
opacity: .5;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.vc-settings-usercss-ie-buttons>div:hover {
|
||||
.vc-settings-usercss-ie-buttons > div:hover {
|
||||
color: var(--interactive-hover);
|
||||
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;
|
||||
}
|
||||
|
|
@ -5,6 +5,8 @@
|
|||
*/
|
||||
|
||||
export * from "./BaseText";
|
||||
export * from "./Button";
|
||||
export * from "./Card";
|
||||
export * from "./CheckedTextInput";
|
||||
export * from "./CodeBlock";
|
||||
export * from "./Divider";
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import "./QuickAction.css";
|
||||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { Card } from "@webpack/common";
|
||||
import { Card } from "@components/Card";
|
||||
import type { ComponentType, PropsWithChildren, ReactNode } from "react";
|
||||
|
||||
const cl = classNameFactory("vc-settings-quickActions-");
|
||||
|
|
|
|||
|
|
@ -19,10 +19,11 @@
|
|||
import "./SpecialCard.css";
|
||||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { Card } from "@components/Card";
|
||||
import { Divider } from "@components/Divider";
|
||||
import { Heading } from "@components/Heading";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { Card, Clickable, React } from "@webpack/common";
|
||||
import { Clickable } from "@webpack/common";
|
||||
import type { PropsWithChildren } from "react";
|
||||
|
||||
const cl = classNameFactory("vc-special-");
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ import { Flex } from "@components/Flex";
|
|||
import { HeadingTertiary } from "@components/Heading";
|
||||
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
|
||||
import { debounce } from "@shared/debounce";
|
||||
import { copyWithToast } from "@utils/discord";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { copyWithToast } from "@utils/misc";
|
||||
import { stripIndent } from "@utils/text";
|
||||
import { ReplaceFn } from "@utils/types";
|
||||
import { search } from "@webpack";
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@
|
|||
*/
|
||||
|
||||
import { showNotice } from "@api/Notices";
|
||||
import { isPluginEnabled, startDependenciesRecursive, startPlugin, stopPlugin } from "@api/PluginManager";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { CogWheel, InfoIcon } from "@components/Icons";
|
||||
import { AddonCard } from "@components/settings/AddonCard";
|
||||
import { proxyLazy } from "@utils/lazy";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { isObjectEmpty } from "@utils/misc";
|
||||
import { Plugin } from "@utils/types";
|
||||
|
|
@ -21,10 +21,6 @@ import { openPluginModal } from "./PluginModal";
|
|||
|
||||
const logger = new Logger("PluginCard");
|
||||
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> {
|
||||
plugin: Plugin;
|
||||
disabled?: boolean;
|
||||
|
|
|
|||
|
|
@ -19,8 +19,10 @@
|
|||
import "./styles.css";
|
||||
|
||||
import * as DataStore from "@api/DataStore";
|
||||
import { isPluginEnabled, stopPlugin } from "@api/PluginManager";
|
||||
import { useSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { Card } from "@components/Card";
|
||||
import { Divider } from "@components/Divider";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { HeadingTertiary } from "@components/Heading";
|
||||
|
|
@ -28,12 +30,12 @@ import { Paragraph } from "@components/Paragraph";
|
|||
import { SettingsTab } from "@components/settings";
|
||||
import { debounce } from "@shared/debounce";
|
||||
import { ChangeList } from "@utils/ChangeList";
|
||||
import { proxyLazy } from "@utils/lazy";
|
||||
import { isTruthy } from "@utils/guards";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { classes } from "@utils/misc";
|
||||
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 Plugins, { ExcludedPlugins, PluginMeta } from "~plugins";
|
||||
|
|
@ -42,9 +44,6 @@ import { PluginCard } from "./PluginCard";
|
|||
import { openWarningModal } from "./PluginModal";
|
||||
import { StockPluginsCard, UserPluginsCard } from "./PluginStatCards";
|
||||
|
||||
// Avoid circular dependency
|
||||
const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() => require("plugins"));
|
||||
|
||||
export const cl = classNameFactory("vc-plugins-");
|
||||
export const logger = new Logger("PluginSettings", "#a6d189");
|
||||
|
||||
|
|
@ -100,8 +99,9 @@ const enum SearchStatus {
|
|||
DISABLED,
|
||||
EQUICORD,
|
||||
VENCORD,
|
||||
CUSTOM,
|
||||
NEW,
|
||||
USER_PLUGINS,
|
||||
API_PLUGINS
|
||||
}
|
||||
|
||||
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; }) {
|
||||
const matchingExcludedPlugins = Object.entries(ExcludedPlugins)
|
||||
.filter(([name]) => name.toLowerCase().includes(search));
|
||||
const matchingExcludedPlugins = search
|
||||
? Object.entries(ExcludedPlugins)
|
||||
.filter(([name]) => name.toLowerCase().includes(search))
|
||||
: [];
|
||||
|
||||
return (
|
||||
<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)
|
||||
.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 onSearch = (query: string) => {
|
||||
|
|
@ -186,17 +202,32 @@ export default function PluginSettings() {
|
|||
|
||||
const pluginFilter = (plugin: typeof Plugins[keyof typeof Plugins]) => {
|
||||
const { status } = searchValue;
|
||||
const enabled = Vencord.Plugins.isPluginEnabled(plugin.name);
|
||||
const pluginMeta = PluginMeta[plugin.name];
|
||||
const isEquicordPlugin = pluginMeta.folderName.startsWith("src/equicordplugins/") ?? false;
|
||||
const isUserplugin = pluginMeta.userPlugin ?? false;
|
||||
const enabled = isPluginEnabled(plugin.name);
|
||||
|
||||
switch (status) {
|
||||
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;
|
||||
|
||||
return (
|
||||
|
|
@ -226,7 +257,7 @@ export default function PluginSettings() {
|
|||
const plugins = [] 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) {
|
||||
if (p.hidden || (!p.options && p.name.endsWith("API") && !showApi))
|
||||
continue;
|
||||
|
|
@ -374,9 +405,10 @@ export default function PluginSettings() {
|
|||
{ label: "Show Disabled", value: SearchStatus.DISABLED },
|
||||
{ label: "Show Equicord", value: SearchStatus.EQUICORD },
|
||||
{ label: "Show Vencord", value: SearchStatus.VENCORD },
|
||||
...(totalUserPlugins > 0 ? [{ label: "Show Custom", value: SearchStatus.CUSTOM }] : []),
|
||||
{ label: "Show New", value: SearchStatus.NEW },
|
||||
]}
|
||||
hasUserPlugins && { label: "Show UserPlugins", value: SearchStatus.USER_PLUGINS },
|
||||
{ label: "Show API Plugins", value: SearchStatus.API_PLUGINS },
|
||||
].filter(isTruthy)}
|
||||
serialize={String}
|
||||
select={onStatusChange}
|
||||
isSelected={v => v === searchValue.status}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
.vc-plugins-grid {
|
||||
margin-top: 16px;
|
||||
display: grid;
|
||||
grid-gap: 16px;
|
||||
gap: 16px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@
|
|||
.vc-cloud-settings-sync-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-gap: 1em;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.vc-cloud-erase-data-danger-btn {
|
||||
|
|
|
|||
|
|
@ -16,70 +16,73 @@
|
|||
* 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 { Heading } from "@components/Heading";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { classes } from "@utils/misc";
|
||||
import { downloadSettingsBackup, uploadSettingsBackup } from "@utils/settingsSync";
|
||||
import { Button, Card } from "@webpack/common";
|
||||
import { Button, Text } from "@webpack/common";
|
||||
|
||||
function BackupAndRestoreTab() {
|
||||
return (
|
||||
<SettingsTab title="Backup & Restore">
|
||||
<Card className={classes("vc-settings-card", "vc-backup-restore-card")}>
|
||||
<Flex flexDirection="column">
|
||||
<strong>Warning</strong>
|
||||
<span>Importing a settings file will overwrite your current settings.</span>
|
||||
<Flex flexDirection="column" gap="0.5em">
|
||||
<Card variant="warning">
|
||||
<Heading tag="h4">Warning</Heading>
|
||||
<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>— Custom QuickCSS</li>
|
||||
<li>— Theme Links</li>
|
||||
<li>— 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>
|
||||
</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>— Custom QuickCSS</li>
|
||||
<li>— Theme Links</li>
|
||||
<li>— Plugin Settings</li>
|
||||
<li>— 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>
|
||||
</SettingsTab>
|
||||
</SettingsTab >
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,8 +16,9 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { showNotification } from "@api/Notifications";
|
||||
import { Settings, useSettings } from "@api/Settings";
|
||||
import { 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 { Divider } from "@components/Divider";
|
||||
import { FormSwitch } from "@components/FormSwitch";
|
||||
|
|
@ -26,9 +27,7 @@ import { Heading } from "@components/Heading";
|
|||
import { Link } from "@components/Link";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
|
||||
import { authorizeCloud, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { deleteCloudSettings, getCloudSettings, putCloudSettings } from "@utils/settingsSync";
|
||||
import { Alerts, Button, Tooltip, useState } from "@webpack/common";
|
||||
|
||||
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() {
|
||||
const { cloud } = useSettings(["cloud.authenticated", "cloud.settingsSync"]);
|
||||
const sectionEnabled = cloud.authenticated && cloud.settingsSync;
|
||||
|
|
@ -196,7 +169,7 @@ function CloudTab() {
|
|||
onClick={() => Alerts.show({
|
||||
title: "Are you sure?",
|
||||
body: "Once your data is erased, we cannot recover it. There's no going back!",
|
||||
onConfirm: eraseAllData,
|
||||
onConfirm: eraseAllCloudData,
|
||||
confirmText: "Erase it!",
|
||||
confirmColor: "vc-cloud-erase-data-danger-btn",
|
||||
cancelText: "Nevermind"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Card } from "@components/Card";
|
||||
import { ErrorCard } from "@components/ErrorCard";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { Link } from "@components/Link";
|
||||
|
|
@ -12,7 +13,7 @@ import { Margins } from "@utils/margins";
|
|||
import { classes } from "@utils/misc";
|
||||
import { relaunch } from "@utils/native";
|
||||
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";
|
||||
|
||||
|
|
@ -31,7 +32,7 @@ export function HashLink({ repo, hash, disabled = false }: { repo: string, hash:
|
|||
|
||||
export function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) {
|
||||
return (
|
||||
<Card style={{ padding: "0 0.5em" }}>
|
||||
<Card style={{ padding: "0 0.5em" }} defaultPadding={false}>
|
||||
{updates.map(({ hash, author, message }) => (
|
||||
<div
|
||||
key={hash}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@
|
|||
* 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 { Button, GuildMemberStore } from "@webpack/common";
|
||||
import BadgeAPI from "plugins/_api/badges";
|
||||
|
||||
export const isDonor = (userId: string) => !!(
|
||||
BadgeAPI.getDonorBadges(userId)?.length > 0
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ import "./VencordTab.css";
|
|||
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
|
||||
import { useSettings } from "@api/Settings";
|
||||
import { Divider } from "@components/Divider";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { FormSwitch } from "@components/FormSwitch";
|
||||
import { Heading } from "@components/Heading";
|
||||
import { FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from "@components/Icons";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { openContributorModal, openPluginModal, SettingsTab, wrapTab } from "@components/settings";
|
||||
import { DonateButton, InviteButton } from "@components/settings/DonateButton";
|
||||
import { DonateButton, InviteButton, openContributorModal, openPluginModal, SettingsTab, wrapTab } from "@components/settings";
|
||||
import { QuickAction, QuickActionCard } from "@components/settings/QuickAction";
|
||||
import { SpecialCard } from "@components/settings/SpecialCard";
|
||||
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 { identity, isAnyPluginDev } from "@utils/misc";
|
||||
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 { openNotificationSettingsModal } from "./NotificationSettings";
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addPatch } from "@api/PluginManager";
|
||||
import { initWs } from "@plugins/devCompanion.dev/initWs";
|
||||
import { Logger } from "@utils/Logger";
|
||||
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 { reporterData } from "./reporterData";
|
||||
|
||||
|
|
|
|||
|
|
@ -16,9 +16,10 @@
|
|||
* 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 { isPluginEnabled, plugins } from "@api/PluginManager";
|
||||
import { migratePluginSettings, Settings, useSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
|
|
@ -34,8 +35,8 @@ function VencordPopout(onClose: () => void) {
|
|||
|
||||
const pluginEntries = [] as ReactNode[];
|
||||
|
||||
for (const plugin of Object.values(Vencord.Plugins.plugins)) {
|
||||
if (plugin.toolboxActions && Vencord.Plugins.isPluginEnabled(plugin.name)) {
|
||||
for (const plugin of Object.values(plugins)) {
|
||||
if (plugin.toolboxActions && isPluginEnabled(plugin.name)) {
|
||||
pluginEntries.push(
|
||||
<Menu.MenuGroup
|
||||
label={plugin.name}
|
||||
|
|
|
|||
16
src/equicordplugins/equicordToolbox/styles.css
Normal file
16
src/equicordplugins/equicordToolbox/styles.css
Normal 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);
|
||||
}
|
||||
|
|
@ -25,8 +25,8 @@ import { CopyIcon, ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Ico
|
|||
import { Paragraph } from "@components/Paragraph";
|
||||
import { Span } from "@components/Span";
|
||||
import { debounce } from "@shared/debounce";
|
||||
import { openImageModal } from "@utils/discord";
|
||||
import { classes, copyWithToast } from "@utils/misc";
|
||||
import { copyWithToast, openImageModal } from "@utils/discord";
|
||||
import { classes } from "@utils/misc";
|
||||
import { ContextMenuApi, FluxDispatcher, Menu, React, useEffect, useState, useStateFromStores } from "@webpack/common";
|
||||
|
||||
import { SeekBar } from "./SeekBar";
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { isPluginEnabled } from "@api/PluginManager";
|
||||
import { Settings } from "@api/Settings";
|
||||
import OpenInAppPlugin from "@plugins/openInApp";
|
||||
import { findByProps, findByPropsLazy, proxyLazyWebpack } from "@webpack";
|
||||
import { Flux, FluxDispatcher } from "@webpack/common";
|
||||
|
||||
|
|
@ -89,7 +91,7 @@ export const SpotifyStore = proxyLazyWebpack(() => {
|
|||
public isSettingPosition = false;
|
||||
|
||||
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 ? "" : ":")
|
||||
: "https://open.spotify.com" + path;
|
||||
|
||||
|
|
|
|||
9
src/globals.d.ts
vendored
9
src/globals.d.ts
vendored
|
|
@ -16,6 +16,8 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Style } from "@api/Styles";
|
||||
|
||||
declare global {
|
||||
/**
|
||||
* 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 Vencord: typeof import("./Vencord");
|
||||
export var VencordStyles: Map<string, {
|
||||
name: string;
|
||||
source: string;
|
||||
classNames: Record<string, string>;
|
||||
dom: HTMLStyleElement | null;
|
||||
}>;
|
||||
export var VencordStyles: Map<string, Style>;
|
||||
export var appSettings: {
|
||||
set(setting: string, v: any): void;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,8 +22,9 @@ import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Bad
|
|||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { openContributorModal } from "@components/settings/tabs";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { copyWithToast } from "@utils/discord";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { copyWithToast, shouldShowContributorBadge, shouldShowEquicordContributorBadge } from "@utils/misc";
|
||||
import { shouldShowContributorBadge, shouldShowEquicordContributorBadge } from "@utils/misc";
|
||||
import definePlugin from "@utils/types";
|
||||
import { User } from "@vencord/discord-types";
|
||||
import { ContextMenuApi, Menu, Toasts, UserStore } from "@webpack/common";
|
||||
|
|
@ -63,10 +64,11 @@ const UserPluginContributorBadge: ProfileBadge = {
|
|||
iconSrc: USERPLUGIN_CONTRIBUTOR_BADGE,
|
||||
position: BadgePosition.START,
|
||||
shouldShow: ({ userId }) => {
|
||||
if (!IS_DEV) return false;
|
||||
const allPlugins = Object.values(Plugins);
|
||||
return allPlugins.some(p => {
|
||||
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)),
|
||||
|
|
|
|||
|
|
@ -17,14 +17,16 @@
|
|||
*/
|
||||
|
||||
import { sendBotMessage } from "@api/Commands";
|
||||
import { isPluginEnabled } from "@api/PluginManager";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { getUserSettingLazy } from "@api/UserSettings";
|
||||
import { BaseText } from "@components/BaseText";
|
||||
import { Card } from "@components/Card";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { Link } from "@components/Link";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { openUpdaterModal } from "@components/settings/tabs/updater";
|
||||
import { openUpdaterModal } from "@components/settings";
|
||||
import { platformName } from "@equicordplugins/equicordHelper/utils";
|
||||
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";
|
||||
|
|
@ -37,7 +39,7 @@ import { onlyOnce } from "@utils/onlyOnce";
|
|||
import { makeCodeblock } from "@utils/text";
|
||||
import definePlugin from "@utils/types";
|
||||
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 gitHash from "~git-hash";
|
||||
|
|
@ -137,7 +139,7 @@ function generatePluginList() {
|
|||
const isApiPlugin = (plugin: string) => plugin.endsWith("API") || plugins[plugin].required;
|
||||
|
||||
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 enabledUserPlugins = enabledPlugins.filter(p => PluginMeta[p].userPlugin);
|
||||
|
|
@ -408,8 +410,8 @@ export default definePlugin({
|
|||
if (RelationshipStore.isFriend(userId) || isAnyPluginDev(UserStore.getCurrentUser()?.id)) return null;
|
||||
|
||||
return (
|
||||
<Card className={`vc-plugins-restart-card ${Margins.top8}`}>
|
||||
Please do not private message plugin developers for support!
|
||||
<Card variant="warning" className={Margins.top8} defaultPadding>
|
||||
Please do not private message Vencord plugin developers for support!
|
||||
<br />
|
||||
Instead, use the support channel: {Parser.parse("https://discord.com/channels/1173279886065029291/1297590739911573585")}
|
||||
{!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { copyWithToast } from "@utils/misc";
|
||||
import { copyWithToast } from "@utils/discord";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@
|
|||
*/
|
||||
|
||||
import { Button } from "@components/Button";
|
||||
import { SessionInfo } from "@plugins/betterSessions/types";
|
||||
import { openModal } from "@utils/modal";
|
||||
|
||||
import { SessionInfo } from "../types";
|
||||
import { RenameModal } from "./RenameModal";
|
||||
|
||||
export function RenameButton({ session, state }: { session: SessionInfo["session"], state: [string, React.Dispatch<React.SetStateAction<string>>]; }) {
|
||||
|
|
|
|||
|
|
@ -16,14 +16,13 @@
|
|||
* 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 { Button, React, TextInput } from "@webpack/common";
|
||||
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>>]; }) {
|
||||
const [title, setTitle] = state;
|
||||
const [value, setValue] = React.useState(savedSessionsCache.get(session.id_hash)?.name ?? "");
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
* 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 { ChromeIcon, DiscordIcon, EdgeIcon, FirefoxIcon, IEIcon, MobileIcon, OperaIcon, SafariIcon, UnknownIcon } from "./components/icons";
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { isPluginEnabled } from "@api/PluginManager";
|
||||
import { openPluginModal } from "@components/settings/tabs";
|
||||
import { getIntlMessage } from "@utils/discord";
|
||||
import { isObjectEmpty } from "@utils/misc";
|
||||
|
|
@ -28,7 +29,7 @@ export default function PluginsSubmenu() {
|
|||
|
||||
const search = query.toLowerCase();
|
||||
const include = (p: typeof Plugins[keyof typeof Plugins]) => (
|
||||
Vencord.Plugins.isPluginEnabled(p.name)
|
||||
isPluginEnabled(p.name)
|
||||
&& p.options && !isObjectEmpty(p.options)
|
||||
&& (
|
||||
p.name.toLowerCase().includes(search)
|
||||
|
|
|
|||
|
|
@ -6,15 +6,15 @@
|
|||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { ErrorCard } from "@components/ErrorCard";
|
||||
import { HeadingPrimary, HeadingSecondary } from "@components/Heading";
|
||||
import { HeadingPrimary,HeadingSecondary } from "@components/Heading";
|
||||
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 { findByCodeLazy, findStoreLazy } from "@webpack";
|
||||
import { Button, ColorPicker, ThemeStore, useStateFromStores } from "@webpack/common";
|
||||
|
||||
import { settings } from "..";
|
||||
import { relativeLuminance } from "../utils/colorUtils";
|
||||
import { createOrUpdateThemeColorVars } from "../utils/styleUtils";
|
||||
|
||||
const saveClientTheme = findByCodeLazy('type:"UNSYNCED_USER_SETTINGS_UPDATE', '"system"===');
|
||||
const NitroThemeStore = findStoreLazy("ClientThemesBackgroundStore");
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ function makeShortcuts() {
|
|||
openModal: { getter: () => ModalAPI.openModal },
|
||||
openModalLazy: { getter: () => ModalAPI.openModalLazy },
|
||||
|
||||
Stores: Webpack.fluxStores,
|
||||
Stores: { getter: () => Object.fromEntries(Webpack.fluxStores) },
|
||||
|
||||
// e.g. "2024-05_desktop_visual_refresh", 0
|
||||
setExperiment: (id: string, bucket: number) => {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { copyWithToast } from "@utils/misc";
|
||||
import { copyWithToast } from "@utils/discord";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Menu } from "@webpack/common";
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import "./style.css";
|
|||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { CopyIcon, NoEntrySignIcon } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { copyWithToast } from "@utils/misc";
|
||||
import { copyWithToast } from "@utils/discord";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Tooltip, useState } from "@webpack/common";
|
||||
|
||||
|
|
|
|||
|
|
@ -17,12 +17,13 @@
|
|||
*/
|
||||
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { isPluginEnabled } from "@api/PluginManager";
|
||||
import ExpressionClonerPlugin from "@plugins/expressionCloner";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { copyWithToast } from "@utils/misc";
|
||||
import { copyWithToast } from "@utils/discord";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Message, Sticker } from "@vencord/discord-types";
|
||||
import { Menu, React, StickersStore } from "@webpack/common";
|
||||
import ExpressionClonerPlugin from "plugins/expressionCloner";
|
||||
|
||||
const StickerExt = [, "png", "png", "json", "gif"] as const;
|
||||
|
||||
|
|
@ -79,7 +80,7 @@ const expressionPickerPatch: NavContextMenuPatchCallback = (children, props: { t
|
|||
|
||||
const sticker = StickersStore.getStickerById(id);
|
||||
if (sticker) {
|
||||
children.push(buildMenuItem(sticker, Vencord.Plugins.isPluginEnabled(ExpressionClonerPlugin.name)));
|
||||
children.push(buildMenuItem(sticker, isPluginEnabled(ExpressionClonerPlugin.name)));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* 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 { Devs } from "@utils/constants";
|
||||
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.";
|
||||
if (
|
||||
Notices.currentNotice?.[1] === backOnlineMessage ||
|
||||
Notices.noticesQueue.some(([, noticeMessage]) => noticeMessage === backOnlineMessage)
|
||||
currentNotice?.[1] === backOnlineMessage ||
|
||||
noticesQueue.some(([, noticeMessage]) => noticeMessage === backOnlineMessage)
|
||||
) return;
|
||||
|
||||
Notices.showNotice(backOnlineMessage, "Exit idle", () => {
|
||||
Notices.popNotice();
|
||||
showNotice(backOnlineMessage, "Exit idle", () => {
|
||||
popNotice();
|
||||
FluxDispatcher.dispatch({
|
||||
type: "IDLE",
|
||||
idle: false
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import "./settings.css";
|
||||
|
||||
import { isPluginEnabled } from "@api/PluginManager";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { Divider } from "@components/Divider";
|
||||
import { Heading } from "@components/Heading";
|
||||
|
|
@ -14,7 +15,7 @@ import { debounce } from "@shared/debounce";
|
|||
import { ActivityType } from "@vencord/discord-types/enums";
|
||||
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-");
|
||||
|
||||
|
|
@ -50,7 +51,7 @@ function isAppIdValid(value: string) {
|
|||
|
||||
const updateRPC = debounce(() => {
|
||||
setRpc(true);
|
||||
if (Vencord.Plugins.isPluginEnabled("CustomRPC")) setRpc();
|
||||
if (isPluginEnabled(CustomRPCPlugin.name)) setRpc();
|
||||
});
|
||||
|
||||
function isStreamLinkDisabled() {
|
||||
|
|
|
|||
|
|
@ -4,14 +4,13 @@
|
|||
* 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 { Logger } from "@utils/Logger";
|
||||
import { openModal } from "@utils/modal";
|
||||
import { OAuth2AuthorizeModal, showToast, Toasts, UserStore, zustandCreate, zustandPersist } from "@webpack/common";
|
||||
|
||||
import { AUTHORIZE_URL, CLIENT_ID } from "../constants";
|
||||
|
||||
interface AuthorizationState {
|
||||
token: string | null;
|
||||
tokens: Record<string, string>;
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@
|
|||
* 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 { UserStore, zustandCreate } from "@webpack/common";
|
||||
|
||||
import { Decoration, deleteDecoration, getUserDecoration, getUserDecorations, NewDecoration, setUserDecoration } from "../api";
|
||||
import { decorationToAsset } from "../utils/decoration";
|
||||
import { useUsersDecorationsStore } from "./UsersDecorationsStore";
|
||||
|
||||
interface UserDecorationsState {
|
||||
|
|
|
|||
|
|
@ -4,15 +4,14 @@
|
|||
* 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 { proxyLazy } from "@utils/lazy";
|
||||
import { User } from "@vencord/discord-types";
|
||||
import { useEffect, useState, zustandCreate } from "@webpack/common";
|
||||
|
||||
import { AvatarDecoration } from "../../";
|
||||
import { getUsersDecorations } from "../api";
|
||||
import { DECORATION_FETCH_COOLDOWN, SKU_ID } from "../constants";
|
||||
|
||||
interface UserDecorationData {
|
||||
asset: string | null;
|
||||
fetchedAt: Date;
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { AvatarDecoration } from "../../";
|
||||
import { Decoration } from "../api";
|
||||
import { SKU_ID } from "../constants";
|
||||
import { AvatarDecoration } from "@plugins/decor";
|
||||
import { Decoration } from "@plugins/decor/lib/api";
|
||||
import { SKU_ID } from "@plugins/decor/lib/constants";
|
||||
|
||||
export function decorationToAsset(decoration: Decoration) {
|
||||
return `${decoration.animated ? "a_" : ""}${decoration.hash}`;
|
||||
|
|
|
|||
|
|
@ -13,13 +13,14 @@ import { closeAllModals } from "@utils/modal";
|
|||
import { OptionType } from "@utils/types";
|
||||
import { FluxDispatcher } from "@webpack/common";
|
||||
|
||||
import DecorPlugin from ".";
|
||||
import DecorSection from "./ui/components/DecorSection";
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
changeDecoration: {
|
||||
type: OptionType.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.
|
||||
</Paragraph>;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@
|
|||
* 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 type { HTMLProps } from "react";
|
||||
|
||||
import { Decoration } from "../../lib/api";
|
||||
import { decorationToAvatarDecoration } from "../../lib/utils/decoration";
|
||||
import { DecorationGridDecoration } from ".";
|
||||
import DecorationContextMenu from "./DecorationContextMenu";
|
||||
|
||||
|
|
|
|||
|
|
@ -6,14 +6,13 @@
|
|||
|
||||
import { Button } from "@components/Button";
|
||||
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 { 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");
|
||||
|
||||
export interface DecorSectionProps {
|
||||
|
|
|
|||
|
|
@ -5,13 +5,12 @@
|
|||
*/
|
||||
|
||||
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 { 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; }) {
|
||||
const { delete: deleteDecoration } = useCurrentUserDecorationsStore();
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,10 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { cl } from "@plugins/decor/ui";
|
||||
import { React } from "@webpack/common";
|
||||
import { JSX } from "react";
|
||||
|
||||
import { cl } from "../";
|
||||
|
||||
export interface GridProps<ItemT> {
|
||||
renderItem: (item: ItemT) => JSX.Element;
|
||||
getItemKey: (item: ItemT) => string;
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { cl } from "@plugins/decor/ui";
|
||||
import { classes } from "@utils/misc";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { React } from "@webpack/common";
|
||||
import { JSX } from "react";
|
||||
|
||||
import { cl } from "../";
|
||||
import Grid, { GridProps } from "./Grid";
|
||||
|
||||
const ScrollerClasses = findByPropsLazy("managedReactiveScroller");
|
||||
|
|
|
|||
|
|
@ -4,12 +4,11 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { AvatarDecoration } from "@plugins/decor";
|
||||
import { findComponentByCode, LazyComponentWebpack } from "@webpack";
|
||||
import { React } from "@webpack/common";
|
||||
import type { ComponentType, HTMLProps, PropsWithChildren } from "react";
|
||||
|
||||
import { AvatarDecoration } from "../..";
|
||||
|
||||
type DecorationGridItemComponent = ComponentType<PropsWithChildren<HTMLProps<HTMLDivElement>> & {
|
||||
onSelect: () => void,
|
||||
isSelected: boolean,
|
||||
|
|
|
|||
|
|
@ -10,26 +10,25 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
|||
import { Flex } from "@components/Flex";
|
||||
import { Heading } from "@components/Heading";
|
||||
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 { copyWithToast } from "@utils/misc";
|
||||
import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { Queue } from "@utils/Queue";
|
||||
import { User } from "@vencord/discord-types";
|
||||
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 { openGuidelinesModal } from "./GuidelinesModal";
|
||||
|
||||
|
|
|
|||
|
|
@ -9,17 +9,16 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
|||
import { Heading } from "@components/Heading";
|
||||
import { Link } from "@components/Link";
|
||||
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 { Margins } from "@utils/margins";
|
||||
import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { filters, findComponentByCodeLazy, mapMangledModuleLazy } from "@webpack";
|
||||
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 { HelpMessage, HelpMessageTypes } = mapMangledModuleLazy('POSITIVE="positive', {
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ import { BaseText } from "@components/BaseText";
|
|||
import { Flex } from "@components/Flex";
|
||||
import { Link } from "@components/Link";
|
||||
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 { Button } from "@webpack/common";
|
||||
|
||||
import { settings } from "../../settings";
|
||||
import { cl, DecorationModalStyles, requireAvatarDecorationModal } from "../";
|
||||
import { openCreateDecorationModal } from "./CreateDecorationModal";
|
||||
|
||||
function GuidelinesModal(props: ModalProps) {
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@
|
|||
import { addMessagePreEditListener, addMessagePreSendListener, removeMessagePreEditListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { ApngBlendOp, ApngDisposeOp, parseAPNG } from "@utils/apng";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
|
||||
import { getCurrentGuild, getEmojiURL } from "@utils/discord";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import definePlugin, { OptionType, Patch } from "@utils/types";
|
||||
|
|
@ -711,9 +711,10 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
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 resolution = settings.store.stickerSize;
|
||||
|
|
@ -762,7 +763,7 @@ export default definePlugin({
|
|||
|
||||
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);
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -17,23 +17,24 @@
|
|||
*/
|
||||
|
||||
// This plugin is a port from Alyxia's Vendetta plugin
|
||||
import "./style.css";
|
||||
import "./styles.css";
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { BaseText } from "@components/BaseText";
|
||||
import { Divider } from "@components/Divider";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { HeadingSecondary } from "@components/Heading";
|
||||
import { Paragraph } from "@components/Paragraph";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { fetchUserProfile } from "@utils/discord";
|
||||
import { copyWithToast, fetchUserProfile } from "@utils/discord";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { classes, copyWithToast } from "@utils/misc";
|
||||
import { classes } from "@utils/misc";
|
||||
import { useAwaiter } from "@utils/react";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { User, UserProfile } from "@vencord/discord-types";
|
||||
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";
|
||||
|
||||
interface Colors {
|
||||
|
|
@ -122,23 +123,19 @@ function SettingsAboutComponent() {
|
|||
<Paragraph>
|
||||
After enabling this plugin, you will see custom colors in
|
||||
the profiles of other people using compatible plugins.{" "}
|
||||
<br />
|
||||
To set your own colors:
|
||||
</Paragraph>
|
||||
<Paragraph className={Margins.top8}>
|
||||
<strong>To set your own profile theme colors:</strong>
|
||||
<ul>
|
||||
<li>
|
||||
• use the color pickers below to choose your colors
|
||||
</li>
|
||||
<li>• click the "Copy 3y3" button</li>
|
||||
<li>• paste the invisible text anywhere in your bio</li>
|
||||
</ul><br />
|
||||
<li>— use the color pickers below to choose your colors</li>
|
||||
<li>— click the "Copy 3y3" button</li>
|
||||
<li>— paste the invisible text anywhere in your bio</li>
|
||||
</ul>
|
||||
<Divider
|
||||
className={classes(Margins.top8, Margins.bottom8)}
|
||||
/>
|
||||
<HeadingSecondary>Color pickers</HeadingSecondary>
|
||||
<Flex
|
||||
direction={Flex.Direction.HORIZONTAL}
|
||||
style={{ gap: "1rem" }}
|
||||
>
|
||||
<Forms.FormTitle tag="h3">Color pickers</Forms.FormTitle>
|
||||
<Flex gap="1em">
|
||||
<ColorPicker
|
||||
color={color1}
|
||||
label={
|
||||
|
|
@ -174,6 +171,7 @@ function SettingsAboutComponent() {
|
|||
}}
|
||||
color={Button.Colors.PRIMARY}
|
||||
size={Button.Sizes.XLARGE}
|
||||
style={{ marginBottom: "auto" }}
|
||||
>
|
||||
Copy 3y3
|
||||
</Button>
|
||||
|
|
|
|||
3
src/plugins/fakeProfileThemes/styles.css
Normal file
3
src/plugins/fakeProfileThemes/styles.css
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
.vc-fpt-preview * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
@ -18,12 +18,11 @@
|
|||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
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 { ELEMENT_ID } from "../constants";
|
||||
import { settings } from "../index";
|
||||
import { waitFor } from "../utils/waitFor";
|
||||
|
||||
interface Vec2 {
|
||||
x: number,
|
||||
y: number;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ import { Logger } from "@utils/Logger";
|
|||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Activity, ActivityAssets, ActivityButton } from "@vencord/discord-types";
|
||||
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 {
|
||||
name: string;
|
||||
|
|
@ -251,13 +251,13 @@ export default definePlugin({
|
|||
|
||||
async getActivity(): Promise<Activity | null> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,13 +16,14 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { isPluginEnabled } from "@api/PluginManager";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import NoReplyMentionPlugin from "@plugins/noReplyMention";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { MessageFlags } from "@vencord/discord-types/enums";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { FluxDispatcher, MessageTypeSets, PermissionsBits, PermissionStore, UserStore, WindowStore } from "@webpack/common";
|
||||
import NoReplyMentionPlugin from "plugins/noReplyMention";
|
||||
|
||||
const MessageActions = findByPropsLazy("deleteMessage", "startEditMessage");
|
||||
const EditStore = findByPropsLazy("isEditing", "isEditingAny");
|
||||
|
|
@ -76,10 +77,36 @@ export default definePlugin({
|
|||
|
||||
onMessageClick(msg, channel, event) {
|
||||
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 (!settings.store.enableDeleteOnClick || !(isMe || PermissionStore.can(PermissionsBits.MANAGE_MESSAGES, channel))) 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();
|
||||
} 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) {
|
||||
FluxDispatcher.dispatch({
|
||||
type: "MESSAGE_DELETE",
|
||||
|
|
@ -90,40 +117,7 @@ export default definePlugin({
|
|||
} else {
|
||||
MessageActions.deleteMessage(channel.id, msg.id);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
.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;
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import { BaseText } from "@components/BaseText";
|
|||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { InfoIcon, OwnerCrownIcon } from "@components/Icons";
|
||||
import { cl, getGuildPermissionSpecMap } from "@plugins/permissionsViewer/utils";
|
||||
import { copyToClipboard } from "@utils/clipboard";
|
||||
import { getIntlMessage, getUniqueUsername } from "@utils/discord";
|
||||
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 { settings } from "..";
|
||||
import { cl, getGuildPermissionSpecMap } from "../utils";
|
||||
import { PermissionAllowedIcon, PermissionDefaultIcon, PermissionDeniedIcon } from "./icons";
|
||||
|
||||
export const enum PermissionType {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
import { BaseText } from "@components/BaseText";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { HeadingTertiary } from "@components/Heading";
|
||||
import { cl, getGuildPermissionSpecMap, getSortedRolesForMember, sortUserRoles } from "@plugins/permissionsViewer/utils";
|
||||
import { getIntlMessage } from "@utils/discord";
|
||||
import { classes } from "@utils/misc";
|
||||
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 { PermissionsSortOrder, settings } from "..";
|
||||
import { cl, getGuildPermissionSpecMap, getSortedRolesForMember, sortUserRoles } from "../utils";
|
||||
import openRolesAndUsersPermissionsModal, { PermissionType, type RoleOrUserPermission } from "./RolesAndUsersPermissions";
|
||||
|
||||
interface UserPermission {
|
||||
|
|
|
|||
|
|
@ -8,13 +8,12 @@ import { classNameFactory } from "@api/Styles";
|
|||
import { BaseText } from "@components/BaseText";
|
||||
import { Divider } from "@components/Divider";
|
||||
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 { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack";
|
||||
import { Button, ColorPicker, TextInput, Toasts, useMemo, useState } from "@webpack/common";
|
||||
|
||||
import { DEFAULT_COLOR, SWATCHES } from "../constants";
|
||||
import { categoryLen, createCategory, getCategory } from "../data";
|
||||
|
||||
interface ColorPickerWithSwatchesProps {
|
||||
defaultColor: number;
|
||||
colors: number[];
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@
|
|||
*/
|
||||
|
||||
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 { addChannelToCategory, canMoveChannelInDirection, currentUserCategories, isPinned, moveChannel, removeChannelFromCategory } from "../data";
|
||||
import { PinOrder, settings } from "../index";
|
||||
import { openCategoryModal } from "./CreateCategoryModal";
|
||||
|
||||
function createPinMenuItem(channelId: string) {
|
||||
|
|
|
|||
|
|
@ -4,11 +4,10 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { PinOrder, PrivateChannelSortStore, settings } from "@plugins/pinDms";
|
||||
import { useForceUpdater } from "@utils/react";
|
||||
import { UserStore } from "@webpack/common";
|
||||
|
||||
import { PinOrder, PrivateChannelSortStore, settings } from "./index";
|
||||
|
||||
export interface Category {
|
||||
id: string;
|
||||
name: string;
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import { definePluginSettings, migratePluginSetting, Settings } from "@api/Setti
|
|||
import { Devs } from "@utils/constants";
|
||||
import { classes } from "@utils/misc";
|
||||
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 { 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 }),
|
||||
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 }),
|
||||
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;
|
||||
|
|
@ -102,11 +103,27 @@ const PlatformIcon = ({ platform, status, small }: PlatformIconProps) => {
|
|||
: platform[0].toUpperCase() + platform.slice(1);
|
||||
let Icon = Icons[platform] ?? Icons.desktop;
|
||||
const { ConsoleIcon } = settings.store;
|
||||
if (platform === "embedded" && ConsoleIcon === "vencord") {
|
||||
Icon = Icons.vencord;
|
||||
if (platform === "embedded" && ConsoleIcon === "oldVencord") {
|
||||
Icon = Icons.oldVencord;
|
||||
}
|
||||
if (platform === "embedded" && ConsoleIcon === "suncord") {
|
||||
Icon = Icons.suncord;
|
||||
if (platform === "embedded") {
|
||||
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} />;
|
||||
|
|
@ -149,15 +166,13 @@ const PlatformIndicator = ({ user, isProfile, isMessage, isMemberList }: Platfor
|
|||
if (user == null || (user.bot && !Settings.plugins.PlatformIndicators.showBots)) return null;
|
||||
useEnsureOwnStatus(user);
|
||||
|
||||
const status: Record<Platform, string> | undefined = useStateFromStores([PresenceStore], () => PresenceStore.getState()?.clientStatuses?.[user.id]);
|
||||
if (status == null) {
|
||||
return null;
|
||||
}
|
||||
const status = useStateFromStores([PresenceStore], () => PresenceStore.getClientStatus(user.id));
|
||||
if (!status) return null;
|
||||
|
||||
const icons = Array.from(Object.entries(status), ([platform, status]) => (
|
||||
<PlatformIcon
|
||||
key={platform}
|
||||
platform={platform as Platform}
|
||||
platform={platform as DiscordPlatform}
|
||||
status={status}
|
||||
small={isProfile || isMemberList}
|
||||
/>
|
||||
|
|
@ -248,8 +263,12 @@ const settings = definePluginSettings({
|
|||
value: "suncord",
|
||||
},
|
||||
{
|
||||
label: "Vencord",
|
||||
value: "vencord",
|
||||
label: "Old Vencord Icon",
|
||||
value: "newVencord",
|
||||
},
|
||||
{
|
||||
label: "Old Vencord Icon",
|
||||
value: "oldVencord",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,14 +16,15 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { isPluginEnabled } from "@api/PluginManager";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import NoBlockedMessagesPlugin from "@plugins/noBlockedMessages";
|
||||
import NoReplyMentionPlugin from "@plugins/noReplyMention";
|
||||
import { Devs, IS_MAC } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Message } from "@vencord/discord-types";
|
||||
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 NoBlockedMessagesPlugin from "plugins/noBlockedMessages";
|
||||
import NoReplyMentionPlugin from "plugins/noReplyMention";
|
||||
|
||||
let currentlyReplyingId: 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;
|
||||
|
||||
const meId = UserStore.getCurrentUser().id;
|
||||
const hasNoBlockedMessages = Vencord.Plugins.isPluginEnabled(NoBlockedMessagesPlugin.name);
|
||||
const hasNoBlockedMessages = isPluginEnabled(NoBlockedMessagesPlugin.name);
|
||||
|
||||
messages = messages.filter(m => {
|
||||
if (m.deleted) return false;
|
||||
|
|
@ -170,7 +171,7 @@ function getNextMessage(isUp: boolean, isReply: boolean) {
|
|||
function shouldMention(message: Message) {
|
||||
switch (settings.store.shouldMention) {
|
||||
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);
|
||||
case MentionOptions.DISABLED:
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
* 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 { getUniqueUsername, openUserProfile } from "@utils/discord";
|
||||
import { FluxStore } from "@vencord/discord-types";
|
||||
|
|
@ -111,7 +112,7 @@ export async function syncAndRunChecks() {
|
|||
|
||||
export function notify(text: string, icon?: string, onClick?: () => void) {
|
||||
if (settings.store.notices)
|
||||
Notices.showNotice(text, "OK", () => Notices.popNotice());
|
||||
showNotice(text, "OK", () => popNotice());
|
||||
|
||||
showNotification({
|
||||
title: "Relationship Notifier",
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@
|
|||
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Flex, Menu } from "@webpack/common";
|
||||
import { Menu } from "@webpack/common";
|
||||
|
||||
const DefaultEngines = {
|
||||
Google: "https://www.google.com/search?q=",
|
||||
|
|
@ -93,7 +94,7 @@ function makeSearchItem(src: string) {
|
|||
key={key}
|
||||
id={key}
|
||||
label={
|
||||
<Flex style={{ alignItems: "center", gap: "0.5em" }}>
|
||||
<Flex gap="0.5em" alignItems="center">
|
||||
<img
|
||||
style={{
|
||||
borderRadius: "50%"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* 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 { openModal } from "@utils/modal";
|
||||
import { OAuth2AuthorizeModal, showToast, Toasts, UserStore } from "@webpack/common";
|
||||
|
|
|
|||
|
|
@ -5,16 +5,15 @@
|
|||
*/
|
||||
|
||||
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 { ModalCloseButton, ModalContent, ModalHeader, ModalRoot, openModal } from "@utils/modal";
|
||||
import { useAwaiter } from "@utils/react";
|
||||
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; }) {
|
||||
return (
|
||||
<Tooltip text="Unblock user">
|
||||
|
|
|
|||
|
|
@ -16,12 +16,11 @@
|
|||
* 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 { HTMLAttributes } from "react";
|
||||
|
||||
import { Badge } from "../entities";
|
||||
import { cl } from "../utils";
|
||||
|
||||
export default function ReviewBadge(badge: Badge & { onClick?(): void; }) {
|
||||
const Wrapper = badge.redirectURL
|
||||
? MaskedLink
|
||||
|
|
|
|||
|
|
@ -16,17 +16,17 @@
|
|||
* 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 { classes } from "@utils/misc";
|
||||
import { LazyComponent } from "@utils/react";
|
||||
import { filters, findBulk } from "@webpack";
|
||||
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 { BlockButton, DeleteButton, ReportButton } from "./MessageButton";
|
||||
import ReviewBadge from "./ReviewBadge";
|
||||
|
|
|
|||
|
|
@ -18,14 +18,14 @@
|
|||
|
||||
import { BaseText } from "@components/BaseText";
|
||||
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 { useForceUpdater } from "@utils/react";
|
||||
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 ReviewsView, { ReviewsInputComponent } from "./ReviewsView";
|
||||
|
||||
|
|
|
|||
|
|
@ -17,15 +17,15 @@
|
|||
*/
|
||||
|
||||
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 { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||
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";
|
||||
|
||||
const Transforms = findByPropsLazy("insertNodes", "textToText");
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* 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 { Devs } from "@utils/constants";
|
||||
import { hasGuildFeature } from "@utils/discord";
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@
|
|||
* 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 { WorkerClient } from "@vap/core/ipc";
|
||||
import type { IShikiTheme, IThemedToken } from "@vap/shiki";
|
||||
|
||||
import { dispatchTheme } from "../hooks/useTheme";
|
||||
import type { ShikiSpec } from "../types";
|
||||
import { getGrammar, languages, loadLanguages, resolveLang } from "./languages";
|
||||
import { themes } from "./themes";
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
* 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";
|
||||
|
||||
export interface ButtonRowProps {
|
||||
|
|
|
|||
|
|
@ -16,11 +16,11 @@
|
|||
* 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 { hljs } from "@webpack/common";
|
||||
import { JSX } from "react";
|
||||
|
||||
import { cl } from "../utils/misc";
|
||||
import { ThemeBase } from "./Highlighter";
|
||||
|
||||
export interface CodeProps {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
* 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> {
|
||||
content: string;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue