docs: markdown documentation section (sidebar nav, per-page toc, search, hljs)

This commit is contained in:
Jean Chevronnet 2026-08-30 19:20:02 +00:00
parent d1ec6962dd
commit fc129a1f1b
No known key found for this signature in database
GPG key ID: 439666D63A9477E4
16 changed files with 841 additions and 3 deletions

View file

@ -13,6 +13,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/style.css">
{% block head %}{% endblock %}
</head>
<body>
<header class="topbar">
@ -29,6 +30,7 @@
<a href="/"{% if active == "home" %} class="on"{% endif %}>Home</a>
<a href="/features"{% if active == "features" %} class="on"{% endif %}>Features</a>
<a href="/connect"{% if active == "connect" %} class="on"{% endif %}>Connect</a>
<a href="/docs"{% if active == "docs" %} class="on"{% endif %}>Docs</a>
<a href="https://orbit.devtronic.pro">Web&nbsp;client</a>
<a href="https://git.devtronic.pro/echo/echoIRCd" class="ext">Source&#8599;</a>
</nav>
@ -58,5 +60,6 @@
</nav>
</div>
</footer>
{% block scripts %}{% endblock %}
</body>
</html>

69
templates/docs.html Normal file
View file

@ -0,0 +1,69 @@
{% extends "base.html" %}
{% block title %}{{ page_title }} — echoIRCd Docs{% endblock %}
{% block head %}
<link rel="stylesheet" href="/static/docs.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github.min.css">
{% endblock %}
{% block content %}
<div class="docs-shell">
<nav class="docs-side" aria-label="documentation navigation">
<div class="docs-search">
<input type="search" id="q" placeholder="Search the docs…" autocomplete="off" spellcheck="false">
<div id="results" class="results" hidden></div>
</div>
{% for s in sections %}
<div class="side-sec">
<div class="side-h">{{ s.title }}</div>
{% for pg in s.pages %}
<a href="/docs/{{ pg.slug }}" class="side-link{% if pg.active %} active{% endif %}">{{ pg.title }}</a>
{% endfor %}
</div>
{% endfor %}
</nav>
<article class="docs-body">
{{ body|safe }}
<nav class="pager">
{% if let Some((slug, title)) = prev %}<a class="pager-prev" href="/docs/{{ slug }}"><span>&#8592; Previous</span><b>{{ title }}</b></a>{% else %}<span></span>{% endif %}
{% if let Some((slug, title)) = next %}<a class="pager-next" href="/docs/{{ slug }}"><span>Next &#8594;</span><b>{{ title }}</b></a>{% endif %}
</nav>
</article>
<aside class="docs-toc">
{% if !toc.is_empty() %}
<div class="toc-h">On this page</div>
<nav>
{% for h in toc %}<a href="#{{ h.id }}" class="toc-l{{ h.level }}">{{ h.text }}</a>{% endfor %}
</nav>
{% endif %}
</aside>
</div>
{% endblock %}
{% block scripts %}
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
<script>
if (window.hljs) hljs.highlightAll();
(function () {
var q = document.getElementById('q'), box = document.getElementById('results'), idx = null;
function esc(s){ return s.replace(/[&<>]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;'}[c])); }
async function load(){ if(idx) return idx; try{ idx = await (await fetch('/docs/search.json')).json(); }catch(e){ idx=[]; } return idx; }
q.addEventListener('input', async function () {
var term = q.value.trim().toLowerCase();
if (term.length < 2) { box.hidden = true; box.innerHTML=''; return; }
var data = await load();
var hits = data.map(function (p) {
var t = p.title.toLowerCase().indexOf(term), b = p.text.toLowerCase().indexOf(term);
if (t < 0 && b < 0) return null;
var pos = b < 0 ? 0 : Math.max(0, b - 30);
var snip = b < 0 ? '' : '' + p.text.substr(pos, 90) + '';
return { slug: p.slug, title: p.title, snip: snip, score: (t >= 0 ? 0 : 1) };
}).filter(Boolean).sort(function(a,b){return a.score-b.score;}).slice(0, 8);
box.innerHTML = hits.length
? hits.map(function (h) { return '<a href="/docs/' + h.slug + '"><b>' + esc(h.title) + '</b><span>' + esc(h.snip) + '</span></a>'; }).join('')
: '<div class="no-res">No matches.</div>';
box.hidden = false;
});
document.addEventListener('click', function (e) { if (!e.target.closest('.docs-search')) box.hidden = true; });
})();
</script>
{% endblock %}