1
0
Fork 1
mirror of https://github.com/Vendicated/Vencord.git synced 2025-01-10 09:56:24 +00:00

Merge branch 'dev' into newDevTools

This commit is contained in:
sadan 2025-01-05 14:26:47 -05:00
commit 32b0f29b2e
No known key found for this signature in database
66 changed files with 2163 additions and 1573 deletions

View file

@ -4,10 +4,9 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
// @ts-check
import stylistic from "@stylistic/eslint-plugin"; import stylistic from "@stylistic/eslint-plugin";
import pathAlias from "eslint-plugin-path-alias"; import pathAlias from "eslint-plugin-path-alias";
import react from "eslint-plugin-react";
import header from "eslint-plugin-simple-header"; import header from "eslint-plugin-simple-header";
import simpleImportSort from "eslint-plugin-simple-import-sort"; import simpleImportSort from "eslint-plugin-simple-import-sort";
import unusedImports from "eslint-plugin-unused-imports"; import unusedImports from "eslint-plugin-unused-imports";
@ -15,6 +14,22 @@ import tseslint from "typescript-eslint";
export default tseslint.config( export default tseslint.config(
{ ignores: ["dist", "browser", "packages/vencord-types"] }, { ignores: ["dist", "browser", "packages/vencord-types"] },
{
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"],
settings: {
react: {
version: "18"
}
},
...react.configs.flat.recommended,
rules: {
...react.configs.flat.recommended.rules,
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"react/display-name": "off",
"react/no-unescaped-entities": "off",
}
},
{ {
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"], files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"],
plugins: { plugins: {
@ -23,7 +38,7 @@ export default tseslint.config(
"@typescript-eslint": tseslint.plugin, "@typescript-eslint": tseslint.plugin,
"simple-import-sort": simpleImportSort, "simple-import-sort": simpleImportSort,
"unused-imports": unusedImports, "unused-imports": unusedImports,
"path-alias": pathAlias, "path-alias": pathAlias
}, },
settings: { settings: {
"import/resolver": { "import/resolver": {

View file

@ -41,48 +41,49 @@
"@vap/shiki": "0.10.5", "@vap/shiki": "0.10.5",
"fflate": "^0.8.2", "fflate": "^0.8.2",
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3", "gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
"monaco-editor": "^0.50.0", "monaco-editor": "^0.52.2",
"nanoid": "^5.0.7", "nanoid": "^5.0.9",
"virtual-merge": "^1.0.1" "virtual-merge": "^1.0.1"
}, },
"devDependencies": { "devDependencies": {
"@stylistic/eslint-plugin": "^2.6.1", "@stylistic/eslint-plugin": "^2.12.1",
"@types/chrome": "^0.0.269", "@types/chrome": "^0.0.287",
"@types/diff": "^5.2.1", "@types/diff": "^6.0.0",
"@types/lodash": "^4.17.7", "@types/lodash": "^4.17.14",
"@types/node": "^22.0.3", "@types/node": "^22.10.5",
"@types/react": "^18.3.3", "@types/react": "^19.0.2",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^19.0.2",
"@types/yazl": "^2.4.5", "@types/yazl": "^2.4.5",
"diff": "^5.2.0", "diff": "^7.0.0",
"discord-types": "^1.3.26", "discord-types": "^1.3.26",
"esbuild": "^0.15.18", "esbuild": "^0.15.18",
"eslint": "^9.8.0", "eslint": "^9.17.0",
"eslint-import-resolver-alias": "^1.1.2", "eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-path-alias": "2.1.0", "eslint-plugin-path-alias": "2.1.0",
"eslint-plugin-simple-header": "^1.1.1", "eslint-plugin-react": "^7.37.3",
"eslint-plugin-simple-header": "^1.2.1",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "^4.0.1", "eslint-plugin-unused-imports": "^4.1.4",
"highlight.js": "10.7.3", "highlight.js": "11.7.0",
"html-minifier-terser": "^7.2.0", "html-minifier-terser": "^7.2.0",
"moment": "^2.30.1", "moment": "^2.22.2",
"puppeteer-core": "^22.15.0", "puppeteer-core": "^23.11.1",
"standalone-electron-types": "^1.0.0", "standalone-electron-types": "^1.0.0",
"stylelint": "^16.8.1", "stylelint": "^16.12.0",
"stylelint-config-standard": "^36.0.1", "stylelint-config-standard": "^36.0.1",
"ts-patch": "^3.2.1", "ts-patch": "^3.3.0",
"ts-pattern": "^5.3.1", "ts-pattern": "^5.6.0",
"tsx": "^4.16.5", "tsx": "^4.19.2",
"type-fest": "^4.23.0", "type-fest": "^4.31.0",
"typescript": "^5.5.4", "typescript": "^5.7.2",
"typescript-eslint": "^8.0.0", "typescript-eslint": "^8.19.0",
"typescript-transform-paths": "^3.4.7", "typescript-transform-paths": "^3.5.3",
"zip-local": "^0.3.5" "zip-local": "^0.3.5"
}, },
"packageManager": "pnpm@9.1.0", "packageManager": "pnpm@9.1.0",
"pnpm": { "pnpm": {
"patchedDependencies": { "patchedDependencies": {
"eslint@9.8.0": "patches/eslint@9.8.0.patch", "eslint@9.17.0": "patches/eslint@9.17.0.patch",
"eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch" "eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch"
}, },
"peerDependencyRules": { "peerDependencyRules": {

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,7 @@ import { Logger } from "@utils/Logger";
import { waitFor } from "@webpack"; import { waitFor } from "@webpack";
import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common"; import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common";
import { Channel } from "discord-types/general"; import { Channel } from "discord-types/general";
import { HTMLProps, MouseEventHandler, ReactNode } from "react"; import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react";
let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>; let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>;
waitFor(["buttonContainer", "channelTextArea"], m => ChannelTextAreaClasses = m); waitFor(["buttonContainer", "channelTextArea"], m => ChannelTextAreaClasses = m);

View file

@ -24,13 +24,13 @@ import type { ReactElement } from "react";
* @param children The rendered context menu elements * @param children The rendered context menu elements
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example * @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
*/ */
export type NavContextMenuPatchCallback = (children: Array<ReactElement | null>, ...args: Array<any>) => void; export type NavContextMenuPatchCallback = (children: Array<ReactElement<any> | null>, ...args: Array<any>) => void;
/** /**
* @param navId The navId of the context menu being patched * @param navId The navId of the context menu being patched
* @param children The rendered context menu elements * @param children The rendered context menu elements
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example * @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
*/ */
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement | null>, ...args: Array<any>) => void; export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement<any> | null>, ...args: Array<any>) => void;
const ContextMenuLogger = new Logger("ContextMenu"); const ContextMenuLogger = new Logger("ContextMenu");
@ -70,7 +70,7 @@ export function addGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback)
* @returns Whether the patch was successfully removed from the context menu(s) * @returns Whether the patch was successfully removed from the context menu(s)
*/ */
export function removeContextMenuPatch<T extends string | Array<string>>(navId: T, patch: NavContextMenuPatchCallback): T extends string ? boolean : Array<boolean> { export function removeContextMenuPatch<T extends string | Array<string>>(navId: T, patch: NavContextMenuPatchCallback): T extends string ? boolean : Array<boolean> {
const navIds = Array.isArray(navId) ? navId : [navId as string]; const navIds: string[] = Array.isArray(navId) ? navId : [navId];
const results = navIds.map(id => navPatches.get(id)?.delete(patch) ?? false); const results = navIds.map(id => navPatches.get(id)?.delete(patch) ?? false);
@ -92,7 +92,7 @@ export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallba
* @param children The context menu children * @param children The context menu children
* @param matchSubstring Whether to check if the id is a substring of the child id * @param matchSubstring Whether to check if the id is a substring of the child id
*/ */
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null | undefined>, matchSubstring = false): Array<ReactElement | null | undefined> | null { export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement<any> | null | undefined>, matchSubstring = false): Array<ReactElement<any> | null | undefined> | null {
for (const child of children) { for (const child of children) {
if (child == null) continue; if (child == null) continue;
@ -124,7 +124,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
interface ContextMenuProps { interface ContextMenuProps {
contextMenuApiArguments?: Array<any>; contextMenuApiArguments?: Array<any>;
navId: string; navId: string;
children: Array<ReactElement | null>; children: Array<ReactElement<any> | null>;
"aria-label": string; "aria-label": string;
onSelect: (() => void) | undefined; onSelect: (() => void) | undefined;
onClose: (callback: (...args: Array<any>) => any) => void; onClose: (callback: (...args: Array<any>) => any) => void;
@ -162,7 +162,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
return props; return props;
} }
function cloneMenuChildren(obj: ReactElement | Array<ReactElement | null> | null) { function cloneMenuChildren(obj: ReactElement<any> | Array<ReactElement<any> | null> | null) {
if (Array.isArray(obj)) { if (Array.isArray(obj)) {
return obj.map(cloneMenuChildren); return obj.map(cloneMenuChildren);
} }

View file

@ -17,6 +17,7 @@
*/ */
import { Channel, User } from "discord-types/general/index.js"; import { Channel, User } from "discord-types/general/index.js";
import { JSX } from "react";
interface DecoratorProps { interface DecoratorProps {
activities: any[]; activities: any[];

View file

@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { JSX } from "react";
export type AccessoryCallback = (props: Record<string, any>) => JSX.Element | null | Array<JSX.Element | null>; export type AccessoryCallback = (props: Record<string, any>) => JSX.Element | null | Array<JSX.Element | null>;
export type Accessory = { export type Accessory = {
callback: AccessoryCallback; callback: AccessoryCallback;

View file

@ -17,6 +17,7 @@
*/ */
import { Channel, Message } from "discord-types/general/index.js"; import { Channel, Message } from "discord-types/general/index.js";
import { JSX } from "react";
interface DecorationProps { interface DecorationProps {
author: { author: {

View file

@ -17,6 +17,7 @@
*/ */
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { JSX } from "react";
const logger = new Logger("ServerListAPI"); const logger = new Logger("ServerListAPI");

View file

@ -23,7 +23,7 @@ import { Logger } from "@utils/Logger";
import { mergeDefaults } from "@utils/mergeDefaults"; import { mergeDefaults } from "@utils/mergeDefaults";
import { putCloudSettings } from "@utils/settingsSync"; import { putCloudSettings } from "@utils/settingsSync";
import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types"; import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types";
import { React } from "@webpack/common"; import { React, useEffect } from "@webpack/common";
import plugins from "~plugins"; import plugins from "~plugins";
@ -192,7 +192,7 @@ export const Settings = SettingsStore.store;
export function useSettings(paths?: UseSettings<Settings>[]) { export function useSettings(paths?: UseSettings<Settings>[]) {
const [, forceUpdate] = React.useReducer(() => ({}), {}); const [, forceUpdate] = React.useReducer(() => ({}), {});
React.useEffect(() => { useEffect(() => {
if (paths) { if (paths) {
paths.forEach(p => SettingsStore.addChangeListener(p, forceUpdate)); paths.forEach(p => SettingsStore.addChangeListener(p, forceUpdate));
return () => paths.forEach(p => SettingsStore.removeChangeListener(p, forceUpdate)); return () => paths.forEach(p => SettingsStore.removeChangeListener(p, forceUpdate));
@ -200,7 +200,7 @@ export function useSettings(paths?: UseSettings<Settings>[]) {
SettingsStore.addGlobalChangeListener(forceUpdate); SettingsStore.addGlobalChangeListener(forceUpdate);
return () => SettingsStore.removeGlobalChangeListener(forceUpdate); return () => SettingsStore.removeGlobalChangeListener(forceUpdate);
} }
}, []); }, [paths]);
return SettingsStore.store; return SettingsStore.store;
} }

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
export function Badge({ text, color }): JSX.Element { export function Badge({ text, color }) {
return ( return (
<div className="vc-plugins-badge" style={{ <div className="vc-plugins-badge" style={{
backgroundColor: color, backgroundColor: color,

View file

@ -27,7 +27,7 @@ interface Props<T = any> {
/** Render nothing if an error occurs */ /** Render nothing if an error occurs */
noop?: boolean; noop?: boolean;
/** Fallback component to render if an error occurs */ /** Fallback component to render if an error occurs */
fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; message: string; stack: string; }>>; fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; message: string; stack: string; wrappedProps: T; }>>;
/** called when an error occurs. The props property is only available if using .wrap */ /** called when an error occurs. The props property is only available if using .wrap */
onError?(data: { error: Error, errorInfo: React.ErrorInfo, props: T; }): void; onError?(data: { error: Error, errorInfo: React.ErrorInfo, props: T; }): void;
/** Custom error message */ /** Custom error message */
@ -80,10 +80,14 @@ const ErrorBoundary = LazyComponent(() => {
if (this.props.noop) return null; if (this.props.noop) return null;
if (this.props.fallback) if (this.props.fallback)
return <this.props.fallback return (
children={this.props.children} <this.props.fallback
{...this.state} wrappedProps={this.props.wrappedProps}
/>; {...this.state}
>
{this.props.children}
</this.props.fallback>
);
const msg = this.props.message || "An error occurred while rendering this Component. More info can be found below and in your console."; const msg = this.props.message || "An error occurred while rendering this Component. More info can be found below and in your console.";

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { CSSProperties } from "react"; import { CSSProperties, JSX } from "react";
interface Props { interface Props {
columns: number; columns: number;

View file

@ -27,7 +27,7 @@ export function Heart() {
> >
<path <path
fill="#db61a2" fill="#db61a2"
fill-rule="evenodd" fillRule="evenodd"
d="M4.25 2.5c-1.336 0-2.75 1.164-2.75 3 0 2.15 1.58 4.144 3.365 5.682A20.565 20.565 0 008 13.393a20.561 20.561 0 003.135-2.211C12.92 9.644 14.5 7.65 14.5 5.5c0-1.836-1.414-3-2.75-3-1.373 0-2.609.986-3.029 2.456a.75.75 0 01-1.442 0C6.859 3.486 5.623 2.5 4.25 2.5zM8 14.25l-.345.666-.002-.001-.006-.003-.018-.01a7.643 7.643 0 01-.31-.17 22.075 22.075 0 01-3.434-2.414C2.045 10.731 0 8.35 0 5.5 0 2.836 2.086 1 4.25 1 5.797 1 7.153 1.802 8 3.02 8.847 1.802 10.203 1 11.75 1 13.914 1 16 2.836 16 5.5c0 2.85-2.045 5.231-3.885 6.818a22.08 22.08 0 01-3.744 2.584l-.018.01-.006.003h-.002L8 14.25zm0 0l.345.666a.752.752 0 01-.69 0L8 14.25z" d="M4.25 2.5c-1.336 0-2.75 1.164-2.75 3 0 2.15 1.58 4.144 3.365 5.682A20.565 20.565 0 008 13.393a20.561 20.561 0 003.135-2.211C12.92 9.644 14.5 7.65 14.5 5.5c0-1.836-1.414-3-2.75-3-1.373 0-2.609.986-3.029 2.456a.75.75 0 01-1.442 0C6.859 3.486 5.623 2.5 4.25 2.5zM8 14.25l-.345.666-.002-.001-.006-.003-.018-.01a7.643 7.643 0 01-.31-.17 22.075 22.075 0 01-3.434-2.414C2.045 10.731 0 8.35 0 5.5 0 2.836 2.086 1 4.25 1 5.797 1 7.153 1.802 8 3.02 8.847 1.802 10.203 1 11.75 1 13.914 1 16 2.836 16 5.5c0 2.85-2.045 5.231-3.885 6.818a22.08 22.08 0 01-3.744 2.584l-.018.01-.006.003h-.002L8 14.25zm0 0l.345.666a.752.752 0 01-.69 0L8 14.25z"
/> />
</svg> </svg>

View file

@ -20,7 +20,7 @@ import "./iconStyles.css";
import { getIntlMessage } from "@utils/discord"; import { getIntlMessage } from "@utils/discord";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import type { PropsWithChildren } from "react"; import type { JSX, PropsWithChildren } from "react";
interface BaseIconProps extends IconProps { interface BaseIconProps extends IconProps {
viewBox: string; viewBox: string;
@ -55,7 +55,7 @@ export function LinkIcon({ height = 24, width = 24, className }: IconProps) {
className={classes(className, "vc-link-icon")} className={classes(className, "vc-link-icon")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<g fill="none" fill-rule="evenodd"> <g fill="none" fillRule="evenodd">
<path fill="currentColor" d="M10.59 13.41c.41.39.41 1.03 0 1.42-.39.39-1.03.39-1.42 0a5.003 5.003 0 0 1 0-7.07l3.54-3.54a5.003 5.003 0 0 1 7.07 0 5.003 5.003 0 0 1 0 7.07l-1.49 1.49c.01-.82-.12-1.64-.4-2.42l.47-.48a2.982 2.982 0 0 0 0-4.24 2.982 2.982 0 0 0-4.24 0l-3.53 3.53a2.982 2.982 0 0 0 0 4.24zm2.82-4.24c.39-.39 1.03-.39 1.42 0a5.003 5.003 0 0 1 0 7.07l-3.54 3.54a5.003 5.003 0 0 1-7.07 0 5.003 5.003 0 0 1 0-7.07l1.49-1.49c-.01.82.12 1.64.4 2.43l-.47.47a2.982 2.982 0 0 0 0 4.24 2.982 2.982 0 0 0 4.24 0l3.53-3.53a2.982 2.982 0 0 0 0-4.24.973.973 0 0 1 0-1.42z" /> <path fill="currentColor" d="M10.59 13.41c.41.39.41 1.03 0 1.42-.39.39-1.03.39-1.42 0a5.003 5.003 0 0 1 0-7.07l3.54-3.54a5.003 5.003 0 0 1 7.07 0 5.003 5.003 0 0 1 0 7.07l-1.49 1.49c.01-.82-.12-1.64-.4-2.42l.47-.48a2.982 2.982 0 0 0 0-4.24 2.982 2.982 0 0 0-4.24 0l-3.53 3.53a2.982 2.982 0 0 0 0 4.24zm2.82-4.24c.39-.39 1.03-.39 1.42 0a5.003 5.003 0 0 1 0 7.07l-3.54 3.54a5.003 5.003 0 0 1-7.07 0 5.003 5.003 0 0 1 0-7.07l1.49-1.49c-.01.82.12 1.64.4 2.43l-.47.47a2.982 2.982 0 0 0 0 4.24 2.982 2.982 0 0 0 4.24 0l3.53-3.53a2.982 2.982 0 0 0 0-4.24.973.973 0 0 1 0-1.42z" />
<rect width={width} height={height} /> <rect width={width} height={height} />
</g> </g>
@ -122,8 +122,8 @@ export function InfoIcon(props: IconProps) {
> >
<path <path
fill="currentColor" fill="currentColor"
fill-rule="evenodd" fillRule="evenodd"
d="M23 12a11 11 0 1 1-22 0 11 11 0 0 1 22 0Zm-9.5-4.75a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Zm-.77 3.96a1 1 0 1 0-1.96-.42l-1.04 4.86a2.77 2.77 0 0 0 4.31 2.83l.24-.17a1 1 0 1 0-1.16-1.62l-.24.17a.77.77 0 0 1-1.2-.79l1.05-4.86Z" clip-rule="evenodd" d="M23 12a11 11 0 1 1-22 0 11 11 0 0 1 22 0Zm-9.5-4.75a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Zm-.77 3.96a1 1 0 1 0-1.96-.42l-1.04 4.86a2.77 2.77 0 0 0 4.31 2.83l.24-.17a1 1 0 1 0-1.16-1.62l-.24.17a.77.77 0 0 1-1.2-.79l1.05-4.86Z" clipRule="evenodd"
/> />
</Icon> </Icon>
); );
@ -212,9 +212,9 @@ export function CogWheel(props: IconProps) {
> >
<path <path
fill="currentColor" fill="currentColor"
fill-rule="evenodd" fillRule="evenodd"
d="M10.56 1.1c-.46.05-.7.53-.64.98.18 1.16-.19 2.2-.98 2.53-.8.33-1.79-.15-2.49-1.1-.27-.36-.78-.52-1.14-.24-.77.59-1.45 1.27-2.04 2.04-.28.36-.12.87.24 1.14.96.7 1.43 1.7 1.1 2.49-.33.8-1.37 1.16-2.53.98-.45-.07-.93.18-.99.64a11.1 11.1 0 0 0 0 2.88c.06.46.54.7.99.64 1.16-.18 2.2.19 2.53.98.33.8-.14 1.79-1.1 2.49-.36.27-.52.78-.24 1.14.59.77 1.27 1.45 2.04 2.04.36.28.87.12 1.14-.24.7-.95 1.7-1.43 2.49-1.1.8.33 1.16 1.37.98 2.53-.07.45.18.93.64.99a11.1 11.1 0 0 0 2.88 0c.46-.06.7-.54.64-.99-.18-1.16.19-2.2.98-2.53.8-.33 1.79.14 2.49 1.1.27.36.78.52 1.14.24.77-.59 1.45-1.27 2.04-2.04.28-.36.12-.87-.24-1.14-.96-.7-1.43-1.7-1.1-2.49.33-.8 1.37-1.16 2.53-.98.45.07.93-.18.99-.64a11.1 11.1 0 0 0 0-2.88c-.06-.46-.54-.7-.99-.64-1.16.18-2.2-.19-2.53-.98-.33-.8.14-1.79 1.1-2.49.36-.27.52-.78.24-1.14a11.07 11.07 0 0 0-2.04-2.04c-.36-.28-.87-.12-1.14.24-.7.96-1.7 1.43-2.49 1.1-.8-.33-1.16-1.37-.98-2.53.07-.45-.18-.93-.64-.99a11.1 11.1 0 0 0-2.88 0ZM16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z" d="M10.56 1.1c-.46.05-.7.53-.64.98.18 1.16-.19 2.2-.98 2.53-.8.33-1.79-.15-2.49-1.1-.27-.36-.78-.52-1.14-.24-.77.59-1.45 1.27-2.04 2.04-.28.36-.12.87.24 1.14.96.7 1.43 1.7 1.1 2.49-.33.8-1.37 1.16-2.53.98-.45-.07-.93.18-.99.64a11.1 11.1 0 0 0 0 2.88c.06.46.54.7.99.64 1.16-.18 2.2.19 2.53.98.33.8-.14 1.79-1.1 2.49-.36.27-.52.78-.24 1.14.59.77 1.27 1.45 2.04 2.04.36.28.87.12 1.14-.24.7-.95 1.7-1.43 2.49-1.1.8.33 1.16 1.37.98 2.53-.07.45.18.93.64.99a11.1 11.1 0 0 0 2.88 0c.46-.06.7-.54.64-.99-.18-1.16.19-2.2.98-2.53.8-.33 1.79.14 2.49 1.1.27.36.78.52 1.14.24.77-.59 1.45-1.27 2.04-2.04.28-.36.12-.87-.24-1.14-.96-.7-1.43-1.7-1.1-2.49.33-.8 1.37-1.16 2.53-.98.45.07.93-.18.99-.64a11.1 11.1 0 0 0 0-2.88c-.06-.46-.54-.7-.99-.64-1.16.18-2.2-.19-2.53-.98-.33-.8.14-1.79 1.1-2.49.36-.27.52-.78.24-1.14a11.07 11.07 0 0 0-2.04-2.04c-.36-.28-.87-.12-1.14.24-.7.96-1.7 1.43-2.49 1.1-.8-.33-1.16-1.37-.98-2.53.07-.45-.18-.93-.64-.99a11.1 11.1 0 0 0-2.88 0ZM16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"
clip-rule="evenodd" clipRule="evenodd"
/> />
</Icon> </Icon>
); );
@ -262,7 +262,7 @@ export function PlusIcon(props: IconProps) {
viewBox="0 0 18 18" viewBox="0 0 18 18"
> >
<polygon <polygon
fill-rule="nonzero" fillRule="nonzero"
fill="currentColor" fill="currentColor"
points="15 10 10 10 10 15 8 15 8 10 3 10 3 8 8 8 8 3 10 3 10 8 15 8" points="15 10 10 10 10 15 8 15 8 10 3 10 3 8 8 8 8 3 10 3 10 8 15 8"
/> />

View file

@ -44,7 +44,7 @@ function ContributorModal({ user }: { user: User; }) {
useEffect(() => { useEffect(() => {
if (!profile && !user.bot && user.id) if (!profile && !user.bot && user.id)
fetchUserProfile(user.id); fetchUserProfile(user.id);
}, [user.id]); }, [user.id, user.bot, profile]);
const githubName = profile?.connectedAccounts?.find(a => a.type === "github")?.name; const githubName = profile?.connectedAccounts?.find(a => a.type === "github")?.name;
const website = profile?.connectedAccounts?.find(a => a.type === "domain")?.name; const website = profile?.connectedAccounts?.find(a => a.type === "domain")?.name;

View file

@ -109,7 +109,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
setAuthors(a => [...a, author]); setAuthors(a => [...a, author]);
} }
})(); })();
}, []); }, [plugin.authors]);
async function saveAndClose() { async function saveAndClose() {
if (!plugin.options) { if (!plugin.options) {

View file

@ -35,6 +35,7 @@ import { useAwaiter } from "@utils/react";
import { Plugin } from "@utils/types"; import { Plugin } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip, useMemo } from "@webpack/common"; import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip, useMemo } from "@webpack/common";
import { JSX } from "react";
import Plugins, { ExcludedPlugins } from "~plugins"; import Plugins, { ExcludedPlugins } from "~plugins";
@ -387,7 +388,7 @@ function makeDependencyList(deps: string[]) {
return ( return (
<React.Fragment> <React.Fragment>
<Forms.FormText>This plugin is required by:</Forms.FormText> <Forms.FormText>This plugin is required by:</Forms.FormText>
{deps.map((dep: string) => <Forms.FormText className={cl("dep-text")}>{dep}</Forms.FormText>)} {deps.map((dep: string) => <Forms.FormText key={dep} className={cl("dep-text")}>{dep}</Forms.FormText>)}
</React.Fragment> </React.Fragment>
); );
} }

View file

@ -111,9 +111,9 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
} }
function renderDiff() { function renderDiff() {
return diff?.map(p => { return diff?.map((p, idx) => {
const color = p.added ? "lime" : p.removed ? "red" : "grey"; const color = p.added ? "lime" : p.removed ? "red" : "grey";
return <div style={{ color, userSelect: "text", wordBreak: "break-all", lineBreak: "anywhere" }}>{p.value}</div>; return <div key={idx} style={{ color, userSelect: "text", wordBreak: "break-all", lineBreak: "anywhere" }}>{p.value}</div>;
}); });
} }

View file

@ -61,7 +61,7 @@ function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>
title: "Oops!", title: "Oops!",
body: ( body: (
<ErrorCard> <ErrorCard>
{err.split("\n").map(line => <div>{Parser.parse(line)}</div>)} {err.split("\n").map((line, idx) => <div key={idx}>{Parser.parse(line)}</div>)}
</ErrorCard> </ErrorCard>
) )
}); });
@ -87,7 +87,7 @@ function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof
return ( return (
<Card style={{ padding: "0 0.5em" }}> <Card style={{ padding: "0 0.5em" }}>
{updates.map(({ hash, author, message }) => ( {updates.map(({ hash, author, message }) => (
<div style={{ <div key={hash} style={{
marginTop: "0.5em", marginTop: "0.5em",
marginBottom: "0.5em" marginBottom: "0.5em"
}}> }}>

View file

@ -34,6 +34,7 @@ import { makeCodeblock } from "@utils/text";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { checkForUpdates, isOutdated, update } from "@utils/updater"; import { checkForUpdates, isOutdated, update } from "@utils/updater";
import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common"; import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common";
import { JSX } from "react";
import gitHash from "~git-hash"; import gitHash from "~git-hash";
import plugins, { PluginMeta } from "~plugins"; import plugins, { PluginMeta } from "~plugins";

View file

@ -85,7 +85,7 @@ export default definePlugin({
replace: "$&onRequestClose:$self.onPopoutClose," replace: "$&onRequestClose:$self.onPopoutClose,"
}, },
{ {
match: /(?<=.avatarWrapper,)/, match: /(?<=\.avatarWrapper,)/,
replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu," replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu,"
} }
] ]

View file

@ -99,7 +99,7 @@ export const UnknownIcon = (props: React.PropsWithChildren<SVGProps<SVGSVGElemen
fill="currentColor" fill="currentColor"
viewBox="0 0 16 16" viewBox="0 0 16 16"
> >
<path fill-rule="evenodd" d="M4.475 5.458c-.284 0-.514-.237-.47-.517C4.28 3.24 5.576 2 7.825 2c2.25 0 3.767 1.36 3.767 3.215 0 1.344-.665 2.288-1.79 2.973-1.1.659-1.414 1.118-1.414 2.01v.03a.5.5 0 0 1-.5.5h-.77a.5.5 0 0 1-.5-.495l-.003-.2c-.043-1.221.477-2.001 1.645-2.712 1.03-.632 1.397-1.135 1.397-2.028 0-.979-.758-1.698-1.926-1.698-1.009 0-1.71.529-1.938 1.402-.066.254-.278.461-.54.461h-.777ZM7.496 14c.622 0 1.095-.474 1.095-1.09 0-.618-.473-1.092-1.095-1.092-.606 0-1.087.474-1.087 1.091S6.89 14 7.496 14Z" /> <path fillRule="evenodd" d="M4.475 5.458c-.284 0-.514-.237-.47-.517C4.28 3.24 5.576 2 7.825 2c2.25 0 3.767 1.36 3.767 3.215 0 1.344-.665 2.288-1.79 2.973-1.1.659-1.414 1.118-1.414 2.01v.03a.5.5 0 0 1-.5.5h-.77a.5.5 0 0 1-.5-.495l-.003-.2c-.043-1.221.477-2.001 1.645-2.712 1.03-.632 1.397-1.135 1.397-2.028 0-.979-.758-1.698-1.926-1.698-1.009 0-1.71.529-1.938 1.402-.066.254-.278.461-.54.461h-.777ZM7.496 14c.622 0 1.095-.474 1.095-1.09 0-.618-.473-1.092-1.095-1.092-.606 0-1.087.474-1.087 1.091S6.89 14 7.496 14Z" />
</svg> </svg>
); );

View file

@ -173,7 +173,7 @@ export default definePlugin({
} }
return this; return this;
}, },
map(render: (item: SettingsEntry) => ReactElement) { map(render: (item: SettingsEntry) => ReactElement<any>) {
return items return items
.filter(a => a.items.length > 0) .filter(a => a.items.length > 0)
.map(({ label, items }) => { .map(({ label, items }) => {
@ -181,11 +181,14 @@ export default definePlugin({
if (label) { if (label) {
return ( return (
<Menu.MenuItem <Menu.MenuItem
key={label}
id={label.replace(/\W/, "_")} id={label.replace(/\W/, "_")}
label={label} label={label}
children={children}
action={children[0].props.action} action={children[0].props.action}
/>); >
{children}
</Menu.MenuItem>
);
} else { } else {
return children; return children;
} }

View file

@ -20,6 +20,7 @@ import { Devs } from "@utils/constants";
import { getCurrentChannel, getCurrentGuild } from "@utils/discord"; import { getCurrentChannel, getCurrentGuild } from "@utils/discord";
import { runtimeHashMessageKey } from "@utils/intlHash"; import { runtimeHashMessageKey } from "@utils/intlHash";
import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy"; import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy";
import { ModalAPI } from "@utils/modal";
import { relaunch } from "@utils/native"; import { relaunch } from "@utils/native";
import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches"; import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches";
import definePlugin, { PluginNative, StartAt } from "@utils/types"; import definePlugin, { PluginNative, StartAt } from "@utils/types";
@ -132,7 +133,10 @@ function makeShortcuts() {
}); });
} }
Common.ReactDOM.render(Common.React.createElement(component, props), doc.body.appendChild(document.createElement("div"))); const root = Common.ReactDOM.createRoot(doc.body.appendChild(document.createElement("div")));
root.render(Common.React.createElement(component, props));
doc.addEventListener("close", () => root.unmount(), { once: true });
}, },
preEnable: (plugin: string) => (Vencord.Settings.plugins[plugin] ??= { enabled: true }).enabled = true, preEnable: (plugin: string) => (Vencord.Settings.plugins[plugin] ??= { enabled: true }).enabled = true,
@ -144,6 +148,8 @@ function makeShortcuts() {
me: { getter: () => Common.UserStore.getCurrentUser(), preload: false }, me: { getter: () => Common.UserStore.getCurrentUser(), preload: false },
meId: { getter: () => Common.UserStore.getCurrentUser().id, preload: false }, meId: { getter: () => Common.UserStore.getCurrentUser().id, preload: false },
messages: { getter: () => Common.MessageStore.getMessages(Common.SelectedChannelStore.getChannelId()), preload: false }, messages: { getter: () => Common.MessageStore.getMessages(Common.SelectedChannelStore.getChannelId()), preload: false },
openModal: { getter: () => ModalAPI.openModal },
openModalLazy: { getter: () => ModalAPI.openModalLazy },
Stores: { Stores: {
getter: () => Object.fromEntries( getter: () => Object.fromEntries(

View file

@ -5,6 +5,7 @@
*/ */
import { React } from "@webpack/common"; import { React } from "@webpack/common";
import { JSX } from "react";
import { cl } from "../"; import { cl } from "../";

View file

@ -7,6 +7,7 @@
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { React } from "@webpack/common"; import { React } from "@webpack/common";
import { JSX } from "react";
import { cl } from "../"; import { cl } from "../";
import Grid, { GridProps } from "./Grid"; import Grid, { GridProps } from "./Grid";

View file

@ -211,7 +211,7 @@ function CloneModal({ data }: { data: Sticker | Emoji; }) {
alignItems: "center" alignItems: "center"
}}> }}>
{guilds.map(g => ( {guilds.map(g => (
<Tooltip text={g.name}> <Tooltip key={g.id} text={g.name}>
{({ onMouseLeave, onMouseEnter }) => ( {({ onMouseLeave, onMouseEnter }) => (
<div <div
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}

View file

@ -514,7 +514,7 @@ export default definePlugin({
return array.filter(item => item != null); return array.filter(item => item != null);
}, },
ensureChildrenIsArray(child: ReactElement) { ensureChildrenIsArray(child: ReactElement<any>) {
if (!Array.isArray(child.props.children)) child.props.children = [child.props.children]; if (!Array.isArray(child.props.children)) child.props.children = [child.props.children];
}, },
@ -524,7 +524,7 @@ export default definePlugin({
let nextIndex = content.length; let nextIndex = content.length;
const transformLinkChild = (child: ReactElement) => { const transformLinkChild = (child: ReactElement<any>) => {
if (settings.store.transformEmojis) { if (settings.store.transformEmojis) {
const fakeNitroMatch = child.props.href.match(fakeNitroEmojiRegex); const fakeNitroMatch = child.props.href.match(fakeNitroEmojiRegex);
if (fakeNitroMatch) { if (fakeNitroMatch) {
@ -558,7 +558,7 @@ export default definePlugin({
return child; return child;
}; };
const transformChild = (child: ReactElement) => { const transformChild = (child: ReactElement<any>) => {
if (child?.props?.trusted != null) return transformLinkChild(child); if (child?.props?.trusted != null) return transformLinkChild(child);
if (child?.props?.children != null) { if (child?.props?.children != null) {
if (!Array.isArray(child.props.children)) { if (!Array.isArray(child.props.children)) {
@ -574,7 +574,7 @@ export default definePlugin({
return child; return child;
}; };
const modifyChild = (child: ReactElement) => { const modifyChild = (child: ReactElement<any>) => {
const newChild = transformChild(child); const newChild = transformChild(child);
if (newChild?.type === "ul" || newChild?.type === "ol") { if (newChild?.type === "ul" || newChild?.type === "ol") {
@ -601,7 +601,7 @@ export default definePlugin({
return newChild; return newChild;
}; };
const modifyChildren = (children: Array<ReactElement>) => { const modifyChildren = (children: Array<ReactElement<any>>) => {
for (const [index, child] of children.entries()) children[index] = modifyChild(child); for (const [index, child] of children.entries()) children[index] = modifyChild(child);
children = this.clearEmptyArrayItems(children); children = this.clearEmptyArrayItems(children);

View file

@ -29,6 +29,7 @@ import definePlugin, { OptionType } from "@utils/types";
import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack"; import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack";
import { Button, Flex, Forms, React, Text, UserProfileStore, UserStore, useState } from "@webpack/common"; import { Button, Flex, Forms, React, Text, UserProfileStore, UserStore, useState } from "@webpack/common";
import { User } from "discord-types/general"; import { User } from "discord-types/general";
import { ReactElement } from "react";
import virtualMerge from "virtual-merge"; import virtualMerge from "virtual-merge";
interface UserProfile extends User { interface UserProfile extends User {
@ -87,7 +88,7 @@ const settings = definePluginSettings({
interface ColorPickerProps { interface ColorPickerProps {
color: number | null; color: number | null;
label: React.ReactElement; label: ReactElement<any>;
showEyeDropper?: boolean; showEyeDropper?: boolean;
suggestedColors?: string[]; suggestedColors?: string[];
onChange(value: number | null): void; onChange(value: number | null): void;

View file

@ -0,0 +1,9 @@
# Full User In Chatbox
Adds the full user mention to the textbox
Adds the avatar if you have mentioned avatars enabled
Provides the full context menu to make it easy to access the users profile, and other common actions
https://github.com/user-attachments/assets/cd9edb33-99c8-4c8d-b669-8cddd05f4b45

View file

@ -0,0 +1,47 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { findComponentByCodeLazy } from "@webpack";
import { ReactNode } from "react";
const UserMentionComponent = findComponentByCodeLazy(".USER_MENTION)");
interface UserMentionComponentProps {
id: string;
channelId: string;
guildId: string;
OriginalComponent: ReactNode;
}
export default definePlugin({
name: "FullUserInChatbox",
description: "Makes the user mention in the chatbox have more functionalities, like left/right clicking",
authors: [Devs.sadan],
patches: [
{
find: ':"text":',
replacement: {
match: /(hidePersonalInformation\).+?)(if\(null!=\i\){.+?return \i)(?=})/,
replace: "$1return $self.UserMentionComponent({...arguments[0],OriginalComponent:(()=>{$2})()});"
}
}
],
UserMentionComponent: ErrorBoundary.wrap((props: UserMentionComponentProps) => (
<UserMentionComponent
// This seems to be constant
className="mention"
userId={props.id}
channelId={props.channelId}
/>
), {
fallback: ({ wrappedProps }) => wrappedProps.OriginalComponent
})
});

View file

@ -24,6 +24,7 @@ import { debounce } from "@shared/debounce";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { Menu, ReactDOM } from "@webpack/common"; import { Menu, ReactDOM } from "@webpack/common";
import { JSX } from "react";
import type { Root } from "react-dom/client"; import type { Root } from "react-dom/client";
import { Magnifier, MagnifierProps } from "./components/Magnifier"; import { Magnifier, MagnifierProps } from "./components/Magnifier";

View file

@ -41,6 +41,7 @@ import {
UserStore UserStore
} from "@webpack/common"; } from "@webpack/common";
import { Channel, Message } from "discord-types/general"; import { Channel, Message } from "discord-types/general";
import { JSX } from "react";
const messageCache = new Map<string, { const messageCache = new Map<string, {
message?: Message; message?: Message;
@ -347,10 +348,10 @@ function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
? parse(message.content) ? parse(message.content)
: [noContent(message.attachments.length, message.embeds.length)] : [noContent(message.attachments.length, message.embeds.length)]
} }
{images.map(a => { {images.map((a, idx) => {
const { width, height } = computeWidthAndHeight(a.width, a.height); const { width, height } = computeWidthAndHeight(a.width, a.height);
return ( return (
<div> <div key={idx}>
<img src={a.url} width={width} height={height} /> <img src={a.url} width={width} height={height} />
</div> </div>
); );

View file

@ -69,6 +69,7 @@ export function HistoryModal({ modalProps, message }: { modalProps: ModalProps;
{timestamps.map((timestamp, index) => ( {timestamps.map((timestamp, index) => (
<TabBar.Item <TabBar.Item
key={index}
className="vc-settings-tab-bar-item" className="vc-settings-tab-bar-item"
id={index} id={index}
> >

View file

@ -169,8 +169,8 @@ export default definePlugin({
return Settings.plugins.MessageLogger.inlineEdits && ( return Settings.plugins.MessageLogger.inlineEdits && (
<> <>
{message.editHistory?.map(edit => ( {message.editHistory?.map((edit, idx) => (
<div className="messagelogger-edited"> <div key={idx} className="messagelogger-edited">
{parseEditContent(edit.content, message)} {parseEditContent(edit.content, message)}
<Timestamp <Timestamp
timestamp={edit.timestamp} timestamp={edit.timestamp}
@ -304,7 +304,7 @@ export default definePlugin({
{...props} {...props}
className={classes("messagelogger-edit-marker", className)} className={classes("messagelogger-edit-marker", className)}
onClick={() => openHistoryModal(message)} onClick={() => openHistoryModal(message)}
aria-role="button" role="button"
> >
{children} {children}
</span> </span>

View file

@ -114,7 +114,7 @@ function SettingsComponent() {
return ( return (
<Flex flexDirection="column"> <Flex flexDirection="column">
{tags.map(t => ( {tags.map(t => (
<Card style={{ padding: "1em 1em 0" }}> <Card key={t.name} style={{ padding: "1em 1em 0" }}>
<Forms.FormTitle style={{ width: "fit-content" }}> <Forms.FormTitle style={{ width: "fit-content" }}>
<Tooltip text={t.description}> <Tooltip text={t.description}>
{({ onMouseEnter, onMouseLeave }) => ( {({ onMouseEnter, onMouseLeave }) => (

View file

@ -24,6 +24,7 @@ import definePlugin from "@utils/types";
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, useMemo, UserStore } from "@webpack/common"; import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, useMemo, UserStore } from "@webpack/common";
import { Channel, User } from "discord-types/general"; import { Channel, User } from "discord-types/general";
import { JSX } from "react";
const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel"); const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel");
const UserUtils = findByPropsLazy("getGlobalName"); const UserUtils = findByPropsLazy("getGlobalName");
@ -55,6 +56,7 @@ function getMutualGDMCountText(user: User) {
function renderClickableGDMs(mutualDms: Channel[], onClose: () => void) { function renderClickableGDMs(mutualDms: Channel[], onClose: () => void) {
return mutualDms.map(c => ( return mutualDms.map(c => (
<Clickable <Clickable
key={c.id}
className={ProfileListClasses.listRow} className={ProfileListClasses.listRow}
onClick={() => { onClick={() => {
onClose(); onClose();

View file

@ -0,0 +1,5 @@
# No Unblock To Jump
Removes the popup preventing you to jump to a message from a blocked/ignored user (eg: in search results)
![A modal popup telling you to unblock a user to jump their message](https://github.com/user-attachments/assets/0e4b859d-f3b3-4101-9a83-829afb473d1e)

View file

@ -26,25 +26,46 @@ export default definePlugin({
authors: [Devs.dzshn], authors: [Devs.dzshn],
patches: [ patches: [
{ {
// Clicking on search results to jump
find: '.id,"Search Results"', find: '.id,"Search Results"',
replacement: { replacement: [
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/, {
replace: "if(false)$1" match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
} replace: "if(false)$1"
},
{
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNIGNORE_TO_JUMP_TITLE})/,
replace: "if(false)$1"
},
]
}, },
{ {
// Jump buttton in top right corner of messages
find: "renderJumpButton()", find: "renderJumpButton()",
replacement: { replacement: [
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/, {
replace: "if(false)$1" match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
} replace: "if(false)$1"
},
{
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNIGNORE_TO_JUMP_TITLE})/,
replace: "if(false)$1"
},
]
}, },
{ {
// Clicking on replied messages to jump
find: "flash:!0,returnMessageId", find: "flash:!0,returnMessageId",
replacement: { replacement: [
match: /.\?(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/, {
replace: "false?$1" match: /.\?(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
} replace: "false?$1"
},
{
match: /.\?(.{1,10}\.show\({.{1,50}#{intl::UNIGNORE_TO_JUMP_TITLE})/,
replace: "false?$1"
},
]
} }
] ]
}); });

View file

@ -57,7 +57,7 @@ const UrlReplacementRules: Record<string, URLReplacementRule> = {
description: "Open Tidal links in the Tidal app", description: "Open Tidal links in the Tidal app",
}, },
itunes: { itunes: {
match: /^https:\/\/music\.apple\.com\/([a-z]{2}\/)?(album|artist|playlist|song|curator)\/([^/?#]+)\/?([^/?#]+)?(?:\?.*)?(?:#.*)?$/, match: /^https:\/\/(?:geo\.)?music\.apple\.com\/([a-z]{2}\/)?(album|artist|playlist|song|curator)\/([^/?#]+)\/?([^/?#]+)?(?:\?.*)?(?:#.*)?$/,
replace: (_, lang, type, name, id) => id ? `itunes://music.apple.com/us/${type}/${name}/${id}` : `itunes://music.apple.com/us/${type}/${name}`, replace: (_, lang, type, name, id) => id ? `itunes://music.apple.com/us/${type}/${name}/${id}` : `itunes://music.apple.com/us/${type}/${name}`,
description: "Open Apple Music links in the iTunes app" description: "Open Apple Music links in the iTunes app"
}, },

View file

@ -113,6 +113,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
return ( return (
<div <div
key={index}
className={cl("modal-list-item-btn")} className={cl("modal-list-item-btn")}
onClick={() => selectItem(index)} onClick={() => selectItem(index)}
role="button" role="button"
@ -178,7 +179,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
<div className={cl("modal-divider")} /> <div className={cl("modal-divider")} />
<ScrollerThin className={cl("modal-perms")} orientation="auto"> <ScrollerThin className={cl("modal-perms")} orientation="auto">
{Object.values(PermissionsBits).map(bit => ( {Object.values(PermissionsBits).map(bit => (
<div className={cl("modal-perms-item")}> <div key={bit} className={cl("modal-perms-item")}>
<div className={cl("modal-perms-item-icon")}> <div className={cl("modal-perms-item-icon")}>
{(() => { {(() => {
const { permissions, overwriteAllow, overwriteDeny } = selectedItem; const { permissions, overwriteAllow, overwriteDeny } = selectedItem;

View file

@ -192,6 +192,7 @@ function UserPermissionsComponent({ guild, guildMember, closePopout }: { guild:
<div className={classes(RoleRootClasses.root)}> <div className={classes(RoleRootClasses.root)}>
{userPermissions.map(({ permission, roleColor, roleName }) => ( {userPermissions.map(({ permission, roleColor, roleName }) => (
<Tooltip <Tooltip
key={permission}
text={<GrantedByTooltip roleName={roleName} roleColor={roleColor} />} text={<GrantedByTooltip roleName={roleName} roleColor={roleColor} />}
tooltipClassName={cl("granted-by-container")} tooltipClassName={cl("granted-by-container")}
tooltipContentClassName={cl("granted-by-content")} tooltipContentClassName={cl("granted-by-content")}

View file

@ -30,13 +30,13 @@ export default definePlugin({
{ {
find: ".removeMosaicItemHoverButton),", find: ".removeMosaicItemHoverButton),",
replacement: { replacement: {
match: /\.nonMediaMosaicItem\]:.{0,40}children:\[(?<=showDownload:(\i).+?isVisualMediaType:(\i).+?)/, match: /(\.nonMediaMosaicItem\]:.{0,40}children:)(\i.slice\(\i\))(?<=showDownload:(\i).+?isVisualMediaType:(\i).+?)/,
replace: "$&$1&&$2&&$self.renderPiPButton()," replace: (_, rest, origChildren, showDownload, isVisualMediaType) => `${rest}[${showDownload}&&${isVisualMediaType}&&$self.PictureInPictureButton(),...${origChildren}]`
} }
} }
], ],
renderPiPButton: ErrorBoundary.wrap(() => { PictureInPictureButton: ErrorBoundary.wrap(() => {
return ( return (
<Tooltip text="Toggle Picture in Picture"> <Tooltip text="Toggle Picture in Picture">
{tooltipProps => ( {tooltipProps => (

View file

@ -33,7 +33,8 @@ function createPinMenuItem(channelId: string) {
{ {
categories.map(category => ( categories.map(category => (
<Menu.MenuItem <Menu.MenuItem
id={`pin-category-${category.name}`} key={category.id}
id={`pin-category-${category.id}`}
label={category.name} label={category.name}
action={() => addChannelToCategory(channelId, category.id).then(forceUpdate)} action={() => addChannelToCategory(channelId, category.id).then(forceUpdate)}
/> />

View file

@ -20,7 +20,7 @@ import { definePluginSettings, Settings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { ChannelStore, FluxDispatcher as Dispatcher, MessageStore, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore } from "@webpack/common"; import { ChannelStore, ComponentDispatch, FluxDispatcher as Dispatcher, MessageStore, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore } from "@webpack/common";
import { Message } from "discord-types/general"; import { Message } from "discord-types/general";
const Kangaroo = findByPropsLazy("jumpToMessage"); const Kangaroo = findByPropsLazy("jumpToMessage");
@ -60,24 +60,24 @@ export default definePlugin({
settings, settings,
start() { start() {
Dispatcher.subscribe("DELETE_PENDING_REPLY", onDeletePendingReply);
Dispatcher.subscribe("MESSAGE_END_EDIT", onEndEdit);
Dispatcher.subscribe("MESSAGE_START_EDIT", onStartEdit);
Dispatcher.subscribe("CREATE_PENDING_REPLY", onCreatePendingReply);
document.addEventListener("keydown", onKeydown); document.addEventListener("keydown", onKeydown);
}, },
stop() { stop() {
Dispatcher.unsubscribe("DELETE_PENDING_REPLY", onDeletePendingReply);
Dispatcher.unsubscribe("MESSAGE_END_EDIT", onEndEdit);
Dispatcher.unsubscribe("MESSAGE_START_EDIT", onStartEdit);
Dispatcher.unsubscribe("CREATE_PENDING_REPLY", onCreatePendingReply);
document.removeEventListener("keydown", onKeydown); document.removeEventListener("keydown", onKeydown);
}, },
});
const onDeletePendingReply = () => replyIdx = -1; flux: {
const onEndEdit = () => editIdx = -1; DELETE_PENDING_REPLY() {
replyIdx = -1;
},
MESSAGE_END_EDIT() {
editIdx = -1;
},
MESSAGE_START_EDIT: onStartEdit,
CREATE_PENDING_REPLY: onCreatePendingReply
}
});
function calculateIdx(messages: Message[], id: string) { function calculateIdx(messages: Message[], id: string) {
const idx = messages.findIndex(m => m.id === id); const idx = messages.findIndex(m => m.id === id);
@ -109,6 +109,8 @@ function onKeydown(e: KeyboardEvent) {
if (!isUp && e.key !== "ArrowDown") return; if (!isUp && e.key !== "ArrowDown") return;
if (!isCtrl(e) || isAltOrMeta(e)) return; if (!isCtrl(e) || isAltOrMeta(e)) return;
e.preventDefault();
if (e.shiftKey) if (e.shiftKey)
nextEdit(isUp); nextEdit(isUp);
else else
@ -194,9 +196,10 @@ function nextReply(isUp: boolean) {
channel, channel,
message, message,
shouldMention: shouldMention(message), shouldMention: shouldMention(message),
showMentionToggle: channel.guild_id !== null && message.author.id !== meId, showMentionToggle: channel.isPrivate() && message.author.id !== meId,
_isQuickReply: true _isQuickReply: true
}); });
ComponentDispatch.dispatchToLastSubscribed("TEXTAREA_FOCUS");
jumpIfOffScreen(channel.id, message.id); jumpIfOffScreen(channel.id, message.id);
} }

View file

@ -159,7 +159,7 @@ export default LazyComponent(() => {
onClick={() => openBlockModal()} onClick={() => openBlockModal()}
/> />
)} )}
{review.sender.badges.map(badge => <ReviewBadge {...badge} />)} {review.sender.badges.map((badge, idx) => <ReviewBadge key={idx} {...badge} />)}
{ {
!settings.store.hideTimestamps && review.type !== ReviewType.System && ( !settings.store.hideTimestamps && review.type !== ReviewType.System && (
@ -170,7 +170,13 @@ export default LazyComponent(() => {
<div className={cl("review-comment")}> <div className={cl("review-comment")}>
{(review.comment.length > 200 && !showAll) {(review.comment.length > 200 && !showAll)
? [Parser.parseGuildEventDescription(review.comment.substring(0, 200)), "...", <br />, (<a onClick={() => setShowAll(true)}>Read more</a>)] ? (
<>
{Parser.parseGuildEventDescription(review.comment.substring(0, 200))}...
<br />
<a onClick={() => setShowAll(true)}>Read more</a>]
</>
)
: Parser.parseGuildEventDescription(review.comment)} : Parser.parseGuildEventDescription(review.comment)}
</div> </div>

View file

@ -147,7 +147,7 @@ const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
viewBox="0 0 24 24" viewBox="0 0 24 24"
style={{ scale: "1.2" }} style={{ scale: "1.2" }}
> >
<g fill="none" fill-rule="evenodd"> <g fill="none" fillRule="evenodd">
<path fill="currentColor" d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7v-5z" /> <path fill="currentColor" d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7v-5z" />
<rect width="24" height="24" /> <rect width="24" height="24" />
</g> </g>

View file

@ -247,6 +247,7 @@ function UserList(type: "friends" | "blocked", guild: Guild, ids: string[], setC
<ScrollerThin fade className={cl("scroller")}> <ScrollerThin fade className={cl("scroller")}>
{members.map(id => {members.map(id =>
<FriendRow <FriendRow
key={id}
user={UserStore.getUser(id)} user={UserStore.getUser(id)}
status={PresenceStore.getStatus(id) || "offline"} status={PresenceStore.getStatus(id) || "offline"}
onSelect={() => openUserProfile(id)} onSelect={() => openUserProfile(id)}

View file

@ -17,6 +17,7 @@
*/ */
import { Clipboard } from "@webpack/common"; import { Clipboard } from "@webpack/common";
import { JSX } from "react";
import { cl } from "../utils/misc"; import { cl } from "../utils/misc";
import { CopyButton } from "./CopyButton"; import { CopyButton } from "./CopyButton";

View file

@ -18,6 +18,7 @@
import type { IThemedToken } from "@vap/shiki"; import type { IThemedToken } from "@vap/shiki";
import { hljs } from "@webpack/common"; import { hljs } from "@webpack/common";
import { JSX } from "react";
import { cl } from "../utils/misc"; import { cl } from "../utils/misc";
import { ThemeBase } from "./Highlighter"; import { ThemeBase } from "./Highlighter";
@ -41,12 +42,12 @@ export const Code = ({
if (useHljs) { if (useHljs) {
try { try {
const { value: hljsHtml } = hljs.highlight(lang!, content, true); const { value: hljsHtml } = hljs.highlight(content, { language: lang!, ignoreIllegals: true });
lines = hljsHtml lines = hljsHtml
.split("\n") .split("\n")
.map((line, i) => <span key={i} dangerouslySetInnerHTML={{ __html: line }} />); .map((line, i) => <span key={i} dangerouslySetInnerHTML={{ __html: line }} />);
} catch { } catch {
lines = content.split("\n").map(line => <span>{line}</span>); lines = content.split("\n").map((line, idx) => <span key={idx}>{line}</span>);
} }
} else { } else {
const renderTokens = const renderTokens =
@ -55,11 +56,11 @@ export const Code = ({
.split("\n") .split("\n")
.map(line => [{ color: theme.plainColor, content: line } as IThemedToken]); .map(line => [{ color: theme.plainColor, content: line } as IThemedToken]);
lines = renderTokens.map(line => { lines = renderTokens.map((line, idx) => {
// [Cynthia] this makes it so when you highlight the codeblock // [Cynthia] this makes it so when you highlight the codeblock
// empty lines are also selected and copied when you Ctrl+C. // empty lines are also selected and copied when you Ctrl+C.
if (line.length === 0) { if (line.length === 0) {
return <span>{"\n"}</span>; return <span key={idx}>{"\n"}</span>;
} }
return ( return (

View file

@ -97,7 +97,7 @@ function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) {
gap: getSpacingPx(settings.store.iconSpacing), gap: getSpacingPx(settings.store.iconSpacing),
flexWrap: "wrap" flexWrap: "wrap"
}}> }}>
{connections.map(connection => <CompactConnectionComponent connection={connection} theme={theme} />)} {connections.map(connection => <CompactConnectionComponent connection={connection} theme={theme} key={connection.id} />)}
</Flex> </Flex>
); );
} }
@ -137,6 +137,7 @@ function CompactConnectionComponent({ connection, theme }: { connection: Connect
className="vc-user-connection" className="vc-user-connection"
href={url} href={url}
target="_blank" target="_blank"
rel="noreferrer"
onClick={e => { onClick={e => {
if (Vencord.Plugins.isPluginEnabled("OpenInApp")) { if (Vencord.Plugins.isPluginEnabled("OpenInApp")) {
const OpenInApp = Vencord.Plugins.plugins.OpenInApp as any as typeof import("../openInApp").default; const OpenInApp = Vencord.Plugins.plugins.OpenInApp as any as typeof import("../openInApp").default;

View file

@ -268,7 +268,7 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
<div className="shc-lock-screen-tags-container"> <div className="shc-lock-screen-tags-container">
<Text variant="text-lg/bold">Available tags:</Text> <Text variant="text-lg/bold">Available tags:</Text>
<div className="shc-lock-screen-tags"> <div className="shc-lock-screen-tags">
{availableTags.map(tag => <TagComponent tag={tag} />)} {availableTags.map(tag => <TagComponent tag={tag} key={tag.id} />)}
</div> </div>
</div> </div>
} }

View file

@ -84,13 +84,12 @@ export default definePlugin({
], ],
TooltipWrapper: ErrorBoundary.wrap(({ message, children, text }: { message: Message; children: FunctionComponent<any>; text: ReactNode; }) => { TooltipWrapper: ErrorBoundary.wrap(({ message, children, text }: { message: Message; children: FunctionComponent<any>; text: ReactNode; }) => {
if (settings.store.displayStyle === DisplayStyle.Tooltip) return <Tooltip if (settings.store.displayStyle === DisplayStyle.Tooltip)
children={children} return <Tooltip text={renderTimeout(message, false)}>{children}</Tooltip>;
text={renderTimeout(message, false)}
/>;
return ( return (
<div className="vc-std-wrapper"> <div className="vc-std-wrapper">
<Tooltip text={text} children={children} /> <Tooltip text={text}>{children}</Tooltip>
<Text variant="text-md/normal" color="status-danger"> <Text variant="text-md/normal" color="status-danger">
{renderTimeout(message, true)} timeout remaining {renderTimeout(message, true)} timeout remaining
</Text> </Text>

View file

@ -78,7 +78,7 @@ const SilentMessageToggle: ChatBarButton = ({ isMainChat }) => {
{!enabled && <> {!enabled && <>
<mask id="vc-silent-msg-mask"> <mask id="vc-silent-msg-mask">
<path fill="#fff" d="M0 0h24v24H0Z" /> <path fill="#fff" d="M0 0h24v24H0Z" />
<path stroke="#000" stroke-width="5.99068" d="M0 24 24 0" /> <path stroke="#000" strokeWidth="5.99068" d="M0 24 24 0" />
</mask> </mask>
<path fill="var(--status-danger)" d="m21.178 1.70703 1.414 1.414L4.12103 21.593l-1.414-1.415L21.178 1.70703Z" /> <path fill="var(--status-danger)" d="m21.178 1.70703 1.414 1.414L4.12103 21.593l-1.414-1.415L21.178 1.70703Z" />
</>} </>}

View file

@ -18,6 +18,7 @@
import "./spotifyStyles.css"; import "./spotifyStyles.css";
import { Settings } from "@api/Settings";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons"; import { ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons";
import { debounce } from "@shared/debounce"; import { debounce } from "@shared/debounce";
@ -130,7 +131,9 @@ function Controls() {
> >
<Shuffle /> <Shuffle />
</Button> </Button>
<Button onClick={() => SpotifyStore.prev()}> <Button onClick={() => {
Settings.plugins.SpotifyControls.previousButtonRestartsTrack && SpotifyStore.position > 3000 ? SpotifyStore.seek(0) : SpotifyStore.prev();
}}>
<SkipPrev /> <SkipPrev />
</Button> </Button>
<Button onClick={() => SpotifyStore.setPlaying(!isPlaying)}> <Button onClick={() => SpotifyStore.setPlaying(!isPlaying)}>

View file

@ -44,6 +44,11 @@ export default definePlugin({
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Open Spotify URIs instead of Spotify URLs. Will only work if you have Spotify installed and might not work on all platforms", description: "Open Spotify URIs instead of Spotify URLs. Will only work if you have Spotify installed and might not work on all platforms",
default: false default: false
},
previousButtonRestartsTrack: {
type: OptionType.BOOLEAN,
description: "Restart currently playing track when pressing the previous button if playtime is >3s",
default: true
} }
}, },
patches: [ patches: [

View file

@ -120,8 +120,8 @@ function ServerTrace({ trace }: ServerTraceProps) {
<Forms.FormSection title="Server Trace" tag="h2"> <Forms.FormSection title="Server Trace" tag="h2">
<code> <code>
<Flex flexDirection="column" style={{ color: "var(--header-primary)", gap: 5, userSelect: "text" }}> <Flex flexDirection="column" style={{ color: "var(--header-primary)", gap: 5, userSelect: "text" }}>
{lines.map(line => ( {lines.map((line, idx) => (
<span>{line}</span> <span key={idx}>{line}</span>
))} ))}
</Flex> </Flex>
</code> </code>

View file

@ -23,6 +23,7 @@ import { openUserProfile } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { Avatar, GuildMemberStore, React, RelationshipStore } from "@webpack/common"; import { Avatar, GuildMemberStore, React, RelationshipStore } from "@webpack/common";
import { User } from "discord-types/general"; import { User } from "discord-types/general";
import { PropsWithChildren } from "react";
const settings = definePluginSettings({ const settings = definePluginSettings({
showAvatars: { showAvatars: {
@ -100,7 +101,7 @@ export default definePlugin({
{ {
// Style the indicator and add function call to modify the children before rendering // Style the indicator and add function call to modify the children before rendering
match: /(?<=children:\[(\i)\.length>0.{0,200}?"aria-atomic":!0,children:)\i(?<=guildId:(\i).+?)/, match: /(?<=children:\[(\i)\.length>0.{0,200}?"aria-atomic":!0,children:)\i(?<=guildId:(\i).+?)/,
replace: "$self.mutateChildren($2,$1,$&),style:$self.TYPING_TEXT_STYLE" replace: "$self.renderTypingUsers({ users: $1, guildId: $2, children: $& }),style:$self.TYPING_TEXT_STYLE"
}, },
{ {
// Changes the indicator to keep the user object when creating the list of typing users // Changes the indicator to keep the user object when creating the list of typing users
@ -125,7 +126,7 @@ export default definePlugin({
buildSeveralUsers, buildSeveralUsers,
mutateChildren(guildId: any, users: User[], children: any) { renderTypingUsers: ErrorBoundary.wrap(({ guildId, users, children }: PropsWithChildren<{ guildId: string, users: User[]; }>) => {
try { try {
if (!Array.isArray(children)) { if (!Array.isArray(children)) {
return children; return children;
@ -133,15 +134,17 @@ export default definePlugin({
let element = 0; let element = 0;
return children.map(c => return children.map(c => {
c.type === "strong" || (typeof c !== "string" && !React.isValidElement(c)) if (c.type !== "strong" && !(typeof c !== "string" && !React.isValidElement(c)))
? <TypingUser guildId={guildId} user={users[element++]} /> return c;
: c
); const user = users[element++];
return <TypingUser key={user.id} guildId={guildId} user={user} />;
});
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
return children; return children;
} }, { noop: true })
}); });

View file

@ -25,6 +25,7 @@ import { wordsToTitle } from "@utils/text";
import definePlugin, { OptionType, PluginOptionsItem, ReporterTestable } from "@utils/types"; import definePlugin, { OptionType, PluginOptionsItem, ReporterTestable } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { Button, ChannelStore, Forms, GuildMemberStore, SelectedChannelStore, SelectedGuildStore, useMemo, UserStore } from "@webpack/common"; import { Button, ChannelStore, Forms, GuildMemberStore, SelectedChannelStore, SelectedGuildStore, useMemo, UserStore } from "@webpack/common";
import { ReactElement } from "react";
interface VoiceState { interface VoiceState {
userId: string; userId: string;
@ -289,7 +290,7 @@ export default definePlugin({
description: "Undeafen Message (only self for now)", description: "Undeafen Message (only self for now)",
default: "{{USER}} undeafened" default: "{{USER}} undeafened"
} }
}; } satisfies Record<string, PluginOptionsItem>;
}, },
settingsAboutComponent({ tempSettings: s }) { settingsAboutComponent({ tempSettings: s }) {
@ -303,7 +304,7 @@ export default definePlugin({
[], [],
); );
let errorComponent: React.ReactElement | null = null; let errorComponent: ReactElement<any> | null = null;
if (!hasVoices) { if (!hasVoices) {
let error = "No narrator voices found. "; let error = "No narrator voices found. ";
error += navigator.platform?.toLowerCase().includes("linux") error += navigator.platform?.toLowerCase().includes("linux")

View file

@ -23,7 +23,7 @@ import { Queue } from "@utils/Queue";
import { useForceUpdater } from "@utils/react"; import { useForceUpdater } from "@utils/react";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, Tooltip } from "@webpack/common"; import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, Tooltip, useEffect, useLayoutEffect } from "@webpack/common";
import { CustomEmoji } from "@webpack/types"; import { CustomEmoji } from "@webpack/types";
import { Message, ReactionEmoji, User } from "discord-types/general"; import { Message, ReactionEmoji, User } from "discord-types/general";
@ -134,18 +134,21 @@ export default definePlugin({
renderUsers(props: RootObject) { renderUsers(props: RootObject) {
return props.message.reactions.length > 10 ? null : ( return props.message.reactions.length > 10 ? null : (
<ErrorBoundary noop> <ErrorBoundary noop>
<this._renderUsers {...props} /> <this.UsersComponent {...props} />
</ErrorBoundary> </ErrorBoundary>
); );
}, },
_renderUsers({ message, emoji, type }: RootObject) {
UsersComponent({ message, emoji, type }: RootObject) {
const forceUpdate = useForceUpdater(); const forceUpdate = useForceUpdater();
React.useLayoutEffect(() => { // bc need to prevent autoscrolling
useLayoutEffect(() => { // bc need to prevent autoscrolling
if (Scroll?.scrollCounter > 0) { if (Scroll?.scrollCounter > 0) {
Scroll.setAutomaticAnchor(null); Scroll.setAutomaticAnchor(null);
} }
}); });
React.useEffect(() => {
useEffect(() => {
const cb = (e: any) => { const cb = (e: any) => {
if (e.messageId === message.id) if (e.messageId === message.id)
forceUpdate(); forceUpdate();
@ -153,7 +156,7 @@ export default definePlugin({
FluxDispatcher.subscribe("MESSAGE_REACTION_ADD_USERS", cb); FluxDispatcher.subscribe("MESSAGE_REACTION_ADD_USERS", cb);
return () => FluxDispatcher.unsubscribe("MESSAGE_REACTION_ADD_USERS", cb); return () => FluxDispatcher.unsubscribe("MESSAGE_REACTION_ADD_USERS", cb);
}, [message.id]); }, [message.id, forceUpdate]);
const reactions = getReactionsWithQueue(message, emoji, type); const reactions = getReactionsWithQueue(message, emoji, type);
const users = Object.values(reactions).filter(Boolean) as User[]; const users = Object.values(reactions).filter(Boolean) as User[];

View file

@ -47,7 +47,7 @@ export interface ModalOptions {
onCloseCallback?: (() => void); onCloseCallback?: (() => void);
} }
type RenderFunction = (props: ModalProps) => ReactNode; type RenderFunction = (props: ModalProps) => ReactNode | Promise<ReactNode>;
export const Modals = findByPropsLazy("ModalRoot", "ModalCloseButton") as { export const Modals = findByPropsLazy("ModalRoot", "ModalCloseButton") as {
ModalRoot: ComponentType<PropsWithChildren<{ ModalRoot: ComponentType<PropsWithChildren<{
@ -141,35 +141,32 @@ export const ModalContent = LazyComponent(() => Modals.ModalContent);
export const ModalFooter = LazyComponent(() => Modals.ModalFooter); export const ModalFooter = LazyComponent(() => Modals.ModalFooter);
export const ModalCloseButton = LazyComponent(() => Modals.ModalCloseButton); export const ModalCloseButton = LazyComponent(() => Modals.ModalCloseButton);
const ModalAPI = findByPropsLazy("openModalLazy"); export const ModalAPI = findByPropsLazy("openModalLazy");
/** /**
* Wait for the render promise to resolve, then open a modal with it. * Wait for the render promise to resolve, then open a modal with it.
* This is equivalent to render().then(openModal) * This is equivalent to render().then(openModal)
* You should use the Modal components exported by this file * You should use the Modal components exported by this file
*/ */
export function openModalLazy(render: () => Promise<RenderFunction>, options?: ModalOptions & { contextKey?: string; }): Promise<string> { export const openModalLazy: (render: () => Promise<RenderFunction>, options?: ModalOptions & { contextKey?: string; }) => Promise<string>
return ModalAPI.openModalLazy(render, options); = proxyLazyWebpack(() => ModalAPI.openModalLazy);
}
/** /**
* Open a Modal with the given render function. * Open a Modal with the given render function.
* You should use the Modal components exported by this file * You should use the Modal components exported by this file
*/ */
export function openModal(render: RenderFunction, options?: ModalOptions, contextKey?: string): string { export const openModal: (render: RenderFunction, options?: ModalOptions, contextKey?: string) => string
return ModalAPI.openModal(render, options, contextKey); = proxyLazyWebpack(() => ModalAPI.openModal);
}
/** /**
* Close a modal by its key * Close a modal by its key
*/ */
export function closeModal(modalKey: string, contextKey?: string): void { export const closeModal: (modalKey: string, contextKey?: string) => void
return ModalAPI.closeModal(modalKey, contextKey); = proxyLazyWebpack(() => ModalAPI.closeModal);
}
/** /**
* Close all open modals * Close all open modals
*/ */
export function closeAllModals(): void { export const closeAllModals: () => void
return ModalAPI.closeAllModals(); = proxyLazyWebpack(() => ModalAPI.closeAllModals);
}

View file

@ -17,6 +17,7 @@
*/ */
import { React, useEffect, useMemo, useReducer, useState } from "@webpack/common"; import { React, useEffect, useMemo, useReducer, useState } from "@webpack/common";
import { ActionDispatch } from "react";
import { checkIntersecting } from "./misc"; import { checkIntersecting } from "./misc";
@ -117,8 +118,8 @@ export function useAwaiter<T>(factory: () => Promise<T>, providedOpts?: AwaiterO
/** /**
* Returns a function that can be used to force rerender react components * Returns a function that can be used to force rerender react components
*/ */
export function useForceUpdater(): () => void; export function useForceUpdater(): ActionDispatch<[]>;
export function useForceUpdater(withDep: true): [unknown, () => void]; export function useForceUpdater(withDep: true): [any, ActionDispatch<[]>];
export function useForceUpdater(withDep?: true) { export function useForceUpdater(withDep?: true) {
const r = useReducer(x => x + 1, 0); const r = useReducer(x => x + 1, 0);
return withDep ? r : r[1]; return withDep ? r : r[1];

View file

@ -19,6 +19,7 @@
import { Command } from "@api/Commands"; import { Command } from "@api/Commands";
import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { FluxEvents } from "@webpack/types"; import { FluxEvents } from "@webpack/types";
import { JSX } from "react";
import { Promisable } from "type-fest"; import { Promisable } from "type-fest";
// exists to export default definePlugin({...}) // exists to export default definePlugin({...})

View file

@ -48,7 +48,7 @@ export const Constants: t.Constants = mapMangledModuleLazy('ME:"/users/@me"', {
export const RestAPI: t.RestAPI = findLazy(m => typeof m === "object" && m.del && m.put); export const RestAPI: t.RestAPI = findLazy(m => typeof m === "object" && m.del && m.put);
export const moment: typeof import("moment") = findByPropsLazy("parseTwoDigitYear"); export const moment: typeof import("moment") = findByPropsLazy("parseTwoDigitYear");
export const hljs: typeof import("highlight.js") = findByPropsLazy("highlight", "registerLanguage"); export const hljs: typeof import("highlight.js").default = findByPropsLazy("highlight", "registerLanguage");
export const { match, P }: Pick<typeof import("ts-pattern"), "match" | "P"> = mapMangledModuleLazy("@ts-pattern/matcher", { export const { match, P }: Pick<typeof import("ts-pattern"), "match" | "P"> = mapMangledModuleLazy("@ts-pattern/matcher", {
match: filters.byCode("return new"), match: filters.byCode("return new"),