"use client"; import React from "react"; import { shortcodeToEmoji } from "@/lib/emoji"; /** * Small, safe inline renderer for message bodies: * **bold** _italic_ ~~strike~~ `code` [label](url) @mention #channel :shortcode: bare-url * Mentions/channels render as highlighted blue chips. No dangerouslySetInnerHTML. */ export function RichText({ text }: { text: string }) { return <>{parseInline(text)}; } const TOKEN = /(\*\*[^*]+\*\*|~~[^~]+~~|_[^_]+_|`[^`]+`|\[[^\]]+\]\(https?:\/\/[^)\s]+\)|:[a-z0-9_+-]{2,30}:|@[a-z0-9_.-]+|#[a-z0-9_-]+|https?:\/\/[^\s]+)/gi; function parseInline(text: string): React.ReactNode[] { const nodes: React.ReactNode[] = []; let last = 0; let m: RegExpExecArray | null; let i = 0; TOKEN.lastIndex = 0; while ((m = TOKEN.exec(text)) !== null) { if (m.index > last) nodes.push(text.slice(last, m.index)); const tok = m[0]; if (tok.startsWith("**")) { nodes.push({tok.slice(2, -2)}); } else if (tok.startsWith("~~")) { nodes.push({tok.slice(2, -2)}); } else if (tok.startsWith("_")) { nodes.push({tok.slice(1, -1)}); } else if (tok.startsWith("`")) { nodes.push({tok.slice(1, -1)}); } else if (tok.startsWith("[")) { const mm = /^\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)$/.exec(tok); nodes.push(mm ? ( {mm[1]} ) : tok); } else if (tok.startsWith(":") && tok.endsWith(":")) { const emoji = shortcodeToEmoji(tok.slice(1, -1)); nodes.push(emoji ? {emoji} : tok); } else if (tok.startsWith("@")) { nodes.push( {tok}, ); } else if (tok.startsWith("#")) { nodes.push( {tok}, ); } else { nodes.push({tok}); } last = m.index + tok.length; } if (last < text.length) nodes.push(text.slice(last)); return nodes; }