Opas-välilehti: GUIDE.md renderöidään sivustolle omana näkymänä

Uusi "Opas"-välilehti (panel-guide) lataa GUIDE.md:n fetchillä ja
renderöi sen inline markdown→HTML -parserilla:
- Otsikot (h1-h3) GitHub-tyylisesti
- Koodiblokit highlight.js-korostuksella
- Taulukot (header + body, border-collapse)
- Listat (bullet + numeroitu)
- Inline-muotoilu: **bold**, *italic*, `code`
- Horisontaaliviivat

GUIDE.md siirretty static/-hakemistoon jotta hub servaa sen suoraan.
Navigointi: #guide hash tai klikkaa "Opas"-välilehteä.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jaakko Vanhala
2026-04-06 08:20:54 +03:00
parent 262fee3b49
commit dd1945ab28
2 changed files with 121 additions and 1 deletions

View File

@@ -143,6 +143,13 @@
}
.code-output .hljs { background: transparent; padding: 0; }
#guide-content { scrollbar-color: #30363d transparent; }
#guide-content h1 { color: #e6edf3; }
#guide-content h2 { color: #e6edf3; }
#guide-content a { color: #58a6ff; }
#guide-content table { border: 1px solid #30363d; border-radius: 6px; overflow: hidden; }
#guide-content pre { scrollbar-color: #30363d transparent; }
.code-task-card {
background: #0d1117;
border: 1px solid var(--border-color);
@@ -701,6 +708,7 @@
<div class="main-tab active" onclick="switchMainTab('network')" data-i18n="tab_network">Laskentaverkko</div>
<div class="main-tab" onclick="switchMainTab('codelab')" data-i18n="tab_codelab">Koodilaboratorio</div>
<div class="main-tab" onclick="switchMainTab('agents')" data-i18n="tab_agents">Kipinä Agentic Playground</div>
<div class="main-tab" onclick="switchMainTab('guide')" data-i18n="tab_guide">Opas</div>
</div>
<!-- PANEELI 1: Laskentaverkko -->
@@ -1115,6 +1123,13 @@
</div>
</div><!-- /panel-agents -->
<!-- PANEELI 4: Opas -->
<div id="panel-guide" class="main-panel">
<div id="guide-content" style="max-width:800px;margin:0 auto;padding:20px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;color:var(--text-color);line-height:1.7;font-size:15px">
<p style="color:#8b949e">Ladataan opasta...</p>
</div>
</div>
</div>
<script type="module">
@@ -1469,7 +1484,7 @@
// URL-hash navigointi
const initHash = window.location.hash.replace('#', '');
if (['codelab', 'agents'].includes(initHash)) {
if (['codelab', 'agents', 'guide'].includes(initHash)) {
switchMainTab(initHash);
}
@@ -3463,6 +3478,111 @@ Write the corrected code.`;
if (window.selectAgent) window.selectAgent('client');
}, 100);
});
// GUIDE.md:n lataus ja renderöinti
(async function loadGuide() {
const container = document.getElementById('guide-content');
if (!container) return;
try {
const res = await fetch('/GUIDE.md');
if (!res.ok) { container.innerHTML = '<p style="color:#f85149">Oppaan lataus epäonnistui.</p>'; return; }
const md = await res.text();
container.innerHTML = renderMarkdown(md);
// Syntaksikorostus koodiblokeille
container.querySelectorAll('pre code').forEach(block => {
if (typeof hljs !== 'undefined') hljs.highlightElement(block);
});
} catch(e) {
container.innerHTML = '<p style="color:#f85149">Virhe: ' + e.message + '</p>';
}
})();
function renderMarkdown(md) {
const lines = md.split('\n');
let html = '';
let inCode = false;
let codeLang = '';
let codeBuffer = '';
let inTable = false;
let tableRows = [];
function flushTable() {
if (!inTable) return;
inTable = false;
if (tableRows.length < 2) return;
const headerCells = tableRows[0].split('|').filter(c => c.trim());
const bodyRows = tableRows.slice(2); // Skip header + separator
html += '<div style="overflow-x:auto;margin:16px 0"><table style="width:100%;border-collapse:collapse;font-size:14px">';
html += '<thead><tr>' + headerCells.map(c => `<th style="text-align:left;padding:8px 12px;border-bottom:2px solid #30363d;color:#58a6ff;font-weight:600">${inlineFormat(c.trim())}</th>`).join('') + '</tr></thead>';
html += '<tbody>';
for (const row of bodyRows) {
const cells = row.split('|').filter(c => c.trim());
if (cells.length === 0) continue;
html += '<tr>' + cells.map(c => `<td style="padding:6px 12px;border-bottom:1px solid #21262d">${inlineFormat(c.trim())}</td>`).join('') + '</tr>';
}
html += '</tbody></table></div>';
tableRows = [];
}
function inlineFormat(text) {
return text
.replace(/`([^`]+)`/g, '<code style="background:#161b22;padding:2px 6px;border-radius:3px;font-size:13px;color:#e6edf3">$1</code>')
.replace(/\*\*([^*]+)\*\*/g, '<strong style="color:#e6edf3">$1</strong>')
.replace(/\*([^*]+)\*/g, '<em>$1</em>');
}
for (const line of lines) {
// Koodiblokit
if (line.startsWith('```')) {
if (inCode) {
html += `<pre style="background:#0d1117;border:1px solid #30363d;border-radius:6px;padding:14px;margin:12px 0;overflow-x:auto"><code class="language-${codeLang}">${codeBuffer.replace(/</g,'&lt;')}</code></pre>`;
inCode = false;
codeBuffer = '';
} else {
flushTable();
inCode = true;
codeLang = line.slice(3).trim() || 'plaintext';
}
continue;
}
if (inCode) { codeBuffer += (codeBuffer ? '\n' : '') + line; continue; }
// Taulukot
if (line.includes('|') && line.trim().startsWith('|')) {
if (!inTable) inTable = true;
tableRows.push(line);
continue;
} else {
flushTable();
}
// Tyhjä rivi
if (!line.trim()) { html += '<div style="height:8px"></div>'; continue; }
// Otsikot
if (line.startsWith('# ')) { html += `<h1 style="color:#e6edf3;font-size:28px;margin:32px 0 12px;border-bottom:1px solid #30363d;padding-bottom:8px">${inlineFormat(line.slice(2))}</h1>`; continue; }
if (line.startsWith('## ')) { html += `<h2 style="color:#e6edf3;font-size:22px;margin:28px 0 10px;border-bottom:1px solid #21262d;padding-bottom:6px">${inlineFormat(line.slice(3))}</h2>`; continue; }
if (line.startsWith('### ')) { html += `<h3 style="color:#e6edf3;font-size:17px;margin:20px 0 8px">${inlineFormat(line.slice(4))}</h3>`; continue; }
// Horisontaalinen viiva
if (line.match(/^-{3,}$/)) { html += '<hr style="border:none;border-top:1px solid #30363d;margin:20px 0">'; continue; }
// Lista
if (line.match(/^[\-\*] /)) {
html += `<div style="padding:2px 0 2px 20px">${inlineFormat(line.replace(/^[\-\*] /, '• '))}</div>`;
continue;
}
if (line.match(/^\d+\. /)) {
html += `<div style="padding:2px 0 2px 20px">${inlineFormat(line)}</div>`;
continue;
}
// Normaali tekstirivi
html += `<p style="margin:4px 0">${inlineFormat(line)}</p>`;
}
flushTable();
return html;
}
</script>
</body>
</html>