mirror of
https://github.com/Equicord/Equicord.git
synced 2025-06-07 13:43:03 -04:00
Merge branch 'dev'
This commit is contained in:
commit
aa1a938860
38 changed files with 1440 additions and 1396 deletions
|
@ -122,7 +122,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
||||||
- QuestCompleter by Amia
|
- QuestCompleter by Amia
|
||||||
- QuestionMarkReplacement by nyx
|
- QuestionMarkReplacement by nyx
|
||||||
- Quoter by Samwich
|
- Quoter by Samwich
|
||||||
- RandomVoice by omaw
|
- RandomVoice by xijexo & omaw
|
||||||
- Remix by MrDiamond
|
- Remix by MrDiamond
|
||||||
- RemixMe by kvba
|
- RemixMe by kvba
|
||||||
- RepeatMessage by Tolgchu
|
- RepeatMessage by Tolgchu
|
||||||
|
|
32
package.json
32
package.json
|
@ -26,8 +26,8 @@
|
||||||
"generatePluginJson": "tsx scripts/generatePluginList.ts",
|
"generatePluginJson": "tsx scripts/generatePluginList.ts",
|
||||||
"generateEquicordPluginJson": "tsx scripts/generateEquicordPluginList.ts",
|
"generateEquicordPluginJson": "tsx scripts/generateEquicordPluginList.ts",
|
||||||
"generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types --allowJs false",
|
"generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types --allowJs false",
|
||||||
"inject": "node scripts/runInstaller.mjs",
|
"inject": "node scripts/runInstaller.mjs -- --install",
|
||||||
"uninject": "node scripts/runInstaller.mjs",
|
"uninject": "node scripts/runInstaller.mjs -- --uninstall",
|
||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
|
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
|
||||||
"lint:fix": "pnpm lint --fix",
|
"lint:fix": "pnpm lint --fix",
|
||||||
|
@ -50,26 +50,26 @@
|
||||||
"jsqr": "1.4.0",
|
"jsqr": "1.4.0",
|
||||||
"idb": "8.0.0",
|
"idb": "8.0.0",
|
||||||
"monaco-editor": "^0.52.2",
|
"monaco-editor": "^0.52.2",
|
||||||
"nanoid": "^5.0.9",
|
"nanoid": "^5.1.5",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
"usercss-meta": "^0.12.0",
|
"usercss-meta": "^0.12.0",
|
||||||
"openai": "^4.30.0",
|
"openai": "^4.30.0",
|
||||||
"virtual-merge": "^1.0.1"
|
"virtual-merge": "^1.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@stylistic/eslint-plugin": "^4.0.0",
|
"@stylistic/eslint-plugin": "^4.2.0",
|
||||||
"@electron/asar": "^3.2.10",
|
"@electron/asar": "^3.2.10",
|
||||||
"@types/chrome": "^0.0.304",
|
"@types/chrome": "^0.0.312",
|
||||||
"@types/diff": "^7.0.1",
|
"@types/diff": "^7.0.2",
|
||||||
"@types/lodash": "^4.17.14",
|
"@types/lodash": "^4.17.14",
|
||||||
"@types/node": "^22.10.5",
|
"@types/node": "^22.13.13",
|
||||||
"@types/react": "^19.0.10",
|
"@types/react": "^19.0.10",
|
||||||
"@types/react-dom": "^19.0.4",
|
"@types/react-dom": "^19.0.4",
|
||||||
"@types/yazl": "^2.4.5",
|
"@types/yazl": "^2.4.5",
|
||||||
"diff": "^7.0.0",
|
"diff": "^7.0.0",
|
||||||
"discord-types": "^1.3.26",
|
"discord-types": "^1.3.26",
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.1",
|
||||||
"eslint": "^9.20.1",
|
"eslint": "9.20.1",
|
||||||
"eslint-import-resolver-alias": "^1.1.2",
|
"eslint-import-resolver-alias": "^1.1.2",
|
||||||
"eslint-plugin-react": "^7.37.3",
|
"eslint-plugin-react": "^7.37.3",
|
||||||
"eslint-plugin-simple-header": "^1.2.1",
|
"eslint-plugin-simple-header": "^1.2.1",
|
||||||
|
@ -78,18 +78,18 @@
|
||||||
"highlight.js": "11.11.1",
|
"highlight.js": "11.11.1",
|
||||||
"html-minifier-terser": "^7.2.0",
|
"html-minifier-terser": "^7.2.0",
|
||||||
"moment": "^2.22.2",
|
"moment": "^2.22.2",
|
||||||
"puppeteer-core": "^24.2.1",
|
"puppeteer-core": "^24.4.0",
|
||||||
"standalone-electron-types": "^34.2.0",
|
"standalone-electron-types": "^34.2.0",
|
||||||
"stylelint": "^16.12.0",
|
"stylelint": "^16.17.0",
|
||||||
"stylelint-config-standard": "^37.0.0",
|
"stylelint-config-standard": "^37.0.0",
|
||||||
"ts-patch": "^3.3.0",
|
"ts-patch": "^3.3.0",
|
||||||
"ts-pattern": "^5.6.0",
|
"ts-pattern": "^5.6.0",
|
||||||
"tsx": "^4.19.2",
|
"tsx": "^4.19.3",
|
||||||
"type-fest": "^4.31.0",
|
"type-fest": "^4.38.0",
|
||||||
"typed-emitter": "^2.1.0",
|
"typed-emitter": "^2.1.0",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.8.2",
|
||||||
"typescript-eslint": "^8.19.0",
|
"typescript-eslint": "^8.28.0",
|
||||||
"typescript-transform-paths": "^3.5.3",
|
"typescript-transform-paths": "^3.5.5",
|
||||||
"zip-local": "^0.3.5",
|
"zip-local": "^0.3.5",
|
||||||
"zustand": "^3.7.2"
|
"zustand": "^3.7.2"
|
||||||
},
|
},
|
||||||
|
|
1261
pnpm-lock.yaml
generated
1261
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -118,8 +118,11 @@ const installerBin = await ensureBinary();
|
||||||
|
|
||||||
console.log("Now running Installer...");
|
console.log("Now running Installer...");
|
||||||
|
|
||||||
|
const argStart = process.argv.indexOf("--");
|
||||||
|
const args = argStart === -1 ? [] : process.argv.slice(argStart + 1);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
execFileSync(installerBin, {
|
execFileSync(installerBin, args, {
|
||||||
stdio: "inherit",
|
stdio: "inherit",
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
|
|
|
@ -51,6 +51,7 @@ export function SettingTextComponent({ option, pluginSettings, definedSettings,
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder={option.placeholder ?? "Enter a value"}
|
placeholder={option.placeholder ?? "Enter a value"}
|
||||||
disabled={option.disabled?.call(definedSettings) ?? false}
|
disabled={option.disabled?.call(definedSettings) ?? false}
|
||||||
|
maxLength={null}
|
||||||
{...option.componentProps}
|
{...option.componentProps}
|
||||||
/>
|
/>
|
||||||
{error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>}
|
{error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>}
|
||||||
|
|
|
@ -128,8 +128,8 @@ function EquicordSettings() {
|
||||||
isEquicordDonor(user?.id) && isVencordDonor(user?.id)
|
isEquicordDonor(user?.id) && isVencordDonor(user?.id)
|
||||||
? "All Vencord users can see your Vencord donor badge, and Equicord users can see your Equicord donor badge. To change your Vencord donor badge, contact @vending.machine. For your Equicord donor badge, make a ticket in Equicord's server."
|
? "All Vencord users can see your Vencord donor badge, and Equicord users can see your Equicord donor badge. To change your Vencord donor badge, contact @vending.machine. For your Equicord donor badge, make a ticket in Equicord's server."
|
||||||
: isVencordDonor(user?.id)
|
: isVencordDonor(user?.id)
|
||||||
? "All Vencord users can see your badge! You can change it at any time by messaging @vending.machine."
|
? "All Vencord users can see your badge! You can manage your perks by messaging @vending.machine."
|
||||||
: "All Equicord users can see your badge! You can change it at any time by making a ticket in Equicord's server."
|
: "All Equicord users can see your badge! You can manage your perks by making a ticket in Equicord's server."
|
||||||
}
|
}
|
||||||
cardImage={VENNIE_DONATOR_IMAGE}
|
cardImage={VENNIE_DONATOR_IMAGE}
|
||||||
backgroundImage={DONOR_BACKGROUND_IMAGE}
|
backgroundImage={DONOR_BACKGROUND_IMAGE}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.visual-refresh .vc-addon-card {
|
.visual-refresh .vc-addon-card {
|
||||||
background-color: var(--button-secondary-background);
|
background-color: var(--card-primary-bg);
|
||||||
border: 1px solid var(--border-subtle);
|
border: 1px solid var(--border-subtle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.visual-refresh .vc-addon-card:hover {
|
.visual-refresh .vc-addon-card:hover {
|
||||||
background-color: var(--button-secondary-background-hover);
|
/* same as non-hover, here to overwrite the non-refresh hover background */
|
||||||
|
background-color: var(--card-primary-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-addon-header {
|
.vc-addon-header {
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2025 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { UserStore } from "@webpack/common";
|
||||||
|
|
||||||
|
import { ActivityTooltipProps } from "../types";
|
||||||
|
import { ActivityView, cl } from "../utils";
|
||||||
|
|
||||||
|
export function ActivityTooltip({ activity, application, user }: Readonly<ActivityTooltipProps>) {
|
||||||
|
const currentUser = UserStore.getCurrentUser();
|
||||||
|
if (!currentUser) return null;
|
||||||
|
return (
|
||||||
|
<ErrorBoundary>
|
||||||
|
<div className={cl("activity-tooltip")}>
|
||||||
|
<ActivityView
|
||||||
|
activity={activity}
|
||||||
|
user={user}
|
||||||
|
application={application}
|
||||||
|
currentUser={currentUser}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2025 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { React, Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
|
import { CarouselControlsProps } from "../types";
|
||||||
|
import { cl } from "../utils";
|
||||||
|
import { Caret } from "./Caret";
|
||||||
|
|
||||||
|
export function CarouselControls({ activities, currentActivity, onActivityChange }: CarouselControlsProps) {
|
||||||
|
const currentIndex = activities.indexOf(currentActivity);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cl("controls")}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip text="Left" tooltipClassName={cl("controls-tooltip")}>{({
|
||||||
|
onMouseEnter,
|
||||||
|
onMouseLeave
|
||||||
|
}) => {
|
||||||
|
return <span
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
onClick={() => {
|
||||||
|
if (currentIndex - 1 >= 0) {
|
||||||
|
onActivityChange(activities[currentIndex - 1]);
|
||||||
|
} else {
|
||||||
|
onActivityChange(activities[activities.length - 1]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Caret
|
||||||
|
disabled={currentIndex < 1}
|
||||||
|
direction="left" />
|
||||||
|
</span>;
|
||||||
|
}}</Tooltip>
|
||||||
|
|
||||||
|
<div className="carousel">
|
||||||
|
{activities.map((activity, index) => (
|
||||||
|
<div
|
||||||
|
key={"dot--" + index}
|
||||||
|
onClick={() => onActivityChange(activity)}
|
||||||
|
className={`dot ${currentActivity === activity ? "selected" : ""}`} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Tooltip text="Right" tooltipClassName={cl("controls-tooltip")}>{({
|
||||||
|
onMouseEnter,
|
||||||
|
onMouseLeave
|
||||||
|
}) => {
|
||||||
|
return <span
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
onClick={() => {
|
||||||
|
if (currentIndex + 1 < activities.length) {
|
||||||
|
onActivityChange(activities[currentIndex + 1]);
|
||||||
|
} else {
|
||||||
|
onActivityChange(activities[0]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Caret
|
||||||
|
disabled={currentIndex >= activities.length - 1}
|
||||||
|
direction="right" />
|
||||||
|
</span>;
|
||||||
|
}}</Tooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -6,467 +6,31 @@
|
||||||
|
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
|
import { migratePluginSettings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
|
||||||
import { PresenceStore, React, Tooltip, useEffect, useMemo, UserStore, useState, useStateFromStores } from "@webpack/common";
|
|
||||||
import { User } from "discord-types/general";
|
|
||||||
import { JSX } from "react";
|
|
||||||
|
|
||||||
import { Caret } from "./components/Caret";
|
import { patchActivityList } from "./patch-helpers/activityList";
|
||||||
import { SpotifyIcon } from "./components/SpotifyIcon";
|
import { showAllActivitiesComponent } from "./patch-helpers/popout";
|
||||||
import { TwitchIcon } from "./components/TwitchIcon";
|
import { settings } from "./settings";
|
||||||
import { Activity, ActivityListIcon, Application, ApplicationIcon, IconCSSProperties } from "./types";
|
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
|
||||||
memberList: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Show activity icons in the member list",
|
|
||||||
default: true,
|
|
||||||
restartNeeded: true,
|
|
||||||
},
|
|
||||||
iconSize: {
|
|
||||||
type: OptionType.SLIDER,
|
|
||||||
description: "Size of the activity icons",
|
|
||||||
markers: [10, 15, 20],
|
|
||||||
default: 15,
|
|
||||||
stickToMarkers: false,
|
|
||||||
},
|
|
||||||
specialFirst: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Show special activities first (Currently Spotify and Twitch)",
|
|
||||||
default: true,
|
|
||||||
restartNeeded: false,
|
|
||||||
},
|
|
||||||
renderGifs: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Allow rendering GIFs",
|
|
||||||
default: true,
|
|
||||||
restartNeeded: false,
|
|
||||||
},
|
|
||||||
showAppDescriptions: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Show application descriptions in the activity tooltip",
|
|
||||||
default: true,
|
|
||||||
restartNeeded: false,
|
|
||||||
},
|
|
||||||
divider: {
|
|
||||||
type: OptionType.COMPONENT,
|
|
||||||
description: "",
|
|
||||||
component: () => (
|
|
||||||
<div style={{
|
|
||||||
width: "100%",
|
|
||||||
height: 1,
|
|
||||||
borderTop: "thin solid var(--background-modifier-accent)",
|
|
||||||
paddingTop: 5,
|
|
||||||
paddingBottom: 5
|
|
||||||
}} />
|
|
||||||
),
|
|
||||||
},
|
|
||||||
userPopout: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Show all activities in the profile popout/sidebar",
|
|
||||||
default: true,
|
|
||||||
restartNeeded: true,
|
|
||||||
},
|
|
||||||
allActivitiesStyle: {
|
|
||||||
type: OptionType.SELECT,
|
|
||||||
description: "Style for showing all activities",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
default: true,
|
|
||||||
label: "Carousel",
|
|
||||||
value: "carousel",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "List",
|
|
||||||
value: "list",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const cl = classNameFactory("vc-bactivities-");
|
|
||||||
|
|
||||||
const ApplicationStore: {
|
|
||||||
getApplication: (id: string) => Application | null;
|
|
||||||
} = findStoreLazy("ApplicationStore");
|
|
||||||
|
|
||||||
const { fetchApplication }: {
|
|
||||||
fetchApplication: (id: string) => Promise<Application | null>;
|
|
||||||
} = findByPropsLazy("fetchApplication");
|
|
||||||
|
|
||||||
const ActivityView = findComponentByCodeLazy<{
|
|
||||||
activity: Activity | null;
|
|
||||||
user: User;
|
|
||||||
application?: Application;
|
|
||||||
currentUser: User;
|
|
||||||
}>('location:"UserProfileActivityCard",');
|
|
||||||
|
|
||||||
// if discord one day decides to change their icon this needs to be updated
|
|
||||||
const DefaultActivityIcon = findComponentByCodeLazy("M6,7 L2,7 L2,6 L6,6 L6,7 Z M8,5 L2,5 L2,4 L8,4 L8,5 Z M8,3 L2,3 L2,2 L8,2 L8,3 Z M8.88888889,0 L1.11111111,0 C0.494444444,0 0,0.494444444 0,1.11111111 L0,8.88888889 C0,9.50253861 0.497461389,10 1.11111111,10 L8.88888889,10 C9.50253861,10 10,9.50253861 10,8.88888889 L10,1.11111111 C10,0.494444444 9.5,0 8.88888889,0 Z");
|
|
||||||
|
|
||||||
const fetchedApplications = new Map<string, Application | null>();
|
|
||||||
|
|
||||||
const xboxUrl = "https://discord.com/assets/9a15d086141be29d9fcd.png"; // TODO: replace with "renderXboxImage"?
|
|
||||||
|
|
||||||
const ActivityTooltip = ({ activity, application, user }: Readonly<{ activity: Activity, application?: Application, user: User; }>) => {
|
|
||||||
const currentUser = UserStore.getCurrentUser();
|
|
||||||
if (!currentUser) return null;
|
|
||||||
return (
|
|
||||||
<ErrorBoundary>
|
|
||||||
<div className={cl("activity-tooltip")}>
|
|
||||||
<ActivityView
|
|
||||||
activity={activity}
|
|
||||||
user={user}
|
|
||||||
application={application}
|
|
||||||
currentUser={currentUser}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function getActivityApplication(activity: Activity | null) {
|
|
||||||
if (!activity) return undefined;
|
|
||||||
const { application_id } = activity;
|
|
||||||
if (!application_id) return undefined;
|
|
||||||
let application = ApplicationStore.getApplication(application_id);
|
|
||||||
if (!application && fetchedApplications.has(application_id)) {
|
|
||||||
application = fetchedApplications.get(application_id) ?? null;
|
|
||||||
}
|
|
||||||
return application ?? undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getApplicationIcons(activities: Activity[], preferSmall = false) {
|
|
||||||
const applicationIcons: ApplicationIcon[] = [];
|
|
||||||
const applications = activities.filter(activity => activity.application_id || activity.platform);
|
|
||||||
|
|
||||||
for (const activity of applications) {
|
|
||||||
const { assets, application_id, platform } = activity;
|
|
||||||
if (!application_id && !platform) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (assets) {
|
|
||||||
const addImage = (image: string, alt: string) => {
|
|
||||||
if (image.startsWith("mp:")) {
|
|
||||||
const discordMediaLink = `https://media.discordapp.net/${image.replace(/mp:/, "")}`;
|
|
||||||
if (settings.store.renderGifs || !discordMediaLink.endsWith(".gif")) {
|
|
||||||
applicationIcons.push({
|
|
||||||
image: { src: discordMediaLink, alt },
|
|
||||||
activity
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const src = `https://cdn.discordapp.com/app-assets/${application_id}/${image}.png`;
|
|
||||||
applicationIcons.push({
|
|
||||||
image: { src, alt },
|
|
||||||
activity
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const smallImage = assets.small_image;
|
|
||||||
const smallText = assets.small_text ?? "Small Text";
|
|
||||||
const largeImage = assets.large_image;
|
|
||||||
const largeText = assets.large_text ?? "Large Text";
|
|
||||||
if (preferSmall) {
|
|
||||||
if (smallImage) {
|
|
||||||
addImage(smallImage, smallText);
|
|
||||||
} else if (largeImage) {
|
|
||||||
addImage(largeImage, largeText);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (largeImage) {
|
|
||||||
addImage(largeImage, largeText);
|
|
||||||
} else if (smallImage) {
|
|
||||||
addImage(smallImage, smallText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (application_id) {
|
|
||||||
let application = ApplicationStore.getApplication(application_id);
|
|
||||||
if (!application) {
|
|
||||||
if (fetchedApplications.has(application_id)) {
|
|
||||||
application = fetchedApplications.get(application_id) as Application | null;
|
|
||||||
} else {
|
|
||||||
fetchedApplications.set(application_id, null);
|
|
||||||
fetchApplication(application_id).then(app => {
|
|
||||||
fetchedApplications.set(application_id, app);
|
|
||||||
}).catch(console.error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (application) {
|
|
||||||
if (application.icon) {
|
|
||||||
const src = `https://cdn.discordapp.com/app-icons/${application.id}/${application.icon}.png`;
|
|
||||||
applicationIcons.push({
|
|
||||||
image: { src, alt: application.name },
|
|
||||||
activity,
|
|
||||||
application
|
|
||||||
});
|
|
||||||
} else if (platform === "xbox") {
|
|
||||||
applicationIcons.push({
|
|
||||||
image: { src: xboxUrl, alt: "Xbox" },
|
|
||||||
activity,
|
|
||||||
application
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (platform === "xbox") {
|
|
||||||
applicationIcons.push({
|
|
||||||
image: { src: xboxUrl, alt: "Xbox" },
|
|
||||||
activity
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (platform === "xbox") {
|
|
||||||
applicationIcons.push({
|
|
||||||
image: { src: xboxUrl, alt: "Xbox" },
|
|
||||||
activity
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return applicationIcons;
|
|
||||||
}
|
|
||||||
|
|
||||||
migratePluginSettings("BetterActivities", "MemberListActivities");
|
migratePluginSettings("BetterActivities", "MemberListActivities");
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "BetterActivities",
|
name: "BetterActivities",
|
||||||
description: "Shows activity icons in the member list and allows showing all activities",
|
description: "Shows activity icons in the member list and allows showing all activities",
|
||||||
authors: [Devs.D3SOX, Devs.Arjix, Devs.AutumnVN],
|
authors: [
|
||||||
|
Devs.D3SOX,
|
||||||
|
Devs.Arjix,
|
||||||
|
Devs.AutumnVN
|
||||||
|
],
|
||||||
tags: ["activity"],
|
tags: ["activity"],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
patchActivityList: ({ activities, user, hideTooltip }: { activities: Activity[], user: User, hideTooltip: boolean; }): JSX.Element | null => {
|
patchActivityList,
|
||||||
const icons: ActivityListIcon[] = [];
|
|
||||||
|
|
||||||
if (user.bot || hideTooltip) return null;
|
showAllActivitiesComponent,
|
||||||
|
|
||||||
const applicationIcons = getApplicationIcons(activities);
|
|
||||||
if (applicationIcons.length) {
|
|
||||||
const compareImageSource = (a: ApplicationIcon, b: ApplicationIcon) => {
|
|
||||||
return a.image.src === b.image.src;
|
|
||||||
};
|
|
||||||
const uniqueIcons = applicationIcons.filter((element, index, array) => {
|
|
||||||
return array.findIndex(el => compareImageSource(el, element)) === index;
|
|
||||||
});
|
|
||||||
for (const appIcon of uniqueIcons) {
|
|
||||||
icons.push({
|
|
||||||
iconElement: <img {...appIcon.image} />,
|
|
||||||
tooltip: <ActivityTooltip activity={appIcon.activity} application={appIcon.application} user={user} />
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const addActivityIcon = (activityName: string, IconComponent: React.ComponentType) => {
|
|
||||||
const activityIndex = activities.findIndex(({ name }) => name === activityName);
|
|
||||||
if (activityIndex !== -1) {
|
|
||||||
const activity = activities[activityIndex];
|
|
||||||
const iconObject: ActivityListIcon = {
|
|
||||||
iconElement: <IconComponent />,
|
|
||||||
tooltip: <ActivityTooltip activity={activity} user={user} />
|
|
||||||
};
|
|
||||||
|
|
||||||
if (settings.store.specialFirst) {
|
|
||||||
icons.unshift(iconObject);
|
|
||||||
} else {
|
|
||||||
icons.splice(activityIndex, 0, iconObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
addActivityIcon("Twitch", TwitchIcon);
|
|
||||||
addActivityIcon("Spotify", SpotifyIcon);
|
|
||||||
|
|
||||||
if (icons.length) {
|
|
||||||
const iconStyle: IconCSSProperties = {
|
|
||||||
"--icon-size": `${settings.store.iconSize}px`,
|
|
||||||
};
|
|
||||||
|
|
||||||
return <ErrorBoundary noop>
|
|
||||||
<div className={cl("row")}>
|
|
||||||
{icons.map(({ iconElement, tooltip }, i) => (
|
|
||||||
<div key={i} className={cl("icon")} style={iconStyle}>
|
|
||||||
{tooltip ? <Tooltip text={tooltip}>
|
|
||||||
{({ onMouseEnter, onMouseLeave }) => (
|
|
||||||
<div
|
|
||||||
onMouseEnter={onMouseEnter}
|
|
||||||
onMouseLeave={onMouseLeave}>
|
|
||||||
{iconElement}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Tooltip> : iconElement}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</ErrorBoundary>;
|
|
||||||
} else {
|
|
||||||
// Show default icon when there are no custom icons
|
|
||||||
// We need to filter out custom statuses
|
|
||||||
const shouldShow = activities.filter(a => a.type !== 4).length !== icons.length;
|
|
||||||
if (shouldShow) {
|
|
||||||
return <DefaultActivityIcon />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
showAllActivitiesComponent({ activity, user, ...props }: Readonly<{ activity: Activity; user: User; application: Application; type: string; }>) {
|
|
||||||
const currentUser = UserStore.getCurrentUser();
|
|
||||||
if (!currentUser) return null;
|
|
||||||
|
|
||||||
const [currentActivity, setCurrentActivity] = useState<Activity | null>(
|
|
||||||
activity?.type !== 4 ? activity! : null
|
|
||||||
);
|
|
||||||
|
|
||||||
const activities = useStateFromStores<Activity[]>(
|
|
||||||
[PresenceStore], () => PresenceStore.getActivities(user.id).filter((activity: Activity) => activity.type !== 4)
|
|
||||||
) ?? [];
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!activities.length) {
|
|
||||||
setCurrentActivity(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!currentActivity || !activities.includes(currentActivity))
|
|
||||||
setCurrentActivity(activities[0]);
|
|
||||||
}, [activities]);
|
|
||||||
|
|
||||||
// we use these for other activities, it would be better to somehow get the corresponding activity props
|
|
||||||
const generalProps = useMemo(() => Object.keys(props).reduce((acc, key) => {
|
|
||||||
// exclude activity specific props to prevent copying them to all activities (e.g. buttons)
|
|
||||||
if (key !== "renderActions" && key !== "application") acc[key] = props[key];
|
|
||||||
return acc;
|
|
||||||
}, {}), [props]);
|
|
||||||
|
|
||||||
if (!activities.length) return null;
|
|
||||||
|
|
||||||
if (settings.store.allActivitiesStyle === "carousel") {
|
|
||||||
return (
|
|
||||||
<ErrorBoundary noop onError={() => { }}>
|
|
||||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
|
||||||
{activity && currentActivity?.id === activity.id ? (
|
|
||||||
<ActivityView
|
|
||||||
activity={currentActivity}
|
|
||||||
user={user}
|
|
||||||
currentUser={currentUser}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<ActivityView
|
|
||||||
activity={currentActivity}
|
|
||||||
user={user}
|
|
||||||
// fetch optional application
|
|
||||||
application={getActivityApplication(currentActivity!)}
|
|
||||||
currentUser={currentUser}
|
|
||||||
{...generalProps}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{activities.length > 1 &&
|
|
||||||
<div
|
|
||||||
className={cl("controls")}
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Tooltip text="Left" tooltipClassName={cl("controls-tooltip")}>{({
|
|
||||||
onMouseEnter,
|
|
||||||
onMouseLeave
|
|
||||||
}) => {
|
|
||||||
return <span
|
|
||||||
onMouseEnter={onMouseEnter}
|
|
||||||
onMouseLeave={onMouseLeave}
|
|
||||||
onClick={() => {
|
|
||||||
const index = activities.indexOf(currentActivity!);
|
|
||||||
if (index - 1 >= 0) {
|
|
||||||
setCurrentActivity(activities[index - 1]);
|
|
||||||
} else {
|
|
||||||
setCurrentActivity(activities[activities.length - 1]);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Caret
|
|
||||||
disabled={activities.indexOf(currentActivity!) < 1}
|
|
||||||
direction="left" />
|
|
||||||
</span>;
|
|
||||||
}}</Tooltip>
|
|
||||||
|
|
||||||
<div className="carousel">
|
|
||||||
{activities.map((activity, index) => (
|
|
||||||
<div
|
|
||||||
key={"dot--" + index}
|
|
||||||
onClick={() => setCurrentActivity(activity)}
|
|
||||||
className={`dot ${currentActivity === activity ? "selected" : ""}`} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Tooltip text="Right" tooltipClassName={cl("controls-tooltip")}>{({
|
|
||||||
onMouseEnter,
|
|
||||||
onMouseLeave
|
|
||||||
}) => {
|
|
||||||
return <span
|
|
||||||
onMouseEnter={onMouseEnter}
|
|
||||||
onMouseLeave={onMouseLeave}
|
|
||||||
onClick={() => {
|
|
||||||
const index = activities.indexOf(currentActivity!);
|
|
||||||
if (index + 1 < activities.length) {
|
|
||||||
setCurrentActivity(activities[index + 1]);
|
|
||||||
} else {
|
|
||||||
setCurrentActivity(activities[0]);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Caret
|
|
||||||
disabled={activities.indexOf(currentActivity!) >= activities.length - 1}
|
|
||||||
direction="right" />
|
|
||||||
</span>;
|
|
||||||
}}</Tooltip>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<ErrorBoundary noop onError={() => { }}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
gap: "5px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{activities.map((activity, index) =>
|
|
||||||
index === 0 ? (
|
|
||||||
<ActivityView
|
|
||||||
key={index}
|
|
||||||
activity={activity}
|
|
||||||
user={user}
|
|
||||||
currentUser={currentUser}
|
|
||||||
{...props}
|
|
||||||
/>) : (
|
|
||||||
<ActivityView
|
|
||||||
key={index}
|
|
||||||
activity={activity}
|
|
||||||
user={user}
|
|
||||||
application={getActivityApplication(activity)}
|
|
||||||
currentUser={currentUser}
|
|
||||||
{...generalProps}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2025 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { findComponentByCodeLazy } from "@webpack";
|
||||||
|
import { React, Tooltip } from "@webpack/common";
|
||||||
|
import { JSX } from "react";
|
||||||
|
|
||||||
|
import { ActivityTooltip } from "../components/ActivityTooltip";
|
||||||
|
import { SpotifyIcon } from "../components/SpotifyIcon";
|
||||||
|
import { TwitchIcon } from "../components/TwitchIcon";
|
||||||
|
import { settings } from "../settings";
|
||||||
|
import { ActivityListIcon, ActivityListProps, ApplicationIcon, IconCSSProperties } from "../types";
|
||||||
|
import { cl, getApplicationIcons } from "../utils";
|
||||||
|
|
||||||
|
// if discord one day decides to change their icon this needs to be updated
|
||||||
|
const DefaultActivityIcon = findComponentByCodeLazy("M6,7 L2,7 L2,6 L6,6 L6,7 Z M8,5 L2,5 L2,4 L8,4 L8,5 Z M8,3 L2,3 L2,2 L8,2 L8,3 Z M8.88888889,0 L1.11111111,0 C0.494444444,0 0,0.494444444 0,1.11111111 L0,8.88888889 C0,9.50253861 0.497461389,10 1.11111111,10 L8.88888889,10 C9.50253861,10 10,9.50253861 10,8.88888889 L10,1.11111111 C10,0.494444444 9.5,0 8.88888889,0 Z");
|
||||||
|
|
||||||
|
export function patchActivityList({ activities, user, hideTooltip }: ActivityListProps): JSX.Element | null {
|
||||||
|
const icons: ActivityListIcon[] = [];
|
||||||
|
|
||||||
|
if (user.bot || hideTooltip) return null;
|
||||||
|
|
||||||
|
const applicationIcons = getApplicationIcons(activities);
|
||||||
|
if (applicationIcons.length) {
|
||||||
|
const compareImageSource = (a: ApplicationIcon, b: ApplicationIcon) => {
|
||||||
|
return a.image.src === b.image.src;
|
||||||
|
};
|
||||||
|
const uniqueIcons = applicationIcons.filter((element, index, array) => {
|
||||||
|
return array.findIndex(el => compareImageSource(el, element)) === index;
|
||||||
|
});
|
||||||
|
for (const appIcon of uniqueIcons) {
|
||||||
|
icons.push({
|
||||||
|
iconElement: <img {...appIcon.image} />,
|
||||||
|
tooltip: <ActivityTooltip activity={appIcon.activity} application={appIcon.application} user={user} />
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const addActivityIcon = (activityName: string, IconComponent: React.ComponentType) => {
|
||||||
|
const activityIndex = activities.findIndex(({ name }) => name === activityName);
|
||||||
|
if (activityIndex !== -1) {
|
||||||
|
const activity = activities[activityIndex];
|
||||||
|
const iconObject: ActivityListIcon = {
|
||||||
|
iconElement: <IconComponent />,
|
||||||
|
tooltip: <ActivityTooltip activity={activity} user={user} />
|
||||||
|
};
|
||||||
|
|
||||||
|
if (settings.store.specialFirst) {
|
||||||
|
icons.unshift(iconObject);
|
||||||
|
} else {
|
||||||
|
icons.splice(activityIndex, 0, iconObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
addActivityIcon("Twitch", TwitchIcon);
|
||||||
|
addActivityIcon("Spotify", SpotifyIcon);
|
||||||
|
|
||||||
|
if (icons.length) {
|
||||||
|
const iconStyle: IconCSSProperties = {
|
||||||
|
"--icon-size": `${settings.store.iconSize}px`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ErrorBoundary noop>
|
||||||
|
<div className={cl("row")}>
|
||||||
|
{icons.map(({ iconElement, tooltip }, i) => (
|
||||||
|
<div key={i} className={cl("icon")} style={iconStyle}>
|
||||||
|
{tooltip ? <Tooltip text={tooltip}>
|
||||||
|
{({ onMouseEnter, onMouseLeave }) => (
|
||||||
|
<div
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}>
|
||||||
|
{iconElement}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Tooltip> : iconElement}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ErrorBoundary>;
|
||||||
|
} else {
|
||||||
|
// Show default icon when there are no custom icons
|
||||||
|
// We need to filter out custom statuses
|
||||||
|
const shouldShow = activities.filter(a => a.type !== 4).length !== icons.length;
|
||||||
|
if (shouldShow) {
|
||||||
|
return <DefaultActivityIcon />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
111
src/equicordplugins/betterActivities/patch-helpers/popout.tsx
Normal file
111
src/equicordplugins/betterActivities/patch-helpers/popout.tsx
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2025 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { PresenceStore, React, useEffect, useMemo, UserStore, useState, useStateFromStores } from "@webpack/common";
|
||||||
|
import { JSX } from "react";
|
||||||
|
|
||||||
|
import { CarouselControls } from "../components/CarouselControls";
|
||||||
|
import { settings } from "../settings";
|
||||||
|
import { Activity, AllActivitiesProps } from "../types";
|
||||||
|
import { ActivityView, getActivityApplication } from "../utils";
|
||||||
|
|
||||||
|
export function showAllActivitiesComponent({ activity, user, ...props }: Readonly<AllActivitiesProps>): JSX.Element | null {
|
||||||
|
const currentUser = UserStore.getCurrentUser();
|
||||||
|
if (!currentUser) return null;
|
||||||
|
|
||||||
|
const [currentActivity, setCurrentActivity] = useState<Activity | null>(
|
||||||
|
activity?.type !== 4 ? activity! : null
|
||||||
|
);
|
||||||
|
|
||||||
|
const activities = useStateFromStores(
|
||||||
|
[PresenceStore],
|
||||||
|
() => PresenceStore.getActivities(user.id).filter((activity: Activity) => activity.type !== 4)
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!activities.length) {
|
||||||
|
setCurrentActivity(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentActivity || !activities.includes(currentActivity))
|
||||||
|
setCurrentActivity(activities[0]);
|
||||||
|
}, [activities]);
|
||||||
|
|
||||||
|
// we use these for other activities, it would be better to somehow get the corresponding activity props
|
||||||
|
const generalProps = useMemo(() => Object.keys(props).reduce((acc, key) => {
|
||||||
|
// exclude activity specific props to prevent copying them to all activities (e.g. buttons)
|
||||||
|
if (key !== "renderActions" && key !== "application") acc[key] = props[key];
|
||||||
|
return acc;
|
||||||
|
}, {}), [props]);
|
||||||
|
|
||||||
|
if (!activities.length) return null;
|
||||||
|
|
||||||
|
if (settings.store.allActivitiesStyle === "carousel") {
|
||||||
|
return (
|
||||||
|
<ErrorBoundary noop>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||||
|
{activity && currentActivity?.id === activity.id ? (
|
||||||
|
<ActivityView
|
||||||
|
activity={currentActivity}
|
||||||
|
user={user}
|
||||||
|
currentUser={currentUser}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ActivityView
|
||||||
|
activity={currentActivity}
|
||||||
|
user={user}
|
||||||
|
// fetch optional application
|
||||||
|
application={getActivityApplication(currentActivity!)}
|
||||||
|
currentUser={currentUser}
|
||||||
|
{...generalProps}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{activities.length > 1 && currentActivity && (
|
||||||
|
<CarouselControls
|
||||||
|
activities={activities}
|
||||||
|
currentActivity={currentActivity}
|
||||||
|
onActivityChange={setCurrentActivity}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<ErrorBoundary noop>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "5px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{activities.map((activity, index) =>
|
||||||
|
index === 0 ? (
|
||||||
|
<ActivityView
|
||||||
|
key={index}
|
||||||
|
activity={activity}
|
||||||
|
user={user}
|
||||||
|
currentUser={currentUser}
|
||||||
|
{...props}
|
||||||
|
/>) : (
|
||||||
|
<ActivityView
|
||||||
|
key={index}
|
||||||
|
activity={activity}
|
||||||
|
user={user}
|
||||||
|
application={getActivityApplication(activity)}
|
||||||
|
currentUser={currentUser}
|
||||||
|
{...generalProps}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
71
src/equicordplugins/betterActivities/settings.tsx
Normal file
71
src/equicordplugins/betterActivities/settings.tsx
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2025 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import { OptionType } from "@utils/types";
|
||||||
|
import { React } from "@webpack/common";
|
||||||
|
|
||||||
|
export const settings = definePluginSettings({
|
||||||
|
memberList: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Show activity icons in the member list",
|
||||||
|
default: true,
|
||||||
|
restartNeeded: true,
|
||||||
|
},
|
||||||
|
iconSize: {
|
||||||
|
type: OptionType.SLIDER,
|
||||||
|
description: "Size of the activity icons",
|
||||||
|
markers: [10, 15, 20],
|
||||||
|
default: 15,
|
||||||
|
stickToMarkers: false,
|
||||||
|
},
|
||||||
|
specialFirst: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Show special activities first (Currently Spotify and Twitch)",
|
||||||
|
default: true,
|
||||||
|
restartNeeded: false,
|
||||||
|
},
|
||||||
|
renderGifs: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Allow rendering GIFs",
|
||||||
|
default: true,
|
||||||
|
restartNeeded: false,
|
||||||
|
},
|
||||||
|
divider: {
|
||||||
|
type: OptionType.COMPONENT,
|
||||||
|
description: "",
|
||||||
|
component: () => (
|
||||||
|
<div style={{
|
||||||
|
width: "100%",
|
||||||
|
height: 1,
|
||||||
|
borderTop: "thin solid var(--background-modifier-accent)",
|
||||||
|
paddingTop: 5,
|
||||||
|
paddingBottom: 5
|
||||||
|
}} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
userPopout: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Show all activities in the profile popout/sidebar",
|
||||||
|
default: true,
|
||||||
|
restartNeeded: true,
|
||||||
|
},
|
||||||
|
allActivitiesStyle: {
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
description: "Style for showing all activities",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
default: true,
|
||||||
|
label: "Carousel",
|
||||||
|
value: "carousel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "List",
|
||||||
|
value: "list",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
|
@ -19,12 +19,8 @@
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="tooltip_"]:has(.vc-bactivities-activity-tooltip) {
|
|
||||||
max-width: 280px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-bactivities-activity-tooltip {
|
.vc-bactivities-activity-tooltip {
|
||||||
margin: -5px;
|
padding: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-bactivities-caret-left,
|
.vc-bactivities-caret-left,
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
/*
|
/*
|
||||||
* Vencord, a Discord client mod
|
* Vencord, a Discord client mod
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
* Copyright (c) 2025 Vendicated and contributors
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { User } from "discord-types/general";
|
||||||
import { CSSProperties, ImgHTMLAttributes, JSX } from "react";
|
import { CSSProperties, ImgHTMLAttributes, JSX } from "react";
|
||||||
|
|
||||||
export interface Timestamp {
|
export interface Timestamp {
|
||||||
|
@ -80,3 +81,36 @@ export interface ActivityListIcon {
|
||||||
export interface IconCSSProperties extends CSSProperties {
|
export interface IconCSSProperties extends CSSProperties {
|
||||||
"--icon-size": string;
|
"--icon-size": string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ActivityListProps {
|
||||||
|
activities: Activity[];
|
||||||
|
user: User;
|
||||||
|
hideTooltip: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ActivityTooltipProps {
|
||||||
|
activity: Activity;
|
||||||
|
application?: Application;
|
||||||
|
user: User;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AllActivitiesProps {
|
||||||
|
activity: Activity;
|
||||||
|
user: User;
|
||||||
|
application: Application;
|
||||||
|
type: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CarouselControlsProps {
|
||||||
|
activities: Activity[];
|
||||||
|
currentActivity: Activity;
|
||||||
|
onActivityChange: (activity: Activity) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ActivityViewProps {
|
||||||
|
activity: Activity | null;
|
||||||
|
user: User;
|
||||||
|
application?: Application;
|
||||||
|
currentUser: User;
|
||||||
|
}
|
||||||
|
|
127
src/equicordplugins/betterActivities/utils.tsx
Normal file
127
src/equicordplugins/betterActivities/utils.tsx
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2025 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { classNameFactory } from "@api/Styles";
|
||||||
|
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
||||||
|
|
||||||
|
import { settings } from "./settings";
|
||||||
|
import { Activity, ActivityViewProps, Application, ApplicationIcon } from "./types";
|
||||||
|
|
||||||
|
const ApplicationStore: {
|
||||||
|
getApplication: (id: string) => Application | null;
|
||||||
|
} = findStoreLazy("ApplicationStore");
|
||||||
|
|
||||||
|
const { fetchApplication }: {
|
||||||
|
fetchApplication: (id: string) => Promise<Application | null>;
|
||||||
|
} = findByPropsLazy("fetchApplication");
|
||||||
|
|
||||||
|
const fetchedApplications = new Map<string, Application | null>();
|
||||||
|
|
||||||
|
const xboxUrl = "https://discord.com/assets/9a15d086141be29d9fcd.png"; // TODO: replace with "renderXboxImage"?
|
||||||
|
|
||||||
|
export const ActivityView = findComponentByCodeLazy<ActivityViewProps>('location:"UserProfileActivityCard",');
|
||||||
|
|
||||||
|
export const cl = classNameFactory("vc-bactivities-");
|
||||||
|
|
||||||
|
export function getActivityApplication(activity: Activity | null) {
|
||||||
|
if (!activity) return undefined;
|
||||||
|
const { application_id } = activity;
|
||||||
|
if (!application_id) return undefined;
|
||||||
|
let application = ApplicationStore.getApplication(application_id);
|
||||||
|
if (!application && fetchedApplications.has(application_id)) {
|
||||||
|
application = fetchedApplications.get(application_id) ?? null;
|
||||||
|
}
|
||||||
|
return application ?? undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getApplicationIcons(activities: Activity[], preferSmall = false): ApplicationIcon[] {
|
||||||
|
const applicationIcons: ApplicationIcon[] = [];
|
||||||
|
const applications = activities.filter(activity => activity.application_id || activity.platform);
|
||||||
|
|
||||||
|
for (const activity of applications) {
|
||||||
|
const { assets, application_id, platform } = activity;
|
||||||
|
if (!application_id && !platform) continue;
|
||||||
|
|
||||||
|
if (assets) {
|
||||||
|
const { small_image, small_text, large_image, large_text } = assets;
|
||||||
|
const smallText = small_text ?? "Small Text";
|
||||||
|
const largeText = large_text ?? "Large Text";
|
||||||
|
|
||||||
|
const addImage = (image: string, alt: string) => {
|
||||||
|
if (image.startsWith("mp:")) {
|
||||||
|
const discordMediaLink = `https://media.discordapp.net/${image.replace(/mp:/, "")}`;
|
||||||
|
if (settings.store.renderGifs || !discordMediaLink.endsWith(".gif")) {
|
||||||
|
applicationIcons.push({
|
||||||
|
image: { src: discordMediaLink, alt },
|
||||||
|
activity
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const src = `https://cdn.discordapp.com/app-assets/${application_id}/${image}.png`;
|
||||||
|
applicationIcons.push({
|
||||||
|
image: { src, alt },
|
||||||
|
activity
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (preferSmall) {
|
||||||
|
if (small_image) {
|
||||||
|
addImage(small_image, smallText);
|
||||||
|
} else if (large_image) {
|
||||||
|
addImage(large_image, largeText);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (large_image) {
|
||||||
|
addImage(large_image, largeText);
|
||||||
|
} else if (small_image) {
|
||||||
|
addImage(small_image, smallText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (application_id) {
|
||||||
|
let application = ApplicationStore.getApplication(application_id);
|
||||||
|
if (!application) {
|
||||||
|
if (fetchedApplications.has(application_id)) {
|
||||||
|
application = fetchedApplications.get(application_id) as Application | null;
|
||||||
|
} else {
|
||||||
|
fetchedApplications.set(application_id, null);
|
||||||
|
fetchApplication(application_id).then(app => {
|
||||||
|
fetchedApplications.set(application_id, app);
|
||||||
|
}).catch(console.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (application) {
|
||||||
|
if (application.icon) {
|
||||||
|
const src = `https://cdn.discordapp.com/app-icons/${application.id}/${application.icon}.png`;
|
||||||
|
applicationIcons.push({
|
||||||
|
image: { src, alt: application.name },
|
||||||
|
activity,
|
||||||
|
application
|
||||||
|
});
|
||||||
|
} else if (platform === "xbox") {
|
||||||
|
applicationIcons.push({
|
||||||
|
image: { src: xboxUrl, alt: "Xbox" },
|
||||||
|
activity,
|
||||||
|
application
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (platform === "xbox") {
|
||||||
|
applicationIcons.push({
|
||||||
|
image: { src: xboxUrl, alt: "Xbox" },
|
||||||
|
activity
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (platform === "xbox") {
|
||||||
|
applicationIcons.push({
|
||||||
|
image: { src: xboxUrl, alt: "Xbox" },
|
||||||
|
activity
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return applicationIcons;
|
||||||
|
}
|
|
@ -81,23 +81,22 @@ export default definePlugin({
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
// this also affects name headers in chats outside of servers
|
// this also affects name headers in chats outside of servers
|
||||||
find: ".USERNAME),{",
|
find: '="SYSTEM_TAG"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /style:"username"===.{0,25}void 0/,
|
match: /(?<=\i.gradientClassName]\),style:.{0,80}:void 0,)/,
|
||||||
replace: "style:{color:$self.colorIfServer(arguments[0])}"
|
replace: "style:{color:$self.colorIfServer(arguments[0])},"
|
||||||
},
|
},
|
||||||
noWarn: true,
|
predicate: () => !Settings.plugins.IrcColors.enabled
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
predicate: () => settings.store.dmList,
|
|
||||||
find: "PrivateChannel.renderAvatar",
|
find: "PrivateChannel.renderAvatar",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(highlighted:\i,)/,
|
match: /(highlighted:\i,)/,
|
||||||
replace: "$1style:{color:`${$self.colorDMList(arguments[0])}`},"
|
replace: "$1style:{color:`${$self.colorDMList(arguments[0])}`},"
|
||||||
},
|
},
|
||||||
|
predicate: () => settings.store.dmList,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
predicate: () => settings.store.dmList,
|
|
||||||
find: "!1,wrapContent",
|
find: "!1,wrapContent",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
|
@ -109,6 +108,7 @@ export default definePlugin({
|
||||||
replace: "style:style||{},"
|
replace: "style:style||{},"
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
predicate: () => settings.store.dmList,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,8 @@ import {
|
||||||
definePluginSettings,
|
definePluginSettings,
|
||||||
Settings,
|
Settings,
|
||||||
} from "@api/Settings";
|
} from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { makeRange } from "@components/PluginSettings/components";
|
||||||
|
import { Devs, EquicordDevs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
|
||||||
const presendObject: MessageSendListener = (channelId, msg) => {
|
const presendObject: MessageSendListener = (channelId, msg) => {
|
||||||
|
@ -21,17 +22,52 @@ const presendObject: MessageSendListener = (channelId, msg) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
|
quickDisable: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Quick disable. Turns off message modifying without requiring a client reload.",
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
blockedWords: {
|
blockedWords: {
|
||||||
type: OptionType.STRING,
|
type: OptionType.STRING,
|
||||||
description: "Words that will not be capitalised",
|
description: "Words that will not be capitalized (comma separated).",
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
|
// fixApostrophes is the only one that defaults to enabled because in the version before this one,
|
||||||
|
// the other features did not exist / had a bug making them not work.
|
||||||
|
fixApostrophes: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Ensure contractions contain apostrophes.",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
expandContractions: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Expand contractions.",
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
fixCapitalization: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Capitalize sentences.",
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
fixPunctuation: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Punctate sentences.",
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
fixPunctuationFrequency: {
|
||||||
|
type: OptionType.SLIDER,
|
||||||
|
description: "Percent period frequency (this majorly annoys some people).",
|
||||||
|
markers: makeRange(0, 100, 10),
|
||||||
|
stickToMarkers: false,
|
||||||
|
default: 100,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "PolishWording",
|
name: "PolishWording",
|
||||||
description: "Tweaks your messages to make them look nicer and have better grammar",
|
description: "Tweaks your messages to make them look nicer and have better grammar. See settings",
|
||||||
authors: [Devs.Samwich],
|
authors: [Devs.Samwich, EquicordDevs.WKoA],
|
||||||
dependencies: ["MessageEventsAPI"],
|
dependencies: ["MessageEventsAPI"],
|
||||||
start: () => addMessagePreSendListener(presendObject),
|
start: () => addMessagePreSendListener(presendObject),
|
||||||
stop: () => removeMessagePreSendListener(presendObject),
|
stop: () => removeMessagePreSendListener(presendObject),
|
||||||
|
@ -39,43 +75,93 @@ export default definePlugin({
|
||||||
});
|
});
|
||||||
|
|
||||||
function textProcessing(input: string) {
|
function textProcessing(input: string) {
|
||||||
|
// Quick disable, without having to reload the client
|
||||||
|
if (settings.store.quickDisable) return input;
|
||||||
|
|
||||||
let text = input;
|
let text = input;
|
||||||
text = cap(text);
|
|
||||||
text = apostrophe(text);
|
// Preserve code blocks
|
||||||
|
const codeBlockRegex = /```[\s\S]*?```|`[\s\S]*?`/g;
|
||||||
|
const codeBlocks: string[] = [];
|
||||||
|
text = text.replace(codeBlockRegex, match => {
|
||||||
|
codeBlocks.push(match);
|
||||||
|
return `__CODE_BLOCK_${codeBlocks.length - 1}__`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run message through formatters.
|
||||||
|
if (settings.store.fixApostrophes || settings.store.expandContractions) text = ensureApostrophe(text); // Note: if expanding contractions, fix them first.
|
||||||
|
if (settings.store.fixCapitalization) text = capitalize(text);
|
||||||
|
if (settings.store.fixPunctuation && (Math.random() * 100 < settings.store.fixPunctuationFrequency)) text = addPeriods(text);
|
||||||
|
if (settings.store.expandContractions) text = expandContractions(text);
|
||||||
|
|
||||||
|
text = text.replace(/__CODE_BLOCK_(\d+)__/g, (_, index) => codeBlocks[parseInt(index)]);
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
function apostrophe(textInput: string): string {
|
// Injecting apostrophe as well as contraction expansion rely on this mapping
|
||||||
const corrected =
|
const contractionsMap: { [key: string]: string; } = {
|
||||||
"wasn't, can't, don't, won't, isn't, aren't, haven't, hasn't, hadn't, doesn't, didn't, shouldn't, wouldn't, couldn't, i'm, you're, he's, she's, it's, they're, that's, who's, what's, there's, here's, how's, where's, when's, why's, let's, you'll, I'll, they'll, it'll, I've, you've, we've, they've, you'd, he'd, she'd, it'd, we'd, they'd, y'all".toLowerCase();
|
"wasn't": "was not",
|
||||||
const words: string[] = corrected.split(", ");
|
"can't": "cannot",
|
||||||
const wordsInputted = textInput.split(" ");
|
"don't": "do not",
|
||||||
|
"won't": "will not",
|
||||||
|
"isn't": "is not",
|
||||||
|
"aren't": "are not",
|
||||||
|
"haven't": "have not",
|
||||||
|
"hasn't": "has not",
|
||||||
|
"hadn't": "had not",
|
||||||
|
"doesn't": "does not",
|
||||||
|
"didn't": "did not",
|
||||||
|
"shouldn't": "should not",
|
||||||
|
"wouldn't": "would not",
|
||||||
|
"couldn't": "could not",
|
||||||
|
"that's": "that is",
|
||||||
|
"what's": "what is",
|
||||||
|
"there's": "there is",
|
||||||
|
"how's": "how is",
|
||||||
|
"where's": "where is",
|
||||||
|
"when's": "when is",
|
||||||
|
"who's": "who is",
|
||||||
|
"why's": "why is",
|
||||||
|
"you'll": "you will",
|
||||||
|
"i'll": "I will",
|
||||||
|
"they'll": "they will",
|
||||||
|
"it'll": "it will",
|
||||||
|
"i'm": "I am",
|
||||||
|
"you're": "you are",
|
||||||
|
"they're": "they are",
|
||||||
|
"he's": "he is",
|
||||||
|
"she's": "she is",
|
||||||
|
"i've": "I have",
|
||||||
|
"you've": "you have",
|
||||||
|
"we've": "we have",
|
||||||
|
"they've": "they have",
|
||||||
|
"you'd": "you would",
|
||||||
|
"he'd": "he would",
|
||||||
|
"she'd": "she would",
|
||||||
|
"it'd": "it would",
|
||||||
|
"we'd": "we would",
|
||||||
|
"they'd": "they would",
|
||||||
|
"y'all": "you all",
|
||||||
|
"here's": "here is",
|
||||||
|
};
|
||||||
|
|
||||||
wordsInputted.forEach(element => {
|
const missingApostropheMap: { [key: string]: string; } = {};
|
||||||
words.forEach(wordelement => {
|
for (const contraction in contractionsMap) {
|
||||||
if (removeApostrophes(wordelement) === element.toLowerCase()) {
|
const withoutApostrophe = removeApostrophes(contraction.toLowerCase());
|
||||||
wordsInputted[wordsInputted.indexOf(element)] = restoreCap(
|
missingApostropheMap[withoutApostrophe] = contraction;
|
||||||
wordelement,
|
|
||||||
getCapData(element),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return wordsInputted.join(" ");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCapData(str: string) {
|
function getCapData(str: string) {
|
||||||
const booleanArray: boolean[] = [];
|
const booleanArray: boolean[] = [];
|
||||||
for (const char of str) {
|
for (const char of str) {
|
||||||
booleanArray.push(char === char.toUpperCase());
|
if (char.match(/[a-zA-Z]/)) { // Only record capitalization for letters
|
||||||
|
booleanArray.push(char === char.toUpperCase());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return booleanArray;
|
return booleanArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeApostrophes(str: string): string {
|
|
||||||
return str.replace(/'/g, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
function restoreCap(str: string, data: boolean[]): string {
|
function restoreCap(str: string, data: boolean[]): string {
|
||||||
let resultString = "";
|
let resultString = "";
|
||||||
let dataIndex = 0;
|
let dataIndex = 0;
|
||||||
|
@ -87,30 +173,152 @@ function restoreCap(str: string, data: boolean[]): string {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isUppercase = data[dataIndex++];
|
const isUppercase = data[dataIndex];
|
||||||
resultString += isUppercase ? char.toUpperCase() : char.toLowerCase();
|
resultString += isUppercase ? char.toUpperCase() : char.toLowerCase();
|
||||||
|
|
||||||
|
// Increment index unless the data in shorter than the string, in which case we use the most recent for the rest
|
||||||
|
if (dataIndex < data.length - 1) dataIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultString;
|
return resultString;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cap(textInput: string): string {
|
function ensureApostrophe(textInput: string): string {
|
||||||
const sentences = textInput.split(/(?<=\w\.)\s/);
|
// This function makes sure all contractions have apostrophes
|
||||||
|
|
||||||
const blockedWordsArray: string[] =
|
const potentialContractions = Object.keys(missingApostropheMap);
|
||||||
Settings.plugins.PolishWording.blockedWords.split(", ");
|
if (potentialContractions.length === 0) {
|
||||||
|
return textInput; // Nothing to check if the map is empty
|
||||||
|
}
|
||||||
|
|
||||||
return sentences
|
const findMissingRegex = new RegExp(
|
||||||
.map(element => {
|
`\\b(${potentialContractions.join("|")})\\b`, // Match any of the keys as whole words
|
||||||
if (
|
"gi" // Global (all occurrences), Case-insensitive
|
||||||
!blockedWordsArray.some(word =>
|
);
|
||||||
element.toLowerCase().startsWith(word.toLowerCase()),
|
|
||||||
)
|
return textInput.replace(findMissingRegex, match => {
|
||||||
) {
|
const lowerCaseMatch = match.toLowerCase();
|
||||||
return element.charAt(0).toUpperCase() + element.slice(1);
|
|
||||||
} else {
|
if (Object.prototype.hasOwnProperty.call(missingApostropheMap, lowerCaseMatch)) {
|
||||||
return element;
|
const correctContraction = missingApostropheMap[lowerCaseMatch];
|
||||||
}
|
return restoreCap(correctContraction, getCapData(match));
|
||||||
})
|
}
|
||||||
.join(" ");
|
return match;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function expandContractions(textInput: string) {
|
||||||
|
const contractionRegex = new RegExp(
|
||||||
|
`\\b(${Object.keys(contractionsMap).join("|")})\\b`,
|
||||||
|
"gi"
|
||||||
|
);
|
||||||
|
|
||||||
|
return textInput.replace(contractionRegex, match => {
|
||||||
|
const lowerCaseMatch = match.toLowerCase();
|
||||||
|
if (Object.prototype.hasOwnProperty.call(contractionsMap, lowerCaseMatch)) {
|
||||||
|
return restoreCap(contractionsMap[lowerCaseMatch], getCapData(match));
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeApostrophes(str: string): string {
|
||||||
|
return str.replace(/'/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function capitalize(textInput: string): string {
|
||||||
|
// This one split ellipsis
|
||||||
|
// const sentenceSplitRegex = /((?<!\w\.\w.)(?<!\b[A-Z][a-z]\.)(?<![A-Z]\.)(?<=[.?!])\s+|\n+)/;
|
||||||
|
|
||||||
|
// Regex modified from several stack overflows, if you change make sure it's safe against https://devina.io/redos-checker
|
||||||
|
const sentenceSplitRegex = /((?<!\w\.\w.)(?<!\b[A-Z][a-z]\.)(?<![A-Z]\.)(?<!\.)(?<=[.?!])\s+|\n+)/;
|
||||||
|
|
||||||
|
const parts = textInput.split(sentenceSplitRegex);
|
||||||
|
const filteredParts = parts.filter(part => part !== undefined && part !== null);
|
||||||
|
|
||||||
|
const blockedWordsArray: string[] = (Settings.plugins.PolishWording.blockedWords || "")
|
||||||
|
.split(/,\s?/)
|
||||||
|
.filter(bw => bw)
|
||||||
|
.map(bw => bw.toLowerCase());
|
||||||
|
|
||||||
|
// Process alternating content and delimiters
|
||||||
|
let result = "";
|
||||||
|
for (let i = 0; i < filteredParts.length; i++) {
|
||||||
|
const element = filteredParts[i];
|
||||||
|
|
||||||
|
const isSentence = !sentenceSplitRegex.test(element); // if it matches the delimiter regex, it's a delimiter
|
||||||
|
|
||||||
|
if (isSentence) {
|
||||||
|
// Check if this is just whitespace
|
||||||
|
if (!element) continue;
|
||||||
|
else if (element.trim() === "") {
|
||||||
|
result += element;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the first actual word character for capitalization check
|
||||||
|
const firstWordMatch = element.match(/^\s*([\w'-]+)/);
|
||||||
|
const firstWord = firstWordMatch ? firstWordMatch[1].toLowerCase() : "";
|
||||||
|
const isBlocked = firstWord ? blockedWordsArray.includes(firstWord) : false;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isBlocked &&
|
||||||
|
!element.startsWith("http") // Don't break links
|
||||||
|
) {
|
||||||
|
// Capitalize the first non-whitespace character (sentence splits can include newlines etc)
|
||||||
|
result += element.replace(/^(\s*)(\S)/, (match, leadingSpace, firstChar) => {
|
||||||
|
return leadingSpace + firstChar.toUpperCase();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
result += element;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This a delimiter (whitespace/newline regex), so we'll add it to the string to properly reconstruct without being lossy
|
||||||
|
if (element) {
|
||||||
|
result += element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll fix capitalization of I's
|
||||||
|
result = result.replace(/\bi[\b']/g, "I");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addPeriods(textInput: string) {
|
||||||
|
if (!textInput) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = textInput.split("\n");
|
||||||
|
const processedLines: string[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = lines[i];
|
||||||
|
const strippedLine = line.trimEnd();
|
||||||
|
|
||||||
|
const urlRegex = /https?:\/\/\S+$|www\.\S+$/;
|
||||||
|
|
||||||
|
if (!strippedLine) {
|
||||||
|
if (i < lines.length - 1) {
|
||||||
|
processedLines.push("");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const lastChar = strippedLine.slice(-1);
|
||||||
|
if (
|
||||||
|
/[A-Za-z0-9]/.test(lastChar) && // If it doesn't already end with punctuation
|
||||||
|
!urlRegex.test(strippedLine) // If it doesn't end with a link
|
||||||
|
) {
|
||||||
|
processedLines.push(strippedLine + ".");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
processedLines.push(strippedLine);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return processedLines.join("\n");
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,7 +141,7 @@ const settings = definePluginSettings({
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "RandomVoice",
|
name: "RandomVoice",
|
||||||
description: "Adds a Button near the Mute button to join a random voice call.",
|
description: "Adds a Button near the Mute button to join a random voice call.",
|
||||||
authors: [EquicordDevs.omaw],
|
authors: [EquicordDevs.xijexo, EquicordDevs.omaw],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
|
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
|
||||||
|
@ -725,4 +725,4 @@ function autoCamera() {
|
||||||
camera.click();
|
camera.click();
|
||||||
}
|
}
|
||||||
}, 50);
|
}, 50);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
|
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
|
||||||
import { CONTRIB_ROLE_ID, Devs, DONOR_ROLE_ID, EQUIBOP_CONTRIB_ROLE_ID, EQUICORD_TEAM, GUILD_ID, SUPPORT_CHANNEL_ID, SUPPORT_CHANNEL_IDS, VC_CONTRIB_ROLE_ID, VC_DONOR_ROLE_ID, VC_GUILD_ID, VC_KNOWN_ISSUES_CHANNEL_ID, VC_REGULAR_ROLE_ID, VC_SUPPORT_CHANNEL_ID, VENBOT_USER_ID, VENCORD_CONTRIB_ROLE_ID } from "@utils/constants";
|
import { CONTRIB_ROLE_ID, Devs, DONOR_ROLE_ID, EQUCORD_HELPERS, EQUIBOP_CONTRIB_ROLE_ID, EQUICORD_TEAM, GUILD_ID, SUPPORT_CHANNEL_ID, VC_CONTRIB_ROLE_ID, VC_DONOR_ROLE_ID, VC_GUILD_ID, VC_REGULAR_ROLE_ID, VC_SUPPORT_CHANNEL_ID, VENCORD_CONTRIB_ROLE_ID } from "@utils/constants";
|
||||||
import { sendMessage } from "@utils/discord";
|
import { sendMessage } from "@utils/discord";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
|
@ -32,7 +32,7 @@ import { onlyOnce } from "@utils/onlyOnce";
|
||||||
import { makeCodeblock } from "@utils/text";
|
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, PermissionsBits, PermissionStore, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common";
|
||||||
import { JSX } from "react";
|
import { JSX } from "react";
|
||||||
|
|
||||||
import gitHash from "~git-hash";
|
import gitHash from "~git-hash";
|
||||||
|
@ -196,7 +196,8 @@ export default definePlugin({
|
||||||
|
|
||||||
flux: {
|
flux: {
|
||||||
async CHANNEL_SELECT({ channelId }) {
|
async CHANNEL_SELECT({ channelId }) {
|
||||||
if (!SUPPORT_CHANNEL_IDS.includes(channelId)) return;
|
const isSupportChannel = channelId === SUPPORT_CHANNEL_ID;
|
||||||
|
if (!isSupportChannel) return;
|
||||||
|
|
||||||
const selfId = UserStore.getCurrentUser()?.id;
|
const selfId = UserStore.getCurrentUser()?.id;
|
||||||
if (!selfId || isPluginDev(selfId) || isEquicordPluginDev(selfId)) return;
|
if (!selfId || isPluginDev(selfId) || isEquicordPluginDev(selfId)) return;
|
||||||
|
@ -281,11 +282,12 @@ export default definePlugin({
|
||||||
renderMessageAccessory(props) {
|
renderMessageAccessory(props) {
|
||||||
const buttons = [] as JSX.Element[];
|
const buttons = [] as JSX.Element[];
|
||||||
|
|
||||||
|
const equicordSupport = GuildMemberStore.getMember(GUILD_ID, props.message.author.id)?.roles?.includes(EQUCORD_HELPERS);
|
||||||
|
|
||||||
const shouldAddUpdateButton =
|
const shouldAddUpdateButton =
|
||||||
!IS_UPDATER_DISABLED
|
!IS_UPDATER_DISABLED
|
||||||
&& (
|
&& (
|
||||||
(props.channel.id === VC_KNOWN_ISSUES_CHANNEL_ID) ||
|
(props.channel.id === SUPPORT_CHANNEL_ID && equicordSupport)
|
||||||
(props.channel.id === VC_SUPPORT_CHANNEL_ID && props.message.author.id === VENBOT_USER_ID)
|
|
||||||
)
|
)
|
||||||
&& props.message.content?.includes("update");
|
&& props.message.content?.includes("update");
|
||||||
|
|
||||||
|
@ -311,7 +313,7 @@ export default definePlugin({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.channel.id === SUPPORT_CHANNEL_ID) {
|
if (props.channel.id === SUPPORT_CHANNEL_ID && PermissionStore.can(PermissionsBits.SEND_MESSAGES, props.channel)) {
|
||||||
if (props.message.content.includes("/equicord-debug") || props.message.content.includes("/equicord-plugins")) {
|
if (props.message.content.includes("/equicord-debug") || props.message.content.includes("/equicord-plugins")) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<Button
|
<Button
|
||||||
|
@ -334,7 +336,7 @@ export default definePlugin({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.message.author.id === VENBOT_USER_ID) {
|
if (equicordSupport) {
|
||||||
const match = CodeBlockRe.exec(props.message.content || props.message.embeds[0]?.rawDescription || "");
|
const match = CodeBlockRe.exec(props.message.content || props.message.embeds[0]?.rawDescription || "");
|
||||||
if (match) {
|
if (match) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
|
|
|
@ -24,8 +24,7 @@ import { Clipboard, Toasts } from "@webpack/common";
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "BetterRoleDot",
|
name: "BetterRoleDot",
|
||||||
authors: [Devs.Ven, Devs.AutumnVN],
|
authors: [Devs.Ven, Devs.AutumnVN],
|
||||||
description:
|
description: "Copy role colour on RoleDot (accessibility setting) click. Also allows using both RoleDot and coloured names simultaneously",
|
||||||
"Copy role colour on RoleDot (accessibility setting) click. Also allows using both RoleDot and coloured names simultaneously",
|
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,12 +27,6 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: '"ChannelAttachButton"',
|
find: '"ChannelAttachButton"',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
|
||||||
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
|
|
||||||
match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,30}?\.\.\.(\i),/,
|
|
||||||
replace: "$&onClick:$1,onContextMenu:$2.onClick,",
|
|
||||||
noWarn: true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,100}\},(\i)\).{0,100}children:\i/,
|
match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,100}\},(\i)\).{0,100}children:\i/,
|
||||||
replace: "$&,onClick:$1,onContextMenu:$2.onClick,",
|
replace: "$&,onClick:$1,onContextMenu:$2.onClick,",
|
||||||
|
|
|
@ -49,18 +49,6 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".decorationGridItem,",
|
find: ".decorationGridItem,",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
|
||||||
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
|
|
||||||
match: /(?<==)\i=>{let{children.{20,200}decorationGridItem/,
|
|
||||||
replace: "$self.DecorationGridItem=$&",
|
|
||||||
noWarn: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
|
|
||||||
match: /(?<==)\i=>{let{user:\i,avatarDecoration/,
|
|
||||||
replace: "$self.DecorationGridDecoration=$&",
|
|
||||||
noWarn: true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
match: /(?<==)\i=>{var{children.{20,200}decorationGridItem/,
|
match: /(?<==)\i=>{var{children.{20,200}decorationGridItem/,
|
||||||
replace: "$self.DecorationGridItem=$&",
|
replace: "$self.DecorationGridItem=$&",
|
||||||
|
|
|
@ -16,7 +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 { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings, Settings } from "@api/Settings";
|
||||||
|
import { getCustomColorString } from "@equicordplugins/customUserColors";
|
||||||
import { hash as h64 } from "@intrnl/xxhash64";
|
import { hash as h64 } from "@intrnl/xxhash64";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
@ -66,7 +67,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: '="SYSTEM_TAG"',
|
find: '="SYSTEM_TAG"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=className:\i\.username,style:.{0,50}:void 0,)/,
|
match: /(?<=\i.gradientClassName]\),style:.{0,80}:void 0,)/,
|
||||||
replace: "style:{color:$self.calculateNameColorForMessageContext(arguments[0])},"
|
replace: "style:{color:$self.calculateNameColorForMessageContext(arguments[0])},"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -84,30 +85,36 @@ export default definePlugin({
|
||||||
const userId: string | undefined = context?.message?.author?.id;
|
const userId: string | undefined = context?.message?.author?.id;
|
||||||
const colorString = context?.author?.colorString;
|
const colorString = context?.author?.colorString;
|
||||||
const color = calculateNameColorForUser(userId);
|
const color = calculateNameColorForUser(userId);
|
||||||
|
const customColor = userId && Settings.plugins.CustomUserColors.enabled ? getCustomColorString(userId, true) : null;
|
||||||
|
|
||||||
// Color preview in role settings
|
// Color preview in role settings
|
||||||
if (context?.message?.channel_id === "1337" && userId === "313337")
|
if (context?.message?.channel_id === "1337" && userId === "313337")
|
||||||
return colorString;
|
return customColor ?? colorString;
|
||||||
|
|
||||||
if (settings.store.applyColorOnlyInDms && !context?.channel?.isPrivate()) {
|
if (settings.store.applyColorOnlyInDms && !context?.channel?.isPrivate()) {
|
||||||
return colorString;
|
return customColor ?? colorString;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (!settings.store.applyColorOnlyToUsersWithoutColor || !colorString)
|
if (!settings.store.applyColorOnlyToUsersWithoutColor || !colorString) {
|
||||||
? color
|
return customColor ?? color;
|
||||||
: colorString;
|
} else {
|
||||||
|
return customColor ?? colorString;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
calculateNameColorForListContext(context: any) {
|
calculateNameColorForListContext(context: any) {
|
||||||
const id = context?.user?.id;
|
const id = context?.user?.id;
|
||||||
const colorString = context?.colorString;
|
const colorString = context?.colorString;
|
||||||
const color = calculateNameColorForUser(id);
|
const color = calculateNameColorForUser(id);
|
||||||
|
const customColor = id && Settings.plugins.CustomUserColors.enabled ? getCustomColorString(id, true) : null;
|
||||||
|
|
||||||
if (settings.store.applyColorOnlyInDms && !context?.channel?.isPrivate()) {
|
if (settings.store.applyColorOnlyInDms && !context?.channel?.isPrivate()) {
|
||||||
return colorString;
|
return customColor ?? colorString;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (!settings.store.applyColorOnlyToUsersWithoutColor || !colorString)
|
if (!settings.store.applyColorOnlyToUsersWithoutColor || !colorString) {
|
||||||
? color
|
return customColor ?? color;
|
||||||
: colorString;
|
} else {
|
||||||
|
return customColor ?? colorString;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -66,12 +66,6 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "{isSidebarVisible:",
|
find: "{isSidebarVisible:",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
|
||||||
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
|
|
||||||
match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
|
|
||||||
replace: ":[$1?.startsWith('members')?$self.render():null,$2",
|
|
||||||
noWarn: true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
match: /(?<=var\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
|
match: /(?<=var\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
|
||||||
replace: ":[$1?.startsWith('members')?$self.render():null,$2",
|
replace: ":[$1?.startsWith('members')?$self.render():null,$2",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Vencord, a modification for Discord's desktop app
|
* Vencord, a modification for Discord's desktop app
|
||||||
* Copyright (c) 2022 Vendicated and contributors
|
* Copyright (c) 2025 Vendicated and contributors
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -26,17 +26,16 @@ export default definePlugin({
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".nsfwAllowed=null",
|
find: ".nsfwAllowed=null",
|
||||||
replacement: {
|
replacement: [
|
||||||
match: /(?<=\.nsfwAllowed=)null!==.+?(?=[,;])/,
|
{
|
||||||
replace: "!0",
|
match: /(?<=\.nsfwAllowed=)null!==.+?(?=[,;])/,
|
||||||
},
|
replace: "true",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".ageVerificationStatus=null",
|
match: /(?<=\.ageVerificationStatus=)null!==.+?(?=[,;])/,
|
||||||
replacement: {
|
replace: "3", // VERIFIED_ADULT
|
||||||
match: /(?<=\.ageVerificationStatus=)null!==.+?(?=[,;])/,
|
}
|
||||||
replace: "3",
|
],
|
||||||
},
|
}
|
||||||
},
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
|
@ -30,12 +30,12 @@ export default definePlugin({
|
||||||
// the second is the four guild preview icons
|
// the second is the four guild preview icons
|
||||||
// always show this one (the plain icons)
|
// always show this one (the plain icons)
|
||||||
{
|
{
|
||||||
match: /\(\w\|\|\w\)&&(\(.{0,40}\(.{1,3}\.animated)/,
|
match: /\(\i\|\|\i\)&&(\(.{0,40}\(\i\.animated)/,
|
||||||
replace: "$1",
|
replace: "$1",
|
||||||
},
|
},
|
||||||
// and never show this one (the guild preview icons)
|
// and never show this one (the guild preview icons)
|
||||||
{
|
{
|
||||||
match: /\(\w\|\|!\w\)&&(\(.{0,40}\(.{1,3}\.animated)/,
|
match: /\(\i\|\|!\i\)&&(\(.{0,40}\(\i\.animated)/,
|
||||||
replace: "false&&$1",
|
replace: "false&&$1",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -84,12 +84,6 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".USER_MENTION)",
|
find: ".USER_MENTION)",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
|
||||||
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
|
|
||||||
match: /onContextMenu:\i,color:\i,\.\.\.\i(?=,children:)(?<=user:(\i),channel:(\i).{0,500}?)/,
|
|
||||||
replace: "$&,color:$self.getColorInt($1?.id,$2?.id)",
|
|
||||||
noWarn: true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
match: /(?<=onContextMenu:\i,color:)\i(?=\},\i\),\{children)(?<=user:(\i),channel:(\i).{0,500}?)/,
|
match: /(?<=onContextMenu:\i,color:)\i(?=\},\i\),\{children)(?<=user:(\i),channel:(\i).{0,500}?)/,
|
||||||
replace: "$self.getColorInt($1?.id,$2?.id)",
|
replace: "$self.getColorInt($1?.id,$2?.id)",
|
||||||
|
|
|
@ -56,7 +56,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: '?"@":""',
|
find: '?"@":""',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=children:)\(\i\?"@":""\)\+\i(?=,|\})/,
|
match: /(?<=onContextMenu:\i,children:)\i\+\i/,
|
||||||
replace: "$self.renderUsername(arguments[0])"
|
replace: "$self.renderUsername(arguments[0])"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -310,7 +310,6 @@ function Info({ track }: { track: Track; }) {
|
||||||
{track.artists.some(a => a.name) && (
|
{track.artists.some(a => a.name) && (
|
||||||
<Forms.FormText variant="text-sm/normal" className={cl(["ellipoverflow", "secondary-song-info"])}>
|
<Forms.FormText variant="text-sm/normal" className={cl(["ellipoverflow", "secondary-song-info"])}>
|
||||||
<span className={cl("song-info-prefix")}>by </span>
|
<span className={cl("song-info-prefix")}>by </span>
|
||||||
by
|
|
||||||
{track.artists.map((a, i) => (
|
{track.artists.map((a, i) => (
|
||||||
<React.Fragment key={a.name}>
|
<React.Fragment key={a.name}>
|
||||||
<span
|
<span
|
||||||
|
@ -329,7 +328,6 @@ function Info({ track }: { track: Track; }) {
|
||||||
{track.album.name && (
|
{track.album.name && (
|
||||||
<Forms.FormText variant="text-sm/normal" className={cl(["ellipoverflow", "secondary-song-info"])}>
|
<Forms.FormText variant="text-sm/normal" className={cl(["ellipoverflow", "secondary-song-info"])}>
|
||||||
<span className={cl("song-info-prefix")}>on </span>
|
<span className={cl("song-info-prefix")}>on </span>
|
||||||
on
|
|
||||||
<span
|
<span
|
||||||
id={cl("album-title")}
|
id={cl("album-title")}
|
||||||
className={cl("album")}
|
className={cl("album")}
|
||||||
|
|
|
@ -12,8 +12,7 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-spotify-artist,
|
.vc-spotify-artist, .vc-spotify-album {
|
||||||
.vc-spotify-album {
|
|
||||||
color: var(--header-primary);
|
color: var(--header-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,26 +26,26 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#vc-spotify-progress-bar>[class^="slider"] {
|
#vc-spotify-progress-bar > [class^="slider"] {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#vc-spotify-progress-bar>[class^="slider"] [class^="bar"] {
|
#vc-spotify-progress-bar > [class^="slider"] [class^="bar"] {
|
||||||
height: 3px !important;
|
height: 3px !important;
|
||||||
top: calc(12px - 4px / 2 + var(--bar-offset));
|
top: calc(12px - 4px / 2 + var(--bar-offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
#vc-spotify-progress-bar>[class^="slider"] [class^="barFill"] {
|
#vc-spotify-progress-bar > [class^="slider"] [class^="barFill"] {
|
||||||
background-color: var(--interactive-active);
|
background-color: var(--interactive-active);
|
||||||
}
|
}
|
||||||
|
|
||||||
#vc-spotify-progress-bar>[class^="slider"]:hover [class^="barFill"] {
|
#vc-spotify-progress-bar > [class^="slider"]:hover [class^="barFill"] {
|
||||||
background-color: var(--vc-spotify-green);
|
background-color: var(--vc-spotify-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
#vc-spotify-progress-bar>[class^="slider"] [class^="grabber"] {
|
#vc-spotify-progress-bar > [class^="slider"] [class^="grabber"] {
|
||||||
background-color: var(--interactive-active);
|
background-color: var(--interactive-active);
|
||||||
width: 16px !important;
|
width: 16px !important;
|
||||||
height: 16px !important;
|
height: 16px !important;
|
||||||
|
@ -68,15 +67,11 @@
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-spotify-repeat-context,
|
.vc-spotify-repeat-context, .vc-spotify-repeat-track, .vc-spotify-shuffle-on {
|
||||||
.vc-spotify-repeat-track,
|
|
||||||
.vc-spotify-shuffle-on {
|
|
||||||
background-color: var(--vc-spotify-green-90);
|
background-color: var(--vc-spotify-green-90);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-spotify-repeat-context:hover,
|
.vc-spotify-repeat-context:hover, .vc-spotify-repeat-track:hover, .vc-spotify-shuffle-on:hover {
|
||||||
.vc-spotify-repeat-track:hover,
|
|
||||||
.vc-spotify-shuffle-on:hover {
|
|
||||||
background-color: var(--vc-spotify-green-80);
|
background-color: var(--vc-spotify-green-80);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,14 +28,6 @@ export default definePlugin({
|
||||||
patches: [{
|
patches: [{
|
||||||
find: "#{intl::ACTIVITY_SETTINGS}",
|
find: "#{intl::ACTIVITY_SETTINGS}",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
|
||||||
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
|
|
||||||
match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+}\)}\))/,
|
|
||||||
replace: (_, commaOrSemi, settings, elements) => "" +
|
|
||||||
`${commaOrSemi}${settings}?.[0]==="CHANGELOG"` +
|
|
||||||
`&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})`,
|
|
||||||
noWarn: true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+\)\)\}\))(?=\)\})/,
|
match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+\)\)\}\))(?=\)\})/,
|
||||||
replace: (_, commaOrSemi, settings, elements) => "" +
|
replace: (_, commaOrSemi, settings, elements) => "" +
|
||||||
|
|
|
@ -55,7 +55,7 @@ export default definePlugin({
|
||||||
],
|
],
|
||||||
|
|
||||||
getAvatarStyles(src: string | null) {
|
getAvatarStyles(src: string | null) {
|
||||||
if (src == null || src.startsWith("data:")) return {};
|
if (!src || src.startsWith("data:")) return {};
|
||||||
|
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
[128, 256, 512, 1024, 2048, 4096].map(size => [
|
[128, 256, 512, 1024, 2048, 4096].map(size => [
|
||||||
|
|
|
@ -42,13 +42,6 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: '="SYSTEM_TAG"',
|
find: '="SYSTEM_TAG"',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
|
||||||
// Add next to username (compact mode)
|
|
||||||
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
|
|
||||||
match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\),(?=\i)/g,
|
|
||||||
replace: "$&$self.CompactPronounsChatComponentWrapper(arguments[0]),",
|
|
||||||
noWarn: true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
// Add next to username (compact mode)
|
// Add next to username (compact mode)
|
||||||
match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\)\),(?=\i)/g,
|
match: /className:\i\(\)\(\i\.className(?:,\i\.clickable)?,\i\)}\)\),(?=\i)/g,
|
||||||
|
|
|
@ -194,12 +194,6 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".overlay:void 0,status:",
|
find: ".overlay:void 0,status:",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
|
||||||
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
|
|
||||||
match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",{...\2,/,
|
|
||||||
replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)},",
|
|
||||||
noWarn: true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",.{0,100}className:\i,/,
|
match: /avatarSrc:(\i),eventHandlers:(\i).+?"div",.{0,100}className:\i,/,
|
||||||
replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)},",
|
replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openAvatar($1)},",
|
||||||
|
|
|
@ -53,13 +53,16 @@ const settings = definePluginSettings({
|
||||||
addBack: {
|
addBack: {
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
description: "Add back the Discord context menus for images, links and the chat input bar",
|
description: "Add back the Discord context menus for images, links and the chat input bar",
|
||||||
|
default: false,
|
||||||
|
restartNeeded: true,
|
||||||
// Web slate menu has proper spellcheck suggestions and image context menu is also pretty good,
|
// Web slate menu has proper spellcheck suggestions and image context menu is also pretty good,
|
||||||
// so disable this by default. Vesktop just doesn't, so enable by default
|
// so disable this by default. Vesktop just doesn't, so we force enable it there
|
||||||
default: result,
|
hidden: result,
|
||||||
restartNeeded: true
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const shouldAddBackMenus = () => result || settings.store.addBack;
|
||||||
|
|
||||||
const MEDIA_PROXY_URL = "https://media.discordapp.net";
|
const MEDIA_PROXY_URL = "https://media.discordapp.net";
|
||||||
const CDN_URL = "cdn.discordapp.com";
|
const CDN_URL = "cdn.discordapp.com";
|
||||||
|
|
||||||
|
@ -92,7 +95,7 @@ export default definePlugin({
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
if (settings.store.addBack) {
|
if (shouldAddBackMenus()) {
|
||||||
window.removeEventListener("contextmenu", ctxMenuCallbacks.contextMenuCallbackWeb);
|
window.removeEventListener("contextmenu", ctxMenuCallbacks.contextMenuCallbackWeb);
|
||||||
window.addEventListener("contextmenu", ctxMenuCallbacks.contextMenuCallbackNative);
|
window.addEventListener("contextmenu", ctxMenuCallbacks.contextMenuCallbackNative);
|
||||||
this.changedListeners = true;
|
this.changedListeners = true;
|
||||||
|
@ -155,7 +158,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: 'navId:"image-context"',
|
find: 'navId:"image-context"',
|
||||||
all: true,
|
all: true,
|
||||||
predicate: () => settings.store.addBack,
|
predicate: shouldAddBackMenus,
|
||||||
replacement: {
|
replacement: {
|
||||||
// return IS_DESKTOP ? React.createElement(Menu, ...)
|
// return IS_DESKTOP ? React.createElement(Menu, ...)
|
||||||
match: /return \i\.\i(?=\?|&&)/,
|
match: /return \i\.\i(?=\?|&&)/,
|
||||||
|
@ -166,7 +169,7 @@ export default definePlugin({
|
||||||
// Add back link context menu
|
// Add back link context menu
|
||||||
{
|
{
|
||||||
find: '"interactionUsernameProfile"',
|
find: '"interactionUsernameProfile"',
|
||||||
predicate: () => settings.store.addBack,
|
predicate: shouldAddBackMenus,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /if\((?="A"===\i\.tagName&&""!==\i\.textContent)/,
|
match: /if\((?="A"===\i\.tagName&&""!==\i\.textContent)/,
|
||||||
replace: "if(false&&"
|
replace: "if(false&&"
|
||||||
|
@ -176,7 +179,7 @@ export default definePlugin({
|
||||||
// Add back slate / text input context menu
|
// Add back slate / text input context menu
|
||||||
{
|
{
|
||||||
find: 'getElementById("slate-toolbar"',
|
find: 'getElementById("slate-toolbar"',
|
||||||
predicate: () => settings.store.addBack,
|
predicate: shouldAddBackMenus,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=handleContextMenu\(\i\)\{.{0,200}isPlatformEmbedded)\)/,
|
match: /(?<=handleContextMenu\(\i\)\{.{0,200}isPlatformEmbedded)\)/,
|
||||||
replace: "||true)"
|
replace: "||true)"
|
||||||
|
@ -184,7 +187,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".SLASH_COMMAND_SUGGESTIONS_TOGGLED,{",
|
find: ".SLASH_COMMAND_SUGGESTIONS_TOGGLED,{",
|
||||||
predicate: () => settings.store.addBack,
|
predicate: shouldAddBackMenus,
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
// if (!IS_DESKTOP) return null;
|
// if (!IS_DESKTOP) return null;
|
||||||
|
@ -200,7 +203,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: '"add-to-dictionary"',
|
find: '"add-to-dictionary"',
|
||||||
predicate: () => settings.store.addBack,
|
predicate: shouldAddBackMenus,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /let\{text:\i=""/,
|
match: /let\{text:\i=""/,
|
||||||
replace: "return [null,null];$&"
|
replace: "return [null,null];$&"
|
||||||
|
|
|
@ -24,6 +24,7 @@ export const GUILD_ID = "1173279886065029291";
|
||||||
export const DONOR_ROLE_ID = "1173316879083896912";
|
export const DONOR_ROLE_ID = "1173316879083896912";
|
||||||
export const CONTRIB_ROLE_ID = "1222677964760682556";
|
export const CONTRIB_ROLE_ID = "1222677964760682556";
|
||||||
export const EQUICORD_TEAM = "1173520023239786538";
|
export const EQUICORD_TEAM = "1173520023239786538";
|
||||||
|
export const EQUCORD_HELPERS = "1326406112144265257";
|
||||||
export const EQUIBOP_CONTRIB_ROLE_ID = "1287079931645263968";
|
export const EQUIBOP_CONTRIB_ROLE_ID = "1287079931645263968";
|
||||||
export const VENCORD_CONTRIB_ROLE_ID = "1173343399470964856";
|
export const VENCORD_CONTRIB_ROLE_ID = "1173343399470964856";
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ export const VENBOT_USER_ID = "1017176847865352332";
|
||||||
export const VC_DONOR_ROLE_ID = "1042507929485586532";
|
export const VC_DONOR_ROLE_ID = "1042507929485586532";
|
||||||
export const VC_CONTRIB_ROLE_ID = "1026534353167208489";
|
export const VC_CONTRIB_ROLE_ID = "1026534353167208489";
|
||||||
export const VC_REGULAR_ROLE_ID = "1026504932959977532";
|
export const VC_REGULAR_ROLE_ID = "1026504932959977532";
|
||||||
|
export const VC_SUPPORT_CATEGORY_ID = "1108135649699180705";
|
||||||
export const VC_KNOWN_ISSUES_CHANNEL_ID = "1222936386626129920";
|
export const VC_KNOWN_ISSUES_CHANNEL_ID = "1222936386626129920";
|
||||||
|
|
||||||
export const GUILD_IDS = [GUILD_ID, VC_GUILD_ID];
|
export const GUILD_IDS = [GUILD_ID, VC_GUILD_ID];
|
||||||
|
@ -807,6 +809,10 @@ export const EquicordDevs = Object.freeze({
|
||||||
name: "Hen",
|
name: "Hen",
|
||||||
id: 279266228151779329n
|
id: 279266228151779329n
|
||||||
},
|
},
|
||||||
|
Crxa: {
|
||||||
|
name: "Crxa",
|
||||||
|
id: 920290194886914069n
|
||||||
|
},
|
||||||
vmohammad: {
|
vmohammad: {
|
||||||
name: "vMohammad",
|
name: "vMohammad",
|
||||||
id: 921098159348924457n
|
id: 921098159348924457n
|
||||||
|
@ -1020,10 +1026,18 @@ export const EquicordDevs = Object.freeze({
|
||||||
name: "talhakf",
|
name: "talhakf",
|
||||||
id: 1140716160560676976n
|
id: 1140716160560676976n
|
||||||
},
|
},
|
||||||
|
xijexo: {
|
||||||
|
name: "xijexo",
|
||||||
|
id: 1284113557201620995n
|
||||||
|
},
|
||||||
omaw: {
|
omaw: {
|
||||||
name: "omaw",
|
name: "omaw",
|
||||||
id: 1155026301791514655n
|
id: 1155026301791514655n
|
||||||
},
|
},
|
||||||
|
WKoA: {
|
||||||
|
name: "WKoA",
|
||||||
|
id: 724416180097384498n
|
||||||
|
},
|
||||||
} satisfies Record<string, Dev>);
|
} satisfies Record<string, Dev>);
|
||||||
|
|
||||||
// iife so #__PURE__ works correctly
|
// iife so #__PURE__ works correctly
|
||||||
|
|
12
src/webpack/common/types/components.d.ts
vendored
12
src/webpack/common/types/components.d.ts
vendored
|
@ -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/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PointerEvent, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react";
|
import type { ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PointerEvent, PropsWithChildren, ReactNode, Ref } from "react";
|
||||||
|
|
||||||
|
|
||||||
export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code";
|
export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code";
|
||||||
|
@ -245,7 +245,8 @@ export type TextInput = ComponentType<PropsWithChildren<{
|
||||||
onChange?(value: string, name?: string): void;
|
onChange?(value: string, name?: string): void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
editable?: boolean;
|
editable?: boolean;
|
||||||
maxLength?: number;
|
/** defaults to 999. Pass null to disable this default */
|
||||||
|
maxLength?: number | null;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
|
||||||
inputClassName?: string;
|
inputClassName?: string;
|
||||||
|
@ -257,13 +258,14 @@ export type TextInput = ComponentType<PropsWithChildren<{
|
||||||
|
|
||||||
/** TextInput.Sizes.DEFAULT */
|
/** TextInput.Sizes.DEFAULT */
|
||||||
size?: string;
|
size?: string;
|
||||||
} & Omit<HTMLProps<HTMLInputElement>, "onChange">>> & {
|
} & Omit<HTMLProps<HTMLInputElement>, "onChange" | "maxLength">>> & {
|
||||||
Sizes: Record<"DEFAULT" | "MINI", string>;
|
Sizes: Record<"DEFAULT" | "MINI", string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TextArea = ComponentType<PropsWithRef<Omit<HTMLProps<HTMLTextAreaElement>, "onChange"> & {
|
export type TextArea = ComponentType<Omit<HTMLProps<HTMLTextAreaElement>, "onChange"> & {
|
||||||
onChange(v: string): void;
|
onChange(v: string): void;
|
||||||
}>>;
|
}>;
|
||||||
|
|
||||||
export interface SelectOption {
|
export interface SelectOption {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
value: any;
|
value: any;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue