neighbour-loader.js
On every note page, neighbour-loader.js, a lightweight 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
})();
© Prabu Anand K 2020-2026