b0aa0438af
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>
144 lines
3.9 KiB
React
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;
|
|
}
|