import React from "react";
import { contentValue, type LocalizedText } from "@/lib/i18n";
type InlineToken = {
kind: "strong" | "em" | "code" | "link" | "text";
value: string;
href?: string;
};
function isSafeHref(value: string): boolean {
return /^(https?:\/\/|mailto:|\/|#)/i.test(value.trim());
}
function tokenizeInline(value: string): InlineToken[] {
const tokens: InlineToken[] = [];
const expression = /(\*\*[^*]+\*\*|`[^`]+`|\[[^\]]+\]\([^\s)]+\)|\*[^*]+\*)/g;
let cursor = 0;
for (const match of value.matchAll(expression)) {
const index = match.index ?? 0;
if (index > cursor) tokens.push({ kind: "text", value: value.slice(cursor, index) });
const token = match[0];
if (token.startsWith("**")) tokens.push({ kind: "strong", value: token.slice(2, -2) });
else if (token.startsWith("`")) tokens.push({ kind: "code", value: token.slice(1, -1) });
else if (token.startsWith("[")) {
const link = /^\[([^\]]+)\]\(([^\s)]+)\)$/.exec(token);
if (link && isSafeHref(link[2])) tokens.push({ kind: "link", value: link[1], href: link[2] });
else tokens.push({ kind: "text", value: token });
} else tokens.push({ kind: "em", value: token.slice(1, -1) });
cursor = index + token.length;
}
if (cursor < value.length) tokens.push({ kind: "text", value: value.slice(cursor) });
return tokens;
}
export function MarkdownInline({ value }: { value: string }) {
const lines = value.split("\n");
return (
<>
{lines.map((line, lineIndex) => (
: null}
{tokenizeInline(line).map((token, index) => {
if (token.kind === "strong") return {token.value};
if (token.kind === "link") {
const external = /^https?:\/\//i.test(token.href || "");
return {token.value};
}
return
{code.join("\n")});
continue;
}
const heading = /^(#{1,6})\s+(.+)$/.exec(line);
if (heading) {
const level = Math.min(5, heading[1].length + 2);
const Tag = `h${level}` as "h3" | "h4" | "h5";
blocks.push(