/** * faq-sidebar.js — VS Code-style embedded FAQ sidebar * * Usage: * import { initFaqSidebar } from '/static/shared/faq-sidebar.js'; * initFaqSidebar('turnbased'); // loads /static/faq/{lang}/turnbased.md */ const STORAGE_KEY_LANG = 'minicpmo_lang'; /* ── lightweight markdown → HTML ── */ function md2html(src) { const lines = src.split('\n'); const out = []; let inUl = false, inOl = false, inCode = false, codeBuf = []; const flush = (tag) => { if (tag === 'ul' && inUl) { out.push(''); inUl = false; } if (tag === 'ol' && inOl) { out.push(''); inOl = false; } }; const flushAll = () => { flush('ul'); flush('ol'); }; const inline = (t) => t .replace(/`([^`]+)`/g, '$1') .replace(/\*\*(.+?)\*\*/g, '$1') .replace(/\*(.+?)\*/g, '$1') .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); for (let i = 0; i < lines.length; i++) { const raw = lines[i]; if (inCode) { if (raw.trimEnd() === '```') { out.push('
' + codeBuf.join('\n').replace(//g, '>') + '
'); codeBuf = []; inCode = false; } else { codeBuf.push(raw); } continue; } if (raw.trimStart().startsWith('```')) { flushAll(); inCode = true; continue; } const trimmed = raw.trim(); if (!trimmed) { flushAll(); continue; } if (trimmed.startsWith('---') && trimmed.replace(/-/g, '').trim() === '') { flushAll(); out.push('
'); continue; } const hm = trimmed.match(/^(#{1,3})\s+(.*)/); if (hm) { flushAll(); const lvl = hm[1].length; out.push(`${inline(hm[2])}`); continue; } if (trimmed.match(/^[-*]\s+/)) { flush('ol'); if (!inUl) { out.push('