aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/pinDms/index.tsx
blob: 249dcd276c67f4217c44ebc87fa5e1a0c5449a42 (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
/*
 * 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 <https://www.gnu.org/licenses/>.
*/

import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { Channel } from "discord-types/general";

import { addContextMenus, removeContextMenus } from "./contextMenus";
import { getPinAt, isPinned, settings, snapshotArray, sortedSnapshot, usePinnedDms } from "./settings";

export default definePlugin({
    name: "PinDMs",
    description: "Allows you to pin private channels to the top of your DM list. To pin/unpin or reorder pins, right click DMs",
    authors: [Devs.Ven, Devs.Strencher],

    settings,

    start: addContextMenus,
    stop: removeContextMenus,

    usePinCount(channelIds: string[]) {
        const pinnedDms = usePinnedDms();
        // See comment on 2nd patch for reasoning
        return channelIds.length ? [pinnedDms.size] : [];
    },

    getChannel(channels: Record<string, Channel>, idx: number) {
        return channels[getPinAt(idx)];
    },

    isPinned,
    getSnapshot: sortedSnapshot,

    getScrollOffset(channelId: string, rowHeight: number, padding: number, preRenderedChildren: number, originalOffset: number) {
        if (!isPinned(channelId))
            return (
                (rowHeight + padding) * 2 // header
                + rowHeight * snapshotArray.length // pins
                + originalOffset // original pin offset minus pins
            );

        return rowHeight * (snapshotArray.indexOf(channelId) + preRenderedChildren) + padding;
    },

    patches: [
        // Patch DM list
        {
            find: ".privateChannelsHeaderContainer,",
            replacement: [
                {
                    // filter Discord's privateChannelIds list to remove pins, and pass
                    // pinCount as prop. This needs to be here so that the entire DM list receives
                    // updates on pin/unpin
                    match: /privateChannelIds:(\i),/,
                    replace: "privateChannelIds:$1.filter(c=>!$self.isPinned(c)),pinCount:$self.usePinCount($1),"
                },
                {
                    // sections is an array of numbers, where each element is a section and
                    // the number is the amount of rows. Add our pinCount in second place
                    // - Section 1: buttons for pages like Friends & Library
                    // - Section 2: our pinned dms
                    // - Section 3: the normal dm list
                    match: /(?<=renderRow:(\i)\.renderRow,)sections:\[\i,/,
                    // For some reason, adding our sections when no private channels are ready yet
                    // makes DMs infinitely load. Thus usePinCount returns either a single element
                    // array with the count, or an empty array. Due to spreading, only in the former
                    // case will an element be added to the outer array
                    // Thanks for the fix, Strencher!
                    replace: "$&...$1.props.pinCount,"
                },
                {
                    // Patch renderSection (renders the header) to set the text to "Pinned DMs" instead of "Direct Messages"
                    // lookbehind is used to lookup parameter name. We could use arguments[0], but
                    // if children ever is wrapped in an iife, it will break
                    match: /children:(\i\.\i\.Messages.DIRECT_MESSAGES)(?<=renderSection=function\((\i)\).+?)/,
                    replace: "children:$2.section===1?'Pinned DMs':$1"
                },
                {
                    // Patch channel lookup inside renderDM
                    // channel=channels[channelIds[row]];
                    match: /(?<=preRenderedChildren,(\i)=)((\i)\[\i\[\i\]\]);/,
                    // section 1 is us, manually get our own channel
                    // section === 1 ? getChannel(channels, row) : channels[channelIds[row]];
                    replace: "arguments[0]===1?$self.getChannel($3,arguments[1]):$2;"
                },
                {
                    // Fix getRowHeight's check for whether this is the DMs section
                    // section === DMS
                    match: /===\i.DMS&&0/,
                    // section -1 === DMS
                    replace: "-1$&"
                },
                {
                    // Override scrollToChannel to properly account for pinned channels
                    match: /(?<=else\{\i\+=)(\i)\*\(.+?(?=;)/,
                    replace: "$self.getScrollOffset(arguments[0],$1,this.props.padding,this.state.preRenderedChildren,$&)"
                }
            ]
        },

        // Fix Alt Up/Down navigation
        {
            find: '"mod+alt+right"',
            replacement: {
                // channelIds = __OVERLAY__ ? stuff : toArray(getStaticPaths()).concat(toArray(channelIds))
                match: /(?<=(\i)=__OVERLAY__\?\i:.{0,10})\.concat\((.{0,10})\)/,
                // ....concat(pins).concat(toArray(channelIds).filter(c => !isPinned(c)))
                replace: ".concat($self.getSnapshot()).concat($2.filter(c=>!$self.isPinned(c)))"
            }
        }
    ]
});