Plugin
MessageLinkTooltip
Like MessageLinkEmbed but without taking space
1
/*2
* FiveCord — a Discord client mod3
* Copyright (c) 2025 FiveCord4
* SPDX-License-Identifier: GPL-3.0-or-later5
*/6
7
import "./style.css";8
9
import ErrorBoundary from "@components/ErrorBoundary";10
import { Devs } from "@utils/constants";11
import { proxyLazy } from "@utils/lazy";12
import definePlugin from "@utils/types";13
import { findComponentByCodeLazy } from "@webpack";14
import { ChannelStore, Forms, MessageStore, RestAPI, Tooltip, useEffect, useState, useStateFromStores } from "@webpack/common";15
import type { ComponentType, HTMLAttributes } from "react";16
17
declare enum SpinnerTypes {18
WANDERING_CUBES = "wanderingCubes",19
CHASING_DOTS = "chasingDots",20
PULSING_ELLIPSIS = "pulsingEllipsis",21
SPINNING_CIRCLE = "spinningCircle",22
SPINNING_CIRCLE_SIMPLE = "spinningCircleSimple",23
LOW_MOTION = "lowMotion",24
}25
26
type Spinner = ComponentType<Omit<HTMLAttributes<HTMLDivElement>, "children"> & {27
type?: SpinnerTypes;28
animated?: boolean;29
className?: string;30
itemClassName?: string;31
"aria-label"?: string;32
}> & {33
Type: typeof SpinnerTypes;34
};35
36
const { Spinner } = proxyLazy(() => Forms as any as {37
Spinner: Spinner,38
SpinnerTypes: typeof SpinnerTypes;39
});40
41
const ChannelMessage = findComponentByCodeLazy("renderSimpleAccessories)") as ComponentType<any>;42
43
export default definePlugin({44
name: "MessageLinkTooltip",45
description: "Like MessageLinkEmbed but without taking space",46
authors: [Devs.FiveCord],47
48
patches: [49
{50
find: 039;,className:"channelMention",children:[039;,51
replacement: {52
match: /(?<=\.jsxs\)\()(\i\.default)/,53
replace: "$self.wrapComponent(arguments[0], $1)"54
}55
}56
],57
58
wrapComponent({ messageId, channelId }, Component: ComponentType) {59
return props => {60
if (messageId === undefined) return <Component {...props} />;61
return <Tooltip62
tooltipClassName="c98-message-link-tooltip"63
text={64
<ErrorBoundary>65
<MessagePreview66
channelId={channelId}67
messageId={messageId}68
/>69
</ErrorBoundary>70
}71
>72
{({ onMouseEnter, onMouseLeave }) =>73
<Component74
{...props}75
onMouseEnter={onMouseEnter}76
onMouseLeave={onMouseLeave}77
/>78
}79
</Tooltip>;80
};81
}82
});83
84
function MessagePreview({ channelId, messageId }) {85
const channel = ChannelStore.getChannel(channelId);86
const message = useMessage(channelId, messageId);87
// TODO handle load failure88
if (!message) {89
return <Spinner type={Spinner.Type.PULSING_ELLIPSIS} />;90
}91
92
return <ChannelMessage93
id={`message-link-tooltip-${messageId}`}94
message={message}95
channel={channel}96
subscribeToComponentDispatch={false}97
/>;98
}99
100
function useMessage(channelId, messageId) {101
const cachedMessage = useStateFromStores(102
// @ts-ignore103
[MessageStore],104
() => MessageStore.getMessage(channelId, messageId)105
);106
const [message, setMessage] = useState(cachedMessage);107
useEffect(() => {108
if (message == null)109
(async () => {110
const res = await RestAPI.get({111
url: `/channels/${channelId}/messages`,112
query: {113
limit: 1,114
around: messageId,115
},116
retries: 2,117
});118
const rawMessage = res.body[0];119
const message = MessageStore.getMessages(channelId)120
.receiveMessage(rawMessage)121
.get(messageId);122
setMessage(message);123
})();124
});125
return message;126
}127