Skip to content

Commit 5910ebb

Browse files
committed
feat: add clickable hyperlinks in transaction memos
1 parent 2174f02 commit 5910ebb

File tree

2 files changed

+53
-4
lines changed

2 files changed

+53
-4
lines changed

frontend/src/components/TransactionItem.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { ALBY_ACCOUNT_APP_NAME } from "src/constants";
3131
import { useApp } from "src/hooks/useApp";
3232
import { useSwap } from "src/hooks/useSwaps";
3333
import { copyToClipboard } from "src/lib/clipboard";
34+
import { linkifyText } from "src/lib/linkify";
3435
import { cn } from "src/lib/utils";
3536
import { Transaction } from "src/types";
3637

@@ -183,7 +184,7 @@ function TransactionItem({ tx }: Props) {
183184
</span>
184185
</div>
185186
<p className="text-sm md:text-base text-muted-foreground break-all line-clamp-1">
186-
{description}
187+
{linkifyText(description || "")}
187188
</p>
188189
</div>
189190
<div className="flex ml-auto space-x-3 shrink-0">
@@ -285,23 +286,23 @@ function TransactionItem({ tx }: Props) {
285286
<div className="mt-6">
286287
<p>Description</p>
287288
<p className="text-muted-foreground break-all">
288-
{tx.description}
289+
{linkifyText(tx.description)}
289290
</p>
290291
</div>
291292
)}
292293
{tx.metadata?.comment && (
293294
<div className="mt-6">
294295
<p>Comment</p>
295296
<p className="text-muted-foreground break-all">
296-
{tx.metadata.comment}
297+
{linkifyText(tx.metadata.comment)}
297298
</p>
298299
</div>
299300
)}
300301
{bolt12Offer?.payer_note && (
301302
<div className="mt-6">
302303
<p>Payer Note</p>
303304
<p className="text-muted-foreground break-all">
304-
{bolt12Offer.payer_note}
305+
{linkifyText(bolt12Offer.payer_note)}
305306
</p>
306307
</div>
307308
)}

frontend/src/lib/linkify.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from "react";
2+
3+
const URL_REGEX =
4+
/(https?:\/\/[^\s]+|(?:www\.)?[a-zA-Z0-9][-a-zA-Z0-9]*\.[a-zA-Z]{2,}(?:\/[^\s]*)?)/gi;
5+
export function linkifyText(text: string): React.ReactNode {
6+
if (!text) {
7+
return text;
8+
}
9+
10+
const parts: React.ReactNode[] = [];
11+
let lastIndex = 0;
12+
let match: RegExpExecArray | null;
13+
URL_REGEX.lastIndex = 0;
14+
15+
while ((match = URL_REGEX.exec(text)) !== null) {
16+
if (match.index > lastIndex) {
17+
parts.push(text.slice(lastIndex, match.index));
18+
}
19+
20+
const url = match[0];
21+
const href = url.startsWith("http") ? url : `https://${url}`;
22+
23+
parts.push(
24+
<a
25+
key={match.index}
26+
href={href}
27+
target="_blank"
28+
rel="noopener noreferrer"
29+
className="text-primary underline hover:no-underline"
30+
onClick={(e) => e.stopPropagation()}
31+
>
32+
{url}
33+
</a>
34+
);
35+
36+
lastIndex = match.index + url.length;
37+
}
38+
39+
if (lastIndex < text.length) {
40+
parts.push(text.slice(lastIndex));
41+
}
42+
43+
if (parts.length === 0) {
44+
return text;
45+
}
46+
47+
return <>{parts}</>;
48+
}

0 commit comments

Comments
 (0)