@@ -23,6 +23,7 @@ import { useAppNavigation } from "@/app/navigation/useAppNavigation";
2323import {
2424 isMessageLink ,
2525 parseMessageLink ,
26+ resolveMessageLinkRenderTarget ,
2627 type ParsedMessageLink ,
2728} from "@/features/messages/lib/messageLink" ;
2829import { UserProfilePopover } from "@/features/profile/ui/UserProfilePopover" ;
@@ -79,6 +80,14 @@ type ImetaEntry = {
7980
8081type ImetaLookup = Map < string , ImetaEntry > ;
8182
83+ type MessageLinkPillProps = {
84+ channels : Channel [ ] ;
85+ href : string ;
86+ interactive : boolean ;
87+ link : ParsedMessageLink ;
88+ onOpenMessageLink : ( link : ParsedMessageLink ) => void ;
89+ } ;
90+
8291let shikiHighlighter : HighlighterGeneric < BundledLanguage , BundledTheme > | null =
8392 null ;
8493let shikiInitPromise : Promise < void > | null = null ;
@@ -1252,6 +1261,46 @@ function getCodeBlockText(children: React.ReactNode) {
12521261 return getReactNodeText ( children ) . replace ( / \n $ / , "" ) ;
12531262}
12541263
1264+ function MessageLinkPill ( {
1265+ channels,
1266+ href,
1267+ interactive,
1268+ link,
1269+ onOpenMessageLink,
1270+ } : MessageLinkPillProps ) {
1271+ const channel = channels . find ( ( c ) => c . id === link . channelId ) ;
1272+ const channelLabel = channel ?. name ?? "channel" ;
1273+ const shortId = link . messageId . slice ( 0 , 6 ) ;
1274+ const label = (
1275+ < >
1276+ #{ channelLabel } · { shortId }
1277+ </ >
1278+ ) ;
1279+
1280+ if ( ! interactive ) {
1281+ return < span data-message-link = "" > { label } </ span > ;
1282+ }
1283+
1284+ return (
1285+ < button
1286+ type = "button"
1287+ data-message-link = ""
1288+ aria-label = { `Open message in ${ channelLabel } ` }
1289+ title = { href }
1290+ className = { cn (
1291+ "cursor-pointer" ,
1292+ MENTION_CHIP_BASE_CLASSES ,
1293+ MENTION_CHIP_HOVER_CLASSES ,
1294+ ) }
1295+ onClick = { ( ) => {
1296+ onOpenMessageLink ( link ) ;
1297+ } }
1298+ >
1299+ { label }
1300+ </ button >
1301+ ) ;
1302+ }
1303+
12551304function InlineEmojiPopover ( {
12561305 alt,
12571306 resolvedSrc,
@@ -1788,18 +1837,32 @@ function createMarkdownComponents(
17881837 // Intercept `buzz://message?channel=…&id=…` links so a click navigates
17891838 // in-app instead of opening the URL in the OS browser. http(s) links
17901839 // continue to use the existing target="_blank" behavior.
1791- if ( isMessageLink ( href ) ) {
1792- const parsed = parseMessageLink ( href ?? "" ) ;
1793- if ( parsed . ok ) {
1794- const target = parsed . value ;
1840+ if ( href ) {
1841+ const messageLinkTarget = resolveMessageLinkRenderTarget ( {
1842+ href,
1843+ label : getReactNodeText ( children ) ,
1844+ } ) ;
1845+ if ( messageLinkTarget . kind !== "none" ) {
1846+ if ( messageLinkTarget . kind === "pill" ) {
1847+ return (
1848+ < MessageLinkPill
1849+ channels = { runtimeRef . current . channels }
1850+ href = { href }
1851+ interactive = { interactive }
1852+ link = { messageLinkTarget . link }
1853+ onOpenMessageLink = { onOpenMessageLink }
1854+ />
1855+ ) ;
1856+ }
1857+
17951858 return (
17961859 < a
17971860 { ...props }
17981861 className = "font-medium text-primary underline underline-offset-4 transition-colors hover:text-primary/80 cursor-pointer"
17991862 href = { href }
18001863 onClick = { ( event ) => {
18011864 event . preventDefault ( ) ;
1802- onOpenMessageLink ( target ) ;
1865+ onOpenMessageLink ( messageLinkTarget . link ) ;
18031866 } }
18041867 >
18051868 { children }
@@ -2095,36 +2158,14 @@ function createMarkdownComponents(
20952158 return < span data-message-link = "" > { href } </ span > ;
20962159 }
20972160
2098- const { channelId, messageId } = parsed . value ;
2099- const channel = channels . find ( ( c ) => c . id === channelId ) ;
2100- const channelLabel = channel ?. name ?? "channel" ;
2101- const shortId = messageId . slice ( 0 , 6 ) ;
2102-
2103- if ( ! interactive ) {
2104- return (
2105- < span data-message-link = "" >
2106- #{ channelLabel } · { shortId }
2107- </ span >
2108- ) ;
2109- }
2110-
21112161 return (
2112- < button
2113- type = "button"
2114- data-message-link = ""
2115- aria-label = { `Open message in ${ channelLabel } ` }
2116- title = { href }
2117- className = { cn (
2118- "cursor-pointer" ,
2119- MENTION_CHIP_BASE_CLASSES ,
2120- MENTION_CHIP_HOVER_CLASSES ,
2121- ) }
2122- onClick = { ( ) => {
2123- onOpenMessageLink ( parsed . value ) ;
2124- } }
2125- >
2126- #{ channelLabel } · { shortId }
2127- </ button >
2162+ < MessageLinkPill
2163+ channels = { channels }
2164+ href = { href }
2165+ interactive = { interactive }
2166+ link = { parsed . value }
2167+ onOpenMessageLink = { onOpenMessageLink }
2168+ />
21282169 ) ;
21292170 } ,
21302171 } as Components ;
0 commit comments