1
0
Fork 1
mirror of https://github.com/Vendicated/Vencord.git synced 2025-01-10 18:06:22 +00:00

Merge branch 'dev' into NeverPausePreviews

This commit is contained in:
vappster 2024-06-26 20:05:51 +02:00 committed by GitHub
commit 12dfda93a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 368 additions and 68 deletions

View file

@ -44,6 +44,11 @@ export interface ProfileBadge {
position?: BadgePosition; position?: BadgePosition;
/** The badge name to display, Discord uses this. Required for component badges */ /** The badge name to display, Discord uses this. Required for component badges */
key?: string; key?: string;
/**
* Allows dynamically returning multiple badges
*/
getBadges?(userInfo: BadgeUserArgs): ProfileBadge[];
} }
const Badges = new Set<ProfileBadge>(); const Badges = new Set<ProfileBadge>();
@ -73,9 +78,16 @@ export function _getBadges(args: BadgeUserArgs) {
const badges = [] as ProfileBadge[]; const badges = [] as ProfileBadge[];
for (const badge of Badges) { for (const badge of Badges) {
if (!badge.shouldShow || badge.shouldShow(args)) { if (!badge.shouldShow || badge.shouldShow(args)) {
const b = badge.getBadges
? badge.getBadges(args).map(b => {
b.component &&= ErrorBoundary.wrap(b.component, { noop: true });
return b;
})
: [{ ...badge, ...args }];
badge.position === BadgePosition.START badge.position === BadgePosition.START
? badges.unshift({ ...badge, ...args }) ? badges.unshift(...b)
: badges.push({ ...badge, ...args }); : badges.push(...b);
} }
} }
const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.userId); const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.userId);

View file

@ -31,10 +31,20 @@ export interface ExpandableHeaderProps {
headerText: string; headerText: string;
children: React.ReactNode; children: React.ReactNode;
buttons?: React.ReactNode[]; buttons?: React.ReactNode[];
forceOpen?: boolean;
} }
export function ExpandableHeader({ children, onMoreClick, buttons, moreTooltipText, defaultState = false, onDropDownClick, headerText }: ExpandableHeaderProps) { export function ExpandableHeader({
const [showContent, setShowContent] = useState(defaultState); children,
onMoreClick,
buttons,
moreTooltipText,
onDropDownClick,
headerText,
defaultState = false,
forceOpen = false,
}: ExpandableHeaderProps) {
const [showContent, setShowContent] = useState(defaultState || forceOpen);
return ( return (
<> <>
@ -90,6 +100,7 @@ export function ExpandableHeader({ children, onMoreClick, buttons, moreTooltipTe
setShowContent(v => !v); setShowContent(v => !v);
onDropDownClick?.(showContent); onDropDownClick?.(showContent);
}} }}
disabled={forceOpen}
> >
<svg <svg
width="24" width="24"

View file

@ -290,3 +290,42 @@ export function NoEntrySignIcon(props: IconProps) {
</Icon> </Icon>
); );
} }
export function SafetyIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-safety-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
fillRule="evenodd"
clipRule="evenodd"
d="M4.27 5.22A2.66 2.66 0 0 0 3 7.5v2.3c0 5.6 3.3 10.68 8.42 12.95.37.17.79.17 1.16 0A14.18 14.18 0 0 0 21 9.78V7.5c0-.93-.48-1.78-1.27-2.27l-6.17-3.76a3 3 0 0 0-3.12 0L4.27 5.22ZM6 7.68l6-3.66V12H6.22C6.08 11.28 6 10.54 6 9.78v-2.1Zm6 12.01V12h5.78A11.19 11.19 0 0 1 12 19.7Z"
/>
</Icon>
);
}
export function NotesIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-notes-icon")}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M8 3C7.44771 3 7 3.44772 7 4V5C7 5.55228 7.44772 6 8 6H16C16.5523 6 17 5.55228 17 5V4C17 3.44772 16.5523 3 16 3H15.1245C14.7288 3 14.3535 2.82424 14.1002 2.52025L13.3668 1.64018C13.0288 1.23454 12.528 1 12 1C11.472 1 10.9712 1.23454 10.6332 1.64018L9.8998 2.52025C9.64647 2.82424 9.27121 3 8.8755 3H8Z"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
fill="currentColor"
d="M19 4.49996V4.99996C19 6.65681 17.6569 7.99996 16 7.99996H8C6.34315 7.99996 5 6.65681 5 4.99996V4.49996C5 4.22382 4.77446 3.99559 4.50209 4.04109C3.08221 4.27826 2 5.51273 2 6.99996V19C2 20.6568 3.34315 22 5 22H19C20.6569 22 22 20.6568 22 19V6.99996C22 5.51273 20.9178 4.27826 19.4979 4.04109C19.2255 3.99559 19 4.22382 19 4.49996ZM8 12C7.44772 12 7 12.4477 7 13C7 13.5522 7.44772 14 8 14H16C16.5523 14 17 13.5522 17 13C17 12.4477 16.5523 12 16 12H8ZM7 17C7 16.4477 7.44772 16 8 16H13C13.5523 16 14 16.4477 14 17C14 17.5522 13.5523 18 13 18H8C7.44772 18 7 17.5522 7 17Z"
/>
</Icon>
);
}

View file

@ -136,6 +136,8 @@ export default definePlugin({
}, },
getBadges(props: { userId: string; user?: User; guildId: string; }) { getBadges(props: { userId: string; user?: User; guildId: string; }) {
if (!props) return [];
try { try {
props.userId ??= props.user?.id!; props.userId ??= props.user?.id!;

View file

@ -109,9 +109,9 @@ interface ProfileModalProps {
} }
const ColorPicker = findComponentByCodeLazy<ColorPickerProps>(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)"); const ColorPicker = findComponentByCodeLazy<ColorPickerProps>(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
const ProfileModal = findComponentByCodeLazy<ProfileModalProps>('"ProfileCustomizationPreview"'); const ProfileModal = findComponentByCodeLazy("isTryItOutFlow:", "pendingThemeColors:", "avatarDecorationOverride:", ".CUSTOM_STATUS");
const requireColorPicker = extractAndLoadChunksLazy(["USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON.format"], /createPromise:\(\)=>\i\.\i\("?(.+?)"?\).then\(\i\.bind\(\i,"?(.+?)"?\)\)/); const requireColorPicker = extractAndLoadChunksLazy(["USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON.format"], /createPromise:\(\)=>\i\.\i(\("?.+?"?\)).then\(\i\.bind\(\i,"?(.+?)"?\)\)/);
export default definePlugin({ export default definePlugin({
name: "FakeProfileThemes", name: "FakeProfileThemes",

View file

@ -49,7 +49,7 @@ export default definePlugin({
find: ".Messages.MUTUAL_GUILDS_WITH_END_COUNT", // Note: the module is lazy-loaded find: ".Messages.MUTUAL_GUILDS_WITH_END_COUNT", // Note: the module is lazy-loaded
replacement: { replacement: {
match: /(?<=\.tabBarItem.{0,50}MUTUAL_GUILDS.+?}\),)(?=.+?(\(0,\i\.jsxs?\)\(.{0,100}id:))/, match: /(?<=\.tabBarItem.{0,50}MUTUAL_GUILDS.+?}\),)(?=.+?(\(0,\i\.jsxs?\)\(.{0,100}id:))/,
replace: '(arguments[0].user.bot||arguments[0].isCurrentUser)?null:$1"MUTUAL_GDMS",children:"Mutual Groups"}),' replace: '$self.isBotOrSelf(arguments[0].user)?null:$1"MUTUAL_GDMS",children:"Mutual Groups"}),'
} }
}, },
{ {
@ -58,9 +58,24 @@ export default definePlugin({
match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/, match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/,
replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs({user: $1, onClose: $2});" replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs({user: $1, onClose: $2});"
} }
},
{
find: ".MUTUAL_FRIENDS?(",
replacement: [
{
match: /(?<=onItemSelect:\i,children:)(\i)\.map/,
replace: "[...$1, ...($self.isBotOrSelf(arguments[0].user) ? [] : [{section:'MUTUAL_GDMS',text:'Mutual Groups'}])].map"
},
{
match: /\(0,\i\.jsx\)\(\i,\{items:\i,section:(\i)/,
replace: "$1==='MUTUAL_GDMS'?$self.renderMutualGDMs(arguments[0]):$&"
}
]
} }
], ],
isBotOrSelf: (user: User) => user.bot || user.id === UserStore.getCurrentUser().id,
renderMutualGDMs: ErrorBoundary.wrap(({ user, onClose }: { user: User, onClose: () => void; }) => { renderMutualGDMs: ErrorBoundary.wrap(({ user, onClose }: { user: User, onClose: () => void; }) => {
const entries = ChannelStore.getSortedPrivateChannels().filter(c => c.isGroupDM() && c.recipients.includes(user.id)).map(c => ( const entries = ChannelStore.getSortedPrivateChannels().filter(c => c.isGroupDM() && c.recipients.includes(user.id)).map(c => (
<Clickable <Clickable

View file

@ -16,10 +16,17 @@
* 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 {
findGroupChildrenByChildId,
NavContextMenuPatchCallback
} from "@api/ContextMenu";
import { definePluginSettings, migratePluginSettings } from "@api/Settings"; import { definePluginSettings, migratePluginSettings } from "@api/Settings";
import { CogWheel } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "@webpack"; import { findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
import { Menu } from "@webpack/common";
import { Guild } from "discord-types/general";
const { updateGuildNotificationSettings } = findByPropsLazy("updateGuildNotificationSettings"); const { updateGuildNotificationSettings } = findByPropsLazy("updateGuildNotificationSettings");
const { toggleShowAllChannels } = mapMangledModuleLazy(".onboardExistingMember(", { const { toggleShowAllChannels } = mapMangledModuleLazy(".onboardExistingMember(", {
@ -73,31 +80,21 @@ const settings = definePluginSettings({
} }
}); });
migratePluginSettings("NewGuildSettings", "MuteNewGuild"); const makeContextMenuPatch: (shouldAddIcon: boolean) => NavContextMenuPatchCallback = (shouldAddIcon: boolean) => (children, { guild }: { guild: Guild, onClose(): void; }) => {
export default definePlugin({ if (!guild) return;
name: "NewGuildSettings",
description: "Automatically mute new servers and change various other settings upon joining",
tags: ["MuteNewGuild", "mute", "server"],
authors: [Devs.Glitch, Devs.Nuckyz, Devs.carince, Devs.Mopi, Devs.GabiRP],
patches: [
{
find: ",acceptInvite(",
replacement: {
match: /INVITE_ACCEPT_SUCCESS.+?,(\i)=null!==.+?;/,
replace: (m, guildId) => `${m}$self.handleMute(${guildId});`
}
},
{
find: "{joinGuild:",
replacement: {
match: /guildId:(\i),lurker:(\i).{0,20}}\)\);/,
replace: (m, guildId, lurker) => `${m}if(!${lurker})$self.handleMute(${guildId});`
}
}
],
settings,
handleMute(guildId: string | null) { const group = findGroupChildrenByChildId("privacy", children);
group?.push(
<Menu.MenuItem
label="Apply NewGuildSettings"
id="vc-newguildsettings-apply"
icon={shouldAddIcon ? CogWheel : void 0}
action={() => applyDefaultSettings(guild.id)}
/>
);
};
function applyDefaultSettings(guildId: string | null) {
if (guildId === "@me" || guildId === "null" || guildId == null) return; if (guildId === "@me" || guildId === "null" || guildId == null) return;
updateGuildNotificationSettings(guildId, updateGuildNotificationSettings(guildId,
{ {
@ -116,5 +113,35 @@ export default definePlugin({
if (settings.store.showAllChannels && isOptInEnabledForGuild(guildId)) { if (settings.store.showAllChannels && isOptInEnabledForGuild(guildId)) {
toggleShowAllChannels(guildId); toggleShowAllChannels(guildId);
} }
}
migratePluginSettings("NewGuildSettings", "MuteNewGuild");
export default definePlugin({
name: "NewGuildSettings",
description: "Automatically mute new servers and change various other settings upon joining",
tags: ["MuteNewGuild", "mute", "server"],
authors: [Devs.Glitch, Devs.Nuckyz, Devs.carince, Devs.Mopi, Devs.GabiRP],
contextMenus: {
"guild-context": makeContextMenuPatch(false),
"guild-header-popout": makeContextMenuPatch(true)
},
patches: [
{
find: ",acceptInvite(",
replacement: {
match: /INVITE_ACCEPT_SUCCESS.+?,(\i)=null!==.+?;/,
replace: (m, guildId) => `${m}$self.applyDefaultSettings(${guildId});`
} }
},
{
find: "{joinGuild:",
replacement: {
match: /guildId:(\i),lurker:(\i).{0,20}}\)\);/,
replace: (m, guildId, lurker) => `${m}if(!${lurker})$self.applyDefaultSettings(${guildId});`
}
}
],
settings,
applyDefaultSettings
}); });

View file

@ -43,7 +43,7 @@ const Classes = proxyLazyWebpack(() =>
)) ))
) as Record<"roles" | "rolePill" | "rolePillBorder" | "desaturateUserColors" | "flex" | "alignCenter" | "justifyCenter" | "svg" | "background" | "dot" | "dotBorderColor" | "roleCircle" | "dotBorderBase" | "flex" | "alignCenter" | "justifyCenter" | "wrap" | "root" | "role" | "roleRemoveButton" | "roleDot" | "roleFlowerStar" | "roleRemoveIcon" | "roleRemoveIconFocused" | "roleVerifiedIcon" | "roleName" | "roleNameOverflow" | "actionButton" | "overflowButton" | "addButton" | "addButtonIcon" | "overflowRolesPopout" | "overflowRolesPopoutArrowWrapper" | "overflowRolesPopoutArrow" | "popoutBottom" | "popoutTop" | "overflowRolesPopoutHeader" | "overflowRolesPopoutHeaderIcon" | "overflowRolesPopoutHeaderText" | "roleIcon", string>; ) as Record<"roles" | "rolePill" | "rolePillBorder" | "desaturateUserColors" | "flex" | "alignCenter" | "justifyCenter" | "svg" | "background" | "dot" | "dotBorderColor" | "roleCircle" | "dotBorderBase" | "flex" | "alignCenter" | "justifyCenter" | "wrap" | "root" | "role" | "roleRemoveButton" | "roleDot" | "roleFlowerStar" | "roleRemoveIcon" | "roleRemoveIconFocused" | "roleVerifiedIcon" | "roleName" | "roleNameOverflow" | "actionButton" | "overflowButton" | "addButton" | "addButtonIcon" | "overflowRolesPopout" | "overflowRolesPopoutArrowWrapper" | "overflowRolesPopoutArrow" | "popoutBottom" | "popoutTop" | "overflowRolesPopoutHeader" | "overflowRolesPopoutHeaderIcon" | "overflowRolesPopoutHeaderText" | "roleIcon", string>;
function UserPermissionsComponent({ guild, guildMember, showBorder }: { guild: Guild; guildMember: GuildMember; showBorder: boolean; }) { function UserPermissionsComponent({ guild, guildMember, showBorder, forceOpen = false }: { guild: Guild; guildMember: GuildMember; showBorder: boolean; forceOpen?: boolean; }) {
const stns = settings.use(["permissionsSortOrder"]); const stns = settings.use(["permissionsSortOrder"]);
const [rolePermissions, userPermissions] = useMemo(() => { const [rolePermissions, userPermissions] = useMemo(() => {
@ -95,6 +95,7 @@ function UserPermissionsComponent({ guild, guildMember, showBorder }: { guild: G
return ( return (
<ExpandableHeader <ExpandableHeader
forceOpen={forceOpen}
headerText="Permissions" headerText="Permissions"
moreTooltipText="Role Details" moreTooltipText="Role Details"
onMoreClick={() => onMoreClick={() =>

View file

@ -20,15 +20,22 @@ import "./styles.css";
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { SafetyIcon } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { classes } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { ChannelStore, GuildMemberStore, GuildStore, Menu, PermissionsBits, UserStore } from "@webpack/common"; import { findByPropsLazy } from "@webpack";
import { Button, ChannelStore, Dialog, GuildMemberStore, GuildStore, Menu, PermissionsBits, Popout, TooltipContainer, UserStore } from "@webpack/common";
import type { Guild, GuildMember } from "discord-types/general"; import type { Guild, GuildMember } from "discord-types/general";
import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "./components/RolesAndUsersPermissions"; import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "./components/RolesAndUsersPermissions";
import UserPermissions from "./components/UserPermissions"; import UserPermissions from "./components/UserPermissions";
import { getSortedRoles, sortPermissionOverwrites } from "./utils"; import { getSortedRoles, sortPermissionOverwrites } from "./utils";
const PopoutClasses = findByPropsLazy("container", "scroller", "list");
const RoleButtonClasses = findByPropsLazy("button", "buttonInner", "icon", "text");
export const enum PermissionsSortOrder { export const enum PermissionsSortOrder {
HighestRole, HighestRole,
LowestRole LowestRole
@ -168,10 +175,45 @@ export default definePlugin({
match: /showBorder:(.{0,60})}\),(?<=guild:(\i),guildMember:(\i),.+?)/, match: /showBorder:(.{0,60})}\),(?<=guild:(\i),guildMember:(\i),.+?)/,
replace: (m, showBoder, guild, guildMember) => `${m}$self.UserPermissions(${guild},${guildMember},${showBoder}),` replace: (m, showBoder, guild, guildMember) => `${m}$self.UserPermissions(${guild},${guildMember},${showBoder}),`
} }
},
{
find: ".VIEW_ALL_ROLES,",
replacement: {
match: /children:"\+"\.concat\(\i\.length-\i\.length\).{0,20}\}\),/,
replace: "$&$self.ViewPermissionsButton(arguments[0]),"
}
} }
], ],
UserPermissions: (guild: Guild, guildMember: GuildMember | undefined, showBoder: boolean) => !!guildMember && <UserPermissions guild={guild} guildMember={guildMember} showBorder={showBoder} />, UserPermissions: (guild: Guild, guildMember: GuildMember | undefined, showBorder: boolean) =>
!!guildMember && <UserPermissions guild={guild} guildMember={guildMember} showBorder={showBorder} />,
ViewPermissionsButton: ErrorBoundary.wrap(({ guild, guildMember }: { guild: Guild; guildMember: GuildMember; }) => (
<Popout
position="bottom"
align="center"
renderPopout={() => (
<Dialog className={PopoutClasses.container} style={{ width: "500px" }}>
<UserPermissions guild={guild} guildMember={guildMember} showBorder forceOpen />
</Dialog>
)}
>
{popoutProps => (
<TooltipContainer text="View Permissions">
<Button
{...popoutProps}
color={Button.Colors.CUSTOM}
look={Button.Looks.FILLED}
size={Button.Sizes.NONE}
innerClassName={classes(RoleButtonClasses.buttonInner, RoleButtonClasses.icon)}
className={classes(RoleButtonClasses.button, RoleButtonClasses.icon, "vc-permviewer-role-button")}
>
<SafetyIcon height="16" width="16" />
</Button>
</TooltipContainer>
)}
</Popout>
), { noop: true }),
contextMenus: { contextMenus: {
"user-context": makeContextMenuPatch("roles", MenuItemParentType.User), "user-context": makeContextMenuPatch("roles", MenuItemParentType.User),

View file

@ -149,3 +149,21 @@
.vc-permviewer-perms-perms-item .vc-info-icon:hover { .vc-permviewer-perms-perms-item .vc-info-icon:hover {
color: var(--interactive-active); color: var(--interactive-active);
} }
/* copy pasted from discord cause impossible to webpack find */
.vc-permviewer-role-button {
border-radius: var(--radius-xs);
background: var(--bg-mod-faint);
color: var(--interactive-normal);
border: 1px solid var(--border-faint);
/* stylelint-disable-next-line value-no-vendor-prefix */
width: -moz-fit-content;
width: fit-content;
height: 24px;
padding: 4px
}
.custom-profile-theme .vc-permviewer-role-button {
background: rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-6));
border-color: var(--profile-body-border-color)
}

View file

@ -16,7 +16,9 @@
* 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 { addBadge, BadgePosition, ProfileBadge, removeBadge } from "@api/Badges"; import "./style.css";
import { addBadge, BadgePosition, BadgeUserArgs, ProfileBadge, removeBadge } from "@api/Badges";
import { addDecorator, removeDecorator } from "@api/MemberListDecorators"; import { addDecorator, removeDecorator } from "@api/MemberListDecorators";
import { addDecoration, removeDecoration } from "@api/MessageDecorations"; import { addDecoration, removeDecoration } from "@api/MessageDecorations";
import { Settings } from "@api/Settings"; import { Settings } from "@api/Settings";
@ -27,7 +29,20 @@ import { findByPropsLazy, findStoreLazy } from "@webpack";
import { PresenceStore, Tooltip, UserStore } from "@webpack/common"; import { PresenceStore, Tooltip, UserStore } from "@webpack/common";
import { User } from "discord-types/general"; import { User } from "discord-types/general";
const SessionsStore = findStoreLazy("SessionsStore"); export interface Session {
sessionId: string;
status: string;
active: boolean;
clientInfo: {
version: number;
os: string;
client: string;
};
}
const SessionsStore = findStoreLazy("SessionsStore") as {
getSessions(): Record<string, Session>;
};
function Icon(path: string, opts?: { viewBox?: string; width?: number; height?: number; }) { function Icon(path: string, opts?: { viewBox?: string; width?: number; height?: number; }) {
return ({ color, tooltip, small }: { color: string; tooltip: string; small: boolean; }) => ( return ({ color, tooltip, small }: { color: string; tooltip: string; small: boolean; }) => (
@ -67,15 +82,11 @@ const PlatformIcon = ({ platform, status, small }: { platform: Platform, status:
return <Icon color={StatusUtils.useStatusFillColor(status)} tooltip={tooltip} small={small} />; return <Icon color={StatusUtils.useStatusFillColor(status)} tooltip={tooltip} small={small} />;
}; };
const getStatus = (id: string): Record<Platform, string> => PresenceStore.getState()?.clientStatuses?.[id]; function ensureOwnStatus(user: User) {
const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, small = false }: { user: User; wantMargin?: boolean; wantTopMargin?: boolean; small?: boolean; }) => {
if (!user || user.bot) return null;
if (user.id === UserStore.getCurrentUser().id) { if (user.id === UserStore.getCurrentUser().id) {
const sessions = SessionsStore.getSessions(); const sessions = SessionsStore.getSessions();
if (typeof sessions !== "object") return null; if (typeof sessions !== "object") return null;
const sortedSessions = Object.values(sessions).sort(({ status: a }: any, { status: b }: any) => { const sortedSessions = Object.values(sessions).sort(({ status: a }, { status: b }) => {
if (a === b) return 0; if (a === b) return 0;
if (a === "online") return 1; if (a === "online") return 1;
if (b === "online") return -1; if (b === "online") return -1;
@ -84,7 +95,7 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma
return 0; return 0;
}); });
const ownStatus = Object.values(sortedSessions).reduce((acc: any, curr: any) => { const ownStatus = Object.values(sortedSessions).reduce((acc, curr) => {
if (curr.clientInfo.client !== "unknown") if (curr.clientInfo.client !== "unknown")
acc[curr.clientInfo.client] = curr.status; acc[curr.clientInfo.client] = curr.status;
return acc; return acc;
@ -93,6 +104,37 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma
const { clientStatuses } = PresenceStore.getState(); const { clientStatuses } = PresenceStore.getState();
clientStatuses[UserStore.getCurrentUser().id] = ownStatus; clientStatuses[UserStore.getCurrentUser().id] = ownStatus;
} }
}
function getBadges({ userId }: BadgeUserArgs): ProfileBadge[] {
const user = UserStore.getUser(userId);
if (!user || user.bot) return [];
ensureOwnStatus(user);
const status = PresenceStore.getState()?.clientStatuses?.[user.id] as Record<Platform, string>;
if (!status) return [];
return Object.entries(status).map(([platform, status]) => ({
component: () => (
<span className="vc-platform-indicator">
<PlatformIcon
key={platform}
platform={platform as Platform}
status={status}
small={false}
/>
</span>
),
key: `vc-platform-indicator-${platform}`
}));
}
const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, small = false }: { user: User; wantMargin?: boolean; wantTopMargin?: boolean; small?: boolean; }) => {
if (!user || user.bot) return null;
ensureOwnStatus(user);
const status = PresenceStore.getState()?.clientStatuses?.[user.id] as Record<Platform, string>; const status = PresenceStore.getState()?.clientStatuses?.[user.id] as Record<Platform, string>;
if (!status) return null; if (!status) return null;
@ -112,17 +154,10 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma
<span <span
className="vc-platform-indicator" className="vc-platform-indicator"
style={{ style={{
display: "inline-flex",
justifyContent: "center",
alignItems: "center",
marginLeft: wantMargin ? 4 : 0, marginLeft: wantMargin ? 4 : 0,
verticalAlign: "top",
position: "relative",
top: wantTopMargin ? 2 : 0, top: wantTopMargin ? 2 : 0,
padding: !wantMargin ? 1 : 0,
gap: 2 gap: 2
}} }}
> >
{icons} {icons}
</span> </span>
@ -130,10 +165,8 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma
}; };
const badge: ProfileBadge = { const badge: ProfileBadge = {
component: p => <PlatformIndicator {...p} user={UserStore.getUser(p.userId)} wantMargin={false} />, getBadges,
position: BadgePosition.START, position: BadgePosition.START,
shouldShow: userInfo => !!Object.keys(getStatus(userInfo.userId) ?? {}).length,
key: "indicator"
}; };
const indicatorLocations = { const indicatorLocations = {

View file

@ -0,0 +1,7 @@
.vc-platform-indicator {
display: inline-flex;
justify-content: center;
align-items: center;
vertical-align: top;
position: relative;
}

View file

@ -13,13 +13,12 @@ import { Flex, Menu } from "@webpack/common";
const DefaultEngines = { const DefaultEngines = {
Google: "https://www.google.com/search?q=", Google: "https://www.google.com/search?q=",
DuckDuckGo: "https://duckduckgo.com/", DuckDuckGo: "https://duckduckgo.com/",
Brave: "https://search.brave.com/search?q=",
Bing: "https://www.bing.com/search?q=", Bing: "https://www.bing.com/search?q=",
Yahoo: "https://search.yahoo.com/search?p=", Yahoo: "https://search.yahoo.com/search?p=",
GitHub: "https://github.com/search?q=",
Kagi: "https://kagi.com/search?q=",
Yandex: "https://yandex.com/search/?text=", Yandex: "https://yandex.com/search/?text=",
AOL: "https://search.aol.com/aol/search?q=", GitHub: "https://github.com/search?q=",
Baidu: "https://www.baidu.com/s?wd=", Reddit: "https://www.reddit.com/search?q=",
Wikipedia: "https://wikipedia.org/w/index.php?search=", Wikipedia: "https://wikipedia.org/w/index.php?search=",
} as const; } as const;
@ -55,7 +54,7 @@ function makeSearchItem(src: string) {
key="search-text" key="search-text"
id="vc-search-text" id="vc-search-text"
> >
{Object.keys(Engines).map((engine, i) => { {Object.keys(Engines).map(engine => {
const key = "vc-search-content-" + engine; const key = "vc-search-content-" + engine;
return ( return (
<Menu.MenuItem <Menu.MenuItem
@ -70,7 +69,7 @@ function makeSearchItem(src: string) {
aria-hidden="true" aria-hidden="true"
height={16} height={16}
width={16} width={16}
src={`https://www.google.com/s2/favicons?domain=${Engines[engine]}`} src={`https://www.google.com/s2/favicons?domain=${Engines[engine]}&sz=64`}
/> />
{engine} {engine}
</Flex> </Flex>

View file

@ -27,7 +27,7 @@ import { cl } from "../utils";
import ReviewComponent from "./ReviewComponent"; import ReviewComponent from "./ReviewComponent";
import ReviewsView, { ReviewsInputComponent } from "./ReviewsView"; import ReviewsView, { ReviewsInputComponent } from "./ReviewsView";
function Modal({ modalProps, discordId, name }: { modalProps: any; discordId: string; name: string; }) { function Modal({ modalProps, modalKey, discordId, name }: { modalProps: any; modalKey: string, discordId: string; name: string; }) {
const [data, setData] = useState<Response>(); const [data, setData] = useState<Response>();
const [signal, refetch] = useForceUpdater(true); const [signal, refetch] = useForceUpdater(true);
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
@ -76,6 +76,7 @@ function Modal({ modalProps, discordId, name }: { modalProps: any; discordId: st
discordId={discordId} discordId={discordId}
name={name} name={name}
refetch={refetch} refetch={refetch}
modalKey={modalKey}
/> />
{!!reviewCount && ( {!!reviewCount && (
@ -95,11 +96,14 @@ function Modal({ modalProps, discordId, name }: { modalProps: any; discordId: st
} }
export function openReviewsModal(discordId: string, name: string) { export function openReviewsModal(discordId: string, name: string) {
const modalKey = "vc-rdb-modal-" + Date.now();
openModal(props => ( openModal(props => (
<Modal <Modal
modalKey={modalKey}
modalProps={props} modalProps={props}
discordId={discordId} discordId={discordId}
name={name} name={name}
/> />
)); ), { modalKey });
} }

View file

@ -119,7 +119,9 @@ function ReviewList({ refetch, reviews, hideOwnReview, profileId }: { refetch():
} }
export function ReviewsInputComponent({ discordId, isAuthor, refetch, name }: { discordId: string, name: string; isAuthor: boolean; refetch(): void; }) { export function ReviewsInputComponent(
{ discordId, isAuthor, refetch, name, modalKey }: { discordId: string, name: string; isAuthor: boolean; refetch(): void; modalKey?: string; }
) {
const { token } = Auth; const { token } = Auth;
const editorRef = useRef<any>(null); const editorRef = useRef<any>(null);
const inputType = ChatInputTypes.FORM; const inputType = ChatInputTypes.FORM;
@ -148,6 +150,7 @@ export function ReviewsInputComponent({ discordId, isAuthor, refetch, name }: {
type={inputType} type={inputType}
disableThemedBackground={true} disableThemedBackground={true}
setEditorRef={ref => editorRef.current = ref} setEditorRef={ref => editorRef.current = ref}
parentModalKey={modalKey}
textValue="" textValue=""
onSubmit={ onSubmit={
async res => { async res => {

View file

@ -21,10 +21,12 @@ import "./style.css";
import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { ExpandableHeader } from "@components/ExpandableHeader"; import { ExpandableHeader } from "@components/ExpandableHeader";
import { OpenExternalIcon } from "@components/Icons"; import { NotesIcon, OpenExternalIcon } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { classes } from "@utils/misc";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { Alerts, Menu, Parser, useState } from "@webpack/common"; import { findByPropsLazy } from "@webpack";
import { Alerts, Button, Menu, Parser, TooltipContainer, useState } from "@webpack/common";
import { Guild, User } from "discord-types/general"; import { Guild, User } from "discord-types/general";
import { Auth, initAuth, updateAuth } from "./auth"; import { Auth, initAuth, updateAuth } from "./auth";
@ -35,6 +37,9 @@ import { getCurrentUserInfo, readNotification } from "./reviewDbApi";
import { settings } from "./settings"; import { settings } from "./settings";
import { showToast } from "./utils"; import { showToast } from "./utils";
const PopoutClasses = findByPropsLazy("container", "scroller", "list");
const RoleButtonClasses = findByPropsLazy("button", "buttonInner", "icon", "text");
const guildPopoutPatch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild, onClose(): void; }) => { const guildPopoutPatch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild, onClose(): void; }) => {
if (!guild) return; if (!guild) return;
children.push( children.push(
@ -69,7 +74,8 @@ export default definePlugin({
"guild-header-popout": guildPopoutPatch, "guild-header-popout": guildPopoutPatch,
"guild-context": guildPopoutPatch, "guild-context": guildPopoutPatch,
"user-context": userContextPatch, "user-context": userContextPatch,
"user-profile-actions": userContextPatch "user-profile-actions": userContextPatch,
"user-profile-overflow-menu": userContextPatch
}, },
patches: [ patches: [
@ -79,6 +85,13 @@ export default definePlugin({
match: /user:(\i),setNote:\i,canDM.+?\}\)/, match: /user:(\i),setNote:\i,canDM.+?\}\)/,
replace: "$&,$self.getReviewsComponent($1)" replace: "$&,$self.getReviewsComponent($1)"
} }
},
{
find: ".VIEW_FULL_PROFILE,",
replacement: {
match: /(?<=\.BITE_SIZE,children:\[)\(0,\i\.jsx\)\(\i\.\i,\{user:(\i),/,
replace: "$self.BiteSizeReviewsButton({user:$1}),$&"
}
} }
], ],
@ -159,5 +172,22 @@ export default definePlugin({
/> />
</ExpandableHeader> </ExpandableHeader>
); );
}, { message: "Failed to render Reviews" }) }, { message: "Failed to render Reviews" }),
BiteSizeReviewsButton: ErrorBoundary.wrap(({ user }: { user: User; }) => {
return (
<TooltipContainer text="View Reviews">
<Button
onClick={() => openReviewsModal(user.id, user.username)}
look={Button.Looks.FILLED}
size={Button.Sizes.NONE}
color={RoleButtonClasses.color}
className={classes(RoleButtonClasses.button, RoleButtonClasses.banner)}
innerClassName={classes(RoleButtonClasses.buttonInner, RoleButtonClasses.banner)}
>
<NotesIcon height={16} width={16} />
</Button>
</TooltipContainer>
);
}, { noop: true })
}); });

View file

@ -211,7 +211,7 @@ export default definePlugin({
} }
}, },
{ {
find: ".BITE_SIZE,onOpenProfile", find: /\.BITE_SIZE,onOpenProfile:\i,usernameIcon:/,
replacement: { replacement: {
match: /currentUser:\i,guild:\i,onOpenProfile:.+?}\)(?=])(?<=user:(\i),bio:null==(\i)\?.+?)/, match: /currentUser:\i,guild:\i,onOpenProfile:.+?}\)(?=])(?<=user:(\i),bio:null==(\i)\?.+?)/,
replace: "$&,$self.profilePopoutComponent({ user: $1, displayProfile: $2, simplified: true })" replace: "$&,$self.profilePopoutComponent({ user: $1, displayProfile: $2, simplified: true })"

View file

@ -105,6 +105,15 @@ export default definePlugin({
replace: "=[]" replace: "=[]"
} }
}, },
// empty 2nd word filter
{
find: '"pepe","nude"',
predicate: () => settings.store.disableDisallowedDiscoveryFilters,
replacement: {
match: /\?\["pepe",.+?\]/,
replace: "?[]",
},
},
// patch request that queries if term is allowed // patch request that queries if term is allowed
{ {
find: ".GUILD_DISCOVERY_VALID_TERM", find: ".GUILD_DISCOVERY_VALID_TERM",

View file

@ -66,12 +66,16 @@ export default definePlugin({
const { nick } = author; const { nick } = author;
const prefix = withMentionPrefix ? "@" : ""; const prefix = withMentionPrefix ? "@" : "";
if (username === nick || isRepliedMessage && !settings.store.inReplies)
if (isRepliedMessage && !settings.store.inReplies || username === nick.toLowerCase())
return <>{prefix}{nick}</>; return <>{prefix}{nick}</>;
if (settings.store.mode === "user-nick") if (settings.store.mode === "user-nick")
return <>{prefix}{username} <span className="vc-smyn-suffix">{nick}</span></>; return <>{prefix}{username} <span className="vc-smyn-suffix">{nick}</span></>;
if (settings.store.mode === "nick-user") if (settings.store.mode === "nick-user")
return <>{prefix}{nick} <span className="vc-smyn-suffix">{username}</span></>; return <>{prefix}{nick} <span className="vc-smyn-suffix">{username}</span></>;
return <>{prefix}{username}</>; return <>{prefix}{username}</>;
} catch { } catch {
return <>{author?.nick}</>; return <>{author?.nick}</>;

View file

@ -33,6 +33,7 @@ export let Card: t.Card;
export let Button: t.Button; export let Button: t.Button;
export let Switch: t.Switch; export let Switch: t.Switch;
export let Tooltip: t.Tooltip; export let Tooltip: t.Tooltip;
export let TooltipContainer: t.TooltipContainer;
export let TextInput: t.TextInput; export let TextInput: t.TextInput;
export let TextArea: t.TextArea; export let TextArea: t.TextArea;
export let Text: t.Text; export let Text: t.Text;
@ -66,6 +67,7 @@ waitFor(["FormItem", "Button"], m => {
Button, Button,
FormSwitch: Switch, FormSwitch: Switch,
Tooltip, Tooltip,
TooltipContainer,
TextInput, TextInput,
TextArea, TextArea,
Text, Text,

View file

@ -101,6 +101,28 @@ export type Tooltip = ComponentType<{
export type TooltipPositions = Record<"BOTTOM" | "CENTER" | "LEFT" | "RIGHT" | "TOP" | "WINDOW_CENTER", string>; export type TooltipPositions = Record<"BOTTOM" | "CENTER" | "LEFT" | "RIGHT" | "TOP" | "WINDOW_CENTER", string>;
export type TooltipContainer = ComponentType<PropsWithChildren<{
text: ReactNode;
element?: "div" | "span";
"aria-label"?: string | false;
delay?: number;
/** Tooltip.Colors.BLACK */
color?: string;
/** TooltipPositions.TOP */
position?: string;
spacing?: number;
className?: string;
tooltipClassName?: string | null;
tooltipContentClassName?: string | null;
allowOverflow?: boolean;
forceOpen?: boolean;
hideOnClick?: boolean;
disableTooltipPointerEvents?: boolean;
}>>;
export type Card = ComponentType<PropsWithChildren<HTMLProps<HTMLDivElement> & { export type Card = ComponentType<PropsWithChildren<HTMLProps<HTMLDivElement> & {
editable?: boolean; editable?: boolean;
outline?: boolean; outline?: boolean;
@ -110,6 +132,26 @@ export type Card = ComponentType<PropsWithChildren<HTMLProps<HTMLDivElement> & {
Types: Record<"BRAND" | "CUSTOM" | "DANGER" | "PRIMARY" | "SUCCESS" | "WARNING", string>; Types: Record<"BRAND" | "CUSTOM" | "DANGER" | "PRIMARY" | "SUCCESS" | "WARNING", string>;
}; };
export type ComboboxPopout = ComponentType<PropsWithChildren<{
value: Set<any>;
placeholder: string;
children(query: string): ReactNode[];
onChange(value: any): void;
itemToString?: (item: any) => string;
onClose?(): void;
className?: string;
listClassName?: string;
autoFocus?: boolean;
multiSelect?: boolean;
maxVisibleItems?: number;
showScrollbar?: boolean;
}>>;
export type Button = ComponentType<PropsWithChildren<Omit<HTMLProps<HTMLButtonElement>, "size"> & { export type Button = ComponentType<PropsWithChildren<Omit<HTMLProps<HTMLButtonElement>, "size"> & {
/** Button.Looks.FILLED */ /** Button.Looks.FILLED */
look?: string; look?: string;
@ -375,7 +417,7 @@ export type Popout = ComponentType<{
Animation: typeof PopoutAnimation; Animation: typeof PopoutAnimation;
}; };
export type Dialog = ComponentType<PropsWithChildren<any>>; export type Dialog = ComponentType<JSX.IntrinsicElements["div"]>;
type Resolve = (data: { theme: "light" | "dark", saturation: number; }) => { type Resolve = (data: { theme: "light" | "dark", saturation: number; }) => {
hex(): string; hex(): string;