Builds a table of contents client-side from rendered heading IDs. Highlights the current section on scroll. Collapses to inline on narrow screens. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
76 lines
2.9 KiB
HTML
76 lines
2.9 KiB
HTML
{{define "title"}} — {{.Doc.Title}}{{end}}
|
|
{{define "container-class"}}read-container{{end}}
|
|
{{define "content"}}
|
|
<div class="read-layout">
|
|
<nav class="read-toc" id="toc" aria-label="Table of contents"></nav>
|
|
<div class="read-main">
|
|
<div class="read-header">
|
|
<h2>{{.Doc.Title}}</h2>
|
|
<div class="read-meta">
|
|
<span>Pushed by {{.Doc.PushedBy}}</span>
|
|
<span>{{.Doc.PushedAt}}</span>
|
|
</div>
|
|
<div class="read-actions">
|
|
{{if .Doc.Read}}
|
|
<form method="POST" action="/d/{{.Doc.Slug}}/unread" style="display:inline">
|
|
{{csrfField}}
|
|
<button type="submit" class="btn-ghost btn btn-sm">Mark unread</button>
|
|
</form>
|
|
{{else}}
|
|
<form method="POST" action="/d/{{.Doc.Slug}}/read" style="display:inline">
|
|
{{csrfField}}
|
|
<button type="submit" class="btn-ghost btn btn-sm">Mark read</button>
|
|
</form>
|
|
{{end}}
|
|
<form method="POST" action="/d/{{.Doc.Slug}}/delete" style="display:inline"
|
|
onsubmit="return confirm('Delete this document?')">
|
|
{{csrfField}}
|
|
<button type="submit" class="btn-ghost btn btn-sm btn-danger">Unqueue</button>
|
|
</form>
|
|
<a href="/" class="btn-ghost btn btn-sm">Back to queue</a>
|
|
</div>
|
|
</div>
|
|
<div class="card markdown-body" id="article">
|
|
{{.HTML}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
(function(){
|
|
var toc=document.getElementById("toc");
|
|
var headings=document.querySelectorAll("#article h1, #article h2, #article h3");
|
|
if(headings.length<2){toc.style.display="none";return;}
|
|
var ul=document.createElement("ul");
|
|
var minLevel=6;
|
|
headings.forEach(function(h){var l=parseInt(h.tagName[1]);if(l<minLevel)minLevel=l;});
|
|
headings.forEach(function(h){
|
|
if(!h.id)return;
|
|
var li=document.createElement("li");
|
|
var a=document.createElement("a");
|
|
a.href="#"+h.id;
|
|
a.textContent=h.textContent;
|
|
var depth=parseInt(h.tagName[1])-minLevel;
|
|
li.style.paddingLeft=(depth*0.75)+"rem";
|
|
li.appendChild(a);
|
|
ul.appendChild(li);
|
|
});
|
|
toc.appendChild(ul);
|
|
/* highlight current section on scroll */
|
|
var links=toc.querySelectorAll("a");
|
|
var ids=[];links.forEach(function(a){ids.push(a.getAttribute("href").slice(1));});
|
|
function onScroll(){
|
|
var current="";
|
|
for(var i=0;i<ids.length;i++){
|
|
var el=document.getElementById(ids[i]);
|
|
if(el&&el.getBoundingClientRect().top<=80)current=ids[i];
|
|
}
|
|
links.forEach(function(a){
|
|
a.classList.toggle("toc-active",a.getAttribute("href")==="#"+current);
|
|
});
|
|
}
|
|
window.addEventListener("scroll",onScroll,{passive:true});
|
|
onScroll();
|
|
})();
|
|
</script>
|
|
{{end}}
|