aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/interactionKeybinds.ts
blob: 3f6c7b4228918c3b0424951664a6e4cd680d9288 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import definePlugin from "../utils/types";
import { Devs } from "../utils/constants";
import { FluxDispatcher as Dispatcher, ChannelStore, SelectedChannelStore, UserStore } from "../webpack/common";
import { filters } from "../webpack";
import { lazyWebpack } from "../utils/misc";
import { Message } from "discord-types/general";

const MessageStore = lazyWebpack(filters.byProps(["getRawMessages"]));

const isMac = navigator.platform.includes("Mac"); // bruh
let replyIdx = -1;
let editIdx = -1;

export default definePlugin({
    name: "InteractionKeybinds",
    authors: [Devs.obscurity, Devs.Ven],
    description: "Reply to (ctrl + up/down) and edit (ctrl + shift + up/down) messages via keybinds",

    start() {
        Dispatcher.subscribe("DELETE_PENDING_REPLY", onDeletePendingReply);
        Dispatcher.subscribe("MESSAGE_END_EDIT", onEndEdit);
        Dispatcher.subscribe("MESSAGE_START_EDIT", onStartEdit);
        Dispatcher.subscribe("CREATE_PENDING_REPLY", onCreatePendingReply);
        document.addEventListener("keydown", onKeydown);
    },

    stop() {
        Dispatcher.unsubscribe("DELETE_PENDING_REPLY", onDeletePendingReply);
        Dispatcher.unsubscribe("MESSAGE_END_EDIT", onEndEdit);
        Dispatcher.unsubscribe("MESSAGE_START_EDIT", onStartEdit);
        Dispatcher.unsubscribe("CREATE_PENDING_REPLY", onCreatePendingReply);
        document.removeEventListener("keydown", onKeydown);
    },
});

const onDeletePendingReply = () => replyIdx = -1;
const onEndEdit = () => editIdx = -1;

function calculateIdx(messages: Message[], id: string) {
    const idx = messages.findIndex(m => m.id === id);
    return idx === -1
        ? idx
        : messages.length - idx - 1;
}

function onStartEdit({ channelId, messageId, _isQuickEdit }: any) {
    if (_isQuickEdit) return;

    const meId = UserStore.getCurrentUser().id;

    const messages = MessageStore.getMessages(channelId)._array.filter(m => m.author.id === meId);
    editIdx = calculateIdx(messages, messageId);
}

function onCreatePendingReply({ message, _isQuickReply }: { message: Message; _isQuickReply: boolean; }) {
    if (_isQuickReply) return;

    replyIdx = calculateIdx(MessageStore.getMessages(message.channel_id)._array, message.id);
}

const isCtrl = (e: KeyboardEvent) => isMac ? e.metaKey : e.ctrlKey;
const isAltOrMeta = (e: KeyboardEvent) => e.altKey || (!isMac && e.metaKey);

function onKeydown(e: KeyboardEvent) {
    const isUp = e.key === "ArrowUp";
    if (!isUp && e.key !== "ArrowDown") return;
    if (!isCtrl(e) || isAltOrMeta(e)) return;

    if (e.shiftKey)
        nextEdit(isUp);
    else
        nextReply(isUp);
}

function getNextMessage(isUp: boolean, isReply: boolean) {
    let messages: Message[] = MessageStore.getMessages(SelectedChannelStore.getChannelId())._array;
    if (!isReply) { // we are editing so only include own
        const meId = UserStore.getCurrentUser().id;
        messages = messages.filter(m => m.author.id === meId);
    }

    const mutate = (i: number) => isUp
        ? Math.min(messages.length - 1, i + 1)
        : Math.max(-1, i - 1);

    let i: number;
    if (isReply)
        replyIdx = i = mutate(replyIdx);
    else
        editIdx = i = mutate(editIdx);

    return i === - 1 ? undefined : messages[messages.length - i - 1];
}

// handle next/prev reply
function nextReply(isUp: boolean) {
    const message = getNextMessage(isUp, true);

    if (!message)
        return void Dispatcher.dispatch({
            type: "DELETE_PENDING_REPLY",
            channelId: SelectedChannelStore.getChannelId(),
        });

    const channel = ChannelStore.getChannel(message.channel_id);
    Dispatcher.dispatch({
        type: "CREATE_PENDING_REPLY",
        channel,
        message,
        shouldMention: true,
        showMentionToggle: channel.guild_id !== null,
        _isQuickReply: true
    });
}

// handle next/prev edit
function nextEdit(isUp: boolean) {
    const message = getNextMessage(isUp, false);

    if (!message)
        Dispatcher.dispatch({
            type: "MESSAGE_END_EDIT",
            channelId: SelectedChannelStore.getChannelId()
        });
    else
        Dispatcher.dispatch({
            type: "MESSAGE_START_EDIT",
            channelId: message.channel_id,
            messageId: message.id,
            content: message.content,
            _isQuickEdit: true
        });
}