---
// the column beside the paper: the page's title (it goes back to the top), then a list with
// one raised header per section and its entries folded under it. configuration and prose
// pages share it, so the two look and behave the same.
import { icon } from "../lib/icons";
export interface TreeItem {
id: string;
label: string;
/** right-aligned, dimmed: a setting's default */
detail?: string;
}
export interface TreeGroup {
id: string;
title: string;
items: TreeItem[];
/** shown after the title, e.g. how many settings it holds */
count?: number;
}
interface Props {
label: string;
icon: string;
groups: TreeGroup[];
/** entries are code (setting names) */
mono?: boolean;
ariaLabel: string;
}
const { label, icon: spec, groups, mono, ariaLabel } = Astro.props;
const esc = (s: string) => s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]!);
// long names like nsids and paths may wrap after a dot or slash instead of overflowing
const breakable = (s: string) => esc(s).replace(/([./])/g, "$1<wbr>");
---
<div class="side" data-tree>
<a class="side-title" href="#" data-to=""><Fragment set:html={icon(spec, 32)} /><span class="k">{label}</span></a>
<nav class={`list tree${mono ? " mono" : ""}`} aria-label={ariaLabel} tabindex="0">
{groups.map((g) => (
<div class="grp" data-grp={g.id}>
<div class="gh">
{g.items.length ? (
<button class="tri" type="button" data-fold={g.id} aria-label={`show what's in ${g.title}`} aria-expanded="false"></button>
) : (
<span class="tri none"></span>
)}
<a href={`#${g.id}`} data-to={g.id}><span class="t" set:html={breakable(g.title)} />{g.count ? <span class="n">{g.count}</span> : null}</a>
</div>
{g.items.length > 0 && (
<div class="kids">
{g.items.map((it) => (
<a class="it" href={`#${it.id}`} data-to={it.id}><span class="k" set:html={mono ? esc(it.label) : breakable(it.label)} />{it.detail && <span class="d">{it.detail}</span>}</a>
))}
</div>
)}
</div>
))}
</nav>
</div>
<script is:inline>
// the entry the #hash names is picked before the first paint, so the list doesn't change
// under you when tree.ts takes over
{
const side = document.currentScript.previousElementSibling;
const key = decodeURIComponent(location.hash.slice(1));
const link = key && side.querySelector(`[data-to="${CSS.escape(key)}"]`);
if (link) {
link.classList.add("sel");
link.closest("[data-grp]")?.classList.add("open");
}
}
</script>