import React from 'react';
interface TacticalMarkdownRendererProps {
content: string;
isChat?: boolean;
}
// Inline parser for bold, italic, inline-code, and tags
const parseInline = (text: string): React.ReactNode[] => {
if (!text) return [];
// Match bold (**text**), inline code (`text`), italic (*text* or _text_)
const tokens: React.ReactNode[] = [];
let remaining = text;
let key = 0;
while (remaining.length > 0) {
// Check for bold **...** or __...__
const boldMatch = remaining.match(/^(\*\*|__)(.*?)\1/);
if (boldMatch) {
tokens.push(
{parseInline(boldMatch[2])}
);
remaining = remaining.slice(boldMatch[0].length);
continue;
}
// Check for inline code `...`
const codeMatch = remaining.match(/^`([^`]+)`/);
if (codeMatch) {
tokens.push(
{codeMatch[1]}
);
remaining = remaining.slice(codeMatch[0].length);
continue;
}
// Check for italic *...* or _..._
const italicMatch = remaining.match(/^(\*|_)(.*?)\1/);
if (italicMatch) {
tokens.push(
{parseInline(italicMatch[2])}
);
remaining = remaining.slice(italicMatch[0].length);
continue;
}
// Regular text until the next special character
const nextSpecial = remaining.search(/[\*_`]/);
if (nextSpecial === -1) {
tokens.push(remaining);
break;
} else if (nextSpecial === 0) {
// Stray character, consume it as plain text
tokens.push(remaining[0]);
remaining = remaining.slice(1);
} else {
tokens.push(remaining.slice(0, nextSpecial));
remaining = remaining.slice(nextSpecial);
}
}
return tokens;
};
export const TacticalMarkdownRenderer: React.FC = ({
content,
isChat = false
}) => {
if (!content) return null;
const lines = content.split('\n');
const elements: React.ReactNode[] = [];
let currentList: { type: 'ul' | 'ol'; items: React.ReactNode[] } | null = null;
let elementKey = 0;
const flushList = () => {
if (currentList) {
if (currentList.type === 'ul') {
elements.push(
{currentList.items.map((item, idx) => (
-
{item}
))}
);
} else {
elements.push(
{currentList.items.map((item, idx) => (
-
{item}
))}
);
}
currentList = null;
}
};
for (let i = 0; i < lines.length; i++) {
const rawLine = lines[i];
const trimmed = rawLine.trim();
// Empty lines
if (!trimmed) {
flushList();
continue;
}
// Horizontal Rule (--- or *** or ___)
if (/^(\-{3,}|\*{3,}|_{3,})$/.test(trimmed)) {
flushList();
elements.push(
);
continue;
}
// Headers
const h1Match = trimmed.match(/^#\s+(.+)$/);
if (h1Match) {
flushList();
elements.push(
{parseInline(h1Match[1])}
);
continue;
}
const h2Match = trimmed.match(/^##\s+(.+)$/);
if (h2Match) {
flushList();
elements.push(
{parseInline(h2Match[1])}
);
continue;
}
const h3Match = trimmed.match(/^###\s+(.+)$/);
if (h3Match) {
flushList();
elements.push(
{parseInline(h3Match[1])}
);
continue;
}
const h4Match = trimmed.match(/^####\s+(.+)$/);
if (h4Match) {
flushList();
elements.push(
{parseInline(h4Match[1])}
);
continue;
}
// Unordered List Items (- or * or •)
const ulMatch = trimmed.match(/^[-*•]\s+(.+)$/);
if (ulMatch) {
if (!currentList || currentList.type !== 'ul') {
flushList();
currentList = { type: 'ul', items: [] };
}
currentList.items.push(parseInline(ulMatch[1]));
continue;
}
// Ordered List Items (1. , 2. )
const olMatch = trimmed.match(/^(\d+)\.\s+(.+)$/);
if (olMatch) {
if (!currentList || currentList.type !== 'ol') {
flushList();
currentList = { type: 'ol', items: [] };
}
currentList.items.push(parseInline(olMatch[2]));
continue;
}
// Blockquote (> ...)
const bqMatch = trimmed.match(/^>\s*(.+)$/);
if (bqMatch) {
flushList();
elements.push(
{parseInline(bqMatch[1])}
);
continue;
}
// Regular Paragraph
flushList();
elements.push(
{parseInline(trimmed)}
);
}
flushList();
return (
{elements}
);
};