"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;
}