From cc885b5bb367add1c4e831ab1a4ab6145f6b0f5c Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 3 Feb 2024 02:11:59 +0100 Subject: [PATCH 01/33] remove lumap --- src/plugins/pictureInPicture/index.tsx | 2 +- src/utils/constants.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/plugins/pictureInPicture/index.tsx b/src/plugins/pictureInPicture/index.tsx index ba4aa838..ca766aff 100644 --- a/src/plugins/pictureInPicture/index.tsx +++ b/src/plugins/pictureInPicture/index.tsx @@ -24,7 +24,7 @@ const settings = definePluginSettings({ export default definePlugin({ name: "PictureInPicture", description: "Adds picture in picture to videos (next to the Download button)", - authors: [Devs.Lumap], + authors: [Devs.Nobody], settings, patches: [ { diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 89993612..55af9360 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -42,6 +42,10 @@ export interface Dev { * If you are fine with attribution but don't want the badge, add badge: false */ export const Devs = /* #__PURE__*/ Object.freeze({ + Nobody: { + name: "Nobody", + id: 0n, + }, Ven: { name: "Vendicated", id: 343383572805058560n @@ -359,10 +363,6 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "bb010g", id: 72791153467990016n, }, - Lumap: { - name: "lumap", - id: 635383782576357407n - }, Dolfies: { name: "Dolfies", id: 852892297661906993n, From 8938f4a3cf9a36aa490703651934acccbd07bdf9 Mon Sep 17 00:00:00 2001 From: EdVraz <88881326+EdVraz@users.noreply.github.com> Date: Sun, 4 Feb 2024 01:50:51 +0100 Subject: [PATCH 02/33] fix moreUserTags (#2146) --- src/plugins/moreUserTags/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/moreUserTags/index.tsx b/src/plugins/moreUserTags/index.tsx index 9921bca8..d1ad941b 100644 --- a/src/plugins/moreUserTags/index.tsx +++ b/src/plugins/moreUserTags/index.tsx @@ -198,7 +198,7 @@ export default definePlugin({ replacement: [ // make the tag show the right text { - match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=(\i\.\i\.Messages)\.BOT_TAG_BOT/, + match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=.{0,40}(\i\.\i\.Messages)\.BOT_TAG_BOT/, replace: (_, origSwitch, variant, tags, displayedText, strings) => `${origSwitch}default:{${displayedText} = $self.getTagText(${tags}[${variant}], ${strings})}` }, From bf977e0047141dd479b26f66cc848923310bcb6b Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 6 Feb 2024 16:29:47 +0100 Subject: [PATCH 03/33] Add chat bar button api ~ fixes buttons for russian users --- src/api/ChatButtons.tsx | 123 ++++++++++++++++++++ src/api/index.ts | 6 + src/plugins/_api/chatButtons.ts | 22 ++++ src/plugins/invisibleChat.desktop/index.tsx | 88 +++++--------- src/plugins/previewMessage/index.tsx | 74 +++++------- src/plugins/sendTimestamps/index.tsx | 101 +++++++--------- src/plugins/silentMessageToggle/index.tsx | 78 +++++-------- src/plugins/silentTyping/index.tsx | 66 ++++------- src/plugins/translate/TranslateIcon.tsx | 52 ++++----- src/plugins/translate/index.tsx | 22 +--- 10 files changed, 325 insertions(+), 307 deletions(-) create mode 100644 src/api/ChatButtons.tsx create mode 100644 src/plugins/_api/chatButtons.ts diff --git a/src/api/ChatButtons.tsx b/src/api/ChatButtons.tsx new file mode 100644 index 00000000..0350965a --- /dev/null +++ b/src/api/ChatButtons.tsx @@ -0,0 +1,123 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import ErrorBoundary from "@components/ErrorBoundary"; +import { Logger } from "@utils/Logger"; +import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common"; +import { Channel } from "discord-types/general"; +import { HTMLProps, MouseEventHandler, ReactNode } from "react"; + +export interface ChatBarProps { + channel: Channel; + disabled: boolean; + isEmpty: boolean; + type: { + analyticsName: string; + attachments: boolean; + autocomplete: { + addReactionShortcut: boolean, + forceChatLayer: boolean, + reactions: boolean; + }, + commands: { + enabled: boolean; + }, + drafts: { + type: number, + commandType: number, + autoSave: boolean; + }, + emojis: { + button: boolean; + }, + gifs: { + button: boolean, + allowSending: boolean; + }, + gifts: { + button: boolean; + }, + permissions: { + requireSendMessages: boolean; + }, + showThreadPromptOnReply: boolean, + stickers: { + button: boolean, + allowSending: boolean, + autoSuggest: boolean; + }, + users: { + allowMentioning: boolean; + }, + submit: { + button: boolean, + ignorePreference: boolean, + disableEnterToSubmit: boolean, + clearOnSubmit: boolean, + useDisabledStylesOnSubmit: boolean; + }, + uploadLongMessages: boolean, + upsellLongMessages: { + iconOnly: boolean; + }, + showCharacterCount: boolean, + sedReplace: boolean; + }; +} + +export type ChatBarButton = (props: ChatBarProps, isMainChat: boolean) => ReactNode; + +const buttonFactories = new Map(); +const logger = new Logger("ChatButtons"); + +export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) { + if (props.type.analyticsName !== "normal") return; + + for (const [key, makeButton] of buttonFactories) { + try { + const res = makeButton(props, props.type.analyticsName === "normal"); + if (res) buttons.push(res); + } catch (e) { + logger.error(`Failed to render button ${key}`, e); + } + } +} + +export const addChatBarButton = (id: string, button: ChatBarButton) => buttonFactories.set(id, button); +export const removeChatBarButton = (id: string) => buttonFactories.delete(id); + +export interface ChatBarButtonProps { + children: ReactNode; + tooltip: string; + onClick: MouseEventHandler; + onContextMenu?: MouseEventHandler; + buttonProps?: Omit, "size" | "onClick" | "onContextMenu">; +} +export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => { + return ( + + {({ onMouseEnter, onMouseLeave }) => ( +
+ +
+ )} +
+ ); +}, { noop: true }); diff --git a/src/api/index.ts b/src/api/index.ts index 08f23810..5dca6310 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -17,6 +17,7 @@ */ import * as $Badges from "./Badges"; +import * as $ChatButtons from "./ChatButtons"; import * as $Commands from "./Commands"; import * as $ContextMenu from "./ContextMenu"; import * as $DataStore from "./DataStore"; @@ -104,3 +105,8 @@ export const Notifications = $Notifications; * An api allowing you to patch and add/remove items to/from context menus */ export const ContextMenu = $ContextMenu; + +/** + * An API allowing you to add buttons to the chat input + */ +export const ChatButtons = $ChatButtons; diff --git a/src/plugins/_api/chatButtons.ts b/src/plugins/_api/chatButtons.ts new file mode 100644 index 00000000..ca85964c --- /dev/null +++ b/src/plugins/_api/chatButtons.ts @@ -0,0 +1,22 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +export default definePlugin({ + name: "ChatInputButtonAPI", + description: "API to add buttons to the chat input", + authors: [Devs.Ven], + + patches: [{ + find: 'location:"ChannelTextAreaButtons"', + replacement: { + match: /if\(!\i\.isMobile\)\{(?=.+?&&(\i)\.push\(.{0,50}"gift")/, + replace: "$&Vencord.Api.ChatButtons._injectButtons($1,arguments[0]);" + } + }] +}); diff --git a/src/plugins/invisibleChat.desktop/index.tsx b/src/plugins/invisibleChat.desktop/index.tsx index c80c4ce5..3184e025 100644 --- a/src/plugins/invisibleChat.desktop/index.tsx +++ b/src/plugins/invisibleChat.desktop/index.tsx @@ -16,13 +16,14 @@ * along with this program. If not, see . */ +import { addChatBarButton, ChatBarButton } from "@api/ChatButtons"; import { addButton, removeButton } from "@api/MessagePopover"; import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { getStegCloak } from "@utils/dependencies"; import definePlugin, { OptionType } from "@utils/types"; -import { Button, ButtonLooks, ButtonWrapperClasses, ChannelStore, FluxDispatcher, RestAPI, Tooltip } from "@webpack/common"; +import { ChannelStore, FluxDispatcher, RestAPI, Tooltip } from "@webpack/common"; import { Message } from "discord-types/general"; import { buildDecModal } from "./components/DecryptionModal"; @@ -64,54 +65,32 @@ function Indicator() { } -function ChatBarIcon(chatBoxProps: { - type: { - analyticsName: string; - }; -}) { - if (chatBoxProps.type.analyticsName !== "normal") return null; +const ChatBarIcon: ChatBarButton = (_, isMainChat) => { + if (!isMainChat) return null; return ( - - {({ onMouseEnter, onMouseLeave }) => ( - // size="" = Button.Sizes.NONE - /* - many themes set "> button" to display: none, as the gift button is - the only directly descending button (all the other elements are divs.) - Thus, wrap in a div here to avoid getting hidden by that. - flex is for some reason necessary as otherwise the button goes flying off - */ -
- -
- ) - } -
+ buildEncModal()} + + buttonProps={{ + "aria-haspopup": "dialog", + style: { padding: "0 2px", scale: "0.9" } + }} + > + + + + ); -} +}; const settings = definePluginSettings({ savedPasswords: { @@ -125,7 +104,7 @@ export default definePlugin({ name: "InvisibleChat", description: "Encrypt your Messages in a non-suspicious way!", authors: [Devs.SammCheese], - dependencies: ["MessagePopoverAPI"], + dependencies: ["MessagePopoverAPI", "ChatInputButtonAPI"], patches: [ { // Indicator @@ -135,13 +114,6 @@ export default definePlugin({ replace: "try {$1 && $self.INV_REGEX.test($1.message.content) ? $1.content.push($self.indicator()) : null } catch {};$&" } }, - { - find: "ChannelTextAreaButtons", - replacement: { - match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/, - replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()", - } - }, ], EMBED_API_URL: "https://embed.sammcheese.net", @@ -154,7 +126,7 @@ export default definePlugin({ const { default: StegCloak } = await getStegCloak(); steggo = new StegCloak(true, false); - addButton("invDecrypt", message => { + addButton("InvisibleChat", message => { return this.INV_REGEX.test(message?.content) ? { label: "Decrypt Message", @@ -170,10 +142,13 @@ export default definePlugin({ } : null; }); + + addChatBarButton("InvisibleChat", ChatBarIcon); }, stop() { - removeButton("invDecrypt"); + removeButton("InvisibleChat"); + removeButton("InvisibleChat"); }, // Gets the Embed of a Link @@ -216,7 +191,6 @@ export default definePlugin({ }); }, - chatBarIcon: ErrorBoundary.wrap(ChatBarIcon, { noop: true }), popOverIcon: () => , indicator: ErrorBoundary.wrap(Indicator, { noop: true }) }); diff --git a/src/plugins/previewMessage/index.tsx b/src/plugins/previewMessage/index.tsx index f2634ae6..1d8b769d 100644 --- a/src/plugins/previewMessage/index.tsx +++ b/src/plugins/previewMessage/index.tsx @@ -16,22 +16,14 @@ * along with this program. If not, see . */ +import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons"; import { generateId, sendBotMessage } from "@api/Commands"; -import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { Button, ButtonLooks, ButtonWrapperClasses, DraftStore, DraftType, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common"; +import { DraftStore, DraftType, SelectedChannelStore, UserStore, useStateFromStores } from "@webpack/common"; import { MessageAttachment } from "discord-types/general"; -interface Props { - type: { - analyticsName: string; - isEmpty: boolean; - attachments: boolean; - }; -} - const UploadStore = findByPropsLazy("getUploads"); const getDraft = (channelId: string) => DraftStore.getDraft(channelId, DraftType.ChannelMessage); @@ -81,13 +73,13 @@ const getAttachments = async (channelId: string) => ); -export function PreviewButton(chatBoxProps: Props) { - const { isEmpty, attachments } = chatBoxProps.type; +const PreviewButton: ChatBarButton = (props, isMainChat) => { + const { isEmpty, type: { attachments } } = props; const channelId = SelectedChannelStore.getChannelId(); const draft = useStateFromStores([DraftStore], () => getDraft(channelId)); - if (chatBoxProps.type.analyticsName !== "normal") return null; + if (!isMainChat) return null; const hasAttachments = attachments && UploadStore.getUploads(channelId, DraftType.ChannelMessage).length > 0; const hasContent = !isEmpty && draft?.length > 0; @@ -95,47 +87,33 @@ export function PreviewButton(chatBoxProps: Props) { if (!hasContent && !hasAttachments) return null; return ( - - {tooltipProps => ( - - )} - + + sendBotMessage( + channelId, + { + content: getDraft(channelId), + author: UserStore.getCurrentUser(), + attachments: hasAttachments ? await getAttachments(channelId) : undefined, + } + )} + buttonProps={{ + style: { padding: "0 2px", height: "100%" } + }} + > + + ); -} +}; export default definePlugin({ name: "PreviewMessage", description: "Lets you preview your message before sending it.", authors: [Devs.Aria], - patches: [ - { - find: "ChannelTextAreaButtons", - replacement: { - match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/, - replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()", - } - }, - ], + dependencies: ["ChatInputButtonAPI"], - chatBarIcon: ErrorBoundary.wrap(PreviewButton, { noop: true }), + start: () => addChatBarButton("previewMessage", PreviewButton), + stop: () => removeChatBarButton("previewMessage"), }); diff --git a/src/plugins/sendTimestamps/index.tsx b/src/plugins/sendTimestamps/index.tsx index 6d488add..bd888a82 100644 --- a/src/plugins/sendTimestamps/index.tsx +++ b/src/plugins/sendTimestamps/index.tsx @@ -18,6 +18,7 @@ import "./styles.css"; +import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons"; import { addPreSendListener, removePreSendListener } from "@api/MessageEvents"; import { definePluginSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; @@ -26,7 +27,7 @@ import { getTheme, insertTextIntoChatInputBox, Theme } from "@utils/discord"; import { Margins } from "@utils/margins"; import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal"; import definePlugin, { OptionType } from "@utils/types"; -import { Button, ButtonLooks, ButtonWrapperClasses, Forms, Parser, Select, Tooltip, useMemo, useState } from "@webpack/common"; +import { Button, Forms, Parser, Select, useMemo, useState } from "@webpack/common"; const settings = definePluginSettings({ replaceMessageContents: { @@ -122,25 +123,51 @@ function PickerModal({ rootProps, close }: { rootProps: ModalProps, close(): voi ); } +const ChatBarIcon: ChatBarButton = (_, isMainChat) => { + if (!isMainChat) return null; + + return ( + { + const key = openModal(props => ( + closeModal(key)} + /> + )); + }} + buttonProps={{ + "aria-haspopup": "dialog", + className: cl("button") + }} + > + + + ); +}; + export default definePlugin({ name: "SendTimestamps", description: "Send timestamps easily via chat box button & text shortcuts. Read the extended description!", authors: [Devs.Ven, Devs.Tyler, Devs.Grzesiek11], - dependencies: ["MessageEventsAPI"], + dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"], - settings: settings, - - patches: [ - { - find: "ChannelTextAreaButtons", - replacement: { - match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/, - replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()", - } - }, - ], + settings, start() { + addChatBarButton("SendTimestamps", ChatBarIcon); this.listener = addPreSendListener((_, msg) => { if (settings.store.replaceMessageContents) { msg.content = msg.content.replace(/`\d{1,2}:\d{2} ?(?:AM|PM)?`/gi, parseTime); @@ -149,56 +176,10 @@ export default definePlugin({ }, stop() { + removeChatBarButton("SendTimestamps"); removePreSendListener(this.listener); }, - chatBarIcon(chatBoxProps: { type: { analyticsName: string; }; }) { - if (chatBoxProps.type.analyticsName !== "normal") return null; - - return ( - - {({ onMouseEnter, onMouseLeave }) => ( -
- -
- ) - } -
- ); - }, - settingsAboutComponent() { const samples = [ "12:00", diff --git a/src/plugins/silentMessageToggle/index.tsx b/src/plugins/silentMessageToggle/index.tsx index b7b33826..6c7d179b 100644 --- a/src/plugins/silentMessageToggle/index.tsx +++ b/src/plugins/silentMessageToggle/index.tsx @@ -16,12 +16,12 @@ * along with this program. If not, see . */ +import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons"; import { addPreSendListener, removePreSendListener, SendListener } from "@api/MessageEvents"; import { definePluginSettings } from "@api/Settings"; -import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { Button, ButtonLooks, ButtonWrapperClasses, React, Tooltip } from "@webpack/common"; +import { React, useEffect, useState } from "@webpack/common"; let lastState = false; @@ -41,19 +41,15 @@ const settings = definePluginSettings({ } }); -function SilentMessageToggle(chatBoxProps: { - type: { - analyticsName: string; - }; -}) { - const [enabled, setEnabled] = React.useState(lastState); +const SilentMessageToggle: ChatBarButton = (_, isMainChat) => { + const [enabled, setEnabled] = useState(lastState); function setEnabledValue(value: boolean) { if (settings.store.persistState) lastState = value; setEnabled(value); } - React.useEffect(() => { + useEffect(() => { const listener: SendListener = (_, message) => { if (enabled) { if (settings.store.autoDisable) setEnabledValue(false); @@ -65,55 +61,37 @@ function SilentMessageToggle(chatBoxProps: { return () => void removePreSendListener(listener); }, [enabled]); - if (chatBoxProps.type.analyticsName !== "normal") return null; + if (!isMainChat) return null; return ( - - {tooltipProps => ( -
- -
- )} -
+ setEnabledValue(!enabled)} + buttonProps={{ + style: { padding: "0 6px" } + }} + > + + + {!enabled && <> + + + + + + } + + ); -} +}; export default definePlugin({ name: "SilentMessageToggle", authors: [Devs.Nuckyz, Devs.CatNoir], description: "Adds a button to the chat bar to toggle sending a silent message.", - dependencies: ["MessageEventsAPI"], - + dependencies: ["MessageEventsAPI", "ChatInputButtonAPI"], settings, - patches: [ - { - find: "ChannelTextAreaButtons", - replacement: { - match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/, - replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()", - } - }, - ], - chatBarIcon: ErrorBoundary.wrap(SilentMessageToggle, { noop: true }), + start: () => addChatBarButton("SilentMessageToggle", SilentMessageToggle), + stop: () => removeChatBarButton("SilentMessageToggle") }); diff --git a/src/plugins/silentTyping/index.tsx b/src/plugins/silentTyping/index.tsx index dae7ad4c..1d336b47 100644 --- a/src/plugins/silentTyping/index.tsx +++ b/src/plugins/silentTyping/index.tsx @@ -16,12 +16,12 @@ * along with this program. If not, see . */ +import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons"; import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands"; import { definePluginSettings } from "@api/Settings"; -import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { Button, ButtonLooks, ButtonWrapperClasses, FluxDispatcher, React, Tooltip } from "@webpack/common"; +import { FluxDispatcher, React } from "@webpack/common"; const settings = definePluginSettings({ showIcon: { @@ -37,45 +37,35 @@ const settings = definePluginSettings({ } }); -function SilentTypingToggle(chatBoxProps: { - type: { - analyticsName: string; - }; -}) { - const { isEnabled } = settings.use(["isEnabled"]); +const SilentTypingToggle: ChatBarButton = (_, isMainChat) => { + const { isEnabled, showIcon } = settings.use(["isEnabled", "showIcon"]); const toggle = () => settings.store.isEnabled = !settings.store.isEnabled; - if (chatBoxProps.type.analyticsName !== "normal") return null; + if (!isMainChat || !showIcon) return null; return ( - - {(tooltipProps: any) => ( -
- -
- )} -
+ + + + {isEnabled && } + + ); -} +}; export default definePlugin({ name: "SilentTyping", authors: [Devs.Ven, Devs.Rini], description: "Hide that you are typing", + dependencies: ["CommandsAPI", "ChatInputButtonAPI"], + settings, + patches: [ { find: '.dispatch({type:"TYPING_START_LOCAL"', @@ -84,17 +74,8 @@ export default definePlugin({ replace: "startTyping:$self.startTyping,stop" } }, - { - find: "ChannelTextAreaButtons", - predicate: () => settings.store.showIcon, - replacement: { - match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/, - replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()", - } - }, ], - dependencies: ["CommandsAPI"], - settings, + commands: [{ name: "silenttype", description: "Toggle whether you're hiding that you're typing or not.", @@ -120,5 +101,6 @@ export default definePlugin({ FluxDispatcher.dispatch({ type: "TYPING_START_LOCAL", channelId }); }, - chatBarIcon: ErrorBoundary.wrap(SilentTypingToggle, { noop: true }), + start: () => addChatBarButton("SilentTyping", SilentTypingToggle), + stop: () => removeChatBarButton("SilentTyping"), }); diff --git a/src/plugins/translate/TranslateIcon.tsx b/src/plugins/translate/TranslateIcon.tsx index 64958943..a7d78927 100644 --- a/src/plugins/translate/TranslateIcon.tsx +++ b/src/plugins/translate/TranslateIcon.tsx @@ -16,9 +16,9 @@ * along with this program. If not, see . */ +import { ChatBarButton } from "@api/ChatButtons"; import { classes } from "@utils/misc"; import { openModal } from "@utils/modal"; -import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common"; import { settings } from "./settings"; import { TranslateModal } from "./TranslateModal"; @@ -37,42 +37,30 @@ export function TranslateIcon({ height = 24, width = 24, className }: { height?: ); } -export function TranslateChatBarIcon({ slateProps }: { slateProps: { type: { analyticsName: string; }; }; }) { +export const TranslateChatBarIcon: ChatBarButton = (props, isMainChat) => { const { autoTranslate } = settings.use(["autoTranslate"]); - if (slateProps.type.analyticsName !== "normal") - return null; + if (!isMainChat) return null; const toggle = () => settings.store.autoTranslate = !autoTranslate; return ( - - {({ onMouseEnter, onMouseLeave }) => ( -
- -
- )} -
+ openModal(props => ( + + )); + }} + onContextMenu={() => toggle()} + buttonProps={{ + "aria-haspopup": "dialog", + style: { padding: "0 4px" } + }} + > + + ); -} +}; diff --git a/src/plugins/translate/index.tsx b/src/plugins/translate/index.tsx index 3b067c63..702e60cf 100644 --- a/src/plugins/translate/index.tsx +++ b/src/plugins/translate/index.tsx @@ -18,11 +18,11 @@ import "./styles.css"; +import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons"; import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu"; import { addAccessory, removeAccessory } from "@api/MessageAccessories"; import { addPreSendListener, removePreSendListener } from "@api/MessageEvents"; import { addButton, removeButton } from "@api/MessagePopover"; -import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; import { ChannelStore, Menu } from "@webpack/common"; @@ -55,25 +55,16 @@ export default definePlugin({ name: "Translate", description: "Translate messages with Google Translate", authors: [Devs.Ven], - dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI"], + dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI", "ChatInputButtonAPI"], settings, // not used, just here in case some other plugin wants it or w/e translate, - patches: [ - { - find: "ChannelTextAreaButtons", - replacement: { - match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/, - replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()", - } - }, - ], - start() { addAccessory("vc-translation", props => ); addContextMenuPatch("message", messageCtxPatch); + addChatBarButton("vc-translate", TranslateChatBarIcon); addButton("vc-translate", message => { if (!message.content) return null; @@ -101,13 +92,8 @@ export default definePlugin({ stop() { removePreSendListener(this.preSend); removeContextMenuPatch("message", messageCtxPatch); + removeChatBarButton("vc-translate"); removeButton("vc-translate"); removeAccessory("vc-translation"); }, - - chatBarIcon: (slateProps: any) => ( - - - - ) }); From 2c198e547ce2424ada0c8e2c067b2d2c7b435c7a Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 6 Feb 2024 16:50:21 +0100 Subject: [PATCH 04/33] Fix PreviewMessage icon being offcentre --- src/api/ChatButton.css | 4 ++++ src/api/ChatButtons.tsx | 8 +++++++- src/plugins/invisibleChat.desktop/index.tsx | 6 +++--- src/plugins/previewMessage/index.tsx | 10 ++++++++-- 4 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 src/api/ChatButton.css diff --git a/src/api/ChatButton.css b/src/api/ChatButton.css new file mode 100644 index 00000000..30869a84 --- /dev/null +++ b/src/api/ChatButton.css @@ -0,0 +1,4 @@ +.vc-chatbar-button { + display: flex; + align-items: center; +} diff --git a/src/api/ChatButtons.tsx b/src/api/ChatButtons.tsx index 0350965a..c995033b 100644 --- a/src/api/ChatButtons.tsx +++ b/src/api/ChatButtons.tsx @@ -4,12 +4,18 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import "./ChatButton.css"; + import ErrorBoundary from "@components/ErrorBoundary"; import { Logger } from "@utils/Logger"; +import { waitFor } from "@webpack"; import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common"; import { Channel } from "discord-types/general"; import { HTMLProps, MouseEventHandler, ReactNode } from "react"; +let CssClasses: { buttonContainer: string; }; +waitFor(["buttonContainer", "channelTextArea"], m => CssClasses = m); + export interface ChatBarProps { channel: Channel; disabled: boolean; @@ -100,7 +106,7 @@ export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => { return ( {({ onMouseEnter, onMouseLeave }) => ( -
+
- {viewAllowedUsersAndRoles && } + {defaultAllowedUsersAndRolesDropdownState && }
From d3bbd2c02ebcab88f0c6a7b19e7687eca6068e1c Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 9 Feb 2024 21:00:15 -0300 Subject: [PATCH 21/33] FakeNitro: option to use hyperlinks or not --- src/plugins/fakeNitro/index.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/plugins/fakeNitro/index.ts b/src/plugins/fakeNitro/index.ts index 2caecc45..ed3ec59d 100644 --- a/src/plugins/fakeNitro/index.ts +++ b/src/plugins/fakeNitro/index.ts @@ -157,6 +157,11 @@ const settings = definePluginSettings({ type: OptionType.BOOLEAN, default: true, restartNeeded: true + }, + useHyperLinks: { + description: "Whether to use hyperlinks when sending fake emojis and stickers", + type: OptionType.BOOLEAN, + default: true } }); @@ -708,7 +713,7 @@ export default definePlugin({ }, getStickerLink(stickerId: string) { - return `https://media.discordapp.net/stickers/${stickerId}.png?size=${Settings.plugins.FakeNitro.stickerSize}`; + return `https://media.discordapp.net/stickers/${stickerId}.png?size=${settings.store.stickerSize}`; }, async sendAnimatedSticker(stickerLink: string, stickerId: string, channelId: string) { @@ -813,7 +818,7 @@ export default definePlugin({ const url = new URL(link); url.searchParams.set("name", sticker.name); - messageObj.content += `${getWordBoundary(messageObj.content, messageObj.content.length - 1)}[${sticker.name}](${url})`; + messageObj.content += `${getWordBoundary(messageObj.content, messageObj.content.length - 1)}${s.useHyperLinks ? `[${sticker.name}](${url})` : url}`; extra.stickers!.length = 0; } } @@ -829,11 +834,11 @@ export default definePlugin({ const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`; const url = new URL(emoji.url); - url.searchParams.set("size", settings.store.emojiSize.toString()); + url.searchParams.set("size", s.emojiSize.toString()); url.searchParams.set("name", emoji.name); messageObj.content = messageObj.content.replace(emojiString, (match, offset, origStr) => { - return `${getWordBoundary(origStr, offset - 1)}[:${emoji.name}:](${url})${getWordBoundary(origStr, offset + match.length)}`; + return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[:${emoji.name}:](${url})` : url}${getWordBoundary(origStr, offset + match.length)}`; }); } } @@ -856,10 +861,10 @@ export default definePlugin({ if (emoji.guildId === guildId && !emoji.animated) return emojiStr; const url = new URL(emoji.url); - url.searchParams.set("size", settings.store.emojiSize.toString()); + url.searchParams.set("size", s.emojiSize.toString()); url.searchParams.set("name", emoji.name); - return `${getWordBoundary(origStr, offset - 1)}[:${emoji.name}:](${url})${getWordBoundary(origStr, offset + emojiStr.length)}`; + return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[:${emoji.name}:](${url})` : url}${getWordBoundary(origStr, offset + emojiStr.length)}`; }); }); }, From cc0d9a90bc503f6c2055e543eb1414b92127e8b3 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 9 Feb 2024 21:27:34 -0300 Subject: [PATCH 22/33] Fix UserVoiceShow patch --- src/plugins/userVoiceShow/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/userVoiceShow/index.tsx b/src/plugins/userVoiceShow/index.tsx index c95307cc..935ff1c5 100644 --- a/src/plugins/userVoiceShow/index.tsx +++ b/src/plugins/userVoiceShow/index.tsx @@ -96,7 +96,7 @@ export default definePlugin({ patches: [ // above message box { - find: ".lastEditedByContainer", + find: ".popularApplicationCommandIds,", replacement: { match: /\(0,\i\.jsx\)\(\i\.\i,{user:\i,setNote/, replace: "$self.patchPopout(arguments[0]),$&", From 38beb93e5f2ea7f7ea0c6021ddaf0ecb8f600406 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sat, 10 Feb 2024 13:42:31 -0300 Subject: [PATCH 23/33] Fix CrashHandler failing to recover and showing gray screen instead --- src/plugins/crashHandler/index.ts | 96 ++++++++++++++----------------- 1 file changed, 44 insertions(+), 52 deletions(-) diff --git a/src/plugins/crashHandler/index.ts b/src/plugins/crashHandler/index.ts index 9d38b7d1..b73641f5 100644 --- a/src/plugins/crashHandler/index.ts +++ b/src/plugins/crashHandler/index.ts @@ -25,7 +25,6 @@ import definePlugin, { OptionType } from "@utils/types"; import { maybePromptToUpdate } from "@utils/updater"; import { filters, findBulk, proxyLazyWebpack } from "@webpack"; import { FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common"; -import type { ReactElement } from "react"; const CrashHandlerLogger = new Logger("CrashHandler"); const { ModalStack, DraftManager, DraftType, closeExpressionPicker } = proxyLazyWebpack(() => { @@ -57,13 +56,12 @@ const settings = definePluginSettings({ } }); -let crashCount: number = 0; -let lastCrashTimestamp: number = 0; -let shouldAttemptNextHandle = false; +let hasCrashedOnce = false; +let shouldAttemptRecover = true; export default definePlugin({ name: "CrashHandler", - description: "Utility plugin for handling and possibly recovering from Crashes without a restart", + description: "Utility plugin for handling and possibly recovering from crashes without a restart", authors: [Devs.Nuckyz], enabledByDefault: true, @@ -74,60 +72,55 @@ export default definePlugin({ find: ".Messages.ERRORS_UNEXPECTED_CRASH", replacement: { match: /(?=this\.setState\()/, - replace: "$self.handleCrash(this)||" + replace: "$self.handleCrash(this);" } } ], - handleCrash(_this: ReactElement & { forceUpdate: () => void; }) { - if (Date.now() - lastCrashTimestamp <= 1_000 && !shouldAttemptNextHandle) return true; + handleCrash(_this: any) { + // 1 ms timeout to avoid react breaking when re-rendering + setTimeout(() => { + if (!shouldAttemptRecover) { + try { + showNotification({ + color: "#eed202", + title: "Discord has crashed!", + body: "Awn :( Discord has crashed two times rapidly, not attempting to recover.", + noPersist: true, + }); + } catch { } - shouldAttemptNextHandle = false; - - if (++crashCount > 5) { - try { - showNotification({ - color: "#eed202", - title: "Discord has crashed!", - body: "Awn :( Discord has crashed more than five times, not attempting to recover.", - noPersist: true, - }); - } catch { } - - lastCrashTimestamp = Date.now(); - return false; - } - - setTimeout(() => crashCount--, 60_000); - - try { - if (crashCount === 1) maybePromptToUpdate("Uh oh, Discord has just crashed... but good news, there is a Vencord update available that might fix this issue! Would you like to update now?", true); - - if (settings.store.attemptToPreventCrashes) { - this.handlePreventCrash(_this); - return true; + return; } - return false; - } catch (err) { - CrashHandlerLogger.error("Failed to handle crash", err); - return false; - } finally { - lastCrashTimestamp = Date.now(); - } + shouldAttemptRecover = false; + // This is enough to avoid a crash loop + setTimeout(() => shouldAttemptRecover = true, 500); + + try { + if (!hasCrashedOnce) { + hasCrashedOnce = true; + maybePromptToUpdate("Uh oh, Discord has just crashed... but good news, there is a Vencord update available that might fix this issue! Would you like to update now?", true); + } + + if (settings.store.attemptToPreventCrashes) { + this.handlePreventCrash(_this); + } + } catch (err) { + CrashHandlerLogger.error("Failed to handle crash", err); + } + }, 1); }, - handlePreventCrash(_this: ReactElement & { forceUpdate: () => void; }) { - if (Date.now() - lastCrashTimestamp >= 1_000) { - try { - showNotification({ - color: "#eed202", - title: "Discord has crashed!", - body: "Attempting to recover...", - noPersist: true, - }); - } catch { } - } + handlePreventCrash(_this: any) { + try { + showNotification({ + color: "#eed202", + title: "Discord has crashed!", + body: "Attempting to recover...", + noPersist: true, + }); + } catch { } try { const channelId = SelectedChannelStore.getChannelId(); @@ -177,8 +170,7 @@ export default definePlugin({ } try { - shouldAttemptNextHandle = true; - _this.forceUpdate(); + _this.setState({ error: null, info: null }); } catch (err) { CrashHandlerLogger.debug("Failed to update crash handler component.", err); } From 8b6a40311bab1c89a11d2384508b997e99c7b59b Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 13 Feb 2024 09:04:28 +0100 Subject: [PATCH 24/33] Bump to v1.6.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 076b2999..08078d3d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.6.7", + "version": "1.6.8", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From 0c9d2a6a21a42111465c9cecde729c878ffd1c08 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Tue, 13 Feb 2024 09:03:25 +0100 Subject: [PATCH 25/33] Fix plugins using the Timestamp component --- src/api/Notifications/notificationLog.tsx | 4 ++-- src/plugins/reviewDB/components/ReviewComponent.tsx | 4 ++-- src/plugins/serverProfile/GuildProfileModal.tsx | 4 ++-- .../components/HiddenChannelLockScreen.tsx | 6 +++--- src/webpack/common/types/components.d.ts | 3 +-- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/api/Notifications/notificationLog.tsx b/src/api/Notifications/notificationLog.tsx index 9535fb62..6f79ef70 100644 --- a/src/api/Notifications/notificationLog.tsx +++ b/src/api/Notifications/notificationLog.tsx @@ -21,7 +21,7 @@ import { Settings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; -import { Alerts, Button, Forms, moment, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common"; +import { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common"; import { nanoid } from "nanoid"; import type { DispatchWithoutAction } from "react"; @@ -129,7 +129,7 @@ function NotificationEntry({ data }: { data: PersistentNotificationData; }) { richBody={
{data.body} - +
} /> diff --git a/src/plugins/reviewDB/components/ReviewComponent.tsx b/src/plugins/reviewDB/components/ReviewComponent.tsx index 977745a2..20b298cc 100644 --- a/src/plugins/reviewDB/components/ReviewComponent.tsx +++ b/src/plugins/reviewDB/components/ReviewComponent.tsx @@ -20,7 +20,7 @@ import { openUserProfile } from "@utils/discord"; import { classes } from "@utils/misc"; import { LazyComponent } from "@utils/react"; import { filters, findBulk } from "@webpack"; -import { Alerts, moment, Parser, Timestamp, useState } from "@webpack/common"; +import { Alerts, Parser, Timestamp, useState } from "@webpack/common"; import { Auth, getToken } from "../auth"; import { Review, ReviewType } from "../entities"; @@ -163,7 +163,7 @@ export default LazyComponent(() => { { !settings.store.hideTimestamps && review.type !== ReviewType.System && ( - + {dateFormat.format(review.timestamp * 1000)} ) } diff --git a/src/plugins/serverProfile/GuildProfileModal.tsx b/src/plugins/serverProfile/GuildProfileModal.tsx index 97b40b76..be1f2cd5 100644 --- a/src/plugins/serverProfile/GuildProfileModal.tsx +++ b/src/plugins/serverProfile/GuildProfileModal.tsx @@ -12,7 +12,7 @@ import { classes } from "@utils/misc"; import { ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; import { findByPropsLazy, findExportedComponentLazy } from "@webpack"; -import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, moment, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common"; +import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common"; import { Guild, User } from "discord-types/general"; const IconUtils = findByPropsLazy("getGuildBannerURL"); @@ -50,7 +50,7 @@ const fetched = { function renderTimestamp(timestamp: number) { return ( - + ); } diff --git a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx index 7904cd10..a8f5735e 100644 --- a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx +++ b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx @@ -20,7 +20,7 @@ import { Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { formatDuration } from "@utils/text"; import { findByPropsLazy, findComponentByCodeLazy, findComponentLazy } from "@webpack"; -import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, moment, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common"; +import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common"; import type { Channel } from "discord-types/general"; import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "../../permissionsViewer/components/RolesAndUsersPermissions"; @@ -216,12 +216,12 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) { {lastMessageId && Last {channel.isForumChannel() ? "post" : "message"} created: - + } {lastPinTimestamp && - Last message pin: + Last message pin: } {(rateLimitPerUser ?? 0) > 0 && Slowmode: {formatDuration(rateLimitPerUser!, "seconds")} diff --git a/src/webpack/common/types/components.d.ts b/src/webpack/common/types/components.d.ts index b9bc434c..72a8a69b 100644 --- a/src/webpack/common/types/components.d.ts +++ b/src/webpack/common/types/components.d.ts @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import type { Moment } from "moment"; import type { ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, KeyboardEvent, MouseEvent, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react"; export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code"; @@ -154,7 +153,7 @@ export type Switch = ComponentType>; export type Timestamp = ComponentType Date: Wed, 14 Feb 2024 15:00:29 -0300 Subject: [PATCH 26/33] Attempt to fix CrashHandler odd issues --- src/plugins/crashHandler/index.ts | 52 ++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/src/plugins/crashHandler/index.ts b/src/plugins/crashHandler/index.ts index b73641f5..f8c76d7f 100644 --- a/src/plugins/crashHandler/index.ts +++ b/src/plugins/crashHandler/index.ts @@ -57,6 +57,7 @@ const settings = definePluginSettings({ }); let hasCrashedOnce = false; +let isRecovering = false; let shouldAttemptRecover = true; export default definePlugin({ @@ -71,38 +72,49 @@ export default definePlugin({ { find: ".Messages.ERRORS_UNEXPECTED_CRASH", replacement: { - match: /(?=this\.setState\()/, - replace: "$self.handleCrash(this);" + match: /this\.setState\((.+?)\)/, + replace: "$self.handleCrash(this,$1);" } } ], - handleCrash(_this: any) { + handleCrash(_this: any, errorState: any) { + _this.setState(errorState); + + // Already recovering, prevent error which happens more than once too fast to trigger another recover + if (isRecovering) return; + isRecovering = true; + // 1 ms timeout to avoid react breaking when re-rendering setTimeout(() => { - if (!shouldAttemptRecover) { - try { - showNotification({ - color: "#eed202", - title: "Discord has crashed!", - body: "Awn :( Discord has crashed two times rapidly, not attempting to recover.", - noPersist: true, - }); - } catch { } + try { + // Prevent a crash loop with an error that could not be handled + if (!shouldAttemptRecover) { + try { + showNotification({ + color: "#eed202", + title: "Discord has crashed!", + body: "Awn :( Discord has crashed two times rapidly, not attempting to recover.", + noPersist: true + }); + } catch { } - return; - } + return; + } - shouldAttemptRecover = false; - // This is enough to avoid a crash loop - setTimeout(() => shouldAttemptRecover = true, 500); + shouldAttemptRecover = false; + // This is enough to avoid a crash loop + setTimeout(() => shouldAttemptRecover = true, 500); + } catch { } try { if (!hasCrashedOnce) { hasCrashedOnce = true; maybePromptToUpdate("Uh oh, Discord has just crashed... but good news, there is a Vencord update available that might fix this issue! Would you like to update now?", true); } + } catch { } + try { if (settings.store.attemptToPreventCrashes) { this.handlePreventCrash(_this); } @@ -118,7 +130,7 @@ export default definePlugin({ color: "#eed202", title: "Discord has crashed!", body: "Attempting to recover...", - noPersist: true, + noPersist: true }); } catch { } @@ -169,6 +181,10 @@ export default definePlugin({ } } + + // Set isRecovering to false before setting the state to allow us to handle the next crash error correcty, in case it happens + setImmediate(() => isRecovering = false); + try { _this.setState({ error: null, info: null }); } catch (err) { From 46ee193cd0d27d6b9f861858621487ec9c78b397 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 14 Feb 2024 15:07:51 -0300 Subject: [PATCH 27/33] Fix MessageLogger edit Timestamp component --- src/plugins/messageLogger/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx index ef0aa03b..ef986bf8 100644 --- a/src/plugins/messageLogger/index.tsx +++ b/src/plugins/messageLogger/index.tsx @@ -26,7 +26,7 @@ import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { ChannelStore, FluxDispatcher, i18n, Menu, moment, Parser, Timestamp, UserStore } from "@webpack/common"; +import { ChannelStore, FluxDispatcher, i18n, Menu, Parser, Timestamp, UserStore } from "@webpack/common"; import overlayStyle from "./deleteStyleOverlay.css?managed"; import textStyle from "./deleteStyleText.css?managed"; @@ -122,7 +122,7 @@ export default definePlugin({ makeEdit(newMessage: any, oldMessage: any): any { return { - timestamp: moment?.call(newMessage.edited_timestamp), + timestamp: new Date(newMessage.edited_timestamp), content: oldMessage.content }; }, From 93b2095d71a0ac9d68d9f9859dd8b59687885be0 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 14 Feb 2024 15:16:31 -0300 Subject: [PATCH 28/33] Fix FakeNitro hyperlinks not working sometimes It is caused by emoji names with conflict with default emojis, but have a letter capitalized --- src/plugins/fakeNitro/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/fakeNitro/index.ts b/src/plugins/fakeNitro/index.ts index ed3ec59d..560cae38 100644 --- a/src/plugins/fakeNitro/index.ts +++ b/src/plugins/fakeNitro/index.ts @@ -838,7 +838,7 @@ export default definePlugin({ url.searchParams.set("name", emoji.name); messageObj.content = messageObj.content.replace(emojiString, (match, offset, origStr) => { - return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[:${emoji.name}:](${url})` : url}${getWordBoundary(origStr, offset + match.length)}`; + return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${emoji.name}](${url})` : url}${getWordBoundary(origStr, offset + match.length)}`; }); } } @@ -864,7 +864,7 @@ export default definePlugin({ url.searchParams.set("size", s.emojiSize.toString()); url.searchParams.set("name", emoji.name); - return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[:${emoji.name}:](${url})` : url}${getWordBoundary(origStr, offset + emojiStr.length)}`; + return `${getWordBoundary(origStr, offset - 1)}${s.useHyperLinks ? `[${emoji.name}](${url})` : url}${getWordBoundary(origStr, offset + emojiStr.length)}`; }); }); }, From f1bdf385ebbd5418cf6798e599e2c1d4240ecadb Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 14 Feb 2024 15:25:10 -0300 Subject: [PATCH 29/33] Bump to 1.6.9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 08078d3d..5ffac7ff 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.6.8", + "version": "1.6.9", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From 89367e3b2a9e02767f8aca56e3ac7c34ac0d285e Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 15 Feb 2024 09:20:27 +0100 Subject: [PATCH 30/33] WebContextMenus: fix copying images it broke because of the new url expiry parameters --- src/plugins/webContextMenus.web/index.ts | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/plugins/webContextMenus.web/index.ts b/src/plugins/webContextMenus.web/index.ts index 5f6beca2..faa24078 100644 --- a/src/plugins/webContextMenus.web/index.ts +++ b/src/plugins/webContextMenus.web/index.ts @@ -47,18 +47,23 @@ const settings = definePluginSettings({ }); const MEDIA_PROXY_URL = "https://media.discordapp.net"; -const CDN_URL = "https://cdn.discordapp.com"; +const CDN_URL = "cdn.discordapp.com"; -function fixImageUrl(urlString: string, explodeWebp: boolean) { +function fixImageUrl(urlString: string) { const url = new URL(urlString); - if (url.origin === CDN_URL) return urlString; - if (url.origin === MEDIA_PROXY_URL) return CDN_URL + url.pathname; + if (url.host === CDN_URL) return urlString; url.searchParams.delete("width"); url.searchParams.delete("height"); - url.searchParams.set("quality", "lossless"); - if (explodeWebp && url.searchParams.get("format") === "webp") - url.searchParams.set("format", "png"); + + if (url.origin === MEDIA_PROXY_URL) { + url.host = CDN_URL; + url.searchParams.delete("size"); + url.searchParams.delete("quality"); + url.searchParams.delete("format"); + } else { + url.searchParams.set("quality", "lossless"); + } return url.toString(); } @@ -199,7 +204,7 @@ export default definePlugin({ ], async copyImage(url: string) { - url = fixImageUrl(url, true); + url = fixImageUrl(url); let imageData = await fetch(url).then(r => r.blob()); if (imageData.type !== "image/png") { @@ -231,7 +236,7 @@ export default definePlugin({ }, async saveImage(url: string) { - url = fixImageUrl(url, false); + url = fixImageUrl(url); const data = await fetchImage(url); if (!data) return; From bc0a55053d709533143f66a2bc4b6914326e907d Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 15 Feb 2024 10:12:33 +0100 Subject: [PATCH 31/33] MessageLinkEmbeds: fix erroring on some invalid message links --- src/plugins/messageLinkEmbeds/index.tsx | 35 ++++++++++--------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/plugins/messageLinkEmbeds/index.tsx b/src/plugins/messageLinkEmbeds/index.tsx index 76282999..56facf2e 100644 --- a/src/plugins/messageLinkEmbeds/index.tsx +++ b/src/plugins/messageLinkEmbeds/index.tsx @@ -31,13 +31,14 @@ import { GuildStore, MessageStore, Parser, + PermissionsBits, PermissionStore, RestAPI, Text, TextAndImagesSettingsStores, UserStore } from "@webpack/common"; -import { Channel, Guild, Message } from "discord-types/general"; +import { Channel, Message } from "discord-types/general"; const messageCache = new Map id && idList.includes(id)); + const isListed = [linkedChannel.guild_id, channelID, message.author.id].some(id => id && idList.includes(id)); if (listMode === "blacklist" && isListed) continue; if (listMode === "whitelist" && !isListed) continue; @@ -265,8 +265,7 @@ function MessageEmbedAccessory({ message }: { message: Message; }) { const messageProps: MessageEmbedProps = { message: withEmbeddedBy(linkedMessage, [...embeddedBy, message.id]), - channel: linkedChannel, - guildID + channel: linkedChannel }; const type = settings.store.automodEmbeds; @@ -280,10 +279,8 @@ function MessageEmbedAccessory({ message }: { message: Message; }) { return accessories.length ? <>{accessories} : null; } -function ChannelMessageEmbedAccessory({ message, channel, guildID }: MessageEmbedProps): JSX.Element | null { - const isDM = guildID === "@me"; - - const guild = !isDM && GuildStore.getGuild(channel.guild_id); +function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps): JSX.Element | null { + const guild = !channel.isDM() && GuildStore.getGuild(channel.guild_id); const dmReceiver = UserStore.getUser(ChannelStore.getChannel(channel.id).recipients?.[0]); @@ -293,11 +290,8 @@ function ChannelMessageEmbedAccessory({ message, channel, guildID }: MessageEmbe color: "var(--background-secondary)", author: { name: - {isDM ? "Direct Message - " : (guild as Guild).name + " - "} - {isDM - ? Parser.parse(`<@${dmReceiver.id}>`) - : Parser.parse(`<#${channel.id}>`) - } + {channel.isDM() && Direct Message - } + {Parser.parse(channel.isDM() ? `<@${dmReceiver.id}>` : `<#${channel.id}>`)} , iconProxyURL: guild ? `https://${window.GLOBAL_ENV.CDN_HOST}/icons/${guild.id}/${guild.icon}.png` @@ -318,9 +312,8 @@ function ChannelMessageEmbedAccessory({ message, channel, guildID }: MessageEmbe } function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null { - const { message, channel, guildID } = props; + const { message, channel } = props; const compact = TextAndImagesSettingsStores.MessageDisplayCompact.useSetting(); - const isDM = guildID === "@me"; const images = getImages(message); const { parse } = Parser; @@ -328,11 +321,11 @@ function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null { channel={channel} childrenAccessories={ - {isDM + {channel.isDM() ? parse(`<@${ChannelStore.getChannel(channel.id).recipients[0]}>`) : parse(`<#${channel.id}>`) } - {isDM ? " - Direct Message" : " - " + GuildStore.getGuild(channel.guild_id)?.name} + {channel.isDM() && - Direct Message} } compact={compact} From a501da692f5c6584ae202fade81794e9fee00ef4 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 15 Feb 2024 10:36:01 +0100 Subject: [PATCH 32/33] MessageLinkEmbeds: fix group dm support, improve ui --- src/plugins/messageLinkEmbeds/index.tsx | 79 +++++++++++-------- src/plugins/mutualGroupDMs/index.tsx | 5 +- .../serverProfile/GuildProfileModal.tsx | 12 +-- src/plugins/viewIcons/index.tsx | 21 ++--- src/webpack/common/types/utils.d.ts | 45 +++++++++++ src/webpack/common/utils.ts | 2 + 6 files changed, 107 insertions(+), 57 deletions(-) diff --git a/src/plugins/messageLinkEmbeds/index.tsx b/src/plugins/messageLinkEmbeds/index.tsx index 56facf2e..50c09ec9 100644 --- a/src/plugins/messageLinkEmbeds/index.tsx +++ b/src/plugins/messageLinkEmbeds/index.tsx @@ -29,6 +29,7 @@ import { ChannelStore, FluxDispatcher, GuildStore, + IconUtils, MessageStore, Parser, PermissionsBits, @@ -50,6 +51,7 @@ const AutoModEmbed = findComponentByCodeLazy(".withFooter]:", "childrenMessageCo const ChannelMessage = findComponentByCodeLazy("renderSimpleAccessories)"); const SearchResultClasses = findByPropsLazy("message", "searchResult"); +const EmbedClasses = findByPropsLazy("embedAuthorIcon", "embedAuthor", "embedAuthor"); const messageLinkRegex = /(?{accessories} : null; } +function getChannelLabelAndIconUrl(channel: Channel) { + if (channel.isDM()) return ["Direct Message", IconUtils.getUserAvatarURL(UserStore.getUser(channel.recipients[0]))]; + if (channel.isGroupDM()) return ["Group DM", IconUtils.getChannelIconURL(channel)]; + return ["Server", IconUtils.getGuildIconURL(GuildStore.getGuild(channel.guild_id))]; +} + function ChannelMessageEmbedAccessory({ message, channel }: MessageEmbedProps): JSX.Element | null { - const guild = !channel.isDM() && GuildStore.getGuild(channel.guild_id); const dmReceiver = UserStore.getUser(ChannelStore.getChannel(channel.id).recipients?.[0]); + const [channelLabel, iconUrl] = getChannelLabelAndIconUrl(channel); - return - {channel.isDM() && Direct Message - } - {Parser.parse(channel.isDM() ? `<@${dmReceiver.id}>` : `<#${channel.id}>`)} - , - iconProxyURL: guild - ? `https://${window.GLOBAL_ENV.CDN_HOST}/icons/${guild.id}/${guild.icon}.png` - : `https://${window.GLOBAL_ENV.CDN_HOST}/avatars/${dmReceiver.id}/${dmReceiver.avatar}` - } - }} - renderDescription={() => ( -
- -
- )} - />; + return ( + + {channelLabel} - + {Parser.parse(channel.isDM() ? `<@${dmReceiver.id}>` : `<#${channel.id}>`)} + , + iconProxyURL: iconUrl + } + }} + renderDescription={() => ( +
+ +
+ )} + /> + ); } function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null { @@ -317,15 +325,20 @@ function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null { const images = getImages(message); const { parse } = Parser; + const [channelLabel, iconUrl] = getChannelLabelAndIconUrl(channel); + return - {channel.isDM() - ? parse(`<@${ChannelStore.getChannel(channel.id).recipients[0]}>`) - : parse(`<#${channel.id}>`) - } - {channel.isDM() && - Direct Message} + + {iconUrl && } + + {channelLabel} - + {channel.isDM() + ? Parser.parse(`<@${ChannelStore.getChannel(channel.id).recipients[0]}>`) + : Parser.parse(`<#${channel.id}>`) + } + } compact={compact} diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index 226d000f..40d5201c 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -20,11 +20,10 @@ import { Devs } from "@utils/constants"; import { isNonNullish } from "@utils/guards"; import definePlugin from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { Avatar, ChannelStore, Clickable, RelationshipStore, ScrollerThin, UserStore } from "@webpack/common"; +import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, UserStore } from "@webpack/common"; import { Channel, User } from "discord-types/general"; const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel"); -const AvatarUtils = findByPropsLazy("getChannelIconURL"); const UserUtils = findByPropsLazy("getGlobalName"); const ProfileListClasses = findByPropsLazy("emptyIconFriends", "emptyIconGuilds"); @@ -71,7 +70,7 @@ export default definePlugin({ }} > diff --git a/src/plugins/serverProfile/GuildProfileModal.tsx b/src/plugins/serverProfile/GuildProfileModal.tsx index be1f2cd5..834367e0 100644 --- a/src/plugins/serverProfile/GuildProfileModal.tsx +++ b/src/plugins/serverProfile/GuildProfileModal.tsx @@ -12,10 +12,9 @@ import { classes } from "@utils/misc"; import { ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; import { findByPropsLazy, findExportedComponentLazy } from "@webpack"; -import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common"; +import { FluxDispatcher, Forms, GuildChannelStore, GuildMemberStore, IconUtils, Parser, PresenceStore, RelationshipStore, ScrollerThin, SnowflakeUtils, TabBar, Timestamp, useEffect, UserStore, UserUtils, useState, useStateFromStores } from "@webpack/common"; import { Guild, User } from "discord-types/general"; -const IconUtils = findByPropsLazy("getGuildBannerURL"); const IconClasses = findByPropsLazy("icon", "acronym", "childWrapper"); const FriendRow = findExportedComponentLazy("FriendRow"); @@ -65,10 +64,7 @@ function GuildProfileModal({ guild }: GuildProps) { const [currentTab, setCurrentTab] = useState(Tabs.ServerInfo); - const bannerUrl = guild.banner && IconUtils.getGuildBannerURL({ - id: guild.id, - banner: guild.banner - }, true).replace(/\?size=\d+$/, "?size=1024"); + const bannerUrl = guild.banner && IconUtils.getGuildBannerURL(guild, true)!.replace(/\?size=\d+$/, "?size=1024"); const iconUrl = guild.icon && IconUtils.getGuildIconURL({ id: guild.id, @@ -89,7 +85,7 @@ function GuildProfileModal({ guild }: GuildProps) { )}
- {guild.icon + {iconUrl ? openImage(BannerStore.getUserAvatarURL(user, true))} + action={() => openImage(IconUtils.getUserAvatarURL(user, true))} icon={ImageIcon} /> {memberAvatar && ( openImage(BannerStore.getGuildMemberAvatarURLSimple({ + action={() => openImage(IconUtils.getGuildMemberAvatarURLSimple({ userId: user.id, avatar: memberAvatar, - guildId, + guildId: guildId!, canAnimate: true - }, true))} + }))} icon={ImageIcon} /> )} @@ -124,11 +122,11 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildCon id="view-icon" label="View Icon" action={() => - openImage(BannerStore.getGuildIconURL({ + openImage(IconUtils.getGuildIconURL({ id, icon, canAnimate: true - })) + })!) } icon={ImageIcon} /> @@ -138,10 +136,7 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildCon id="view-banner" label="View Banner" action={() => - openImage(BannerStore.getGuildBannerURL({ - id, - banner, - }, true)) + openImage(IconUtils.getGuildBannerURL(guild, true)!) } icon={ImageIcon} /> diff --git a/src/webpack/common/types/utils.d.ts b/src/webpack/common/types/utils.d.ts index 24665914..2005581a 100644 --- a/src/webpack/common/types/utils.d.ts +++ b/src/webpack/common/types/utils.d.ts @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import { Guild, GuildMember } from "discord-types/general"; import type { ReactNode } from "react"; import type { FluxEvents } from "./fluxEvents"; @@ -182,3 +183,47 @@ export interface NavigationRouter { getLastRouteChangeSource(): any; getLastRouteChangeSourceLocationStack(): any; } + +export interface IconUtils { + getUserAvatarURL(user: User, canAnimate?: boolean, size?: number, format?: string): string; + getDefaultAvatarURL(id: string, discriminator?: string): string; + getUserBannerURL(data: { id: string, banner: string, canAnimate?: boolean, size: number; }): string | undefined; + getAvatarDecorationURL(dara: { avatarDecoration: string, size: number; canCanimate?: boolean; }): string | undefined; + + getGuildMemberAvatarURL(member: GuildMember, canAnimate?: string): string | null; + getGuildMemberAvatarURLSimple(data: { guildId: string, userId: string, avatar: string, canAnimate?: boolean; size?: number; }): string; + getGuildMemberBannerURL(data: { id: string, guildId: string, banner: string, canAnimate?: boolean, size: number; }): string | undefined; + + getGuildIconURL(data: { id: string, icon?: string, size?: number, canAnimate?: boolean; }): string | undefined; + getGuildBannerURL(guild: Guild, canAnimate?: boolean): string | null; + + getChannelIconURL(data: { id: string; icon?: string; applicationId?: string; size?: number; }): string | undefined; + getEmojiURL(data: { id: string, animated: boolean, size: number, forcePNG?: boolean; }): string; + + hasAnimatedGuildIcon(guild: Guild): boolean; + isAnimatedIconHash(hash: string): boolean; + + getGuildSplashURL: any; + getGuildDiscoverySplashURL: any; + getGuildHomeHeaderURL: any; + getResourceChannelIconURL: any; + getNewMemberActionIconURL: any; + getGuildTemplateIconURL: any; + getApplicationIconURL: any; + getGameAssetURL: any; + getVideoFilterAssetURL: any; + + getGuildMemberAvatarSource: any; + getUserAvatarSource: any; + getGuildSplashSource: any; + getGuildDiscoverySplashSource: any; + makeSource: any; + getGameAssetSource: any; + getGuildIconSource: any; + getGuildTemplateIconSource: any; + getGuildBannerSource: any; + getGuildHomeHeaderSource: any; + getChannelIconSource: any; + getApplicationIconSource: any; + getAnimatableSourceWithFallback: any; +} diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index c62f745a..7060573d 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -137,3 +137,5 @@ export const { persist: zustandPersist }: typeof import("zustand/middleware") = export const MessageActions = findByPropsLazy("editMessage", "sendMessage"); export const UserProfileActions = findByPropsLazy("openUserProfileModal", "closeUserProfileModal"); export const InviteActions = findByPropsLazy("resolveInvite"); + +export const IconUtils: t.IconUtils = findByPropsLazy("getGuildBannerURL", "getUserAvatarURL"); From 7b960716439cba093202f9ba37dd6b258be39017 Mon Sep 17 00:00:00 2001 From: Syncx <47534062+Syncxv@users.noreply.github.com> Date: Fri, 16 Feb 2024 17:52:09 +1100 Subject: [PATCH 33/33] fix ImageZoom patch (#2181) --- src/plugins/imageZoom/components/Magnifier.tsx | 11 ++++++----- src/plugins/imageZoom/index.tsx | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/plugins/imageZoom/components/Magnifier.tsx b/src/plugins/imageZoom/components/Magnifier.tsx index 6a3fc05a..81671735 100644 --- a/src/plugins/imageZoom/components/Magnifier.tsx +++ b/src/plugins/imageZoom/components/Magnifier.tsx @@ -123,14 +123,13 @@ export const Magnifier: React.FC = ({ instance, size: initialSiz waitFor(() => instance.state.readyState === "READY", () => { const elem = document.getElementById(ELEMENT_ID) as HTMLDivElement; element.current = elem; - elem.firstElementChild!.setAttribute("draggable", "false"); + elem.querySelector("img,video")?.setAttribute("draggable", "false"); if (instance.props.animated) { originalVideoElementRef.current = elem!.querySelector("video")!; originalVideoElementRef.current.addEventListener("timeupdate", syncVideos); - setReady(true); - } else { - setReady(true); } + + setReady(true); }); document.addEventListener("keydown", onKeyDown); document.addEventListener("keyup", onKeyUp); @@ -155,7 +154,9 @@ export const Magnifier: React.FC = ({ instance, size: initialSiz if (!ready) return null; - const box = element.current!.getBoundingClientRect(); + const box = element.current?.getBoundingClientRect(); + + if (!box) return null; return (