Make links clickable

This commit is contained in:
Jack Kingsman
2026-01-29 17:55:24 -08:00
parent 67a6a0727f
commit 86ead4f29f
8 changed files with 85 additions and 50 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -13,8 +13,8 @@
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<script type="module" crossorigin src="/assets/index-CHDI_cR7.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DiCOP9Mw.css">
<script type="module" crossorigin src="/assets/index-ei_i_-1r.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DJA5wYVF.css">
</head>
<body>
<div id="root"></div>

View File

@@ -19,10 +19,45 @@ interface MessageListProps {
config?: RadioConfig | null;
}
// Helper to render text with highlighted @[Name] mentions
function renderTextWithMentions(text: string, radioName?: string): ReactNode {
if (!radioName) return text;
// URL regex for linkifying plain text
const URL_PATTERN =
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/g;
// Helper to convert URLs in a plain text string into clickable links
function linkifyText(text: string, keyPrefix: string): ReactNode[] {
const parts: ReactNode[] = [];
let lastIndex = 0;
let match: RegExpExecArray | null;
let keyIndex = 0;
URL_PATTERN.lastIndex = 0;
while ((match = URL_PATTERN.exec(text)) !== null) {
if (match.index > lastIndex) {
parts.push(text.slice(lastIndex, match.index));
}
parts.push(
<a
key={`${keyPrefix}-link-${keyIndex++}`}
href={match[0]}
target="_blank"
rel="noopener noreferrer"
className="text-primary underline hover:text-primary/80"
>
{match[0]}
</a>
);
lastIndex = match.index + match[0].length;
}
if (lastIndex === 0) return [text];
if (lastIndex < text.length) {
parts.push(text.slice(lastIndex));
}
return parts;
}
// Helper to render text with highlighted @[Name] mentions and clickable URLs
function renderTextWithMentions(text: string, radioName?: string): ReactNode {
const mentionPattern = /@\[([^\]]+)\]/g;
const parts: ReactNode[] = [];
let lastIndex = 0;
@@ -30,17 +65,17 @@ function renderTextWithMentions(text: string, radioName?: string): ReactNode {
let keyIndex = 0;
while ((match = mentionPattern.exec(text)) !== null) {
// Add text before the match
// Add text before the match (with linkification)
if (match.index > lastIndex) {
parts.push(text.slice(lastIndex, match.index));
parts.push(...linkifyText(text.slice(lastIndex, match.index), `pre-${keyIndex}`));
}
const mentionedName = match[1];
const isOwnMention = mentionedName === radioName;
const isOwnMention = radioName ? mentionedName === radioName : false;
parts.push(
<span
key={keyIndex++}
key={`mention-${keyIndex++}`}
className={cn(
'rounded px-0.5',
isOwnMention ? 'bg-primary/30 text-primary font-medium' : 'bg-muted-foreground/20'
@@ -53,9 +88,9 @@ function renderTextWithMentions(text: string, radioName?: string): ReactNode {
lastIndex = match.index + match[0].length;
}
// Add remaining text after last match
// Add remaining text after last match (with linkification)
if (lastIndex < text.length) {
parts.push(text.slice(lastIndex));
parts.push(...linkifyText(text.slice(lastIndex), `post-${keyIndex}`));
}
return parts.length > 0 ? parts : text;