/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
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, 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";
export const jumper: any = findByPropsLazy("jumpToMessage");
const FindReplyIcon = () => {
return ;
};
let root: Root | null = null;
let element: HTMLDivElement | null = null;
let madeComponent = false;
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) {
found.push(other);
}
if (Vencord.Settings.plugins.FindReply.includePings) {
if (other.content?.includes(`<@${message.author.id}>`)) {
found.push(other);
}
}
if (Vencord.Settings.plugins.FindReply.includeAuthor) {
if (messages.find(m => m.id === other.messageReference?.message_id)?.author.id === message.author.id) {
found.push(other);
}
}
}
return found;
}
export default definePlugin({
name: "FindReply",
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 replies = findReplies(message);
if (Vencord.Settings.plugins.FindReply.hideButtonIfNoReply && !replies.length) return null;
return {
label: "Jump to Reply",
icon: FindReplyIcon,
message,
channel: ChannelStore.getChannel(message.channel_id),
onClick: async () => {
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(),
message: "Couldn't find a reply.",
type: Toasts.Type.FAILURE
});
}
}
};
});
},
stop() {
removeButton("vc-findreply");
root && root.unmount();
element?.remove();
disableStyle(styles);
},
options: {
includePings: {
type: OptionType.BOOLEAN,
description: "Will also search for messages that @ the author directly",
default: false,
restartNeeded: false
},
includeAuthor: {
type: OptionType.BOOLEAN,
description: "Will also search for messages that reply to the author in general, not just that exact message",
default: false,
restartNeeded: false
},
hideButtonIfNoReply: {
type: OptionType.BOOLEAN,
description: "Hides the button if there are no replies to the message",
default: true,
restartNeeded: true
}
}
});