Cheat Sheet
{Object.entries({
"\\i": "Special regex escape sequence that matches identifiers (varnames, classnames, etc.)",
diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx
index 6d41ab86..f718ab11 100644
--- a/src/components/VencordSettings/ThemesTab.tsx
+++ b/src/components/VencordSettings/ThemesTab.tsx
@@ -18,21 +18,17 @@
import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
-import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex";
import { DeleteIcon, FolderIcon, PaintbrushIcon, PencilIcon, PlusIcon, RestartIcon } from "@components/Icons";
import { Link } from "@components/Link";
import { openPluginModal } from "@components/PluginSettings/PluginModal";
import type { UserThemeHeader } from "@main/themes";
-import { CspBlockedUrls, useCspErrors } from "@utils/cspViolations";
import { openInviteModal } from "@utils/discord";
import { Margins } from "@utils/margins";
-import { classes } from "@utils/misc";
-import { relaunch } from "@utils/native";
-import { useForceUpdater } from "@utils/react";
-import { getStylusWebStoreUrl } from "@utils/web";
+import { showItemInFolder } from "@utils/native";
+import { useAwaiter } from "@utils/react";
import { findLazy } from "@webpack";
-import { Alerts, Button, Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
+import { Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
import type { ComponentType, Ref, SyntheticEvent } from "react";
import Plugins from "~plugins";
@@ -52,6 +48,62 @@ const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue &
const cl = classNameFactory("vc-settings-theme-");
+function Validator({ link }: { link: string; }) {
+ const [res, err, pending] = useAwaiter(() => fetch(link).then(res => {
+ if (res.status > 300) throw `${res.status} ${res.statusText}`;
+ const contentType = res.headers.get("Content-Type");
+ if (!contentType?.startsWith("text/css") && !contentType?.startsWith("text/plain"))
+ throw "Not a CSS file. Remember to use the raw link!";
+
+ return "Okay!";
+ }));
+
+ const text = pending
+ ? "Checking..."
+ : err
+ ? `Error: ${err instanceof Error ? err.message : String(err)}`
+ : "Valid!";
+
+ return
{text};
+}
+
+function Validators({ themeLinks }: { themeLinks: string[]; }) {
+ if (!themeLinks.length) return null;
+
+ return (
+ <>
+
Validator
+
This section will tell you whether your themes can successfully be loaded
+
+ {themeLinks.map(rawLink => {
+ const { label, link } = (() => {
+ const match = /^@(light|dark) (.*)/.exec(rawLink);
+ if (!match) return { label: rawLink, link: rawLink };
+
+ const [, mode, link] = match;
+ return { label: `[${mode} mode only] ${link}`, link };
+ })();
+
+ return
+
+ {label}
+
+
+ ;
+ })}
+
+ >
+ );
+}
+
interface ThemeCardProps {
theme: UserThemeHeader;
enabled: boolean;
@@ -107,6 +159,7 @@ function ThemesTab() {
const [currentTab, setCurrentTab] = useState(ThemeTab.LOCAL);
const [themeText, setThemeText] = useState(settings.themeLinks.join("\n"));
const [userThemes, setUserThemes] = useState
(null);
+ const [themeDir, , themeDirPending] = useAwaiter(VencordNative.themes.getThemesDir);
useEffect(() => {
refreshLocalThemes();
@@ -166,12 +219,6 @@ function ThemesTab() {
If using the BD site, click on "Download" and place the downloaded .theme.css file into your themes folder.
-
- External Resources
- For security reasons, loading resources (styles, fonts, images, ...) from most sites is blocked.
- Make sure all your assets are hosted on GitHub, GitLab, Codeberg, Imgur, Discord or Google Fonts.
-
-
<>
@@ -194,7 +241,8 @@ function ThemesTab() {
) : (
VencordNative.themes.openFolder()}
+ action={() => showItemInFolder(themeDir!)}
+ disabled={themeDirPending}
Icon={FolderIcon}
/>
)}
@@ -253,13 +301,7 @@ function ThemesTab() {
function renderOnlineThemes() {
return (
<>
-
-
- This section is for advanced users. If you are having difficulties using it, use the
- Local Themes tab instead.
-
-
-
+
Paste links to css files here
One link per line
You can prefix lines with @light or @dark to toggle them based on your Discord theme
@@ -271,11 +313,12 @@ function ThemesTab() {
value={themeText}
onChange={setThemeText}
className={"vc-settings-theme-links"}
- placeholder="Enter Theme Links..."
+ placeholder="Theme Links"
spellCheck={false}
onBlur={onBlur}
rows={10}
/>
+
>
);
@@ -304,99 +347,10 @@ function ThemesTab() {
-
{currentTab === ThemeTab.LOCAL && renderLocalThemes()}
{currentTab === ThemeTab.ONLINE && renderOnlineThemes()}
);
}
-export function CspErrorCard() {
- if (IS_WEB) return null;
-
- const errors = useCspErrors();
- const forceUpdate = useForceUpdater();
-
- if (!errors.length) return null;
-
- const isImgurHtmlDomain = (url: string) => url.startsWith("https://imgur.com/");
-
- const allowUrl = async (url: string) => {
- const { origin: baseUrl, host } = new URL(url);
-
- const result = await VencordNative.csp.requestAddOverride(baseUrl, ["connect-src", "img-src", "style-src", "font-src"], "Vencord Themes");
- if (result !== "ok") return;
-
- CspBlockedUrls.forEach(url => {
- if (new URL(url).host === host) {
- CspBlockedUrls.delete(url);
- }
- });
-
- forceUpdate();
-
- Alerts.show({
- title: "Restart Required",
- body: "A restart is required to apply this change",
- confirmText: "Restart now",
- cancelText: "Later!",
- onConfirm: relaunch
- });
- };
-
- const hasImgurHtmlDomain = errors.some(isImgurHtmlDomain);
-
- return (
-
- Blocked Resources
- Some images, styles, or fonts were blocked because they come from disallowed domains.
- It is highly recommended to move them to GitHub or Imgur. But you may also allow domains if you fully trust them.
-
- After allowing a domain, you have to fully close (from tray / task manager) and restart {IS_DISCORD_DESKTOP ? "Discord" : "Vesktop"} to apply the change.
-
-
- Blocked URLs
-
- {errors.map((url, i) => (
-
- {i !== 0 &&
}
-
- {url}
-
-
-
- ))}
-
-
- {hasImgurHtmlDomain && (
- <>
-
-
- Imgur links should be direct links in the form of https://i.imgur.com/...
-
- To obtain a direct link, right-click the image and select "Copy image address".
- >
- )}
-
- );
-}
-
-function UserscriptThemesTab() {
- return (
-
-
- Themes are not supported on the Userscript!
-
-
- You can instead install themes with the Stylus extension!
-
-
-
- );
-}
-
-export default IS_USERSCRIPT
- ? wrapTab(UserscriptThemesTab, "Themes")
- : wrapTab(ThemesTab, "Themes");
+export default wrapTab(ThemesTab, "Themes");
diff --git a/src/components/VencordSettings/UpdaterTab.tsx b/src/components/VencordSettings/UpdaterTab.tsx
index f5e3d4b2..e29d7dfd 100644
--- a/src/components/VencordSettings/UpdaterTab.tsx
+++ b/src/components/VencordSettings/UpdaterTab.tsx
@@ -94,7 +94,7 @@ function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof
{message} - {author}