diff --git a/packages/discord-types/src/menu.d.ts b/packages/discord-types/src/menu.d.ts index 20c3494c3..78810296a 100644 --- a/packages/discord-types/src/menu.d.ts +++ b/packages/discord-types/src/menu.d.ts @@ -1,4 +1,4 @@ -import type { ComponentType, CSSProperties, MouseEvent, PropsWithChildren, ReactNode, UIEvent } from "react"; +import type { ComponentType, CSSProperties, ForwardRefRenderFunction, MouseEvent, PropsWithChildren, ReactNode, UIEvent } from "react"; type RC = ComponentType>>; @@ -46,6 +46,8 @@ export interface Menu { MenuControlItem: RC<{ id: string; interactive?: boolean; + label?: string; + control: ForwardRefRenderFunction; }>; MenuSliderControl: RC<{ minValue?: number, diff --git a/scripts/build/build.mjs b/scripts/build/build.mjs index 8a1186716..748553645 100644 --- a/scripts/build/build.mjs +++ b/scripts/build/build.mjs @@ -21,7 +21,7 @@ import { createPackage } from "@electron/asar"; import { readdir, writeFile } from "fs/promises"; -import { dirname, join } from "path"; +import { dirname, join, resolve } from "path"; import { fileURLToPath } from "url"; import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_COMPANION_TEST, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, commonRendererPlugins, watch, buildOrWatchAll, stringifyValues, IS_ANTI_CRASH_TEST } from "./common.mjs"; @@ -81,6 +81,10 @@ const globNativesPlugin = { let code = ""; let natives = "\n"; let i = 0; + /** + * @type {string[]} + */ + const watchFiles = []; for (const dir of pluginDirs) { const dirPath = join("src", dir); if (!await exists(dirPath)) continue; @@ -90,6 +94,8 @@ const globNativesPlugin = { const nativePath = join(dirPath, fileName, "native.ts"); const indexNativePath = join(dirPath, fileName, "native/index.ts"); + watchFiles.push(resolve(nativePath), resolve(indexNativePath)); + if (!(await exists(nativePath)) && !(await exists(indexNativePath))) continue; @@ -104,7 +110,9 @@ const globNativesPlugin = { code += `export default {${natives}};`; return { contents: code, - resolveDir: "./src" + resolveDir: "./src", + watchDirs: pluginDirs.map(d => resolve("src", d)), + watchFiles, }; }); } diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs index dc1949e13..da858e2ec 100644 --- a/scripts/build/common.mjs +++ b/scripts/build/common.mjs @@ -26,7 +26,7 @@ import esbuild, { build, context } from "esbuild"; import { constants as FsConstants, readFileSync } from "fs"; import { access, readdir, readFile } from "fs/promises"; import { minify as minifyHtml } from "html-minifier-terser"; -import { dirname, join, relative } from "path"; +import { dirname, join, relative, resolve } from "path"; import { fileURLToPath } from "url"; import { promisify } from "util"; @@ -195,7 +195,8 @@ export const globPlugins = kind => ({ code += `export default {${pluginsCode}};export const PluginMeta={${metaCode}};export const ExcludedPlugins={${excludedCode}};`; return { contents: code, - resolveDir: "./src" + resolveDir: "./src", + watchDirs: pluginDirs.map(d => resolve("src", d)), }; }); } diff --git a/src/Vencord.ts b/src/Vencord.ts index 9fede6591..3f1576f69 100644 --- a/src/Vencord.ts +++ b/src/Vencord.ts @@ -29,7 +29,7 @@ export * as WebpackPatcher from "./webpack/patchWebpack"; export { PlainSettings, Settings }; import { addVencordUiStyles } from "@components/css"; -import { openUpdaterModal } from "@components/settings/tabs/updater"; +import { openSettingsTabModal, UpdaterTab } from "@components/settings"; import { debounce } from "@shared/debounce"; import { IS_WINDOWS } from "@utils/constants"; import { createAndAppendStyle } from "@utils/css"; @@ -123,6 +123,8 @@ async function syncSettings() { let notifiedForUpdatesThisSession = false; async function runUpdateCheck() { + if (IS_UPDATER_DISABLED) return; + const notify = (data: NotificationData) => { if (notifiedForUpdatesThisSession) return; notifiedForUpdatesThisSession = true; @@ -153,7 +155,7 @@ async function runUpdateCheck() { notify({ title: "A Equicord update is available!", body: "Click here to view the update", - onClick: openUpdaterModal! + onClick: () => openSettingsTabModal(UpdaterTab!) }); } catch (err) { UpdateLogger.error("Failed to check for updates", err); diff --git a/src/api/ChatButtons.tsx b/src/api/ChatButtons.tsx index 3c5e59a73..52052a528 100644 --- a/src/api/ChatButtons.tsx +++ b/src/api/ChatButtons.tsx @@ -9,11 +9,15 @@ import "./ChatButton.css"; import ErrorBoundary from "@components/ErrorBoundary"; import { Logger } from "@utils/Logger"; import { classes } from "@utils/misc"; +import { IconComponent } from "@utils/types"; import { Channel } from "@vencord/discord-types"; import { waitFor } from "@webpack"; -import { ButtonWrapperClasses, Clickable, Tooltip } from "@webpack/common"; +import { ButtonWrapperClasses, Clickable, Menu, Tooltip } from "@webpack/common"; import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react"; +import { addContextMenuPatch, findGroupChildrenByChildId } from "./ContextMenu"; +import { useSettings } from "./Settings"; + let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>; waitFor(["buttonContainer", "channelTextArea"], m => ChannelTextAreaClasses = m); @@ -76,26 +80,51 @@ export interface ChatBarProps { } export type ChatBarButtonFactory = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null; +export type ChatBarButtonData = { + render: ChatBarButtonFactory; + /** + * This icon is used only for Settings UI. Your render function must still render an icon, + * and it can be different from this one. + */ + icon: IconComponent; +}; -const buttonFactories = new Map(); +/** + * Don't use this directly, use {@link addChatBarButton} and {@link removeChatBarButton} instead. + */ +export const ChatBarButtonMap = new Map(); const logger = new Logger("ChatButtons"); +function VencordChatBarButtons(props: ChatBarProps) { + const { chatBarButtons } = useSettings(["uiElements.chatBarButtons.*"]).uiElements; + + return ( + <> + {Array.from(ChatBarButtonMap) + .filter(([key]) => chatBarButtons[key]?.enabled !== false) + .map(([key, { render: Button }]) => ( + logger.error(`Failed to render ${key}`, e.error)}> +