26RK

Plugins

vencord userplugins i've written. one-click install with the userplugin installer.

Readme.txt

  1. get UserpluginInstaller on your vencord dev build (it needs git).
  2. hit copy install link on a plugin below.
  3. open vencord settings → UserPlugins, paste the link and install. it clones the plugin and rebuilds vencord for you.
  4. updates show up in the same tab with an update button.

no installer? hit copy source and paste it into src/userplugins/<name>/index.tsx, then rebuild.

jumpToPing

tagsChat Utility
fileindex.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 })
});