jumpToPing
| tags | Chat Utility |
|---|---|
| file | index.tsx · 174 lines |
Adds a button to jump to messages that pinged you in a channel, with arrows to step through older/newer pings
https://git.26rk.dev/26rk/jumpToPing
view source · index.tsx
import ErrorBoundary from "@components/ErrorBoundary";
import { UpArrowIcon } from "@components/Icons";
import { proxyLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger";
import definePlugin from "@utils/types";
import type { Channel } from "@vencord/discord-types";
import { findComponentByCodeLazy } from "@webpack";
import { Flux, FluxDispatcher, MessageActions, UserStore, useStateFromStores } from "@webpack/common";
const ChannelToolbarIcon = findComponentByCodeLazy("tooltipPosition:", '"aria-haspopup":', '"data-jump-section":');
const logger = new Logger("JumpToPing");
const MAX_TRACKED_PINGS = 50;
const EMPTY_PINGS: string[] = [];
function PingIcon(props: React.ComponentProps<"svg">) {
return (
<svg {...props} width="20" height="20" viewBox="0 0 20 20">
<text x="50%" y="76%" textAnchor="middle" fontSize="19" fontWeight="700" fill="currentColor">@</text>
</svg>
);
}
function DownArrowIcon(props: React.ComponentProps<typeof UpArrowIcon>) {
return <UpArrowIcon {...props} style={{ transform: "rotate(180deg)" }} />;
}
function isMentioned(message: any, userId: string): boolean {
if (typeof message.mentioned === "boolean") return message.mentioned;
if (message.mentionEveryone ?? message.mention_everyone) return true;
const { mentions } = message;
if (mentions instanceof Set) return mentions.has(userId);
if (Array.isArray(mentions)) return mentions.some((m: any) => (typeof m === "string" ? m : m?.id) === userId);
return false;
}
const PingStore = proxyLazy(() => {
const pingsByChannel = new Map<string, string[]>();
const cursorByChannel = new Map<string, number>();
class PingStore extends Flux.Store {
getPings(channelId: string): string[] {
return pingsByChannel.get(channelId) ?? EMPTY_PINGS;
}
getCursor(channelId: string): number {
return cursorByChannel.get(channelId) ?? -1;
}
setCursor(channelId: string, cursor: number) {
cursorByChannel.set(channelId, cursor);
}
}
const store = new PingStore(FluxDispatcher, {
MESSAGE_CREATE({ message, optimistic }: any) {
if (optimistic || !message) return;
const userId = UserStore.getCurrentUser()?.id;
if (!userId || message.author?.id === userId) return;
const mentioned = isMentioned(message, userId);
if (!mentioned) return;
const channelId: string | undefined = message.channel_id ?? message.channelId;
if (!channelId) return;
const list = pingsByChannel.get(channelId) ?? [];
if (list.includes(message.id)) return;
list.unshift(message.id);
if (list.length > MAX_TRACKED_PINGS) list.length = MAX_TRACKED_PINGS;
pingsByChannel.set(channelId, list);
cursorByChannel.set(channelId, -1);
store.emitChange();
},
MESSAGE_DELETE({ id, channelId }: any) {
const list = pingsByChannel.get(channelId);
if (!list) return;
const idx = list.indexOf(id);
if (idx === -1) return;
list.splice(idx, 1);
if (!list.length) pingsByChannel.delete(channelId);
store.emitChange();
}
});
return store;
});
function jumpToPing(channelId: string, direction: "cycle" | "older" | "newer") {
const list = PingStore.getPings(channelId);
if (!list.length) return;
let cursor = PingStore.getCursor(channelId);
switch (direction) {
case "cycle":
cursor = cursor + 1 >= list.length ? 0 : cursor + 1;
break;
case "older":
cursor = Math.min(cursor + 1, list.length - 1);
break;
case "newer":
cursor = Math.max(cursor - 1, 0);
break;
}
PingStore.setCursor(channelId, cursor);
MessageActions.jumpToMessage({
channelId,
messageId: list[cursor],
flash: true,
jumpType: "INSTANT"
});
}
export default definePlugin({
name: "JumpToPing",
description: "Adds a button to jump to messages that pinged you in a channel, with arrows to step through older/newer pings",
authors: [{ name: "26rk", id: 811214306946842624n }],
tags: ["Chat", "Utility"],
patches: [{
find: "Missing channel in Channel.renderHeaderToolbar",
replacement: {
match: /(?<=renderHeaderToolbar"\);let (\i)=\[\];)/,
replace: "$1.push($self.renderJumpButtons(this.props));"
}
}],
renderJumpButtons: ErrorBoundary.wrap(({ channel }: { channel: Channel }) => {
const pings = useStateFromStores([PingStore], () => PingStore.getPings(channel.id));
if (channel.isForumPost() || !pings.length) {
return null;
}
return (
<>
{pings.length > 1 && (
<ChannelToolbarIcon
tooltip="Jump to Newer Ping"
icon={DownArrowIcon}
onClick={() => jumpToPing(channel.id, "newer")}
/>
)}
<ChannelToolbarIcon
tooltip="Jump to Ping"
icon={PingIcon}
onClick={() => jumpToPing(channel.id, "cycle")}
/>
{pings.length > 1 && (
<ChannelToolbarIcon
tooltip="Jump to Older Ping"
icon={UpArrowIcon}
onClick={() => jumpToPing(channel.id, "older")}
/>
)}
</>
);
}, { noop: true })
});