neighbour-loader.js
Table of Contents
This literate configuration file tangles to neighbour-loader.js. On every note page, this lightweight neighbour-loader.js script is loaded via the partial neighbour-panel.html. It has no external dependencies compared to graph.js.
The panel is populated on demand when the user clicks "Neighbours" — or immediately on page load if sessionStorage records it was previously open. It renders a neighbour list directly into #neighbour-panel in the aside, above the TOC. Closing the panel clears its contents and removes the sessionStorage flag.
The Neighbour panel topbar includes a graph icon linking to the note's entry point in the graphical view.
The neighbour list is rendered via neighbour-utils.js which owns the
data fetch, grouping into Links and Backlinks, and returns a DOM
fragment to the caller. _fetchAborted guards against a race
condition where destroy() is called while a fetch is still in flight
— if the flag is set when the callback fires, the panel is cleared
silently.
Initialisation
This section has trigger guard, lnk, _fetchAborted
const trigger = document.getElementById("neighbour-trigger"); if (!trigger) return; const lnk = trigger.dataset.lnk; let _fetchAborted = false;
Render List
This section includes topbar construction and NeighbourUtils.buildList call.
function renderList() { const panel = document.getElementById("neighbour-panel"); if (!panel) return; _fetchAborted = false; const topBar = document.createElement("div"); topBar.className = "np-topbar"; const count = document.createElement("span"); count.className = "np-count"; const graphLink = document.createElement("a"); graphLink.href = `/map/?focus=${encodeURIComponent(lnk)}#graph-wrapper`; graphLink.title = "Explore visually"; graphLink.className = "np-graph-link"; graphLink.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> <circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/> <circle cx="18" cy="19" r="3"/> <line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/> <line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/> </svg>`; const closeBtn = document.createElement("button"); closeBtn.textContent = "✕"; closeBtn.className = "np-close"; closeBtn.setAttribute("aria-label", "Close neighbour panel"); closeBtn.addEventListener("click", destroy, { once: true }); topBar.appendChild(graphLink); topBar.appendChild(count); topBar.appendChild(closeBtn); panel.innerHTML = ""; panel.appendChild(topBar); panel.scrollIntoView({ behavior: "smooth", block: "start" }); NeighbourUtils.buildList(lnk, "np-list", (n, fragment) => { if (_fetchAborted) { panel.innerHTML = ""; return; } count.textContent = `${n} neighbour${n !== 1 ? "s" : ""}`; if (fragment) panel.appendChild(fragment); else { const m = document.createElement("div"); m.className = "np-empty"; m.textContent = "This note has no connections yet."; panel.appendChild(m); } }, () => { panel.innerHTML = '<div class="np-empty">Could not load.</div>'; } ); }
Destroy
This section includes the abort flag and clears the panel and sessionStorage flag.
function destroy() { _fetchAborted = true; const panel = document.getElementById("neighbour-panel"); if (panel) panel.innerHTML = ""; sessionStorage.removeItem("neighbourPanelOpen"); }
Event Listeners
Handles sessionStorage check on load, click toggle
if (sessionStorage.getItem("neighbourPanelOpen") === "1") { renderList(); } trigger.addEventListener("click", () => { const panel = document.getElementById("neighbour-panel"); if (panel && panel.innerHTML !== "") { destroy(); } else { sessionStorage.setItem("neighbourPanelOpen", "1"); renderList(); } });
Assembly
The complete IIFE shell with noweb references is as follows. The complete tangled version of this file can be found here.
(function (){ //guard → renderlist → destroy → eventlisteners })();