summaryrefslogtreecommitdiffstats
path: root/docs/theme/TocPage.js
blob: 77bad1e219699773fea2d0f665acf1555853a226 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import { on } from "./jxui.js";

const ACTIVE = "active";
const SEL_BACKTOTOP = ".cd-back-to-top"
const SEL_PAGETOC = ".cd-toc-page"
const SEL_TARGET = `${SEL_PAGETOC} a`;
const SEL_ACTIVE = `${SEL_TARGET}.${ACTIVE}`;
const SEL_PAGE = "#main.page";
const SEL_SECTIONS = `${SEL_PAGE} section[id]`;
const DESKTOP_THRESHOLD = 1024;

on("click", SEL_TARGET, handleClick);
on("click", SEL_BACKTOTOP, backToTop);

function handleClick(event, target) {
  removeHighlight();
  setTimeout(function () { updateHighlight(target) }, 10);
}

function updateHighlight (elem) {
  if (window.innerWidth > DESKTOP_THRESHOLD && !elem?.classList.contains(ACTIVE)) {
    removeHighlight();
    if (!elem) return;
    elem.classList.add(ACTIVE);
  }
}

function removeHighlight () {
  document.querySelectorAll(SEL_ACTIVE).forEach(function (node) {
    node.classList.remove(ACTIVE);
  });
}

function resetNavPosition () {
  var pagetoc = document.querySelector(SEL_TOC);
  pagetoc?.scroll({ top: 0 });
}

export function backToTop () {
  window.scrollTo({ top: 0, behavior: "smooth" });
  resetNavPosition();
}

export function scrollSpy() {
  const sections = Array.from(document.querySelectorAll(SEL_SECTIONS));

  function matchingNavLink(elem) {
    if (!elem) return;
    var index = sections.indexOf(elem);

    var match;
    while (index >= 0 && !match) {
      var sectionId = sections[index].getAttribute("id");
      if (sectionId) {
        match = document.querySelector(`${SEL_PAGETOC} [href="#${sectionId}"]`);
      }
      index--;
    }
    return match;
  }

  function belowBottomHalf(i) {
    return i.boundingClientRect.bottom > (i.rootBounds.bottom + i.rootBounds.top) / 2;
  }

  function prevElem(elem) {
    var index = sections.indexOf(elem);
    if (index <= 0) {
      return null;
    }
    return sections[index - 1];
  }

  const PAGE_LOAD_BUFFER = 1000;

  function navHighlight(entries) {
    entries.forEach(function (entry) {
      if (entry.isIntersecting) {
        updateHighlight(matchingNavLink(entry.target));
      } else if (entry.time >= PAGE_LOAD_BUFFER && belowBottomHalf(entry)) {
        updateHighlight(matchingNavLink(prevElem(entry.target)));
      }
    });
  }

  const observer = new IntersectionObserver(navHighlight, {
    threshold: 0,
    rootMargin: "0% 0px -95% 0px"
  });

  sections.forEach(function (elem) {
    observer.observe(elem);
  })
  observer.observe(document.querySelector(SEL_PAGE));
}

document.addEventListener("DOMContentLoaded", scrollSpy);