Files
NexusOS/interface/web/src/Markdown.jsx
T
jon b0aa0438af Initial commit: NexusOS project + desktop theme baseline
Captures the known-good state after theme consolidation and
consistency reconciliation:
- NexusOS GTK/xfwm4/icon theme assets (assets/themes, management/Mint-Y-Nexus)
- Tightened right-click menus, visible separators, no menu icons
- assets/themes/install-theme.sh: idempotent restore of all wiring
  (symlinks, xfconf xsettings+xfwm4, GTK 3/4 settings.ini)
- .gitignore excludes venv/ollama/models/runtime/db

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 13:39:48 -05:00

144 lines
3.9 KiB
React

export function Markdown({ content }) {
if (!content) return null;
const blocks = [];
const codeBlockRe = /```(\w*)\n?([\s\S]*?)```/g;
let last = 0;
let match;
while ((match = codeBlockRe.exec(content)) !== null) {
if (match.index > last) {
blocks.push({ type: "text", value: content.slice(last, match.index) });
}
blocks.push({ type: "code", lang: match[1], value: match[2] });
last = match.index + match[0].length;
}
if (last < content.length) {
blocks.push({ type: "text", value: content.slice(last) });
}
return (
<div style={{ lineHeight: "1.6" }}>
{blocks.map((block, i) =>
block.type === "code" ? (
<pre key={i} style={{
background: "#0d0d0d",
border: "1px solid #2a2a2a",
borderRadius: "6px",
padding: "0.75rem 1rem",
overflowX: "auto",
fontSize: "0.85rem",
lineHeight: "1.5",
margin: "0.5rem 0",
fontFamily: "monospace",
}}>
<code>{block.value.trimEnd()}</code>
</pre>
) : (
<TextBlock key={i} text={block.value} />
)
)}
</div>
);
}
function TextBlock({ text }) {
const lines = text.split("\n");
const elements = [];
let i = 0;
while (i < lines.length) {
const line = lines[i];
// Heading
const hMatch = line.match(/^(#{1,3})\s+(.+)/);
if (hMatch) {
const level = hMatch[1].length;
const sizes = { 1: "1.2rem", 2: "1.05rem", 3: "0.95rem" };
elements.push(
<div key={i} style={{ fontWeight: "700", fontSize: sizes[level], margin: "0.6rem 0 0.2rem", color: "#fff" }}>
{inlineMarkdown(hMatch[2])}
</div>
);
i++;
continue;
}
// Unordered list
if (/^[-*]\s+/.test(line)) {
const items = [];
while (i < lines.length && /^[-*]\s+/.test(lines[i])) {
items.push(<li key={i}>{inlineMarkdown(lines[i].replace(/^[-*]\s+/, ""))}</li>);
i++;
}
elements.push(<ul key={`ul-${i}`} style={{ paddingLeft: "1.25rem", margin: "0.25rem 0" }}>{items}</ul>);
continue;
}
// Ordered list
if (/^\d+\.\s+/.test(line)) {
const items = [];
while (i < lines.length && /^\d+\.\s+/.test(lines[i])) {
items.push(<li key={i}>{inlineMarkdown(lines[i].replace(/^\d+\.\s+/, ""))}</li>);
i++;
}
elements.push(<ol key={`ol-${i}`} style={{ paddingLeft: "1.25rem", margin: "0.25rem 0" }}>{items}</ol>);
continue;
}
// Horizontal rule
if (/^---+$/.test(line.trim())) {
elements.push(<hr key={i} style={{ border: "none", borderTop: "1px solid #333", margin: "0.5rem 0" }} />);
i++;
continue;
}
// Empty line → small gap
if (line.trim() === "") {
elements.push(<div key={i} style={{ height: "0.4rem" }} />);
i++;
continue;
}
// Regular paragraph line
elements.push(<div key={i}>{inlineMarkdown(line)}</div>);
i++;
}
return <>{elements}</>;
}
function inlineMarkdown(text) {
const parts = [];
const re = /(\*\*(.+?)\*\*|\*(.+?)\*|`([^`]+)`)/g;
let last = 0;
let m;
while ((m = re.exec(text)) !== null) {
if (m.index > last) parts.push(text.slice(last, m.index));
if (m[2] !== undefined) {
parts.push(<strong key={m.index}>{m[2]}</strong>);
} else if (m[3] !== undefined) {
parts.push(<em key={m.index}>{m[3]}</em>);
} else if (m[4] !== undefined) {
parts.push(
<code key={m.index} style={{
background: "#1e1e1e",
border: "1px solid #333",
borderRadius: "3px",
padding: "0.1rem 0.35rem",
fontSize: "0.85em",
fontFamily: "monospace",
}}>
{m[4]}
</code>
);
}
last = m.index + m[0].length;
}
if (last < text.length) parts.push(text.slice(last));
return parts.length === 1 && typeof parts[0] === "string" ? parts[0] : parts;
}