add context menu icons to various plugins

This commit is contained in:
Vendicated 2026-08-18 01:37:09 +02:00
parent ef29bbeb61
commit 3e03b47b45
No known key found for this signature in database
GPG key ID: D66986BAF75ECF18
14 changed files with 94 additions and 58 deletions

View file

@ -1,11 +1,14 @@
import type { ComponentType, CSSProperties, ForwardRefRenderFunction, MouseEvent, PropsWithChildren, ReactNode, UIEvent } from "react";
import { LiteralUnion } from "type-fest";
type RC<C> = ComponentType<PropsWithChildren<C & Record<string, any>>>;
type LeadingAccessory =
type Accessory =
| {
type: "icon";
icon: ComponentType<any>;
color?: string;
size?: LiteralUnion<"refresh_sm", string>;
}
| {
type: "emoji";
@ -36,7 +39,7 @@ export interface Menu {
label: ReactNode;
action?(e: MouseEvent): void;
icon?: ComponentType<any>;
leadingAccessory?: LeadingAccessory;
leadingAccessory?: Accessory;
color?: string;
render?: ComponentType<any>;

View file

@ -640,6 +640,20 @@ export function RightArrow(props: IconProps) {
);
}
export function TopRightArrow(props: IconProps) {
return (
<Icon
{...props}
viewBox="0 0 24 24"
>
<path
fill={props.fill || "currentColor"}
d="M8 5a1 1 0 0 0 0 2h7.59L5.29 17.3a1 1 0 1 0 1.42 1.4L17 8.42V16a1 1 0 1 0 2 0V6a1 1 0 0 0-1-1H8Z"
/>
</Icon>
);
}
export function StarFilled(props: IconProps) {
return (
<Icon
@ -667,3 +681,34 @@ export function StarOutlined(props: IconProps) {
</Icon>
);
}
export function SearchIcon(props: IconProps) {
return (
<Icon
{...props}
viewBox="0 0 24 24"
>
<path
fill={props.fill || "currentColor"}
fillRule="evenodd"
d="M15.62 17.03a9 9 0 1 1 1.41-1.41l4.68 4.67a1 1 0 0 1-1.42 1.42l-4.67-4.68ZM17 10a7 7 0 1 1-14 0 7 7 0 0 1 14 0Z"
clipRule="evenodd"
/>
</Icon>
);
}
export function EyeIcon(props: IconProps) {
return (
<Icon
{...props}
viewBox="0 0 24 24"
fillRule="evenodd"
>
<path
fill={props.fill || "currentColor"}
d="M22.89 11.7c.07.2.07.4 0 .6C22.27 13.9 19.1 21 12 21c-7.11 0-10.27-7.11-10.89-8.7a.83.83 0 0 1 0-.6C1.73 10.1 4.9 3 12 3c7.11 0 10.27 7.11 10.89 8.7Zm-4.5-3.62A15.11 15.11 0 0 1 20.85 12c-.38.88-1.18 2.47-2.46 3.92C16.87 17.62 14.8 19 12 19c-2.8 0-4.87-1.38-6.39-3.08A15.11 15.11 0 0 1 3.15 12c.38-.88 1.18-2.47 2.46-3.92C7.13 6.38 9.2 5 12 5c2.8 0 4.87 1.38 6.39 3.08ZM15.56 11.77c.2-.1.44.02.44.23a4 4 0 1 1-4-4c.21 0 .33.25.23.44a2.5 2.5 0 0 0 3.32 3.32Z"
/>
</Icon>
);
}

View file

@ -22,6 +22,7 @@ import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Bad
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { Heart } from "@components/Heart";
import { CopyIcon, LinkIcon } from "@components/Icons";
import DonateButton from "@components/settings/DonateButton";
import { openContributorModal } from "@components/settings/tabs";
import { Devs } from "@utils/constants";
@ -68,6 +69,7 @@ function BadgeContextMenu({ badge }: { badge: Omit<ProfileBadge, "id"> & BadgeUs
id="vc-badge-copy-name"
label="Copy Badge Name"
action={() => copyWithToast(badge.description!)}
leadingAccessory={{ type: "icon", icon: CopyIcon }}
/>
)}
{badge.iconSrc && (
@ -75,6 +77,7 @@ function BadgeContextMenu({ badge }: { badge: Omit<ProfileBadge, "id"> & BadgeUs
id="vc-badge-copy-link"
label="Copy Badge Image Link"
action={() => copyWithToast(badge.iconSrc!)}
leadingAccessory={{ type: "icon", icon: LinkIcon }}
/>
)}
</Menu.Menu>

View file

@ -150,6 +150,7 @@ export function buildExtraRoleContextMenuItems(role: Role, guild: Guild, popoutR
{...popoutProps}
>
<div className={MenuItemClasses.label}>View Role Members</div>
{/* FIXME: update to new icon style */}
<div className={MenuItemClasses.iconContainer}>
<RoleMembersIcon />
</div>

View file

@ -5,6 +5,7 @@
*/
import { definePluginSettings } from "@api/Settings";
import { CopyIcon } from "@components/Icons";
import { Devs } from "@utils/constants";
import { copyWithToast } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
@ -69,6 +70,7 @@ export default definePlugin({
"Success! Copied emoji markdown."
);
}}
leadingAccessory={{ type: "icon", icon: CopyIcon }}
/>
);
},

View file

@ -18,6 +18,7 @@
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { isPluginEnabled } from "@api/PluginManager";
import { LinkIcon, TopRightArrow } from "@components/Icons";
import ExpressionClonerPlugin from "@plugins/expressionCloner";
import { Devs } from "@utils/constants";
import { copyWithToast } from "@utils/discord";
@ -43,14 +44,17 @@ function buildMenuItem(sticker: PartialSticker, addBottomSeparator: boolean) {
<Menu.MenuItem
id="vc-copy-sticker-link"
key="vc-copy-sticker-link"
label="Copy Link"
label="Copy Sticker Link"
leadingAccessory={{ type: "icon", icon: LinkIcon }}
action={() => copyWithToast(getUrl(sticker), "Link copied!")}
/>
<Menu.MenuItem
id="vc-open-sticker-link"
key="vc-open-sticker-link"
label="Open Link"
label="Open Sticker Link"
leadingAccessory={{ type: "icon", icon: LinkIcon }}
trailingIndicator={{ type: "icon", icon: TopRightArrow }}
action={() => VencordNative.native.openExternal(getUrl(sticker))}
/>
</Menu.MenuGroup>

View file

@ -21,6 +21,7 @@ import { migratePluginSettings } from "@api/Settings";
import { BaseText } from "@components/BaseText";
import { CheckedTextInput } from "@components/CheckedTextInput";
import { Flex } from "@components/Flex";
import { PlusIcon } from "@components/Icons";
import { Devs } from "@utils/constants";
import { getGuildAcronym, hasGuildFeature } from "@utils/discord";
import { Logger } from "@utils/Logger";
@ -327,6 +328,7 @@ function buildMenuItem(type: "Emoji" | "Sticker", fetchData: () => Promisable<Om
id="emote-cloner"
key="emote-cloner"
label={`Clone ${type}`}
leadingAccessory={{ type: "icon", icon: PlusIcon }}
action={() =>
openModalLazy(async () => {
const res = await fetchData();

View file

@ -23,6 +23,7 @@ import { updateMessage } from "@api/MessageUpdater";
import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { DeleteIcon, EyeIcon } from "@components/Icons";
import { Devs, SUPPORT_CATEGORY_ID, VENBOT_USER_ID } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import { Logger } from "@utils/Logger";
@ -176,6 +177,7 @@ const patchMessageContextMenu: NavContextMenuPatchCallback = (children, props) =
id={TOGGLE_DELETE_STYLE_ID}
key={TOGGLE_DELETE_STYLE_ID}
label="Toggle Deleted Highlight"
leadingAccessory={{ type: "icon", icon: EyeIcon }}
action={() => domElement.classList.toggle("messagelogger-deleted")}
/>
));
@ -186,6 +188,7 @@ const patchMessageContextMenu: NavContextMenuPatchCallback = (children, props) =
id={REMOVE_HISTORY_ID}
key={REMOVE_HISTORY_ID}
label="Remove Message History"
leadingAccessory={{ type: "icon", icon: DeleteIcon }}
color="danger"
action={() => {
clearMessageHistory(message);

View file

@ -271,6 +271,7 @@ function UserContextMenu({ userId }: { userId: string; }) {
<Menu.MenuItem
id={cl("copy-user-id")}
label={getIntlMessage("COPY_ID_USER")}
leadingAccessory={{ type: "icon", icon: CopyIdIcon }}
action={() => {
copyToClipboard(userId);
}}

View file

@ -59,13 +59,14 @@ export const settings = definePluginSettings({
},
});
function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) {
function MenuItem(guildId: string, { id, type, withIcon }: { id?: string, type?: MenuItemParentType, withIcon?: boolean; }) {
if (type === MenuItemParentType.User && !GuildMemberStore.isMember(guildId, id!)) return null;
return (
<Menu.MenuItem
id="perm-viewer-permissions"
label="Permissions"
label="View Permissions"
leadingAccessory={withIcon ? { type: "icon", icon: SafetyIcon } : undefined}
action={() => {
const guild = GuildStore.getGuild(guildId);
@ -125,7 +126,7 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) {
);
}
function makeContextMenuPatch(childId: string | string[], type?: MenuItemParentType): NavContextMenuPatchCallback {
function makeContextMenuPatch(childId: string | string[], type?: MenuItemParentType, withIcon = false): NavContextMenuPatchCallback {
return (children, props) => {
if (
!props ||
@ -139,9 +140,9 @@ function makeContextMenuPatch(childId: string | string[], type?: MenuItemParentT
const group = findGroupChildrenByChildId(childId, children);
const item = match(type)
.with(MenuItemParentType.User, () => MenuItem(props.guildId, props.user.id, type))
.with(MenuItemParentType.Channel, () => MenuItem(props.guild.id, props.channel.id, type))
.with(MenuItemParentType.Guild, () => MenuItem(props.guild.id))
.with(MenuItemParentType.User, () => MenuItem(props.guildId, { id: props.user.id, type, withIcon }))
.with(MenuItemParentType.Channel, () => MenuItem(props.guild.id, { id: props.channel.id, type, withIcon }))
.with(MenuItemParentType.Guild, () => MenuItem(props.guild.id, { withIcon }))
.otherwise(() => null);
if (item == null) return;
@ -214,6 +215,6 @@ export default definePlugin({
"user-context": makeContextMenuPatch("roles", MenuItemParentType.User),
"channel-context": makeContextMenuPatch(["mute-channel", "unmute-channel"], MenuItemParentType.Channel),
"guild-context": makeContextMenuPatch("privacy", MenuItemParentType.Guild),
"guild-header-popout": makeContextMenuPatch("privacy", MenuItemParentType.Guild)
"guild-header-popout": makeContextMenuPatch("privacy", MenuItemParentType.Guild, true)
}
});

View file

@ -18,6 +18,7 @@
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
import { generateId, sendBotMessage } from "@api/Commands";
import { EyeIcon } from "@components/Icons";
import { Devs } from "@utils/constants";
import definePlugin, { IconComponent, StartAt } from "@utils/types";
import { CloudUpload, MessageAttachment } from "@vencord/discord-types";
@ -75,17 +76,12 @@ const getAttachments = async (channelId: string) =>
const PreviewIcon: IconComponent = ({ height = 20, width = 20, className }) => {
return (
<svg
fill="currentColor"
fillRule="evenodd"
<EyeIcon
width={width}
height={height}
className={className}
viewBox="0 0 24 24"
style={{ scale: "1.096", translate: "0 -1px" }}
>
<path d="M22.89 11.7c.07.2.07.4 0 .6C22.27 13.9 19.1 21 12 21c-7.11 0-10.27-7.11-10.89-8.7a.83.83 0 0 1 0-.6C1.73 10.1 4.9 3 12 3c7.11 0 10.27 7.11 10.89 8.7Zm-4.5-3.62A15.11 15.11 0 0 1 20.85 12c-.38.88-1.18 2.47-2.46 3.92C16.87 17.62 14.8 19 12 19c-2.8 0-4.87-1.38-6.39-3.08A15.11 15.11 0 0 1 3.15 12c.38-.88 1.18-2.47 2.46-3.92C7.13 6.38 9.2 5 12 5c2.8 0 4.87 1.38 6.39 3.08ZM15.56 11.77c.2-.1.44.02.44.23a4 4 0 1 1-4-4c.21 0 .33.25.23.44a2.5 2.5 0 0 0 3.32 3.32Z" />
</svg>
/>
);
};

View file

@ -6,7 +6,7 @@
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings";
import { Flex } from "@components/Flex";
import { SearchIcon } from "@components/Icons";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { Menu } from "@webpack/common";
@ -89,6 +89,7 @@ function makeSearchItem(src: string) {
label="Search Text"
key="search-text"
id="vc-search-text"
leadingAccessory={{ type: "icon", icon: SearchIcon }}
>
{Object.keys(Engines).map(engine => {
const key = "vc-search-content-" + engine;
@ -96,20 +97,8 @@ function makeSearchItem(src: string) {
<Menu.MenuItem
key={key}
id={key}
label={
<Flex gap="0.5em" alignItems="center">
<img
style={{
borderRadius: "50%"
}}
aria-hidden="true"
height={16}
width={16}
src={`https://icons.duckduckgo.com/ip3/${new URL(Engines[engine]).hostname}.ico`}
/>
{engine}
</Flex>
}
label={engine}
leadingAccessory={{ type: "image", src: `https://icons.duckduckgo.com/ip3/${new URL(Engines[engine]).hostname}.ico` }}
action={() => search(src, Engines[engine])}
/>
);

View file

@ -17,8 +17,7 @@
*/
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { Flex } from "@components/Flex";
import { OpenExternalIcon } from "@components/Icons";
import { OpenExternalIcon, SearchIcon } from "@components/Icons";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { Menu } from "@webpack/common";
@ -43,6 +42,7 @@ function makeSearchItem(src: string) {
label="Search Image"
key="search-image"
id="search-image"
leadingAccessory={{ type: "icon", icon: SearchIcon }}
>
{Object.keys(Engines).map((engine, i) => {
const key = "search-image-" + engine;
@ -50,20 +50,8 @@ function makeSearchItem(src: string) {
<Menu.MenuItem
key={key}
id={key}
label={
<Flex alignItems="center" gap="0.5em">
<img
style={{
borderRadius: "50%",
}}
aria-hidden="true"
height={16}
width={16}
src={`https://icons.duckduckgo.com/ip3/${new URL(Engines[engine]).host}.ico`}
/>
{engine}
</Flex>
}
label={engine}
leadingAccessory={{ type: "image", src: `https://icons.duckduckgo.com/ip3/${new URL(Engines[engine]).host}.ico` }}
action={() => search(src, Engines[engine])}
/>
);
@ -71,12 +59,8 @@ function makeSearchItem(src: string) {
<Menu.MenuItem
key="search-image-all"
id="search-image-all"
label={
<Flex alignItems="center" gap="0.5em">
<OpenExternalIcon height={16} width={16} />
All
</Flex>
}
label="All"
leadingAccessory={{ type: "icon", icon: OpenExternalIcon }}
action={() => Object.values(Engines).forEach(e => search(src, e))}
/>
</Menu.MenuItem>

View file

@ -5,6 +5,7 @@
*/
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { InfoIcon } from "@components/Icons";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { Guild } from "@vencord/discord-types";
@ -12,13 +13,14 @@ import { Menu } from "@webpack/common";
import { openGuildInfoModal } from "./GuildInfoModal";
const Patch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild; }) => {
const makePatch: (showIcon: boolean) => NavContextMenuPatchCallback = showIcon => (children, { guild }: { guild: Guild; }) => {
const group = findGroupChildrenByChildId("privacy", children);
group?.push(
<Menu.MenuItem
id="vc-server-info"
label="Server Info"
leadingAccessory={showIcon ? { type: "icon", icon: InfoIcon } : undefined}
action={() => openGuildInfoModal(guild)}
/>
);
@ -33,7 +35,7 @@ export default definePlugin({
searchTerms: ["guild", "info", "ServerProfile"],
contextMenus: {
"guild-context": Patch,
"guild-header-popout": Patch
"guild-context": makePatch(false),
"guild-header-popout": makePatch(true)
}
});