void>>();
+ private globalListeners = new Set<(newData: T, path: string) => void>();
+
+ /**
+ * The store object. Making changes to this object will trigger the applicable change listeners
+ */
+ public declare store: T;
+ /**
+ * The plain data. Changes to this object will not trigger any change listeners
+ */
+ public declare plain: T;
+
+ public constructor(plain: T, options: SettingsStoreOptions = {}) {
+ this.plain = plain;
+ this.store = this.makeProxy(plain);
+ Object.assign(this, options);
+ }
+
+ private makeProxy(object: any, root: T = object, path: string = "") {
+ const self = this;
+
+ return new Proxy(object, {
+ get(target, key: string) {
+ let v = target[key];
+
+ if (!(key in target) && self.getDefaultValue) {
+ v = self.getDefaultValue({
+ target,
+ key,
+ root,
+ path
+ });
+ }
+
+ if (typeof v === "object" && v !== null && !Array.isArray(v))
+ return self.makeProxy(v, root, `${path}${path && "."}${key}`);
+
+ return v;
+ },
+ set(target, key: string, value) {
+ if (target[key] === value) return true;
+
+ Reflect.set(target, key, value);
+ const setPath = `${path}${path && "."}${key}`;
+
+ self.globalListeners.forEach(cb => cb(value, setPath));
+ self.pathListeners.get(setPath)?.forEach(cb => cb(value));
+
+ return true;
+ }
+ });
+ }
+
+ /**
+ * Set the data of the store.
+ * This will update this.store and this.plain (and old references to them will be stale! Avoid storing them in variables)
+ *
+ * Additionally, all global listeners (and those for pathToNotify, if specified) will be called with the new data
+ * @param value New data
+ * @param pathToNotify Optional path to notify instead of globally. Used to transfer path via ipc
+ */
+ public setData(value: T, pathToNotify?: string) {
+ if (this.readOnly) throw new Error("SettingsStore is read-only");
+
+ this.plain = value;
+ this.store = this.makeProxy(value);
+
+ if (pathToNotify) {
+ let v = value;
+
+ const path = pathToNotify.split(".");
+ for (const p of path) {
+ if (!v) {
+ console.warn(
+ `Settings#setData: Path ${pathToNotify} does not exist in new data. Not dispatching update`
+ );
+ return;
+ }
+ v = v[p];
+ }
+
+ this.pathListeners.get(pathToNotify)?.forEach(cb => cb(v));
+ }
+
+ this.markAsChanged();
+ }
+
+ /**
+ * Add a global change listener, that will fire whenever any setting is changed
+ *
+ * @param data The new data. This is either the new value set on the path, or the new root object if it was changed
+ * @param path The path of the setting that was changed. Empty string if the root object was changed
+ */
+ public addGlobalChangeListener(cb: (data: any, path: string) => void) {
+ this.globalListeners.add(cb);
+ }
+
+ /**
+ * Add a scoped change listener that will fire whenever a setting matching the specified path is changed.
+ *
+ * For example if path is `"foo.bar"`, the listener will fire on
+ * ```js
+ * Setting.store.foo.bar = "hi"
+ * ```
+ * but not on
+ * ```js
+ * Setting.store.foo.baz = "hi"
+ * ```
+ * @param path
+ * @param cb
+ */
+ public addChangeListener>(
+ path: P,
+ cb: (data: ResolvePropDeep) => void
+ ) {
+ const listeners = this.pathListeners.get(path as string) ?? new Set();
+ listeners.add(cb);
+ this.pathListeners.set(path as string, listeners);
+ }
+
+ /**
+ * Remove a global listener
+ * @see {@link addGlobalChangeListener}
+ */
+ public removeGlobalChangeListener(cb: (data: any, path: string) => void) {
+ this.globalListeners.delete(cb);
+ }
+
+ /**
+ * Remove a scoped listener
+ * @see {@link addChangeListener}
+ */
+ public removeChangeListener(path: LiteralUnion, cb: (data: any) => void) {
+ const listeners = this.pathListeners.get(path as string);
+ if (!listeners) return;
+
+ listeners.delete(cb);
+ if (!listeners.size) this.pathListeners.delete(path as string);
+ }
+
+ /**
+ * Call all global change listeners
+ */
+ public markAsChanged() {
+ this.globalListeners.forEach(cb => cb(this.plain, ""));
+ }
+}
diff --git a/src/utils/quickCss.ts b/src/utils/quickCss.ts
index 81320319d..99f06004c 100644
--- a/src/utils/quickCss.ts
+++ b/src/utils/quickCss.ts
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-import { addSettingsListener, Settings } from "@api/Settings";
+import { Settings, SettingsStore } from "@api/Settings";
let style: HTMLStyleElement;
@@ -81,10 +81,10 @@ document.addEventListener("DOMContentLoaded", () => {
initThemes();
toggle(Settings.useQuickCss);
- addSettingsListener("useQuickCss", toggle);
+ SettingsStore.addChangeListener("useQuickCss", toggle);
- addSettingsListener("themeLinks", initThemes);
- addSettingsListener("enabledThemes", initThemes);
+ SettingsStore.addChangeListener("themeLinks", initThemes);
+ SettingsStore.addChangeListener("enabledThemes", initThemes);
if (!IS_WEB)
VencordNative.quickCss.addThemeChangeListener(initThemes);
diff --git a/src/utils/settingsSync.ts b/src/utils/settingsSync.ts
index 9a0f260af..843922f2f 100644
--- a/src/utils/settingsSync.ts
+++ b/src/utils/settingsSync.ts
@@ -36,14 +36,14 @@ export async function importSettings(data: string) {
if ("settings" in parsed && "quickCss" in parsed) {
Object.assign(PlainSettings, parsed.settings);
- await VencordNative.settings.set(JSON.stringify(parsed.settings, null, 4));
+ 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 = JSON.parse(VencordNative.settings.get());
+ const settings = VencordNative.settings.get();
const quickCss = await VencordNative.quickCss.get();
return JSON.stringify({ settings, quickCss }, null, minify ? undefined : 4);
}
@@ -137,7 +137,7 @@ export async function putCloudSettings(manual?: boolean) {
const { written } = await res.json();
PlainSettings.cloud.settingsSyncVersion = written;
- VencordNative.settings.set(JSON.stringify(PlainSettings, null, 4));
+ VencordNative.settings.set(PlainSettings);
cloudSettingsLogger.info("Settings uploaded to cloud successfully");
@@ -222,7 +222,7 @@ export async function getCloudSettings(shouldNotify = true, force = false) {
// sync with server timestamp instead of local one
PlainSettings.cloud.settingsSyncVersion = written;
- VencordNative.settings.set(JSON.stringify(PlainSettings, null, 4));
+ VencordNative.settings.set(PlainSettings);
cloudSettingsLogger.info("Settings loaded from cloud successfully");
if (shouldNotify)
diff --git a/tsconfig.json b/tsconfig.json
index 4563f3f86..e9c926408 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -11,7 +11,7 @@
"esnext.asynciterable",
"esnext.symbol"
],
- "module": "commonjs",
+ "module": "esnext",
"moduleResolution": "node",
"strict": true,
"noImplicitAny": false,
@@ -20,13 +20,15 @@
"baseUrl": "./src/",
"paths": {
+ "@main/*": ["./main/*"],
"@api/*": ["./api/*"],
"@components/*": ["./components/*"],
"@utils/*": ["./utils/*"],
+ "@shared/*": ["./shared/*"],
"@webpack/types": ["./webpack/common/types"],
"@webpack/common": ["./webpack/common"],
"@webpack": ["./webpack/webpack"]
}
},
- "include": ["src/**/*"]
+ "include": ["src/**/*", "browser/**/*", "scripts/**/*"]
}
From afdcf0edb9d2dc776a77e13e095e3419098b3f52 Mon Sep 17 00:00:00 2001
From: Vendicated
Date: Wed, 13 Mar 2024 21:59:09 +0100
Subject: [PATCH 29/30] refactor shared utils to more obviously separate
contexts
---
src/VencordNative.ts | 2 +-
src/api/Settings.ts | 2 +-
src/components/VencordSettings/PatchHelperTab.tsx | 2 +-
src/main/ipcMain.ts | 4 ++--
src/main/ipcPlugins.ts | 2 +-
src/main/patcher.ts | 2 +-
src/main/settings.ts | 2 +-
src/main/updater/git.ts | 2 +-
src/main/updater/http.ts | 4 ++--
.../decor/lib/stores/UsersDecorationsStore.ts | 2 +-
src/plugins/imageZoom/index.tsx | 2 +-
src/plugins/pinDms/index.tsx | 2 +-
src/plugins/pronoundb/pronoundbUtils.ts | 4 ++--
src/plugins/spotifyControls/PlayerComponent.tsx | 2 +-
src/preload.ts | 2 +-
src/{utils => shared}/IpcEvents.ts | 0
src/{utils => shared}/debounce.ts | 0
src/{utils => shared}/onceDefined.ts | 0
src/shared/vencordUserAgent.ts | 12 ++++++++++++
src/utils/constants.ts | 13 -------------
src/utils/index.ts | 4 ++--
21 files changed, 32 insertions(+), 33 deletions(-)
rename src/{utils => shared}/IpcEvents.ts (100%)
rename src/{utils => shared}/debounce.ts (100%)
rename src/{utils => shared}/onceDefined.ts (100%)
create mode 100644 src/shared/vencordUserAgent.ts
diff --git a/src/VencordNative.ts b/src/VencordNative.ts
index 10381c900..42e697452 100644
--- a/src/VencordNative.ts
+++ b/src/VencordNative.ts
@@ -6,7 +6,7 @@
import { PluginIpcMappings } from "@main/ipcPlugins";
import type { UserThemeHeader } from "@main/themes";
-import { IpcEvents } from "@utils/IpcEvents";
+import { IpcEvents } from "@shared/IpcEvents";
import { IpcRes } from "@utils/types";
import type { Settings } from "api/Settings";
import { ipcRenderer } from "electron";
diff --git a/src/api/Settings.ts b/src/api/Settings.ts
index bd4a4e929..0b7975300 100644
--- a/src/api/Settings.ts
+++ b/src/api/Settings.ts
@@ -16,8 +16,8 @@
* along with this program. If not, see .
*/
+import { debounce } from "@shared/debounce";
import { SettingsStore as SettingsStoreClass } from "@shared/SettingsStore";
-import { debounce } from "@utils/debounce";
import { localStorage } from "@utils/localStorage";
import { Logger } from "@utils/Logger";
import { mergeDefaults } from "@utils/misc";
diff --git a/src/components/VencordSettings/PatchHelperTab.tsx b/src/components/VencordSettings/PatchHelperTab.tsx
index 35f46ef50..7e68ec1e7 100644
--- a/src/components/VencordSettings/PatchHelperTab.tsx
+++ b/src/components/VencordSettings/PatchHelperTab.tsx
@@ -18,7 +18,7 @@
import { CheckedTextInput } from "@components/CheckedTextInput";
import { CodeBlock } from "@components/CodeBlock";
-import { debounce } from "@utils/debounce";
+import { debounce } from "@shared/debounce";
import { Margins } from "@utils/margins";
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
import { makeCodeblock } from "@utils/text";
diff --git a/src/main/ipcMain.ts b/src/main/ipcMain.ts
index 609a581a7..9c9741db5 100644
--- a/src/main/ipcMain.ts
+++ b/src/main/ipcMain.ts
@@ -20,8 +20,8 @@ import "./updater";
import "./ipcPlugins";
import "./settings";
-import { debounce } from "@utils/debounce";
-import { IpcEvents } from "@utils/IpcEvents";
+import { debounce } from "@shared/debounce";
+import { IpcEvents } from "@shared/IpcEvents";
import { BrowserWindow, ipcMain, shell, systemPreferences } from "electron";
import { FSWatcher, mkdirSync, watch, writeFileSync } from "fs";
import { open, readdir, readFile } from "fs/promises";
diff --git a/src/main/ipcPlugins.ts b/src/main/ipcPlugins.ts
index 5d679fc0b..5236dbec4 100644
--- a/src/main/ipcPlugins.ts
+++ b/src/main/ipcPlugins.ts
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-import { IpcEvents } from "@utils/IpcEvents";
+import { IpcEvents } from "@shared/IpcEvents";
import { ipcMain } from "electron";
import PluginNatives from "~pluginNatives";
diff --git a/src/main/patcher.ts b/src/main/patcher.ts
index ff63ec82d..0d79a96f6 100644
--- a/src/main/patcher.ts
+++ b/src/main/patcher.ts
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-import { onceDefined } from "@utils/onceDefined";
+import { onceDefined } from "@shared/onceDefined";
import electron, { app, BrowserWindowConstructorOptions, Menu } from "electron";
import { dirname, join } from "path";
diff --git a/src/main/settings.ts b/src/main/settings.ts
index 6fe2c3be7..96efdd672 100644
--- a/src/main/settings.ts
+++ b/src/main/settings.ts
@@ -5,8 +5,8 @@
*/
import type { Settings } from "@api/Settings";
+import { IpcEvents } from "@shared/IpcEvents";
import { SettingsStore } from "@shared/SettingsStore";
-import { IpcEvents } from "@utils/IpcEvents";
import { ipcMain } from "electron";
import { mkdirSync, readFileSync, writeFileSync } from "fs";
diff --git a/src/main/updater/git.ts b/src/main/updater/git.ts
index 20a5d7003..82c38b6bc 100644
--- a/src/main/updater/git.ts
+++ b/src/main/updater/git.ts
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-import { IpcEvents } from "@utils/IpcEvents";
+import { IpcEvents } from "@shared/IpcEvents";
import { execFile as cpExecFile } from "child_process";
import { ipcMain } from "electron";
import { join } from "path";
diff --git a/src/main/updater/http.ts b/src/main/updater/http.ts
index 605e8d7cf..9e5a1cef4 100644
--- a/src/main/updater/http.ts
+++ b/src/main/updater/http.ts
@@ -16,8 +16,8 @@
* along with this program. If not, see .
*/
-import { VENCORD_USER_AGENT } from "@utils/constants";
-import { IpcEvents } from "@utils/IpcEvents";
+import { IpcEvents } from "@shared/IpcEvents";
+import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent";
import { ipcMain } from "electron";
import { writeFile } from "fs/promises";
import { join } from "path";
diff --git a/src/plugins/decor/lib/stores/UsersDecorationsStore.ts b/src/plugins/decor/lib/stores/UsersDecorationsStore.ts
index 7295a3b17..b29945f82 100644
--- a/src/plugins/decor/lib/stores/UsersDecorationsStore.ts
+++ b/src/plugins/decor/lib/stores/UsersDecorationsStore.ts
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
-import { debounce } from "@utils/debounce";
+import { debounce } from "@shared/debounce";
import { proxyLazy } from "@utils/lazy";
import { useEffect, useState, zustandCreate } from "@webpack/common";
import { User } from "discord-types/general";
diff --git a/src/plugins/imageZoom/index.tsx b/src/plugins/imageZoom/index.tsx
index 048c0ed5b..cbaa07d2a 100644
--- a/src/plugins/imageZoom/index.tsx
+++ b/src/plugins/imageZoom/index.tsx
@@ -20,8 +20,8 @@ import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import { makeRange } from "@components/PluginSettings/components";
+import { debounce } from "@shared/debounce";
import { Devs } from "@utils/constants";
-import { debounce } from "@utils/debounce";
import definePlugin, { OptionType } from "@utils/types";
import { Menu, React, ReactDOM } from "@webpack/common";
import type { Root } from "react-dom/client";
diff --git a/src/plugins/pinDms/index.tsx b/src/plugins/pinDms/index.tsx
index 943f0f1b1..45172328e 100644
--- a/src/plugins/pinDms/index.tsx
+++ b/src/plugins/pinDms/index.tsx
@@ -26,7 +26,7 @@ import { getPinAt, isPinned, settings, snapshotArray, sortedSnapshot, usePinnedD
export default definePlugin({
name: "PinDMs",
description: "Allows you to pin private channels to the top of your DM list. To pin/unpin or reorder pins, right click DMs",
- authors: [Devs.Ven, Devs.Strencher],
+ authors: [Devs.Ven],
settings,
contextMenus,
diff --git a/src/plugins/pronoundb/pronoundbUtils.ts b/src/plugins/pronoundb/pronoundbUtils.ts
index eac204b7d..6373c56a0 100644
--- a/src/plugins/pronoundb/pronoundbUtils.ts
+++ b/src/plugins/pronoundb/pronoundbUtils.ts
@@ -17,8 +17,8 @@
*/
import { Settings } from "@api/Settings";
-import { VENCORD_USER_AGENT } from "@utils/constants";
-import { debounce } from "@utils/debounce";
+import { debounce } from "@shared/debounce";
+import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent";
import { getCurrentChannel } from "@utils/discord";
import { useAwaiter } from "@utils/react";
import { UserProfileStore, UserStore } from "@webpack/common";
diff --git a/src/plugins/spotifyControls/PlayerComponent.tsx b/src/plugins/spotifyControls/PlayerComponent.tsx
index 8b3f04bf2..ae28631c9 100644
--- a/src/plugins/spotifyControls/PlayerComponent.tsx
+++ b/src/plugins/spotifyControls/PlayerComponent.tsx
@@ -21,7 +21,7 @@ import "./spotifyStyles.css";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons";
-import { debounce } from "@utils/debounce";
+import { debounce } from "@shared/debounce";
import { openImageModal } from "@utils/discord";
import { classes, copyWithToast } from "@utils/misc";
import { ContextMenuApi, FluxDispatcher, Forms, Menu, React, useEffect, useState, useStateFromStores } from "@webpack/common";
diff --git a/src/preload.ts b/src/preload.ts
index 08243000d..e79eb02cc 100644
--- a/src/preload.ts
+++ b/src/preload.ts
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-import { debounce } from "@utils/debounce";
+import { debounce } from "@shared/debounce";
import { contextBridge, webFrame } from "electron";
import { readFileSync, watch } from "fs";
import { join } from "path";
diff --git a/src/utils/IpcEvents.ts b/src/shared/IpcEvents.ts
similarity index 100%
rename from src/utils/IpcEvents.ts
rename to src/shared/IpcEvents.ts
diff --git a/src/utils/debounce.ts b/src/shared/debounce.ts
similarity index 100%
rename from src/utils/debounce.ts
rename to src/shared/debounce.ts
diff --git a/src/utils/onceDefined.ts b/src/shared/onceDefined.ts
similarity index 100%
rename from src/utils/onceDefined.ts
rename to src/shared/onceDefined.ts
diff --git a/src/shared/vencordUserAgent.ts b/src/shared/vencordUserAgent.ts
new file mode 100644
index 000000000..0cb1882bf
--- /dev/null
+++ b/src/shared/vencordUserAgent.ts
@@ -0,0 +1,12 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import gitHash from "~git-hash";
+import gitRemote from "~git-remote";
+
+export { gitHash, gitRemote };
+
+export const VENCORD_USER_AGENT = `Vencord/${gitHash}${gitRemote ? ` (https://github.com/${gitRemote})` : ""}`;
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index 40965d08c..f609fa644 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -16,17 +16,8 @@
* along with this program. If not, see .
*/
-import gitHash from "~git-hash";
-import gitRemote from "~git-remote";
-
-export {
- gitHash,
- gitRemote
-};
-
export const WEBPACK_CHUNK = "webpackChunkdiscord_app";
export const REACT_GLOBAL = "Vencord.Webpack.Common.React";
-export const VENCORD_USER_AGENT = `Vencord/${gitHash}${gitRemote ? ` (https://github.com/${gitRemote})` : ""}`;
export const SUPPORT_CHANNEL_ID = "1026515880080842772";
export interface Dev {
@@ -291,10 +282,6 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "RyanCaoDev",
id: 952235800110694471n,
},
- Strencher: {
- name: "Strencher",
- id: 415849376598982656n
- },
FieryFlames: {
name: "Fiery",
id: 890228870559698955n
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 90bf86082..ea4adce4a 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -16,9 +16,10 @@
* along with this program. If not, see .
*/
+export * from "../shared/debounce";
+export * from "../shared/onceDefined";
export * from "./ChangeList";
export * from "./constants";
-export * from "./debounce";
export * from "./discord";
export * from "./guards";
export * from "./lazy";
@@ -27,7 +28,6 @@ export * from "./Logger";
export * from "./margins";
export * from "./misc";
export * from "./modal";
-export * from "./onceDefined";
export * from "./onlyOnce";
export * from "./patches";
export * from "./Queue";
From f3ee43fe668ab015e5dc33eb02f956a9c0b71886 Mon Sep 17 00:00:00 2001
From: stupid cat
Date: Wed, 13 Mar 2024 17:23:04 -0400
Subject: [PATCH 30/30] favGifSearch: don't error on favourited non-urls
(#2260)
---
src/plugins/favGifSearch/index.tsx | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/plugins/favGifSearch/index.tsx b/src/plugins/favGifSearch/index.tsx
index 592d8f547..d71f56795 100644
--- a/src/plugins/favGifSearch/index.tsx
+++ b/src/plugins/favGifSearch/index.tsx
@@ -200,7 +200,14 @@ function SearchBar({ instance, SearchBarComponent }: { instance: Instance; Searc
export function getTargetString(urlStr: string) {
- const url = new URL(urlStr);
+ let url: URL;
+ try {
+ url = new URL(urlStr);
+ } catch (err) {
+ // Can't resolve URL, return as-is
+ return urlStr;
+ }
+
switch (settings.store.searchOption) {
case "url":
return url.href;