first commit
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
"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(<strong key={i++} className="font-semibold text-ink">{tok.slice(2, -2)}</strong>);
|
||||
} else if (tok.startsWith("~~")) {
|
||||
nodes.push(<span key={i++} className="line-through opacity-80">{tok.slice(2, -2)}</span>);
|
||||
} else if (tok.startsWith("_")) {
|
||||
nodes.push(<em key={i++}>{tok.slice(1, -1)}</em>);
|
||||
} else if (tok.startsWith("`")) {
|
||||
nodes.push(<code key={i++} className="rounded bg-hover px-1 py-0.5 font-mono text-[12.5px] text-accent">{tok.slice(1, -1)}</code>);
|
||||
} else if (tok.startsWith("[")) {
|
||||
const mm = /^\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)$/.exec(tok);
|
||||
nodes.push(mm ? (
|
||||
<a key={i++} href={mm[2]} target="_blank" rel="noreferrer" className="text-info underline underline-offset-2 hover:text-accent">{mm[1]}</a>
|
||||
) : tok);
|
||||
} else if (tok.startsWith(":") && tok.endsWith(":")) {
|
||||
const emoji = shortcodeToEmoji(tok.slice(1, -1));
|
||||
nodes.push(emoji ? <span key={i++}>{emoji}</span> : tok);
|
||||
} else if (tok.startsWith("@")) {
|
||||
nodes.push(
|
||||
<span key={i++} className="rounded px-1 font-medium text-info" style={{ background: "rgba(91,157,255,0.14)" }}>{tok}</span>,
|
||||
);
|
||||
} else if (tok.startsWith("#")) {
|
||||
nodes.push(
|
||||
<span key={i++} className="rounded px-1 font-medium text-info" style={{ background: "rgba(91,157,255,0.14)" }}>{tok}</span>,
|
||||
);
|
||||
} else {
|
||||
nodes.push(<a key={i++} href={tok} target="_blank" rel="noreferrer" className="text-info underline underline-offset-2 hover:text-accent">{tok}</a>);
|
||||
}
|
||||
last = m.index + tok.length;
|
||||
}
|
||||
if (last < text.length) nodes.push(text.slice(last));
|
||||
return nodes;
|
||||
}
|
||||
Reference in New Issue
Block a user