This commit is contained in:
thororen1234 2025-07-15 17:03:22 -04:00
parent 1c2d328f5a
commit fc5bfef589
No known key found for this signature in database
7 changed files with 1 additions and 508 deletions

View file

@ -1,86 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { ErrorCard } from "@components/ErrorCard";
import { Link } from "@components/Link";
import { CspBlockedUrls, useCspErrors } from "@utils/cspViolations";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { relaunch } from "@utils/native";
import { useForceUpdater } from "@utils/react";
import { Alerts, Button, Forms } from "@webpack/common";
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 (
<ErrorCard className="vc-settings-card">
<Forms.FormTitle tag="h5">Blocked Resources</Forms.FormTitle>
<Forms.FormText>Some images, styles, or fonts were blocked because they come from disallowed domains.</Forms.FormText>
<Forms.FormText>It is highly recommended to move them to GitHub or Imgur. But you may also allow domains if you fully trust them.</Forms.FormText>
<Forms.FormText>
After allowing a domain, you have to fully close (from tray / task manager) and restart {IS_DISCORD_DESKTOP ? "Discord" : "Vesktop"} to apply the change.
</Forms.FormText>
<Forms.FormTitle tag="h5" className={classes(Margins.top16, Margins.bottom8)}>Blocked URLs</Forms.FormTitle>
<div className="vc-settings-csp-list">
{errors.map((url, i) => (
<div key={url}>
{i !== 0 && <Forms.FormDivider className={Margins.bottom8} />}
<div className="vc-settings-csp-row">
<Link href={url}>{url}</Link>
<Button color={Button.Colors.PRIMARY} onClick={() => allowUrl(url)} disabled={isImgurHtmlDomain(url)}>
Allow
</Button>
</div>
</div>
))}
</div>
{hasImgurHtmlDomain && (
<>
<Forms.FormDivider className={classes(Margins.top8, Margins.bottom16)} />
<Forms.FormText>
Imgur links should be direct links in the form of <code>https://i.imgur.com/...</code>
</Forms.FormText>
<Forms.FormText>To obtain a direct link, right-click the image and select "Copy image address".</Forms.FormText>
</>
)}
</ErrorCard>
);
}

View file

@ -1,170 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { FolderIcon, PaintbrushIcon, PencilIcon, PlusIcon, RestartIcon } from "@components/Icons";
import { Link } from "@components/Link";
import { QuickAction, QuickActionCard } from "@components/settings/QuickAction";
import { openPluginModal } from "@components/settings/tabs/plugins/PluginModal";
import { UserThemeHeader } from "@main/themes";
import { findLazy } from "@webpack";
import { Card, Forms, useEffect, useRef, useState } from "@webpack/common";
import ClientThemePlugin from "plugins/clientTheme";
import type { ComponentType, Ref, SyntheticEvent } from "react";
import { ThemeCard } from "./ThemeCard";
const cl = classNameFactory("vc-settings-theme-");
type FileInput = ComponentType<{
ref: Ref<HTMLInputElement>;
onChange: (e: SyntheticEvent<HTMLInputElement>) => void;
multiple?: boolean;
filters?: { name?: string; extensions: string[]; }[];
}>;
const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue && m.prototype.setRef);
// When a local theme is enabled/disabled, update the settings
function onLocalThemeChange(fileName: string, value: boolean) {
if (value) {
if (Settings.enabledThemes.includes(fileName)) return;
Settings.enabledThemes = [...Settings.enabledThemes, fileName];
} else {
Settings.enabledThemes = Settings.enabledThemes.filter(f => f !== fileName);
}
}
async function onFileUpload(e: SyntheticEvent<HTMLInputElement>) {
e.stopPropagation();
e.preventDefault();
if (!e.currentTarget?.files?.length) return;
const { files } = e.currentTarget;
const uploads = Array.from(files, file => {
const { name } = file;
if (!name.endsWith(".css")) return;
return new Promise<void>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
VencordNative.themes.uploadTheme(name, reader.result as string)
.then(resolve)
.catch(reject);
};
reader.readAsText(file);
});
});
await Promise.all(uploads);
}
export function LocalThemesTab() {
const settings = useSettings(["enabledThemes"]);
const fileInputRef = useRef<HTMLInputElement>(null);
const [userThemes, setUserThemes] = useState<UserThemeHeader[] | null>(null);
useEffect(() => {
refreshLocalThemes();
}, []);
async function refreshLocalThemes() {
const themes = await VencordNative.themes.getThemesList();
setUserThemes(themes);
}
return (
<>
<Card className="vc-settings-card">
<Forms.FormTitle tag="h5">Find Themes:</Forms.FormTitle>
<div style={{ marginBottom: ".5em", display: "flex", flexDirection: "column" }}>
<Link style={{ marginRight: ".5em" }} href="https://betterdiscord.app/themes">
BetterDiscord Themes
</Link>
<Link href="https://github.com/search?q=discord+theme">GitHub</Link>
</div>
<Forms.FormText>If using the BD site, click on "Download" and place the downloaded .theme.css file into your themes folder.</Forms.FormText>
</Card>
<Card className="vc-settings-card">
<Forms.FormTitle tag="h5">External Resources</Forms.FormTitle>
<Forms.FormText>For security reasons, loading resources (styles, fonts, images, ...) from most sites is blocked.</Forms.FormText>
<Forms.FormText>Make sure all your assets are hosted on GitHub, GitLab, Codeberg, Imgur, Discord or Google Fonts.</Forms.FormText>
</Card>
<Forms.FormSection title="Local Themes">
<QuickActionCard>
<>
{IS_WEB ?
(
<QuickAction
text={
<span style={{ position: "relative" }}>
Upload Theme
<FileInput
ref={fileInputRef}
onChange={async e => {
await onFileUpload(e);
refreshLocalThemes();
}}
multiple={true}
filters={[{ extensions: ["css"] }]}
/>
</span>
}
Icon={PlusIcon}
/>
) : (
<QuickAction
text="Open Themes Folder"
action={() => VencordNative.themes.openFolder()}
Icon={FolderIcon}
/>
)}
<QuickAction
text="Load missing Themes"
action={refreshLocalThemes}
Icon={RestartIcon}
/>
<QuickAction
text="Edit QuickCSS"
action={() => VencordNative.quickCss.openEditor()}
Icon={PaintbrushIcon}
/>
{Vencord.Plugins.isPluginEnabled(ClientThemePlugin.name) && (
<QuickAction
text="Edit ClientTheme"
action={() => openPluginModal(ClientThemePlugin)}
Icon={PencilIcon}
/>
)}
</>
</QuickActionCard>
<div className={cl("grid")}>
{userThemes?.map(theme => (
<ThemeCard
key={theme.fileName}
enabled={settings.enabledThemes.includes(theme.fileName)}
onChange={enabled => onLocalThemeChange(theme.fileName, enabled)}
onDelete={async () => {
onLocalThemeChange(theme.fileName, false);
await VencordNative.themes.deleteTheme(theme.fileName);
refreshLocalThemes();
}}
theme={theme}
/>
))}
</div>
</Forms.FormSection>
</>
);
}

View file

@ -1,56 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { useSettings } from "@api/Settings";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { Card, Forms, TextArea, useState } from "@webpack/common";
export function OnlineThemesTab() {
const settings = useSettings(["themeLinks"]);
const [themeText, setThemeText] = useState(settings.themeLinks.join("\n"));
// When the user leaves the online theme textbox, update the settings
function onBlur() {
settings.themeLinks = [...new Set(
themeText
.trim()
.split(/\n+/)
.map(s => s.trim())
.filter(Boolean)
)];
}
return (
<>
<Card className={classes("vc-warning-card", Margins.bottom16)}>
<Forms.FormText>
This section is for advanced users. If you are having difficulties using it, use the
Local Themes tab instead.
</Forms.FormText>
</Card>
<Card className="vc-settings-card">
<Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle>
<Forms.FormText>One link per line</Forms.FormText>
<Forms.FormText>You can prefix lines with @light or @dark to toggle them based on your Discord theme</Forms.FormText>
<Forms.FormText>Make sure to use direct links to files (raw or github.io)!</Forms.FormText>
</Card>
<Forms.FormSection title="Online Themes" tag="h5">
<TextArea
value={themeText}
onChange={setThemeText}
className={"vc-settings-theme-links"}
placeholder="Enter Theme Links..."
spellCheck={false}
onBlur={onBlur}
rows={10}
/>
</Forms.FormSection>
</>
);
}

View file

@ -1,56 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Flex } from "@components/Flex";
import { DeleteIcon } from "@components/Icons";
import { Link } from "@components/Link";
import { AddonCard } from "@components/settings/AddonCard";
import { UserThemeHeader } from "@main/themes";
import { openInviteModal } from "@utils/discord";
import { showToast } from "@webpack/common";
interface ThemeCardProps {
theme: UserThemeHeader;
enabled: boolean;
onChange: (enabled: boolean) => void;
onDelete: () => void;
}
export function ThemeCard({ theme, enabled, onChange, onDelete }: ThemeCardProps) {
return (
<AddonCard
name={theme.name}
description={theme.description}
author={theme.author}
enabled={enabled}
setEnabled={onChange}
infoButton={
IS_WEB && (
<div style={{ cursor: "pointer", color: "var(--status-danger" }} onClick={onDelete}>
<DeleteIcon />
</div>
)
}
footer={
<Flex flexDirection="row" style={{ gap: "0.2em" }}>
{!!theme.website && <Link href={theme.website}>Website</Link>}
{!!(theme.website && theme.invite) && " • "}
{!!theme.invite && (
<Link
href={`https://discord.gg/${theme.invite}`}
onClick={async e => {
e.preventDefault();
theme.invite != null && openInviteModal(theme.invite).catch(() => showToast("Invalid or expired invite"));
}}
>
Discord Server
</Link>
)}
</Flex>
}
/>
);
}

View file

@ -1,85 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 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 "./styles.css";
import { Link } from "@components/Link";
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
import { getStylusWebStoreUrl } from "@utils/web";
import { Card, Forms, React, TabBar, useState } from "@webpack/common";
import { CspErrorCard } from "./CspErrorCard";
import { LocalThemesTab } from "./LocalThemesTab";
import { OnlineThemesTab } from "./OnlineThemesTab";
const enum ThemeTab {
LOCAL,
ONLINE
}
function ThemesTab() {
const [currentTab, setCurrentTab] = useState(ThemeTab.LOCAL);
return (
<SettingsTab title="Themes">
<TabBar
type="top"
look="brand"
className="vc-settings-tab-bar"
selectedItem={currentTab}
onItemSelect={setCurrentTab}
>
<TabBar.Item
className="vc-settings-tab-bar-item"
id={ThemeTab.LOCAL}
>
Local Themes
</TabBar.Item>
<TabBar.Item
className="vc-settings-tab-bar-item"
id={ThemeTab.ONLINE}
>
Online Themes
</TabBar.Item>
</TabBar>
<CspErrorCard />
{currentTab === ThemeTab.LOCAL && <LocalThemesTab />}
{currentTab === ThemeTab.ONLINE && <OnlineThemesTab />}
</SettingsTab>
);
}
function UserscriptThemesTab() {
return (
<SettingsTab title="Themes">
<Card className="vc-settings-card">
<Forms.FormTitle tag="h5">Themes are not supported on the Userscript!</Forms.FormTitle>
<Forms.FormText>
You can instead install themes with the <Link href={getStylusWebStoreUrl()}>Stylus extension</Link>!
</Forms.FormText>
</Card>
</SettingsTab>
);
}
export default IS_USERSCRIPT
? wrapTab(UserscriptThemesTab, "Themes")
: wrapTab(ThemesTab, "Themes");

View file

@ -1,52 +0,0 @@
.vc-settings-theme-grid {
display: grid;
grid-gap: 16px;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
.vc-settings-theme-card {
display: flex;
flex-direction: column;
background-color: var(--background-base-lower-alt);
color: var(--interactive-active);
border-radius: 8px;
padding: 1em;
width: 100%;
transition: 0.1s ease-out;
transition-property: box-shadow, transform, background, opacity;
}
.vc-settings-theme-card-text {
text-overflow: ellipsis;
height: 1.2em;
margin-bottom: 2px;
white-space: nowrap;
overflow: hidden;
}
.vc-settings-theme-author::before {
content: "by ";
}
.vc-settings-csp-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.vc-settings-csp-row {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
& a {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
line-height: 1.2em;
}
--custom-button-button-md-height: 26px;
}

View file

@ -14,7 +14,7 @@ const settings = definePluginSettings({
SavedIds: {
description: "The amount of soundboard ids you want to save at a time (0 lets you save infinite)",
type: OptionType.COMPONENT,
component: ({ setValue, setError }) => {
component: ({ setValue }) => {
const value = settings.store.SavedIds ?? 50;
const [state, setState] = useState(`${value}`);
const [shouldShowWarning, setShouldShowWarning] = useState(false);
@ -24,7 +24,6 @@ const settings = definePluginSettings({
const changed = Number(newValue);
if (Number.isNaN(changed) || changed % 1 !== 0 || changed < 0) {
setError(true);
let errorMsg = "";
errorMsg += Number.isNaN(changed) ? "The value is not a number.\n" : "";
errorMsg += (changed % 1 !== 0) ? "The value can't be a decimal number.\n" : "";
@ -32,7 +31,6 @@ const settings = definePluginSettings({
setErrorMessage(errorMsg);
return;
} else {
setError(false);
setErrorMessage(null);
}