diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs
index cdbb26ee..eb7ab905 100644
--- a/scripts/build/common.mjs
+++ b/scripts/build/common.mjs
@@ -89,15 +89,20 @@ export const globPlugins = kind => ({
const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins"];
let code = "";
let plugins = "\n";
+ let meta = "\n";
let i = 0;
for (const dir of pluginDirs) {
+ const userPlugin = dir === "userplugins";
+
if (!await exists(`./src/${dir}`)) continue;
- const files = await readdir(`./src/${dir}`);
+ const files = await readdir(`./src/${dir}`, { withFileTypes: true });
for (const file of files) {
- if (file.startsWith("_") || file.startsWith(".")) continue;
- if (file === "index.ts") continue;
+ const fileName = file.name;
+ if (fileName.startsWith("_") || fileName.startsWith(".")) continue;
+ if (fileName === "index.ts") continue;
+
+ const target = getPluginTarget(fileName);
- const target = getPluginTarget(file);
if (target && !IS_REPORTER) {
if (target === "dev" && !watch) continue;
if (target === "web" && kind === "discordDesktop") continue;
@@ -106,13 +111,16 @@ export const globPlugins = kind => ({
if (target === "vencordDesktop" && kind !== "vencordDesktop") continue;
}
+ const folderName = `src/${dir}/${fileName}`.replace(/^src\/plugins\//, "");
+
const mod = `p${i}`;
- code += `import ${mod} from "./${dir}/${file.replace(/\.tsx?$/, "")}";\n`;
+ code += `import ${mod} from "./${dir}/${fileName.replace(/\.tsx?$/, "")}";\n`;
plugins += `[${mod}.name]:${mod},\n`;
+ meta += `[${mod}.name]:${JSON.stringify({ folderName, userPlugin })},\n`; // TODO: add excluded plugins to display in the UI?
i++;
}
}
- code += `export default {${plugins}};`;
+ code += `export default {${plugins}};export const PluginMeta={${meta}};`;
return {
contents: code,
resolveDir: "./src"
diff --git a/src/components/PluginSettings/ContributorModal.tsx b/src/components/PluginSettings/ContributorModal.tsx
index 99a8da16..c3c36f1e 100644
--- a/src/components/PluginSettings/ContributorModal.tsx
+++ b/src/components/PluginSettings/ContributorModal.tsx
@@ -11,20 +11,16 @@ import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Link } from "@components/Link";
import { DevsById } from "@utils/constants";
-import { fetchUserProfile, getTheme, Theme } from "@utils/discord";
-import { pluralise } from "@utils/misc";
+import { fetchUserProfile } from "@utils/discord";
+import { classes, pluralise } from "@utils/misc";
import { ModalContent, ModalRoot, openModal } from "@utils/modal";
-import { Forms, MaskedLink, showToast, Tooltip, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common";
+import { Forms, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common";
import { User } from "discord-types/general";
import Plugins from "~plugins";
import { PluginCard } from ".";
-
-const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
-const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
-const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
-const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
+import { GithubButton, WebsiteButton } from "./LinkIconButton";
const cl = classNameFactory("vc-author-modal-");
@@ -40,16 +36,6 @@ export function openContributorModal(user: User) {
);
}
-function GithubIcon() {
- const src = getTheme() === Theme.Light ? GithubIconLight : GithubIconDark;
- return ;
-}
-
-function WebsiteIcon() {
- const src = getTheme() === Theme.Light ? WebsiteIconLight : WebsiteIconDark;
- return ;
-}
-
function ContributorModal({ user }: { user: User; }) {
useSettings();
@@ -86,24 +72,18 @@ function ContributorModal({ user }: { user: User; }) {
/>
{user.username}
-
+
{website && (
-
- {props => (
-
-
-
- )}
-
+
)}
{githubName && (
-
- {props => (
-
-
-
- )}
-
+
)}
diff --git a/src/components/PluginSettings/LinkIconButton.css b/src/components/PluginSettings/LinkIconButton.css
new file mode 100644
index 00000000..1055d6c7
--- /dev/null
+++ b/src/components/PluginSettings/LinkIconButton.css
@@ -0,0 +1,12 @@
+.vc-settings-modal-link-icon {
+ height: 32px;
+ width: 32px;
+ border-radius: 50%;
+ border: 4px solid var(--background-tertiary);
+ box-sizing: border-box
+}
+
+.vc-settings-modal-links {
+ display: flex;
+ gap: 0.2em;
+}
diff --git a/src/components/PluginSettings/LinkIconButton.tsx b/src/components/PluginSettings/LinkIconButton.tsx
new file mode 100644
index 00000000..ea36dda2
--- /dev/null
+++ b/src/components/PluginSettings/LinkIconButton.tsx
@@ -0,0 +1,45 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import "./LinkIconButton.css";
+
+import { getTheme, Theme } from "@utils/discord";
+import { MaskedLink, Tooltip } from "@webpack/common";
+
+const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
+const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
+const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
+const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
+
+export function GithubIcon() {
+ const src = getTheme() === Theme.Light ? GithubIconLight : GithubIconDark;
+ return ;
+}
+
+export function WebsiteIcon() {
+ const src = getTheme() === Theme.Light ? WebsiteIconLight : WebsiteIconDark;
+ return ;
+}
+
+interface Props {
+ text: string;
+ href: string;
+}
+
+function LinkIcon({ text, href, Icon }: Props & { Icon: React.ComponentType; }) {
+ return (
+
+ {props => (
+
+
+
+ )}
+
+ );
+}
+
+export const WebsiteButton = (props: Props) => ;
+export const GithubButton = (props: Props) => ;
diff --git a/src/components/PluginSettings/PluginModal.css b/src/components/PluginSettings/PluginModal.css
new file mode 100644
index 00000000..1f4b9aaa
--- /dev/null
+++ b/src/components/PluginSettings/PluginModal.css
@@ -0,0 +1,7 @@
+.vc-plugin-modal-info {
+ align-items: center;
+}
+
+.vc-plugin-modal-description {
+ flex-grow: 1;
+}
diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx
index 34de43c2..e5da01f3 100644
--- a/src/components/PluginSettings/PluginModal.tsx
+++ b/src/components/PluginSettings/PluginModal.tsx
@@ -16,10 +16,14 @@
* along with this program. If not, see .
*/
+import "./PluginModal.css";
+
import { generateId } from "@api/Commands";
import { useSettings } from "@api/Settings";
+import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
+import { gitRemote } from "@shared/vencordUserAgent";
import { proxyLazy } from "@utils/lazy";
import { Margins } from "@utils/margins";
import { classes, isObjectEmpty } from "@utils/misc";
@@ -30,6 +34,8 @@ import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserSto
import { User } from "discord-types/general";
import { Constructor } from "type-fest";
+import { PluginMeta } from "~plugins";
+
import {
ISettingElementProps,
SettingBooleanComponent,
@@ -40,6 +46,9 @@ import {
SettingTextComponent
} from "./components";
import { openContributorModal } from "./ContributorModal";
+import { GithubButton, WebsiteButton } from "./LinkIconButton";
+
+const cl = classNameFactory("vc-plugin-modal-");
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
@@ -180,16 +189,54 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
);
}
+ /*
+ function switchToPopout() {
+ onClose();
+
+ const PopoutKey = `DISCORD_VENCORD_PLUGIN_SETTINGS_MODAL_${plugin.name}`;
+ PopoutActions.open(
+ PopoutKey,
+ () => PopoutActions.close(PopoutKey)}
+ />
+ );
+ }
+ */
+
+ const pluginMeta = PluginMeta[plugin.name];
+
return (
{plugin.name}
+
+ {/*
+
+ */}
- About {plugin.name}
- {plugin.description}
+
+ {plugin.description}
+ {!pluginMeta.userPlugin && (
+
+
+
+
+ )}
+
Authors
;
export default plugins;
+ export const PluginMeta: Record;
}
declare module "~pluginNatives" {
diff --git a/src/utils/modal.tsx b/src/utils/modal.tsx
index 4203068c..79f77708 100644
--- a/src/utils/modal.tsx
+++ b/src/utils/modal.tsx
@@ -38,7 +38,7 @@ const enum ModalTransitionState {
export interface ModalProps {
transitionState: ModalTransitionState;
- onClose(): Promise;
+ onClose(): void;
}
export interface ModalOptions {
diff --git a/src/webpack/common/types/utils.d.ts b/src/webpack/common/types/utils.d.ts
index 1cd2bf69..7f6249be 100644
--- a/src/webpack/common/types/utils.d.ts
+++ b/src/webpack/common/types/utils.d.ts
@@ -18,6 +18,7 @@
import { Guild, GuildMember } from "discord-types/general";
import type { ReactNode } from "react";
+import { LiteralUnion } from "type-fest";
import type { FluxEvents } from "./fluxEvents";
import { i18nMessages } from "./i18nMessages";
@@ -221,3 +222,37 @@ export interface Constants {
UserFlags: Record;
FriendsSections: Record;
}
+
+export interface ExpressionPickerStore {
+ closeExpressionPicker(activeViewType?: any): void;
+ openExpressionPicker(activeView: LiteralUnion<"emoji" | "gif" | "sticker", string>, activeViewType?: any): void;
+}
+
+export interface BrowserWindowFeatures {
+ toolbar?: boolean;
+ menubar?: boolean;
+ location?: boolean;
+ directories?: boolean;
+ width?: number;
+ height?: number;
+ defaultWidth?: number;
+ defaultHeight?: number;
+ left?: number;
+ top?: number;
+ defaultAlwaysOnTop?: boolean;
+ movable?: boolean;
+ resizable?: boolean;
+ frame?: boolean;
+ alwaysOnTop?: boolean;
+ hasShadow?: boolean;
+ transparent?: boolean;
+ skipTaskbar?: boolean;
+ titleBarStyle?: string | null;
+ backgroundColor?: string;
+}
+
+export interface PopoutActions {
+ open(key: string, render: (windowKey: string) => ReactNode, features?: BrowserWindowFeatures);
+ close(key: string): void;
+ setAlwaysOnTop(key: string, alwaysOnTop: boolean): void;
+}
diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts
index a724769c..a6853c84 100644
--- a/src/webpack/common/utils.ts
+++ b/src/webpack/common/utils.ts
@@ -160,9 +160,15 @@ export const InviteActions = findByPropsLazy("resolveInvite");
export const IconUtils: t.IconUtils = findByPropsLazy("getGuildBannerURL", "getUserAvatarURL");
-const openExpressionPickerMatcher = canonicalizeMatch(/setState\({activeView:\i/);
+const openExpressionPickerMatcher = canonicalizeMatch(/setState\({activeView:\i,activeViewType:/);
// TODO: type
-export const ExpressionPickerStore = mapMangledModuleLazy("expression-picker-last-active-view", {
+export const ExpressionPickerStore: t.ExpressionPickerStore = mapMangledModuleLazy("expression-picker-last-active-view", {
closeExpressionPicker: filters.byCode("setState({activeView:null"),
openExpressionPicker: m => typeof m === "function" && openExpressionPickerMatcher.test(m.toString()),
});
+
+export const PopoutActions: t.PopoutActions = mapMangledModuleLazy('type:"POPOUT_WINDOW_OPEN"', {
+ open: filters.byCode('type:"POPOUT_WINDOW_OPEN"'),
+ close: filters.byCode('type:"POPOUT_WINDOW_CLOSE"'),
+ setAlwaysOnTop: filters.byCode('type:"POPOUT_WINDOW_SET_ALWAYS_ON_TOP"'),
+});