import React, { useEffect, useRef } from 'react';
// ─── Minimal Markdown → HTML renderer ────────────────────────────────────────
// Handles: headings, bold, inline-code, fenced code blocks, tables, hr,
// unordered lists, ordered lists, and paragraphs.
function mdToHtml(md) {
const lines = md.split('\n');
const out = [];
let i = 0;
let inUl = false;
let inOl = false;
let inTable = false;
let tableHead = false;
const closeOpenLists = () => {
if (inUl) { out.push(''); inUl = false; }
if (inOl) { out.push(''); inOl = false; }
if (inTable) { out.push(''); inTable = false; tableHead = false; }
};
const inline = (s) =>
s
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/\*\*(.+?)\*\*/g, '$1 ')
.replace(/`([^`]+)`/g, '$1');
while (i < lines.length) {
const line = lines[i];
// Fenced code block
if (line.startsWith('```')) {
closeOpenLists();
const lang = line.slice(3).trim();
const codeLines = [];
i++;
while (i < lines.length && !lines[i].startsWith('```')) {
codeLines.push(lines[i].replace(/&/g,'&').replace(//g,'>'));
i++;
}
out.push(`
${codeLines.join('\n')} `);
i++;
continue;
}
// HR
if (/^---+$/.test(line.trim())) {
closeOpenLists();
out.push(' ');
i++;
continue;
}
// Headings
const hMatch = line.match(/^(#{1,4})\s+(.+)/);
if (hMatch) {
closeOpenLists();
const level = hMatch[1].length;
const id = hMatch[2].toLowerCase().replace(/[^a-z0-9]+/g, '-');
out.push(`${inline(hMatch[2])} `);
i++;
continue;
}
// Table row
if (line.trim().startsWith('|')) {
const cells = line.trim().replace(/^\||\|$/g, '').split('|').map(c => c.trim());
if (!inTable) {
closeOpenLists();
inTable = true;
tableHead = true;
out.push('');
cells.forEach(c => out.push(`${inline(c)} `));
out.push(' ');
i++;
// skip separator row
if (i < lines.length && lines[i].trim().startsWith('|') && /^[\|\s\-:]+$/.test(lines[i])) i++;
continue;
} else {
out.push('');
cells.forEach(c => out.push(`${inline(c)} `));
out.push(' ');
i++;
continue;
}
}
// Unordered list
const ulMatch = line.match(/^[-*]\s+(.*)/);
if (ulMatch) {
if (inTable) closeOpenLists();
if (!inUl) { if (inOl) { out.push(''); inOl = false; } out.push(''); inUl = true; }
out.push(`${inline(ulMatch[1])} `);
i++;
continue;
}
// Ordered list
const olMatch = line.match(/^\d+\.\s+(.*)/);
if (olMatch) {
if (inTable) closeOpenLists();
if (!inOl) { if (inUl) { out.push(' '); inUl = false; } out.push(''); inOl = true; }
out.push(`${inline(olMatch[1])} `);
i++;
continue;
}
// Blank line
if (line.trim() === '') {
closeOpenLists();
i++;
continue;
}
// Paragraph
closeOpenLists();
out.push(`${inline(line)}
`);
i++;
}
closeOpenLists();
return out.join('\n');
}
// ─── Styles ───────────────────────────────────────────────────────────────────
const overlay = {
position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.75)',
zIndex: 2000, display: 'flex', alignItems: 'flex-start', justifyContent: 'flex-end',
};
const panel = {
background: '#111217', color: '#f8f9fa', width: '760px', maxWidth: '95vw',
height: '100vh', overflowY: 'auto', boxShadow: '-4px 0 32px rgba(0,0,0,0.8)',
display: 'flex', flexDirection: 'column',
};
const header = {
background: 'linear-gradient(135deg, #000000, #151622)', color: 'white',
padding: '22px 28px', position: 'sticky', top: 0, zIndex: 10,
borderBottom: '1px solid #222', display: 'flex', alignItems: 'center',
justifyContent: 'space-between',
};
const closeBtn = {
background: 'none', border: 'none', color: 'white',
fontSize: '22px', cursor: 'pointer', lineHeight: 1,
};
const body = {
padding: '28px 32px', flex: 1, fontSize: '13px', lineHeight: '1.7',
};
// Injected
e.stopPropagation()}>
{/* Header */}
📋 CPAS Tracker — Documentation
Admin reference · use Esc or click outside to close
✕
{/* TOC strip */}
{toc.filter(h => h.level <= 2).map((h) => (
scrollTo(h.id)}
style={{
background: 'none', border: 'none', cursor: 'pointer', padding: '2px 0',
color: h.level === 1 ? '#f8f9fa' : '#90caf9',
fontWeight: h.level === 1 ? 700 : 400,
fontSize: '11px',
}}
>
{h.level === 2 ? '↳ ' : ''}{h.text}
))}
{/* Body */}
{/* Footer */}
CPAS Violation Tracker · internal admin use only
);
}