import type { ReactNode } from "react";
const SAFE_HTTP_URL = /^https?:\/\//i;
type InlineToken =
| { type: "text"; value: string }
| { type: "link"; label: string; href: string };
function parseLinks(text: string): InlineToken[] {
const tokens: InlineToken[] = [];
const pattern = /\[([^\]]+)\]\(([^)\s]+)\)/g;
let lastIndex = 0;
let match: RegExpExecArray | null;
while ((match = pattern.exec(text)) !== null) {
if (match.index > lastIndex) {
tokens.push({ type: "text", value: text.slice(lastIndex, match.index) });
}
const label = match[1] ?? "";
const href = match[2] ?? "";
if (SAFE_HTTP_URL.test(href)) {
tokens.push({ type: "link", label, href });
} else {
tokens.push({ type: "text", value: match[0] });
}
lastIndex = pattern.lastIndex;
}
if (lastIndex < text.length) {
tokens.push({ type: "text", value: text.slice(lastIndex) });
}
return tokens;
}
function renderEmphasis(text: string, keyPrefix: string): ReactNode[] {
const nodes: ReactNode[] = [];
const pattern = /(\*\*\*[^*]+?\*\*\*|\*\*[^*]+?\*\*|\*[^*]+?\*)/g;
let lastIndex = 0;
let match: RegExpExecArray | null;
let idx = 0;
while ((match = pattern.exec(text)) !== null) {
if (match.index > lastIndex) {
nodes.push(text.slice(lastIndex, match.index));
}
const token = match[0];
const key = `${keyPrefix}-em-${idx++}`;
if (token.startsWith("***")) {
nodes.push(
{token.slice(3, -3)}
);
} else if (token.startsWith("**")) {
nodes.push({token.slice(2, -2)});
} else {
nodes.push({token.slice(1, -1)});
}
lastIndex = pattern.lastIndex;
}
if (lastIndex < text.length) {
nodes.push(text.slice(lastIndex));
}
return nodes;
}
export function renderInlineMarkdown(text: string): ReactNode[] {
const withLines = text.split("\n");
const nodes: ReactNode[] = [];
withLines.forEach((line, lineIndex) => {
const tokens = parseLinks(line);
tokens.forEach((token, tokenIndex) => {
const keyBase = `ln-${lineIndex}-tok-${tokenIndex}`;
if (token.type === "text") {
nodes.push(...renderEmphasis(token.value, keyBase));
return;
}
nodes.push(
{renderEmphasis(token.label, `${keyBase}-label`)}
);
});
if (lineIndex < withLines.length - 1) {
nodes.push(
);
}
});
return nodes;
}