diff --git a/package.json b/package.json
index ee4abbe0..f40b4d62 100644
--- a/package.json
+++ b/package.json
@@ -1,18 +1,18 @@
{
- "name": "vencord",
+ "name": "suncord",
"private": "true",
- "version": "1.6.6",
- "description": "The cutest Discord client mod",
- "homepage": "https://github.com/Vendicated/Vencord#readme",
+ "version": "1.6.7",
+ "description": "A fork of vencord",
+ "homepage": "https://github.com/verticalsync/Suncord#readme",
"bugs": {
- "url": "https://github.com/Vendicated/Vencord/issues"
+ "url": "https://github.com/verticalsync/Suncord/issues"
},
"repository": {
"type": "git",
- "url": "git+https://github.com/Vendicated/Vencord.git"
+ "url": "git+https://github.com/verticalsync/Suncord.git"
},
"license": "GPL-3.0-or-later",
- "author": "Vendicated",
+ "author": "verticalsync",
"directories": {
"doc": "docs"
},
diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx
index 2808494a..dd564bd7 100644
--- a/src/components/VencordSettings/ThemesTab.tsx
+++ b/src/components/VencordSettings/ThemesTab.tsx
@@ -21,9 +21,11 @@ import { classNameFactory } from "@api/Styles";
import { Flex } from "@components/Flex";
import { DeleteIcon } from "@components/Icons";
import { Link } from "@components/Link";
+import PluginModal from "@components/PluginSettings/PluginModal";
import { openInviteModal } from "@utils/discord";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
+import { openModal } from "@utils/modal";
import { showItemInFolder } from "@utils/native";
import { useAwaiter } from "@utils/react";
import { findByPropsLazy, findLazy } from "@webpack";
@@ -248,6 +250,22 @@ function ThemesTab() {
>
Edit QuickCSS
+
+
+ {Vencord.Settings.plugins.ClientTheme.enabled && (
+
+ )}
>
diff --git a/src/main/ipcMain.ts b/src/main/ipcMain.ts
index ee6357bc..e57ac1dd 100644
--- a/src/main/ipcMain.ts
+++ b/src/main/ipcMain.ts
@@ -139,8 +139,15 @@ export function initIpc(mainWindow: BrowserWindow) {
}
ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {
+ const title = "Suncord QuickCSS Editor";
+ const existingWindow = BrowserWindow.getAllWindows().find(w => w.title === title);
+ if (existingWindow && !existingWindow.isDestroyed()) {
+ existingWindow.focus();
+ return;
+ }
+
const win = new BrowserWindow({
- title: "Suncord QuickCSS Editor",
+ title: title,
autoHideMenuBar: true,
darkTheme: true,
webPreferences: {
diff --git a/src/main/updater/git.ts b/src/main/updater/git.ts
index e9602644..2ff3ba51 100644
--- a/src/main/updater/git.ts
+++ b/src/main/updater/git.ts
@@ -73,6 +73,8 @@ async function build() {
const command = isFlatpak ? "flatpak-spawn" : "node";
const args = isFlatpak ? ["--host", "node", "scripts/build/build.mjs"] : ["scripts/build/build.mjs"];
+ if (IS_DEV) args.push("--dev");
+
const res = await execFile(command, args, opts);
return !res.stderr.includes("Build failed");
diff --git a/src/plugins/anonymiseFileNames/index.ts b/src/plugins/anonymiseFileNames/index.ts
deleted file mode 100644
index 9e69d7a9..00000000
--- a/src/plugins/anonymiseFileNames/index.ts
+++ /dev/null
@@ -1,94 +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 .
-*/
-
-import { Settings } from "@api/Settings";
-import { Devs } from "@utils/constants";
-import definePlugin, { OptionType } from "@utils/types";
-
-const enum Methods {
- Random,
- Consistent,
- Timestamp,
-}
-
-const tarExtMatcher = /\.tar\.\w+$/;
-
-export default definePlugin({
- name: "AnonymiseFileNames",
- authors: [Devs.obscurity],
- description: "Anonymise uploaded file names",
- patches: [
- {
- find: "instantBatchUpload:function",
- replacement: {
- match: /uploadFiles:(.{1,2}),/,
- replace:
- "uploadFiles:(...args)=>(args[0].uploads.forEach(f=>f.filename=$self.anonymise(f.filename)),$1(...args)),",
- },
- },
- ],
-
- options: {
- method: {
- description: "Anonymising method",
- type: OptionType.SELECT,
- options: [
- { label: "Random Characters", value: Methods.Random, default: true },
- { label: "Consistent", value: Methods.Consistent },
- { label: "Timestamp (4chan-like)", value: Methods.Timestamp },
- ],
- },
- randomisedLength: {
- description: "Random characters length",
- type: OptionType.NUMBER,
- default: 7,
- disabled: () => Settings.plugins.AnonymiseFileNames.method !== Methods.Random,
- },
- consistent: {
- description: "Consistent filename",
- type: OptionType.STRING,
- default: "image",
- disabled: () => Settings.plugins.AnonymiseFileNames.method !== Methods.Consistent,
- },
- },
-
- anonymise(file: string) {
- let name = "image";
- const tarMatch = tarExtMatcher.exec(file);
- const extIdx = tarMatch?.index ?? file.lastIndexOf(".");
- const ext = extIdx !== -1 ? file.slice(extIdx) : "";
-
- switch (Settings.plugins.AnonymiseFileNames.method) {
- case Methods.Random:
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
- name = Array.from(
- { length: Settings.plugins.AnonymiseFileNames.randomisedLength },
- () => chars[Math.floor(Math.random() * chars.length)]
- ).join("");
- break;
- case Methods.Consistent:
- name = Settings.plugins.AnonymiseFileNames.consistent;
- break;
- case Methods.Timestamp:
- // UNIX timestamp in nanos, i could not find a better dependency-less way
- name = `${Math.floor(Date.now() / 1000)}${Math.floor(window.performance.now())}`;
- break;
- }
- return name + ext;
- },
-});
diff --git a/src/plugins/anonymiseFileNames/index.tsx b/src/plugins/anonymiseFileNames/index.tsx
new file mode 100644
index 00000000..845aa756
--- /dev/null
+++ b/src/plugins/anonymiseFileNames/index.tsx
@@ -0,0 +1,130 @@
+/*
+ * 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 .
+*/
+
+import { Upload } from "@api/MessageEvents";
+import { definePluginSettings } from "@api/Settings";
+import ErrorBoundary from "@components/ErrorBoundary";
+import { Devs } from "@utils/constants";
+import definePlugin, { OptionType } from "@utils/types";
+import { findByCodeLazy, findByPropsLazy } from "@webpack";
+
+type AnonUpload = Upload & { anonymise?: boolean; };
+
+const ActionBarIcon = findByCodeLazy(".actionBarIcon)");
+const UploadDraft = findByPropsLazy("popFirstFile", "update");
+
+const enum Methods {
+ Random,
+ Consistent,
+ Timestamp,
+}
+
+const tarExtMatcher = /\.tar\.\w+$/;
+
+const settings = definePluginSettings({
+ anonymiseByDefault: {
+ description: "Whether to anonymise file names by default",
+ type: OptionType.BOOLEAN,
+ default: true,
+ },
+ method: {
+ description: "Anonymising method",
+ type: OptionType.SELECT,
+ options: [
+ { label: "Random Characters", value: Methods.Random, default: true },
+ { label: "Consistent", value: Methods.Consistent },
+ { label: "Timestamp", value: Methods.Timestamp },
+ ],
+ },
+ randomisedLength: {
+ description: "Random characters length",
+ type: OptionType.NUMBER,
+ default: 7,
+ disabled: () => settings.store.method !== Methods.Random,
+ },
+ consistent: {
+ description: "Consistent filename",
+ type: OptionType.STRING,
+ default: "image",
+ disabled: () => settings.store.method !== Methods.Consistent,
+ },
+});
+
+export default definePlugin({
+ name: "AnonymiseFileNames",
+ authors: [Devs.obscurity],
+ description: "Anonymise uploaded file names",
+ patches: [
+ {
+ find: "instantBatchUpload:function",
+ replacement: {
+ match: /uploadFiles:(.{1,2}),/,
+ replace:
+ "uploadFiles:(...args)=>(args[0].uploads.forEach(f=>f.filename=$self.anonymise(f)),$1(...args)),",
+ },
+ },
+ {
+ find: ".Messages.ATTACHMENT_UTILITIES_SPOILER",
+ replacement: {
+ match: /(?<=children:\[)(?=.{10,80}tooltip:\i\.\i\.Messages\.ATTACHMENT_UTILITIES_SPOILER)/,
+ replace: "arguments[0].canEdit!==false?$self.renderIcon(arguments[0]):null,"
+ },
+ },
+ ],
+ settings,
+
+ renderIcon: ErrorBoundary.wrap(({ upload, channelId, draftType }: { upload: AnonUpload; draftType: unknown; channelId: string; }) => {
+ const anonymise = upload.anonymise ?? settings.store.anonymiseByDefault;
+ return (
+ {
+ upload.anonymise = !anonymise;
+ UploadDraft.update(channelId, upload.id, draftType, {}); // dummy update so component rerenders
+ }}
+ >
+ {anonymise
+ ?
+ :
+ }
+
+ );
+ }, { noop: true }),
+
+ anonymise(upload: AnonUpload) {
+ if ((upload.anonymise ?? settings.store.anonymiseByDefault) === false) return upload.filename;
+
+ const file = upload.filename;
+ const tarMatch = tarExtMatcher.exec(file);
+ const extIdx = tarMatch?.index ?? file.lastIndexOf(".");
+ const ext = extIdx !== -1 ? file.slice(extIdx) : "";
+
+ switch (settings.store.method) {
+ case Methods.Random:
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ return Array.from(
+ { length: settings.store.randomisedLength },
+ () => chars[Math.floor(Math.random() * chars.length)]
+ ).join("") + ext;
+ case Methods.Consistent:
+ return settings.store.consistent + ext;
+ case Methods.Timestamp:
+ return Date.now() + ext;
+ }
+ },
+});
diff --git a/src/plugins/betterGifPicker/index.ts b/src/plugins/betterGifPicker/index.ts
new file mode 100644
index 00000000..09bb570d
--- /dev/null
+++ b/src/plugins/betterGifPicker/index.ts
@@ -0,0 +1,23 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { Devs } from "@utils/constants";
+import definePlugin from "@utils/types";
+
+export default definePlugin({
+ name: "BetterGifPicker",
+ description: "Makes the gif picker open the favourite category by default",
+ authors: [Devs.Samwich],
+ patches: [
+ {
+ find: ".GIFPickerResultTypes.SEARCH",
+ replacement: [{
+ match: "this.state={resultType:null}",
+ replace: 'this.state={resultType:"Favorites"}'
+ }]
+ }
+ ]
+});
diff --git a/src/plugins/clearURLs/defaultRules.ts b/src/plugins/clearURLs/defaultRules.ts
index 0633b717..95a59c03 100644
--- a/src/plugins/clearURLs/defaultRules.ts
+++ b/src/plugins/clearURLs/defaultRules.ts
@@ -153,5 +153,6 @@ export const defaultRules = [
"utm_term",
"si@open.spotify.com",
"igshid",
+ "igsh",
"share_id@reddit.com",
];
diff --git a/src/plugins/clientTheme/clientTheme.css b/src/plugins/clientTheme/clientTheme.css
index 023f88bd..64aefdcf 100644
--- a/src/plugins/clientTheme/clientTheme.css
+++ b/src/plugins/clientTheme/clientTheme.css
@@ -19,6 +19,16 @@
border: thin solid var(--background-modifier-accent) !important;
}
-.client-theme-warning {
+.client-theme-warning * {
color: var(--text-danger);
}
+
+.client-theme-contrast-warning {
+ background-color: var(--background-primary);
+ padding: 0.5rem;
+ border-radius: .5rem;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+}
diff --git a/src/plugins/clientTheme/index.tsx b/src/plugins/clientTheme/index.tsx
index d7592996..5d8cd4dc 100644
--- a/src/plugins/clientTheme/index.tsx
+++ b/src/plugins/clientTheme/index.tsx
@@ -8,19 +8,19 @@ import "./clientTheme.css";
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
-import { getTheme, Theme } from "@utils/discord";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import definePlugin, { OptionType, StartAt } from "@utils/types";
-import { findComponentByCodeLazy } from "@webpack";
-import { Button, Forms } from "@webpack/common";
+import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
+import { Button, Forms, lodash as _, useStateFromStores } from "@webpack/common";
const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
const colorPresets = [
"#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D",
"#3A483D", "#344242", "#313D4B", "#2D2F47", "#322B42",
- "#3C2E42", "#422938"
+ "#3C2E42", "#422938", "#b6908f", "#bfa088", "#d3c77d",
+ "#86ac86", "#88aab3", "#8693b5", "#8a89ba", "#ad94bb",
];
function onPickColor(color: number) {
@@ -30,9 +30,35 @@ function onPickColor(color: number) {
updateColorVars(hexColor);
}
+const { saveClientTheme } = findByPropsLazy("saveClientTheme");
+
+function setTheme(theme: string) {
+ saveClientTheme({ theme });
+}
+
+const ThemeStore = findStoreLazy("ThemeStore");
+const NitroThemeStore = findStoreLazy("ClientThemesBackgroundStore");
+
function ThemeSettings() {
- const lightnessWarning = hexToLightness(settings.store.color) > 45;
- const lightModeWarning = getTheme() === Theme.Light;
+ const theme = useStateFromStores([ThemeStore], () => ThemeStore.theme);
+ const isLightTheme = theme === "light";
+ const oppositeTheme = isLightTheme ? "dark" : "light";
+
+ const nitroTheme = useStateFromStores([NitroThemeStore], () => NitroThemeStore.gradientPreset);
+ const nitroThemeEnabled = nitroTheme !== undefined;
+
+ const selectedLuminance = relativeLuminance(settings.store.color);
+
+ let contrastWarning = false, fixableContrast = true;
+ if ((isLightTheme && selectedLuminance < 0.26) || !isLightTheme && selectedLuminance > 0.12)
+ contrastWarning = true;
+ if (selectedLuminance < 0.26 && selectedLuminance > 0.12)
+ fixableContrast = false;
+ // light mode with values greater than 65 leads to background colors getting crushed together and poor text contrast for muted channels
+ if (isLightTheme && selectedLuminance > 0.65) {
+ contrastWarning = true;
+ fixableContrast = false;
+ }
return (
@@ -48,15 +74,18 @@ function ThemeSettings() {
suggestedColors={colorPresets}
/>
- {lightnessWarning || lightModeWarning
- ?
-
-
Your theme won't look good:
- {lightnessWarning &&
Selected color is very light}
- {lightModeWarning &&
Light mode isn't supported}
+ {(contrastWarning || nitroThemeEnabled) && (<>
+
+
+
+ Warning, your theme won't look good:
+ {contrastWarning && Selected color won't contrast well with text}
+ {nitroThemeEnabled && Nitro themes aren't supported}
+
+ {(contrastWarning && fixableContrast) &&
}
+ {(nitroThemeEnabled) &&
}
- : null
- }
+ >)}
);
}
@@ -87,9 +116,12 @@ export default definePlugin({
settings,
startAt: StartAt.DOMContentLoaded,
- start() {
+ async start() {
updateColorVars(settings.store.color);
- generateColorOffsets();
+
+ const styles = await getStyles();
+ generateColorOffsets(styles);
+ generateLightModeFixes(styles);
},
stop() {
@@ -98,56 +130,86 @@ export default definePlugin({
}
});
-const variableRegex = /(--primary-[5-9]\d{2}-hsl):.*?(\S*)%;/g;
-
-async function generateColorOffsets() {
-
- const styleLinkNodes = document.querySelectorAll('link[rel="stylesheet"]');
- const variableLightness = {} as Record;
-
- // Search all stylesheets for color variables
- for (const styleLinkNode of styleLinkNodes) {
- const cssLink = styleLinkNode.getAttribute("href");
- if (!cssLink) continue;
-
- const res = await fetch(cssLink);
- const cssString = await res.text();
-
- // Get lightness values of --primary variables >=500
- let variableMatch = variableRegex.exec(cssString);
- while (variableMatch !== null) {
- const [, variable, lightness] = variableMatch;
- variableLightness[variable] = parseFloat(lightness);
- variableMatch = variableRegex.exec(cssString);
- }
- }
+const variableRegex = /(--primary-\d{3}-hsl):.*?(\S*)%;/g;
+const lightVariableRegex = /^--primary-[1-5]\d{2}-hsl/g;
+const darkVariableRegex = /^--primary-[5-9]\d{2}-hsl/g;
- // Generate offsets
- const lightnessOffsets = Object.entries(variableLightness)
+// generates variables per theme by:
+// - matching regex (so we can limit what variables are included in light/dark theme, otherwise text becomes unreadable)
+// - offset from specified center (light/dark theme get different offsets because light uses 100 for background-primary, while dark uses 600)
+function genThemeSpecificOffsets(variableLightness: Record, regex: RegExp, centerVariable: string): string {
+ return Object.entries(variableLightness).filter(([key]) => key.search(regex) > -1)
.map(([key, lightness]) => {
- const lightnessOffset = lightness - variableLightness["--primary-600-hsl"];
+ const lightnessOffset = lightness - variableLightness[centerVariable];
const plusOrMinus = lightnessOffset >= 0 ? "+" : "-";
return `${key}: var(--theme-h) var(--theme-s) calc(var(--theme-l) ${plusOrMinus} ${Math.abs(lightnessOffset).toFixed(2)}%);`;
})
.join("\n");
+}
- const style = document.createElement("style");
- style.setAttribute("id", "clientThemeOffsets");
- style.textContent = `:root:root {
- ${lightnessOffsets}
- }`;
- document.head.appendChild(style);
+
+function generateColorOffsets(styles) {
+ const variableLightness = {} as Record;
+
+ // Get lightness values of --primary variables
+ let variableMatch = variableRegex.exec(styles);
+ while (variableMatch !== null) {
+ const [, variable, lightness] = variableMatch;
+ variableLightness[variable] = parseFloat(lightness);
+ variableMatch = variableRegex.exec(styles);
+ }
+
+ createStyleSheet("clientThemeOffsets", [
+ `.theme-light {\n ${genThemeSpecificOffsets(variableLightness, lightVariableRegex, "--primary-345-hsl")} \n}`,
+ `.theme-dark {\n ${genThemeSpecificOffsets(variableLightness, darkVariableRegex, "--primary-600-hsl")} \n}`,
+ ].join("\n\n"));
+}
+
+function generateLightModeFixes(styles) {
+ const groupLightUsesW500Regex = /\.theme-light[^{]*\{[^}]*var\(--white-500\)[^}]*}/gm;
+ // get light capturing groups that mention --white-500
+ const relevantStyles = [...styles.matchAll(groupLightUsesW500Regex)].flat();
+
+ const groupBackgroundRegex = /^([^{]*)\{background:var\(--white-500\)/m;
+ const groupBackgroundColorRegex = /^([^{]*)\{background-color:var\(--white-500\)/m;
+ // find all capturing groups that assign background or background-color directly to w500
+ const backgroundGroups = mapReject(relevantStyles, entry => captureOne(entry, groupBackgroundRegex)).join(",\n");
+ const backgroundColorGroups = mapReject(relevantStyles, entry => captureOne(entry, groupBackgroundColorRegex)).join(",\n");
+ // create css to reassign them to --primary-100
+ const reassignBackgrounds = `${backgroundGroups} {\n background: var(--primary-100) \n}`;
+ const reassignBackgroundColors = `${backgroundColorGroups} {\n background-color: var(--primary-100) \n}`;
+
+ const groupBgVarRegex = /\.theme-light\{([^}]*--[^:}]*(?:background|bg)[^:}]*:var\(--white-500\)[^}]*)\}/m;
+ const bgVarRegex = /^(--[^:]*(?:background|bg)[^:]*):var\(--white-500\)/m;
+ // get all global variables used for backgrounds
+ const lightVars = mapReject(relevantStyles, style => captureOne(style, groupBgVarRegex)) // get the insides of capture groups that have at least one background var with w500
+ .map(str => str.split(";")).flat(); // captureGroupInsides[] -> cssRule[]
+ const lightBgVars = mapReject(lightVars, variable => captureOne(variable, bgVarRegex)); // remove vars that aren't for backgrounds or w500
+ // create css to reassign every var
+ const reassignVariables = `.theme-light {\n ${lightBgVars.map(variable => `${variable}: var(--primary-100);`).join("\n")} \n}`;
+
+ createStyleSheet("clientThemeLightModeFixes", [
+ reassignBackgrounds,
+ reassignBackgroundColors,
+ reassignVariables,
+ ].join("\n\n"));
+}
+
+function captureOne(str, regex) {
+ const result = str.match(regex);
+ return (result === null) ? null : result[1];
+}
+
+function mapReject(arr, mapFunc, rejectFunc = _.isNull) {
+ return _.reject(arr.map(mapFunc), rejectFunc);
}
function updateColorVars(color: string) {
const { hue, saturation, lightness } = hexToHSL(color);
let style = document.getElementById("clientThemeVars");
- if (!style) {
- style = document.createElement("style");
- style.setAttribute("id", "clientThemeVars");
- document.head.appendChild(style);
- }
+ if (!style)
+ style = createStyleSheet("clientThemeVars");
style.textContent = `:root {
--theme-h: ${hue};
@@ -156,6 +218,28 @@ function updateColorVars(color: string) {
}`;
}
+function createStyleSheet(id, content = "") {
+ const style = document.createElement("style");
+ style.setAttribute("id", id);
+ style.textContent = content.split("\n").map(line => line.trim()).join("\n");
+ document.body.appendChild(style);
+ return style;
+}
+
+// returns all of discord's native styles in a single string
+async function getStyles(): Promise {
+ let out = "";
+ const styleLinkNodes = document.querySelectorAll('link[rel="stylesheet"]');
+ for (const styleLinkNode of styleLinkNodes) {
+ const cssLink = styleLinkNode.getAttribute("href");
+ if (!cssLink) continue;
+
+ const res = await fetch(cssLink);
+ out += await res.text();
+ }
+ return out;
+}
+
// https://css-tricks.com/converting-color-spaces-in-javascript/
function hexToHSL(hexCode: string) {
// Hex => RGB normalized to 0-1
@@ -198,17 +282,14 @@ function hexToHSL(hexCode: string) {
return { hue, saturation, lightness };
}
-// Minimized math just for lightness, lowers lag when changing colors
-function hexToLightness(hexCode: string) {
- // Hex => RGB normalized to 0-1
- const r = parseInt(hexCode.substring(0, 2), 16) / 255;
- const g = parseInt(hexCode.substring(2, 4), 16) / 255;
- const b = parseInt(hexCode.substring(4, 6), 16) / 255;
-
- const cMax = Math.max(r, g, b);
- const cMin = Math.min(r, g, b);
+// https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
+function relativeLuminance(hexCode: string) {
+ const normalize = (x: number) =>
+ x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
- const lightness = 100 * ((cMax + cMin) / 2);
+ const r = normalize(parseInt(hexCode.substring(0, 2), 16) / 255);
+ const g = normalize(parseInt(hexCode.substring(2, 4), 16) / 255);
+ const b = normalize(parseInt(hexCode.substring(4, 6), 16) / 255);
- return lightness;
+ return r * 0.2126 + g * 0.7152 + b * 0.0722;
}
diff --git a/src/plugins/fixCodeblockGap/index.ts b/src/plugins/fixCodeblockGap/index.ts
new file mode 100644
index 00000000..13340995
--- /dev/null
+++ b/src/plugins/fixCodeblockGap/index.ts
@@ -0,0 +1,35 @@
+/*
+ * 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 .
+*/
+
+import { Devs } from "@utils/constants";
+import definePlugin from "@utils/types";
+
+export default definePlugin({
+ name: "FixCodeblockGap",
+ description: "Removes the gap between codeblocks and text below it",
+ authors: [Devs.Grzesiek11],
+ patches: [
+ {
+ find: ".default.Messages.DELETED_ROLE_PLACEHOLDER",
+ replacement: {
+ match: String.raw`/^${"```"}(?:([a-z0-9_+\-.#]+?)\n)?\n*([^\n][^]*?)\n*${"```"}`,
+ replace: "$&\\n?",
+ },
+ },
+ ],
+});
diff --git a/src/plugins/permissionsViewer/index.tsx b/src/plugins/permissionsViewer/index.tsx
index 985b2384..9e0131e6 100644
--- a/src/plugins/permissionsViewer/index.tsx
+++ b/src/plugins/permissionsViewer/index.tsx
@@ -126,7 +126,9 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) {
function makeContextMenuPatch(childId: string | string[], type?: MenuItemParentType): NavContextMenuPatchCallback {
return (children, props) => () => {
- if (!props || (type === MenuItemParentType.User && !props.user) || (type === MenuItemParentType.Guild && !props.guild)) return children;
+ if (!props) return;
+ if ((type === MenuItemParentType.User && !props.user) || (type === MenuItemParentType.Guild && !props.guild) || (type === MenuItemParentType.Channel && (!props.channel || !props.guild)))
+ return children;
const group = findGroupChildrenByChildId(childId, children);
diff --git a/src/plugins/reviewDB/auth.tsx b/src/plugins/reviewDB/auth.tsx
index 1d95e47d..e7a36921 100644
--- a/src/plugins/reviewDB/auth.tsx
+++ b/src/plugins/reviewDB/auth.tsx
@@ -14,7 +14,7 @@ import { ReviewDBAuth } from "./entities";
const DATA_STORE_KEY = "rdb-auth";
-const OAuth = findByPropsLazy("OAuth2AuthorizeModal");
+const { OAuth2AuthorizeModal } = findByPropsLazy("OAuth2AuthorizeModal");
export let Auth: ReviewDBAuth = {};
@@ -46,7 +46,7 @@ export async function updateAuth(newAuth: ReviewDBAuth) {
export function authorize(callback?: any) {
openModal(props =>
- {
- // this is terrible, blame ven
+ // this is terrible, blame mantika
const p = filters.byProps;
const [
{ cozyMessage, buttons, message, buttonsInner, groupStart },
diff --git a/src/plugins/reviewDB/components/ReviewsView.tsx b/src/plugins/reviewDB/components/ReviewsView.tsx
index abb856b9..cfd5477d 100644
--- a/src/plugins/reviewDB/components/ReviewsView.tsx
+++ b/src/plugins/reviewDB/components/ReviewsView.tsx
@@ -28,8 +28,8 @@ import { cl } from "../utils";
import ReviewComponent from "./ReviewComponent";
-const Slate = findByPropsLazy("Editor", "Transforms");
-const InputTypes = findByPropsLazy("ChatInputTypes");
+const { Editor, Transforms } = findByPropsLazy("Editor", "Transforms");
+const { ChatInputTypes } = findByPropsLazy("ChatInputTypes");
const InputComponent = LazyComponent(() => find(m => m.default?.type?.render?.toString().includes("default.CHANNEL_TEXT_AREA")).default);
@@ -122,7 +122,7 @@ function ReviewList({ refetch, reviews, hideOwnReview, profileId }: { refetch():
export function ReviewsInputComponent({ discordId, isAuthor, refetch, name }: { discordId: string, name: string; isAuthor: boolean; refetch(): void; }) {
const { token } = Auth;
const editorRef = useRef(null);
- const inputType = InputTypes.ChatInputTypes.FORM;
+ const inputType = ChatInputTypes.FORM;
inputType.disableAutoFocus = true;
const channel = {
@@ -172,10 +172,9 @@ export function ReviewsInputComponent({ discordId, isAuthor, refetch, name }: {
refetch();
const slateEditor = editorRef.current.ref.current.getSlateEditor();
- const { Editor, Transform } = Slate;
// clear editor
- Transform.delete(slateEditor, {
+ Transforms.delete(slateEditor, {
at: {
anchor: Editor.start(slateEditor, []),
focus: Editor.end(slateEditor, []),
diff --git a/src/plugins/reviewDB/reviewDbApi.ts b/src/plugins/reviewDB/reviewDbApi.ts
index a87fbcb8..54d934ea 100644
--- a/src/plugins/reviewDB/reviewDbApi.ts
+++ b/src/plugins/reviewDB/reviewDbApi.ts
@@ -22,7 +22,7 @@ import { Auth, authorize, getToken, updateAuth } from "./auth";
import { Review, ReviewDBCurrentUser, ReviewDBUser } from "./entities";
import { settings } from "./settings";
-const API_URL = "https://manti.vendicated.dev";
+const API_URL = "https://manti.vendicated.dev/api/reviewdb";
export const REVIEWS_PER_PAGE = 50;
@@ -45,13 +45,13 @@ export async function getReviews(id: string, offset = 0): Promise {
flags: String(flags),
offset: String(offset)
});
- const req = await fetch(`${API_URL}/api/reviewdb/users/${id}/reviews?${params}`);
+ const req = await fetch(`${API_URL}/users/${id}/reviews?${params}`);
const res = (req.status === 200)
? await req.json() as Response
: {
success: false,
- message: "An Error occured while fetching reviews. Please try again later.",
+ message: req.status === 429 ? "You are sending requests too fast. Wait a few seconds and try again." : "An Error occured while fetching reviews. Please try again later.",
reviews: [],
updated: false,
hasNextPage: false,
@@ -65,14 +65,14 @@ export async function getReviews(id: string, offset = 0): Promise {
reviews: [
{
id: 0,
- comment: "An Error occured while fetching reviews. Please try again later.",
+ comment: res.message,
star: 0,
timestamp: 0,
sender: {
id: 0,
- username: "Error",
- profilePhoto: "https://cdn.discordapp.com/attachments/1045394533384462377/1084900598035513447/646808599204593683.png?size=128",
- discordID: "0",
+ username: "ReviewDB",
+ profilePhoto: "https://cdn.discordapp.com/avatars/1134864775000629298/3f87ad315b32ee464d84f1270c8d1b37.png?size=256&format=webp&quality=lossless",
+ discordID: "1134864775000629298",
badges: []
}
}
@@ -92,7 +92,7 @@ export async function addReview(review: any): Promise {
return null;
}
- return fetch(API_URL + `/api/reviewdb/users/${review.userid}/reviews`, {
+ return fetch(API_URL + `/users/${review.userid}/reviews`, {
method: "PUT",
body: JSON.stringify(review),
headers: {
@@ -107,7 +107,7 @@ export async function addReview(review: any): Promise {
}
export async function deleteReview(id: number): Promise {
- return fetch(API_URL + `/api/reviewdb/users/${id}/reviews`, {
+ return fetch(API_URL + `/users/${id}/reviews`, {
method: "DELETE",
headers: new Headers({
"Content-Type": "application/json",
@@ -121,7 +121,7 @@ export async function deleteReview(id: number): Promise {
}
export async function reportReview(id: number) {
- const res = await fetch(API_URL + "/api/reviewdb/reports", {
+ const res = await fetch(API_URL + "/reports", {
method: "PUT",
headers: new Headers({
"Content-Type": "application/json",
@@ -137,7 +137,7 @@ export async function reportReview(id: number) {
}
async function patchBlock(action: "block" | "unblock", userId: string) {
- const res = await fetch(API_URL + "/api/reviewdb/blocks", {
+ const res = await fetch(API_URL + "/blocks", {
method: "PATCH",
headers: new Headers({
"Content-Type": "application/json",
@@ -168,7 +168,7 @@ export const blockUser = (userId: string) => patchBlock("block", userId);
export const unblockUser = (userId: string) => patchBlock("unblock", userId);
export async function fetchBlocks(): Promise {
- const res = await fetch(API_URL + "/api/reviewdb/blocks", {
+ const res = await fetch(API_URL + "/blocks", {
method: "GET",
headers: new Headers({
Accept: "application/json",
@@ -181,14 +181,14 @@ export async function fetchBlocks(): Promise {
}
export function getCurrentUserInfo(token: string): Promise {
- return fetch(API_URL + "/api/reviewdb/users", {
+ return fetch(API_URL + "/users", {
body: JSON.stringify({ token }),
method: "POST",
}).then(r => r.json());
}
export async function readNotification(id: number) {
- return fetch(API_URL + `/api/reviewdb/notifications?id=${id}`, {
+ return fetch(API_URL + `/notifications?id=${id}`, {
method: "PATCH",
headers: {
"Authorization": await getToken() || "",
diff --git a/src/plugins/roleColorEverywhere/index.tsx b/src/plugins/roleColorEverywhere/index.tsx
index d718f402..96802716 100644
--- a/src/plugins/roleColorEverywhere/index.tsx
+++ b/src/plugins/roleColorEverywhere/index.tsx
@@ -72,10 +72,6 @@ export default definePlugin({
{
find: 'tutorialId:"whos-online',
replacement: [
- {
- match: /\i.roleIcon,\.\.\.\i/,
- replace: "$&,color:$self.roleGroupColor(arguments[0])"
- },
{
match: /null,\i," — ",\i\]/,
replace: "null,$self.roleGroupColor(arguments[0])]"
@@ -83,6 +79,16 @@ export default definePlugin({
],
predicate: () => settings.store.memberList,
},
+ {
+ find: ".Messages.THREAD_BROWSER_PRIVATE",
+ replacement: [
+ {
+ match: /children:\[\i," — ",\i\]/,
+ replace: "children:[$self.roleGroupColor(arguments[0])]"
+ },
+ ],
+ predicate: () => settings.store.memberList,
+ },
{
find: "renderPrioritySpeaker",
replacement: [
diff --git a/src/plugins/viewRaw/index.tsx b/src/plugins/viewRaw/index.tsx
index f516b5d7..9179293e 100644
--- a/src/plugins/viewRaw/index.tsx
+++ b/src/plugins/viewRaw/index.tsx
@@ -27,7 +27,7 @@ import { Margins } from "@utils/margins";
import { copyWithToast } from "@utils/misc";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
-import { Button, ChannelStore, Forms, Menu, Text } from "@webpack/common";
+import { Button, ChannelStore, Forms, i18n, Menu, Text } from "@webpack/common";
import { Message } from "discord-types/general";
@@ -117,9 +117,12 @@ const settings = definePluginSettings({
}
});
-function MakeContextCallback(name: string) {
+function MakeContextCallback(name: "Guild" | "User" | "Channel") {
const callback: NavContextMenuPatchCallback = (children, props) => () => {
- if ((name === "Guild" && !props.guild) || (name === "User" && !props.user)) return;
+ const value = props[name.toLowerCase()];
+ if (!value) return;
+ if (props.label === i18n.Messages.CHANNEL_ACTIONS_MENU_LABEL) return; // random shit like notification settings
+
const lastChild = children.at(-1);
if (lastChild?.key === "developer-actions") {
const p = lastChild.props;
@@ -132,7 +135,7 @@ function MakeContextCallback(name: string) {
openViewRawModal(JSON.stringify(props[name.toLowerCase()], null, 4), name)}
+ action={() => openViewRawModal(JSON.stringify(value, null, 4), name)}
icon={CopyIcon}
/>
);
diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts
index 50a1b90a..bb98c61d 100644
--- a/src/plugins/webContextMenus.web/index.ts
+++ b/src/plugins/webContextMenus.web/index.ts
@@ -182,6 +182,12 @@ export default definePlugin({
],
async copyImage(url: string) {
+ if (IS_VESKTOP && VesktopNative.clipboard) {
+ const data = await fetch(url).then(r => r.arrayBuffer());
+ VesktopNative.clipboard.copyImage(data, url);
+ return;
+ }
+
// Clipboard only supports image/png, jpeg and similar won't work. Thus, we need to convert it to png
// via canvas first
const img = new Image();
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index d29de4a6..9820bf9c 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -402,7 +402,11 @@ export const Devs = /* #__PURE__*/ Object.freeze({
Grzesiek11: {
name: "Grzesiek11",
id: 368475654662127616n,
- }
+ },
+ Samwich: {
+ name: "Samwich",
+ id: 976176454511509554n,
+ },
} satisfies Record);
export const SuncordDevs = /* #__PURE__*/ Object.freeze({