157 lines
6.4 KiB
TypeScript
157 lines
6.4 KiB
TypeScript
/*
|
|
* Vencord, a Discord client mod
|
|
* Copyright (c) 2025 Vendicated and contributors
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
import { classNameFactory } from "@utils/css";
|
|
import { ModalCloseButton, ModalContent, ModalHeader, ModalRoot, ModalSize } from "@utils/modal";
|
|
import { Channel } from "@vencord/discord-types";
|
|
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
|
import { Avatar, Button, ChannelStore, MessageStore, React, Text, UserStore } from "@webpack/common";
|
|
|
|
const cl = classNameFactory("vc-boo-");
|
|
|
|
function formatMessageDate(timestamp: string | Date): string {
|
|
const date = new Date(timestamp);
|
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
const day = String(date.getDate()).padStart(2, "0");
|
|
const year = String(date.getFullYear()).slice(-2);
|
|
return `${month}/${day}/${year}`;
|
|
}
|
|
|
|
const GroupDmsRecipientsIcon = findComponentByCodeLazy('["aria-hidden"],"aria-label":');
|
|
const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel");
|
|
|
|
function GroupDmsIcon({ channel }: { channel: Channel; }) {
|
|
return channel.icon ? <Avatar
|
|
src={`https://cdn.discordapp.com/channel-icons/${channel.id}/${channel.icon}.png`}
|
|
size="SIZE_40"
|
|
aria-label={channel?.name || "Unnamed Group"}
|
|
/> : <GroupDmsRecipientsIcon
|
|
recipients={channel.recipients}
|
|
size="SIZE_40"
|
|
isTyping={null}
|
|
/>;
|
|
}
|
|
|
|
interface GhostedUsersModalProps {
|
|
modalProps: any;
|
|
ghostedChannels: string[];
|
|
onClose: () => void;
|
|
onClearGhost: (channelId: string) => void;
|
|
}
|
|
|
|
export function getChannelDisplayName(channelId: string): string {
|
|
const channel = ChannelStore.getChannel(channelId);
|
|
if (!channel) return "Unknown";
|
|
|
|
if (channel.isGroupDM()) {
|
|
return channel?.name || "Unnamed Group";
|
|
}
|
|
|
|
// 1-on-1 DM
|
|
const recipientId = channel.recipients?.[0];
|
|
const user = UserStore.getUser(recipientId);
|
|
return user?.username || "Unknown User";
|
|
}
|
|
|
|
export function GhostedUsersModal({ modalProps, ghostedChannels: initialChannels, onClose, onClearGhost }: GhostedUsersModalProps) {
|
|
const [ghostedChannels, setGhostedChannels] = React.useState(initialChannels);
|
|
|
|
const handleChannelClick = (channelId: string) => {
|
|
const channel = ChannelStore.getChannel(channelId);
|
|
if (channel) {
|
|
SelectedChannelActionCreators.selectPrivateChannel(channelId);
|
|
onClose();
|
|
}
|
|
};
|
|
|
|
const handleClearClick = (e: React.MouseEvent, channelId: string) => {
|
|
e.stopPropagation();
|
|
onClearGhost(channelId);
|
|
// update local state to remove the cleared channel
|
|
setGhostedChannels(prev => prev.filter(id => id !== channelId));
|
|
};
|
|
|
|
const handleClearAll = () => {
|
|
for (const channelId of initialChannels) {
|
|
onClearGhost(channelId);
|
|
}
|
|
setGhostedChannels([]);
|
|
};
|
|
|
|
return (
|
|
<ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
|
|
<ModalHeader>
|
|
<Text
|
|
variant="heading-lg/semibold"
|
|
className={cl("modal-header")}
|
|
>
|
|
Ghosted Users ({ghostedChannels.length})
|
|
</Text>
|
|
{ghostedChannels.length > 0 && (
|
|
<Button
|
|
size={Button.Sizes.SMALL}
|
|
color={Button.Colors.PRIMARY}
|
|
onClick={handleClearAll}
|
|
className={cl("clear")}
|
|
>
|
|
Clear All
|
|
</Button>
|
|
)}
|
|
<ModalCloseButton onClick={onClose} />
|
|
</ModalHeader>
|
|
<ModalContent>
|
|
<div className={cl("modal-content")}>
|
|
{ghostedChannels.length === 0 ? (
|
|
<Text variant="text-md/normal">No ghosts here!</Text>
|
|
) : (
|
|
ghostedChannels.map(channelId => {
|
|
const channel = ChannelStore.getChannel(channelId);
|
|
if (!channel) return null;
|
|
|
|
const lastMessage = MessageStore.getMessages(channelId)?.last();
|
|
const lastMessageDate = lastMessage?.timestamp ? formatMessageDate(lastMessage.timestamp) : "";
|
|
|
|
const displayName = getChannelDisplayName(channel.id);
|
|
return (
|
|
<div
|
|
key={channelId}
|
|
onClick={() => handleChannelClick(channelId)}
|
|
className={cl("ghosted-entry")}
|
|
>
|
|
{channel.isGroupDM() ?
|
|
<GroupDmsIcon
|
|
channel={channel}
|
|
/> : <Avatar
|
|
src={UserStore.getUser(channel.recipients?.[0])?.getAvatarURL(undefined, 128, true) || ""}
|
|
size="SIZE_40"
|
|
aria-label={displayName}
|
|
/>}
|
|
<div className={cl("user-info")}>
|
|
<Text variant="text-md/normal">
|
|
{displayName}
|
|
</Text>
|
|
{lastMessageDate && (
|
|
<Text variant="text-xs/normal" className={cl("modal-text")}>
|
|
{lastMessageDate}
|
|
</Text>
|
|
)}
|
|
</div>
|
|
<Button
|
|
size={Button.Sizes.SMALL}
|
|
color={Button.Colors.PRIMARY}
|
|
onClick={e => handleClearClick(e, channelId)}
|
|
>
|
|
Clear
|
|
</Button>
|
|
</div>
|
|
);
|
|
})
|
|
)}
|
|
</div>
|
|
</ModalContent>
|
|
</ModalRoot>
|
|
);
|
|
}
|