website

Personal website
git clone https://github.com/michaelneuper/website.git
README | Files | Log | Refs

website/src/scripts/post/outline.js (1403B)


// The *Ilist* window beside a post marks the heading you're reading: the last
// one above a line 30% down the screen. A long list scrolls to keep that entry
// in view, and following a link opens the heading if it's folded.

import { $, $$ } from "../util.js";
import { headings, reveal } from "./folding.js";

export function initOutline() {
  const toc = $(".post-toc");
  const tocLinks = toc ? $$("a", toc) : [];
  if (!tocLinks.length) return;
  const heads = headings();
  let raf = 0;
  const mark = () => {
    raf = 0;
    const line = innerHeight * 0.3;
    let cur = null;
    for (const h of heads) {
      if (h.offsetParent === null) continue; // folded away
      if (h.getBoundingClientRect().top > line) break;
      cur = h;
    }
    for (const a of tocLinks) {
      const on = !!cur && a.hash === `#${cur.id}`;
      if (on && a.getAttribute("aria-current") !== "true" && toc.scrollHeight > toc.clientHeight) {
        const y = a.offsetTop - toc.scrollTop;
        if (y < 40 || y > toc.clientHeight - 40) toc.scrollTop = a.offsetTop - toc.clientHeight / 2;
      }
      a.setAttribute("aria-current", String(on));
    }
  };
  addEventListener("scroll", () => { raf ||= requestAnimationFrame(mark); }, { passive: true });
  mark();
  for (const a of tocLinks) {
    a.addEventListener("click", () => reveal(document.getElementById(decodeURIComponent(a.hash.slice(1)))));
  }
}