import { Fragment, type ReactNode } from "react"; // Known platform → profile base URL, for ": @handle" mentions in descriptions. const PLATFORMS: Record = { x: "https://x.com/", twitter: "https://x.com/", instagram: "https://instagram.com/", insta: "https://instagram.com/", ig: "https://instagram.com/", facebook: "https://facebook.com/", fb: "https://facebook.com/", tiktok: "https://www.tiktok.com/@", youtube: "https://www.youtube.com/@", yt: "https://www.youtube.com/@", twitch: "https://www.twitch.tv/", threads: "https://www.threads.net/@", telegram: "https://t.me/", soundcloud: "https://soundcloud.com/", }; // Two link shapes, combined into one scanner: // URL — explicit http(s):// URLs, www.* hosts, or domain.tld/path bare URLs (required path + // letters-only TLD keep version numbers / file names out). Non-http gets an https:// href. // MENTION — ": @handle" where is one of the known keys above. The colon/arrow // separator is REQUIRED, so a stray "x" in prose won't match. Only the @handle is linked // (to the platform's profile URL); the ": " label stays as text. Bare @handles // with no platform, and #hashtags, are left plain. const URL_SOURCE = "(?:https?://|www\\.)[^\\s<]+[^\\s<.,!?;:)\\]}\"']|[a-z0-9-]+(?:\\.[a-z0-9-]+)*\\.[a-z]{2,}/[^\\s<]*[^\\s<.,!?;:)\\]}\"']"; const MENTION_SOURCE = "\\b(" + Object.keys(PLATFORMS).join("|") + ")\\b[ \\t]*[::►▶▸]+[ \\t]*@([a-z0-9_](?:[a-z0-9_.]{0,28}[a-z0-9_])?)"; const TOKEN_RE = new RegExp("(" + URL_SOURCE + ")|" + MENTION_SOURCE, "gi"); /** * Turn links inside a plain-text string into clickable s that open in a new tab (safe rel): * bare URLs (http(s)/www/domain.tld/path) and ": @handle" social mentions. Everything * else is returned as text, so it composes with `whitespace-pre-wrap` (line breaks preserved). */ export function linkify(text: string): ReactNode[] { const out: ReactNode[] = []; let last = 0; let key = 0; TOKEN_RE.lastIndex = 0; const link = (href: string, label: string) => ( {label} ); let m: RegExpExecArray | null; while ((m = TOKEN_RE.exec(text)) !== null) { if (m.index > last) out.push({text.slice(last, m.index)}); if (m[1]) { // Plain URL. const raw = m[1]; out.push(link(/^https?:\/\//i.test(raw) ? raw : `https://${raw}`, raw)); } else { // Reached only when the MENTION alternative matched, so its groups are present: m[2] is the // platform (always one of PLATFORMS' own keys — the pattern is built from them) and m[3] the // handle. ": @handle" — keep the ": " prefix as text, link only the handle. const platform = m[2]!; const handle = m[3]!; const base = PLATFORMS[platform.toLowerCase()]!; out.push({m[0].slice(0, m[0].length - handle.length - 1)}); out.push(link(base + handle, "@" + handle)); } last = m.index + m[0].length; } if (last < text.length) out.push({text.slice(last)}); return out; }