diff --git a/fluxer_app/src/features/channel/components/embeds/channel_embed/EmbedLink.tsx b/fluxer_app/src/features/channel/components/embeds/channel_embed/EmbedLink.tsx index 95d604abe..99dc3df66 100644 --- a/fluxer_app/src/features/channel/components/embeds/channel_embed/EmbedLink.tsx +++ b/fluxer_app/src/features/channel/components/embeds/channel_embed/EmbedLink.tsx @@ -8,6 +8,7 @@ import { } from '@app/features/channel/components/embeds/channel_embed/ChannelEmbedShared'; import {SUPPRESS_EMBEDS_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors'; import * as MessageCommands from '@app/features/messaging/commands/MessageCommands'; +import {useOpenInBrowserOnMiddleClick} from '@app/features/messaging/hooks/useOpenInBrowserOnMiddleClick'; import type {Message} from '@app/features/messaging/models/MessagingMessage'; import {openExternalUrlWithWarning} from '@app/features/messaging/utils/ExternalLinkUtils'; import TrustedDomain from '@app/features/trusted_domain/state/TrustedDomain'; @@ -31,6 +32,7 @@ export const EmbedLink: FC = observer(({url, children, class logger.warn('Invalid URL in embed link:', url); } }; + const openInBrowser = useOpenInBrowserOnMiddleClick(url); return ( = observer(({url, children, class rel="noopener noreferrer" target="_blank" onClick={handleClick} + onAuxClick={openInBrowser.onAuxClick} data-flx="channel.embeds.embed.link-component.a" > {children} diff --git a/fluxer_app/src/features/messaging/components/markdown/renderers/LinkRenderer.tsx b/fluxer_app/src/features/messaging/components/markdown/renderers/LinkRenderer.tsx index a629229df..5fec9d925 100644 --- a/fluxer_app/src/features/messaging/components/markdown/renderers/LinkRenderer.tsx +++ b/fluxer_app/src/features/messaging/components/markdown/renderers/LinkRenderer.tsx @@ -31,8 +31,11 @@ import { type RendererProps, } from '@app/features/messaging/components/markdown/renderers/RendererTypes'; import {ExternalLinkWarningModal} from '@app/features/messaging/components/modals/ExternalLinkWarningModal'; +import { + MIDDLE_MOUSE_BUTTON, + useOpenInBrowserOnMiddleClick, +} from '@app/features/messaging/hooks/useOpenInBrowserOnMiddleClick'; import AttachmentUrlRefresher from '@app/features/messaging/state/AttachmentUrlRefresher'; -import {openExternalUrlWithWarning} from '@app/features/messaging/utils/ExternalLinkUtils'; import {goToMessage} from '@app/features/messaging/utils/MessageNavigator'; import type {LinkNode} from '@app/features/messaging/utils/markdown/parser/Nodes'; import * as NavigationCommands from '@app/features/navigation/commands/NavigationCommands'; @@ -205,6 +208,19 @@ function promptForJumpGate(channel: Channel, scope: string | null | undefined, o }); } +function useJumpLinkAuxClick(url: string, interactive: boolean) { + return useCallback( + (event: React.MouseEvent) => { + if (!interactive) return; + if (event.button !== MIDDLE_MOUSE_BUTTON) return; + event.preventDefault(); + event.stopPropagation(); + if (url) void openExternalUrl(url); + }, + [interactive, url], + ); +} + type CompactGuildMentionIconStyle = React.CSSProperties & { '--jump-link-guild-icon-image'?: string; }; @@ -324,16 +340,7 @@ const JumpLinkMention = observer(function JumpLinkMention({ }, [channel, guild?.id, interactive, navigateToJumpTarget], ); - const handleAuxClick = useCallback( - (event: React.MouseEvent) => { - if (!interactive) return; - if (event.button !== 1) return; - event.preventDefault(); - event.stopPropagation(); - if (url) void openExternalUrl(url); - }, - [interactive, url], - ); + const handleAuxClick = useJumpLinkAuxClick(url, interactive); const handleContextMenu = useCallback( (event: React.MouseEvent) => { if (!interactive) return; @@ -469,16 +476,7 @@ function InaccessibleJumpLinkMention({url, i18n, interactive = true}: Inaccessib }, [i18n, interactive], ); - const handleAuxClick = useCallback( - (event: React.MouseEvent) => { - if (!interactive) return; - if (event.button !== 1) return; - event.preventDefault(); - event.stopPropagation(); - if (url) void openExternalUrl(url); - }, - [interactive, url], - ); + const handleAuxClick = useJumpLinkAuxClick(url, interactive); const handleContextMenu = useCallback( (event: React.MouseEvent) => { if (!interactive) return; @@ -555,16 +553,7 @@ const DiscoverableJumpLinkMention = observer(function DiscoverableJumpLinkMentio }, [channelId, guildId, interactive, joining, messageId], ); - const handleAuxClick = useCallback( - (event: React.MouseEvent) => { - if (!interactive) return; - if (event.button !== 1) return; - event.preventDefault(); - event.stopPropagation(); - void openExternalUrl(url); - }, - [interactive, url], - ); + const handleAuxClick = useJumpLinkAuxClick(url, interactive); const handleContextMenu = useCallback( (event: React.MouseEvent) => { if (!interactive) return; @@ -705,16 +694,7 @@ function SettingsJumpLinkMention({target, url, i18n, interactive = true}: Settin }, [interactive, url], ); - const handleAuxClick = useCallback( - (event: React.MouseEvent) => { - if (!interactive) return; - if (event.button !== 1) return; - event.preventDefault(); - event.stopPropagation(); - if (url) void openExternalUrl(url); - }, - [interactive, url], - ); + const handleAuxClick = useJumpLinkAuxClick(url, interactive); const handleContextMenu = useCallback( (event: React.MouseEvent) => { if (!interactive) return; @@ -808,16 +788,7 @@ function AppPageJumpLinkMention({page, url, i18n, interactive = true}: AppPageJu }, [interactive, page], ); - const handleAuxClick = useCallback( - (event: React.MouseEvent) => { - if (!interactive) return; - if (event.button !== 1) return; - event.preventDefault(); - event.stopPropagation(); - void openExternalUrl(url); - }, - [interactive, url], - ); + const handleAuxClick = useJumpLinkAuxClick(url, interactive); const handleContextMenu = useCallback( (event: React.MouseEvent) => { if (!interactive) return; @@ -876,6 +847,7 @@ export const LinkRenderer = observer(function LinkRenderer({ }: RendererProps): React.ReactElement { const i18n = options.i18n!; const {url, text} = node; + const openInBrowser = useOpenInBrowserOnMiddleClick(url); const content = text ? renderChildren([text], LINK_CONTENT_OPTION_OVERRIDES) : url; const inviteCode = InviteUtils.findInvite(url); const themeCode = ThemeUtils.findTheme(url); @@ -1199,14 +1171,7 @@ export const LinkRenderer = observer(function LinkRenderer({ void openExternalUrl(url); } }} - onAuxClick={(e) => { - if (e.button !== 1 || isInternal) { - return; - } - e.preventDefault(); - e.stopPropagation(); - openExternalUrlWithWarning(url); - }} + onAuxClick={isInternal ? undefined : openInBrowser.onAuxClick} className={markupStyles.link} data-flx="messaging.markdown.renderers.link-renderer.a" > diff --git a/fluxer_app/src/features/messaging/hooks/useOpenInBrowserOnMiddleClick.ts b/fluxer_app/src/features/messaging/hooks/useOpenInBrowserOnMiddleClick.ts index acbb0abba..14cf102fd 100644 --- a/fluxer_app/src/features/messaging/hooks/useOpenInBrowserOnMiddleClick.ts +++ b/fluxer_app/src/features/messaging/hooks/useOpenInBrowserOnMiddleClick.ts @@ -1,10 +1,10 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -import {openExternalUrl} from '@app/features/ui/utils/NativeUtils'; +import {openExternalUrlWithWarning} from '@app/features/messaging/utils/ExternalLinkUtils'; import type React from 'react'; import {useMemo} from 'react'; -const MIDDLE_MOUSE_BUTTON = 1; +export const MIDDLE_MOUSE_BUTTON = 1; interface MiddleClickOpenHandlers { onMouseDown: (event: React.MouseEvent) => void; @@ -21,7 +21,7 @@ export function useOpenInBrowserOnMiddleClick(url: string | null | undefined, en if (event.button !== MIDDLE_MOUSE_BUTTON || !enabled || !url) return; event.preventDefault(); event.stopPropagation(); - void openExternalUrl(url); + openExternalUrlWithWarning(url); }, }), [url, enabled],