181 lines
4.9 KiB
JavaScript
181 lines
4.9 KiB
JavaScript
(function () {
|
|
"use strict";
|
|
|
|
var slides = Array.prototype.slice.call(document.querySelectorAll("section.slide"));
|
|
var total = slides.length;
|
|
var progressEl = document.querySelector(".progress");
|
|
var baseTitle = document.title;
|
|
var renderedMermaid = {};
|
|
var fragmentsCache = {};
|
|
var current = 1;
|
|
var fragmentIndex = 0;
|
|
var gridMode = false;
|
|
|
|
function clamp(n) {
|
|
if (n < 1) return 1;
|
|
if (n > total) return total;
|
|
return n;
|
|
}
|
|
|
|
// fragmentsFor returns the "fragment" elements of the given 1-based slide
|
|
// index, in document order, computed and cached once per slide. A slide
|
|
// marked with data-incremental="true" gets its top-level list items
|
|
// auto-tagged as fragments in addition to any explicitly marked via raw
|
|
// HTML (class="fragment").
|
|
function fragmentsFor(index) {
|
|
if (fragmentsCache[index]) return fragmentsCache[index];
|
|
var slide = slides[index - 1];
|
|
if (!slide) return [];
|
|
|
|
if (slide.dataset.incremental === "true") {
|
|
var autoItems = slide.querySelectorAll(":scope > ul > li, :scope > ol > li");
|
|
Array.prototype.forEach.call(autoItems, function (li) {
|
|
li.classList.add("fragment");
|
|
});
|
|
}
|
|
|
|
var frags = Array.prototype.slice.call(slide.querySelectorAll(".fragment"));
|
|
frags.forEach(function (el, i) {
|
|
el.setAttribute("data-fragment-index", String(i + 1));
|
|
});
|
|
fragmentsCache[index] = frags;
|
|
return frags;
|
|
}
|
|
|
|
function applyFragments(index, count) {
|
|
fragmentsFor(index).forEach(function (el, i) {
|
|
el.classList.toggle("fragment-visible", i < count);
|
|
});
|
|
}
|
|
|
|
function parseHash() {
|
|
var raw = location.hash.replace("#", "");
|
|
var parts = raw.split(".");
|
|
var slide = parseInt(parts[0], 10);
|
|
var fragment = parseInt(parts[1], 10);
|
|
return {
|
|
slide: isNaN(slide) ? 1 : clamp(slide),
|
|
fragment: isNaN(fragment) ? 0 : Math.max(0, fragment)
|
|
};
|
|
}
|
|
|
|
function renderMermaid(index) {
|
|
if (renderedMermaid[index]) return;
|
|
var slide = slides[index - 1];
|
|
if (!slide) return;
|
|
var nodes = slide.querySelectorAll("pre.mermaid");
|
|
if (nodes.length === 0) return;
|
|
renderedMermaid[index] = true;
|
|
mermaid.run({ nodes: Array.prototype.slice.call(nodes) });
|
|
}
|
|
|
|
function show(index, fragment, updateHash) {
|
|
current = clamp(index);
|
|
var frags = fragmentsFor(current);
|
|
fragmentIndex = typeof fragment === "number" ? Math.min(Math.max(fragment, 0), frags.length) : 0;
|
|
|
|
slides.forEach(function (slide, i) {
|
|
slide.classList.toggle("active", i === current - 1);
|
|
});
|
|
applyFragments(current, fragmentIndex);
|
|
|
|
if (progressEl) {
|
|
progressEl.textContent = current + " / " + total;
|
|
}
|
|
var slideTitle = slides[current - 1].dataset.title;
|
|
document.title = slideTitle ? slideTitle + " · " + baseTitle : baseTitle;
|
|
if (updateHash !== false) {
|
|
location.hash = fragmentIndex > 0 ? current + "." + fragmentIndex : String(current);
|
|
}
|
|
renderMermaid(current);
|
|
}
|
|
|
|
function next() {
|
|
var frags = fragmentsFor(current);
|
|
if (fragmentIndex < frags.length) {
|
|
show(current, fragmentIndex + 1);
|
|
return;
|
|
}
|
|
show(current + 1, 0);
|
|
}
|
|
|
|
function prev() {
|
|
if (fragmentIndex > 0) {
|
|
show(current, fragmentIndex - 1);
|
|
return;
|
|
}
|
|
var target = current - 1;
|
|
if (target < 1) return;
|
|
show(target, fragmentsFor(target).length);
|
|
}
|
|
|
|
function toggleGrid() {
|
|
gridMode = !gridMode;
|
|
document.body.classList.toggle("grid-mode", gridMode);
|
|
}
|
|
|
|
function exitGrid() {
|
|
if (!gridMode) return;
|
|
gridMode = false;
|
|
document.body.classList.remove("grid-mode");
|
|
}
|
|
|
|
window.addEventListener("hashchange", function () {
|
|
var h = parseHash();
|
|
show(h.slide, h.fragment, false);
|
|
});
|
|
|
|
document.addEventListener("keydown", function (e) {
|
|
if (e.key === "o" || e.key === "O") {
|
|
e.preventDefault();
|
|
toggleGrid();
|
|
return;
|
|
}
|
|
if (gridMode) {
|
|
if (e.key === "Escape") {
|
|
e.preventDefault();
|
|
exitGrid();
|
|
}
|
|
return;
|
|
}
|
|
switch (e.key) {
|
|
case "ArrowRight":
|
|
case " ":
|
|
case "PageDown":
|
|
e.preventDefault();
|
|
next();
|
|
break;
|
|
case "ArrowLeft":
|
|
case "PageUp":
|
|
e.preventDefault();
|
|
prev();
|
|
break;
|
|
case "Home":
|
|
e.preventDefault();
|
|
show(1, 0);
|
|
break;
|
|
case "End":
|
|
e.preventDefault();
|
|
show(total, 0);
|
|
break;
|
|
}
|
|
});
|
|
|
|
document.querySelectorAll(".nav-zone.prev, .nav-button.prev").forEach(function (el) {
|
|
el.addEventListener("click", prev);
|
|
});
|
|
document.querySelectorAll(".nav-zone.next, .nav-button.next").forEach(function (el) {
|
|
el.addEventListener("click", next);
|
|
});
|
|
|
|
slides.forEach(function (slide, i) {
|
|
slide.addEventListener("click", function () {
|
|
if (!gridMode) return;
|
|
exitGrid();
|
|
show(i + 1, 0);
|
|
});
|
|
});
|
|
|
|
var initial = parseHash();
|
|
show(initial.slide, initial.fragment, true);
|
|
})();
|