mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
fix(links): warn on embed link middle clicks (#3305)
This commit is contained in:
1 parent
abfaff16c2
commit
ebf91ccc76
3 files changed
+30
-62
No files matched your search
@@ -8,6 +8,7 @@ import {
|
|||||||
} from '@app/features/channel/components/embeds/channel_embed/ChannelEmbedShared';
|
} from '@app/features/channel/components/embeds/channel_embed/ChannelEmbedShared';
|
||||||
import {SUPPRESS_EMBEDS_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
|
import {SUPPRESS_EMBEDS_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors';
|
||||||
import * as MessageCommands from '@app/features/messaging/commands/MessageCommands';
|
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 type {Message} from '@app/features/messaging/models/MessagingMessage';
|
||||||
import {openExternalUrlWithWarning} from '@app/features/messaging/utils/ExternalLinkUtils';
|
import {openExternalUrlWithWarning} from '@app/features/messaging/utils/ExternalLinkUtils';
|
||||||
import TrustedDomain from '@app/features/trusted_domain/state/TrustedDomain';
|
import TrustedDomain from '@app/features/trusted_domain/state/TrustedDomain';
|
||||||
@@ -31,6 +32,7 @@ export const EmbedLink: FC<LinkComponentProps> = observer(({url, children, class
|
|||||||
logger.warn('Invalid URL in embed link:', url);
|
logger.warn('Invalid URL in embed link:', url);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const openInBrowser = useOpenInBrowserOnMiddleClick(url);
|
||||||
return (
|
return (
|
||||||
<FocusRing data-flx="channel.embeds.embed.link-component.focus-ring">
|
<FocusRing data-flx="channel.embeds.embed.link-component.focus-ring">
|
||||||
<a
|
<a
|
||||||
@@ -39,6 +41,7 @@ export const EmbedLink: FC<LinkComponentProps> = observer(({url, children, class
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
|
onAuxClick={openInBrowser.onAuxClick}
|
||||||
data-flx="channel.embeds.embed.link-component.a"
|
data-flx="channel.embeds.embed.link-component.a"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -31,8 +31,11 @@ import {
|
|||||||
type RendererProps,
|
type RendererProps,
|
||||||
} from '@app/features/messaging/components/markdown/renderers/RendererTypes';
|
} from '@app/features/messaging/components/markdown/renderers/RendererTypes';
|
||||||
import {ExternalLinkWarningModal} from '@app/features/messaging/components/modals/ExternalLinkWarningModal';
|
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 AttachmentUrlRefresher from '@app/features/messaging/state/AttachmentUrlRefresher';
|
||||||
import {openExternalUrlWithWarning} from '@app/features/messaging/utils/ExternalLinkUtils';
|
|
||||||
import {goToMessage} from '@app/features/messaging/utils/MessageNavigator';
|
import {goToMessage} from '@app/features/messaging/utils/MessageNavigator';
|
||||||
import type {LinkNode} from '@app/features/messaging/utils/markdown/parser/Nodes';
|
import type {LinkNode} from '@app/features/messaging/utils/markdown/parser/Nodes';
|
||||||
import * as NavigationCommands from '@app/features/navigation/commands/NavigationCommands';
|
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<HTMLButtonElement | HTMLSpanElement>) => {
|
||||||
|
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 & {
|
type CompactGuildMentionIconStyle = React.CSSProperties & {
|
||||||
'--jump-link-guild-icon-image'?: string;
|
'--jump-link-guild-icon-image'?: string;
|
||||||
};
|
};
|
||||||
@@ -324,16 +340,7 @@ const JumpLinkMention = observer(function JumpLinkMention({
|
|||||||
},
|
},
|
||||||
[channel, guild?.id, interactive, navigateToJumpTarget],
|
[channel, guild?.id, interactive, navigateToJumpTarget],
|
||||||
);
|
);
|
||||||
const handleAuxClick = useCallback(
|
const handleAuxClick = useJumpLinkAuxClick(url, interactive);
|
||||||
(event: React.MouseEvent<HTMLButtonElement | HTMLSpanElement>) => {
|
|
||||||
if (!interactive) return;
|
|
||||||
if (event.button !== 1) return;
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
if (url) void openExternalUrl(url);
|
|
||||||
},
|
|
||||||
[interactive, url],
|
|
||||||
);
|
|
||||||
const handleContextMenu = useCallback(
|
const handleContextMenu = useCallback(
|
||||||
(event: React.MouseEvent<HTMLButtonElement | HTMLSpanElement>) => {
|
(event: React.MouseEvent<HTMLButtonElement | HTMLSpanElement>) => {
|
||||||
if (!interactive) return;
|
if (!interactive) return;
|
||||||
@@ -469,16 +476,7 @@ function InaccessibleJumpLinkMention({url, i18n, interactive = true}: Inaccessib
|
|||||||
},
|
},
|
||||||
[i18n, interactive],
|
[i18n, interactive],
|
||||||
);
|
);
|
||||||
const handleAuxClick = useCallback(
|
const handleAuxClick = useJumpLinkAuxClick(url, interactive);
|
||||||
(event: React.MouseEvent<HTMLButtonElement | HTMLSpanElement>) => {
|
|
||||||
if (!interactive) return;
|
|
||||||
if (event.button !== 1) return;
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
if (url) void openExternalUrl(url);
|
|
||||||
},
|
|
||||||
[interactive, url],
|
|
||||||
);
|
|
||||||
const handleContextMenu = useCallback(
|
const handleContextMenu = useCallback(
|
||||||
(event: React.MouseEvent<HTMLButtonElement | HTMLSpanElement>) => {
|
(event: React.MouseEvent<HTMLButtonElement | HTMLSpanElement>) => {
|
||||||
if (!interactive) return;
|
if (!interactive) return;
|
||||||
@@ -555,16 +553,7 @@ const DiscoverableJumpLinkMention = observer(function DiscoverableJumpLinkMentio
|
|||||||
},
|
},
|
||||||
[channelId, guildId, interactive, joining, messageId],
|
[channelId, guildId, interactive, joining, messageId],
|
||||||
);
|
);
|
||||||
const handleAuxClick = useCallback(
|
const handleAuxClick = useJumpLinkAuxClick(url, interactive);
|
||||||
(event: React.MouseEvent<HTMLButtonElement | HTMLSpanElement>) => {
|
|
||||||
if (!interactive) return;
|
|
||||||
if (event.button !== 1) return;
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
void openExternalUrl(url);
|
|
||||||
},
|
|
||||||
[interactive, url],
|
|
||||||
);
|
|
||||||
const handleContextMenu = useCallback(
|
const handleContextMenu = useCallback(
|
||||||
(event: React.MouseEvent<HTMLButtonElement | HTMLSpanElement>) => {
|
(event: React.MouseEvent<HTMLButtonElement | HTMLSpanElement>) => {
|
||||||
if (!interactive) return;
|
if (!interactive) return;
|
||||||
@@ -705,16 +694,7 @@ function SettingsJumpLinkMention({target, url, i18n, interactive = true}: Settin
|
|||||||
},
|
},
|
||||||
[interactive, url],
|
[interactive, url],
|
||||||
);
|
);
|
||||||
const handleAuxClick = useCallback(
|
const handleAuxClick = useJumpLinkAuxClick(url, interactive);
|
||||||
(event: React.MouseEvent<HTMLButtonElement | HTMLSpanElement>) => {
|
|
||||||
if (!interactive) return;
|
|
||||||
if (event.button !== 1) return;
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
if (url) void openExternalUrl(url);
|
|
||||||
},
|
|
||||||
[interactive, url],
|
|
||||||
);
|
|
||||||
const handleContextMenu = useCallback(
|
const handleContextMenu = useCallback(
|
||||||
(event: React.MouseEvent<HTMLButtonElement | HTMLSpanElement>) => {
|
(event: React.MouseEvent<HTMLButtonElement | HTMLSpanElement>) => {
|
||||||
if (!interactive) return;
|
if (!interactive) return;
|
||||||
@@ -808,16 +788,7 @@ function AppPageJumpLinkMention({page, url, i18n, interactive = true}: AppPageJu
|
|||||||
},
|
},
|
||||||
[interactive, page],
|
[interactive, page],
|
||||||
);
|
);
|
||||||
const handleAuxClick = useCallback(
|
const handleAuxClick = useJumpLinkAuxClick(url, interactive);
|
||||||
(event: React.MouseEvent<HTMLButtonElement | HTMLSpanElement>) => {
|
|
||||||
if (!interactive) return;
|
|
||||||
if (event.button !== 1) return;
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
void openExternalUrl(url);
|
|
||||||
},
|
|
||||||
[interactive, url],
|
|
||||||
);
|
|
||||||
const handleContextMenu = useCallback(
|
const handleContextMenu = useCallback(
|
||||||
(event: React.MouseEvent<HTMLButtonElement | HTMLSpanElement>) => {
|
(event: React.MouseEvent<HTMLButtonElement | HTMLSpanElement>) => {
|
||||||
if (!interactive) return;
|
if (!interactive) return;
|
||||||
@@ -876,6 +847,7 @@ export const LinkRenderer = observer(function LinkRenderer({
|
|||||||
}: RendererProps<LinkNode>): React.ReactElement {
|
}: RendererProps<LinkNode>): React.ReactElement {
|
||||||
const i18n = options.i18n!;
|
const i18n = options.i18n!;
|
||||||
const {url, text} = node;
|
const {url, text} = node;
|
||||||
|
const openInBrowser = useOpenInBrowserOnMiddleClick(url);
|
||||||
const content = text ? renderChildren([text], LINK_CONTENT_OPTION_OVERRIDES) : url;
|
const content = text ? renderChildren([text], LINK_CONTENT_OPTION_OVERRIDES) : url;
|
||||||
const inviteCode = InviteUtils.findInvite(url);
|
const inviteCode = InviteUtils.findInvite(url);
|
||||||
const themeCode = ThemeUtils.findTheme(url);
|
const themeCode = ThemeUtils.findTheme(url);
|
||||||
@@ -1199,14 +1171,7 @@ export const LinkRenderer = observer(function LinkRenderer({
|
|||||||
void openExternalUrl(url);
|
void openExternalUrl(url);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onAuxClick={(e) => {
|
onAuxClick={isInternal ? undefined : openInBrowser.onAuxClick}
|
||||||
if (e.button !== 1 || isInternal) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
openExternalUrlWithWarning(url);
|
|
||||||
}}
|
|
||||||
className={markupStyles.link}
|
className={markupStyles.link}
|
||||||
data-flx="messaging.markdown.renderers.link-renderer.a"
|
data-flx="messaging.markdown.renderers.link-renderer.a"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// 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 type React from 'react';
|
||||||
import {useMemo} from 'react';
|
import {useMemo} from 'react';
|
||||||
|
|
||||||
const MIDDLE_MOUSE_BUTTON = 1;
|
export const MIDDLE_MOUSE_BUTTON = 1;
|
||||||
|
|
||||||
interface MiddleClickOpenHandlers {
|
interface MiddleClickOpenHandlers {
|
||||||
onMouseDown: (event: React.MouseEvent) => void;
|
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;
|
if (event.button !== MIDDLE_MOUSE_BUTTON || !enabled || !url) return;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
void openExternalUrl(url);
|
openExternalUrlWithWarning(url);
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[url, enabled],
|
[url, enabled],
|
||||||
|
|||||||
Reference in new issue
Block a user