diff --git a/src/plugins/findReply/ReplyNavigator.tsx b/src/plugins/findReply/ReplyNavigator.tsx new file mode 100644 index 000000000..0db830bfc --- /dev/null +++ b/src/plugins/findReply/ReplyNavigator.tsx @@ -0,0 +1,73 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import ErrorBoundary from "@components/ErrorBoundary"; +import { ModalCloseButton } from "@utils/modal"; +import { findByPropsLazy } from "@webpack"; +import { Paginator, React, useRef, useState } from "@webpack/common"; +import { Message } from "discord-types/general"; +import { MutableRefObject } from "react"; + +import { jumper } from "./index"; + +const containerStyles = findByPropsLazy("containerBottom", "containerTop"); + +export default function ReplyNavigator({ replies }: { replies: Message[]; }) { + const [page, setPage] = useState(1); + const [visible, setVisible] = useState(true); + const ref: MutableRefObject = useRef(null); + React.useEffect(() => { + setPage(1); + setVisible(true); + }, [replies]); + React.useEffect(() => { + function onMouseDown(event: MouseEvent) { + if (ref.current && event.target instanceof Element && !ref.current.contains(event.target)) { + setVisible(false); + } + } + + document.addEventListener("mousedown", onMouseDown); + return () => { + document.removeEventListener("mousedown", onMouseDown); + }; + }, [ref]); + return ( + +
+ + setVisible(false)}/> +
+
+ ); + + function processPageChange(page: number) { + setPage(page); + jumper.jumpToMessage({ + channelId: replies[page - 1].channel_id, + messageId: replies[page - 1].id, + flash: true, + jumpType: "INSTANT" + }); + } +} diff --git a/src/plugins/findReply/index.tsx b/src/plugins/findReply/index.tsx index 0792d1bfe..f5cabf94f 100644 --- a/src/plugins/findReply/index.tsx +++ b/src/plugins/findReply/index.tsx @@ -17,41 +17,53 @@ */ import { addButton, removeButton } from "@api/MessagePopover"; +import { disableStyle, enableStyle } from "@api/Styles"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { ChannelStore, MessageStore, Toasts } from "@webpack/common"; +import { ChannelStore, MessageStore, ReactDOM, Toasts } from "@webpack/common"; import Message from "discord-types/general/Message"; +import { Root } from "react-dom/client"; + +import ReplyNavigator from "./ReplyNavigator"; +import styles from "./styles.css?managed"; -const jumper = findByPropsLazy("jumpToMessage"); +export const jumper: any = findByPropsLazy("jumpToMessage"); const FindReplyIcon = () => { return ; }; +let root: Root | null = null; +let element: HTMLDivElement | null = null; +let madeComponent = false; -function findReply(message: Message) { - const messages: Array = [...MessageStore.getMessages(message.channel_id)?._array ?? []].filter(m => !m.deleted).sort((a, b) => { +function findReplies(message: Message) { + const messages: Array = [...MessageStore.getMessages(message.channel_id)?._array ?? []].filter(m => !m.deleted).sort((a, b) => { return a.timestamp.toString().localeCompare(b.timestamp.toString()); }); // Need to deep copy Message array when sorting + const found: Message[] = []; for (const other of messages) { if (other.timestamp.toString().localeCompare(message.timestamp.toString()) <= 0) continue; if (other.messageReference?.message_id === message.id) { - return other; + found.push(other); } if (Vencord.Settings.plugins.FindReply.includePings) { if (other.content?.includes(`<@${message.author.id}>`)) { - return other; + found.push(other); } } if (Vencord.Settings.plugins.FindReply.includeAuthor) { if (messages.find(m => m.id === other.messageReference?.message_id)?.author.id === message.author.id) { - return other; + found.push(other); } } } - return null; + return found; } export default definePlugin({ @@ -59,25 +71,40 @@ export default definePlugin({ description: "Jumps to the earliest reply to a message in a channel (lets you follow past conversations more easily).", authors: [Devs.newwares], start() { + enableStyle(styles); addButton("vc-findreply", message => { if (!message.id) return null; - const reply = findReply(message); - if (Vencord.Settings.plugins.FindReply.hideButtonIfNoReply && !reply) return null; + const replies = findReplies(message); + if (Vencord.Settings.plugins.FindReply.hideButtonIfNoReply && !replies) return null; return { label: "Jump to Reply", icon: FindReplyIcon, message, channel: ChannelStore.getChannel(message.channel_id), onClick: async () => { - if (reply) { - const channelId = reply.channel_id; - const messageId = reply.id; + if (replies.length) { + const channelId = replies[0].channel_id; + const messageId = replies[0].id; jumper.jumpToMessage({ channelId, messageId, flash: true, jumpType: "INSTANT" }); + if (replies.length > 1) { + Toasts.show({ + id: Toasts.genId(), + message: "Use the bottom panel to navigate between replies.", + type: Toasts.Type.MESSAGE + }); + if (!madeComponent) { + madeComponent = true; + element = document.createElement("div"); + document.querySelector("[class^=base__]")!.appendChild(element); + root = ReactDOM.createRoot(element); + } + root!.render(); + } } else { Toasts.show({ id: Toasts.genId(), @@ -91,6 +118,9 @@ export default definePlugin({ }, stop() { removeButton("vc-findreply"); + root && root.unmount(); + element?.remove(); + disableStyle(styles); }, options: { includePings: { @@ -108,7 +138,7 @@ export default definePlugin({ hideButtonIfNoReply: { type: OptionType.BOOLEAN, description: "Hides the button if there are no replies to the message", - default: false, + default: true, restartNeeded: true } } diff --git a/src/plugins/findReply/styles.css b/src/plugins/findReply/styles.css new file mode 100644 index 000000000..c1438f418 --- /dev/null +++ b/src/plugins/findReply/styles.css @@ -0,0 +1,8 @@ +.vc-findreply-paginator { + margin-top: inherit !important; +} + +.vc-findreply-close { + align-items: inherit !important; + line-height: 0 !important; +} diff --git a/src/webpack/common/types/components.d.ts b/src/webpack/common/types/components.d.ts index 3e3ffa4bd..b01287c83 100644 --- a/src/webpack/common/types/components.d.ts +++ b/src/webpack/common/types/components.d.ts @@ -393,7 +393,7 @@ export type Paginator = ComponentType<{ maxVisiblePages: number; pageSize: number; totalCount: number; - + className?: string; onPageChange?(page: number): void; hideMaxPage?: boolean; }>;