diff --git a/src/app/components/url-preview/UrlPreviewCard.tsx b/src/app/components/url-preview/UrlPreviewCard.tsx index 2a51969..f055a79 100644 --- a/src/app/components/url-preview/UrlPreviewCard.tsx +++ b/src/app/components/url-preview/UrlPreviewCard.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; +import React, { MouseEvent, useCallback, useEffect, useRef, useState } from 'react'; import { IPreviewUrlResponse } from 'matrix-js-sdk'; import { Box, Icon, IconButton, Icons, Scroll, Spinner, Text, as, color, config } from 'folds'; import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; @@ -12,9 +12,21 @@ import * as css from './UrlPreviewCard.css'; import { tryDecodeURIComponent } from '../../utils/dom'; import { mxcUrlToHttp } from '../../utils/matrix'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; +import { openExternalUrl } from '../../utils/tauri'; const linkStyles = { color: color.Success.Main }; +/** + * Click handler for external links - uses Tauri shell on desktop/mobile + */ +const handleExternalLinkClick = (e: MouseEvent) => { + const href = e.currentTarget.href; + if (href && (href.startsWith('http://') || href.startsWith('https://'))) { + e.preventDefault(); + openExternalUrl(href); + } +}; + export const UrlPreviewCard = as<'div', { url: string; ts: number }>( ({ url, ts, ...props }, ref) => { const mx = useMatrixClient(); @@ -45,6 +57,7 @@ export const UrlPreviewCard = as<'div', { url: string; ts: number }>( rel="no-referrer" size="T200" priority="300" + onClick={handleExternalLinkClick} > {typeof prev['og:site_name'] === 'string' && `${prev['og:site_name']} | `} {tryDecodeURIComponent(url)} diff --git a/src/app/plugins/react-custom-html-parser.tsx b/src/app/plugins/react-custom-html-parser.tsx index ad249a6..2c73d51 100644 --- a/src/app/plugins/react-custom-html-parser.tsx +++ b/src/app/plugins/react-custom-html-parser.tsx @@ -1,6 +1,7 @@ /* eslint-disable jsx-a11y/alt-text */ import React, { ComponentPropsWithoutRef, + MouseEvent, ReactEventHandler, Suspense, lazy, @@ -42,15 +43,28 @@ import { onEnterOrSpace } from '../utils/keyboard'; import { copyToClipboard, tryDecodeURIComponent } from '../utils/dom'; import { useTimeoutToggle } from '../hooks/useTimeoutToggle'; import { AuthenticatedImg } from '../components/authenticated-media'; +import { openExternalUrl } from '../utils/tauri'; const ReactPrism = lazy(() => import('./react-prism/ReactPrism')); const EMOJI_REG_G = new RegExp(`${URL_NEG_LB}(${EMOJI_PATTERN})`, 'g'); +/** + * Click handler for external links - uses Tauri shell on desktop/mobile + */ +const handleExternalLinkClick = (e: MouseEvent) => { + const href = e.currentTarget.href; + if (href && (href.startsWith('http://') || href.startsWith('https://'))) { + e.preventDefault(); + openExternalUrl(href); + } +}; + export const LINKIFY_OPTS: LinkifyOpts = { attributes: { target: '_blank', rel: 'noreferrer noopener', + onClick: handleExternalLinkClick, }, validate: { url: (value) => /^(https|http|ftp|mailto|magnet)?:/.test(value), @@ -161,7 +175,7 @@ export const factoryRenderLinkifyWithMention = ( if (mention) return mention; } - return {content}; + return {content}; }; return render; }; @@ -441,19 +455,31 @@ export const getReactCustomHtmlParser = ( } } - if (name === 'a' && testMatrixTo(tryDecodeURIComponent(props.href))) { - const content = children.find((child) => !(child instanceof DOMText)) - ? undefined - : children.map((c) => (c instanceof DOMText ? c.data : '')).join(); + if (name === 'a') { + const href = tryDecodeURIComponent(props.href); + // Handle matrix.to links as mentions + if (testMatrixTo(href)) { + const content = children.find((child) => !(child instanceof DOMText)) + ? undefined + : children.map((c) => (c instanceof DOMText ? c.data : '')).join(); - const mention = renderMatrixMention( - mx, - roomId, - tryDecodeURIComponent(props.href), - makeMentionCustomProps(params.handleMentionClick, content) - ); + const mention = renderMatrixMention( + mx, + roomId, + href, + makeMentionCustomProps(params.handleMentionClick, content) + ); - if (mention) return mention; + if (mention) return mention; + } + // Handle external links - add click handler for Tauri + if (props.href?.startsWith('http://') || props.href?.startsWith('https://')) { + return ( + + {domToReact(children, opts)} + + ); + } } if (name === 'span' && 'data-mx-spoiler' in props) {