1234function timelineTips() {5 const dots = document.querySelectorAll(".timeline .dot");6 if (dots.length === 0) return;7 const tip = document.createElement("div");8 tip.className = "timeline-tip";9 tip.hidden = true;10 document.body.append(tip);11 for (const dot of dots) {12 13 const title = dot.querySelector("title");14 const [id, message, meta] = (title?.textContent ?? "").split("\n");15 title?.remove();16 dot.setAttribute("aria-label", `${id} ${message}`);17 dot.addEventListener("pointerenter", (event) => {18 tip.replaceChildren(line("tip-id", id), line("tip-msg", message), line("tip-meta", meta));19 tip.hidden = false;20 const left = event.clientX + 14;21 tip.style.left = `${left + 340 > window.innerWidth ? Math.max(8, event.clientX - 354) : left}px`;22 tip.style.top = `${event.clientY - 10}px`;23 });24 dot.addEventListener("pointerleave", () => {25 tip.hidden = true;26 });27 }28}2930function line(className, text) {31 const div = document.createElement("div");32 div.className = className;33 div.textContent = text ?? "";34 return div;35}3637function copyButtons() {38 for (const button of document.querySelectorAll(".copy-btn[data-copy]")) {39 button.hidden = false;40 button.addEventListener("click", async () => {41 await navigator.clipboard.writeText(button.dataset.copy);42 button.textContent = "copied";43 button.classList.add("copied");44 setTimeout(() => {45 button.textContent = "copy";46 button.classList.remove("copied");47 }, 1400);48 });49 }50}515253function lineRanges() {54 const code = document.querySelector(".code");55 if (!code) return;56 let anchor = null;57 const mark = () => {58 for (const marked of code.querySelectorAll(".line.hl")) marked.classList.remove("hl");59 const match = /^#L(\d+)(?:-L(\d+))?$/.exec(location.hash);60 if (!match) return;61 const from = Number(match[1]);62 const to = Number(match[2] ?? match[1]);63 anchor = from;64 for (let n = Math.min(from, to); n <= Math.max(from, to); n++) {65 document.getElementById(`L${n}`)?.classList.add("hl");66 }67 document.getElementById(`L${Math.min(from, to)}`)?.scrollIntoView({ block: "center" });68 };69 code.addEventListener("click", (event) => {70 const link = event.target.closest(".ln");71 if (!link || !event.shiftKey || anchor === null) return;72 event.preventDefault();73 const n = Number(link.textContent);74 history.replaceState(null, "", `#L${Math.min(anchor, n)}-L${Math.max(anchor, n)}`);75 mark();76 });77 window.addEventListener("hashchange", mark);78 mark();79}80818283function atlas() {84 const root = document.querySelector(".atlas");85 const filter = document.querySelector(".atlas-filter");86 if (!root || !filter) return;87 const panel = root.querySelector(".atlas-panel");88 const folders = [...root.querySelectorAll(".atlas-folder")];89 const files = [...root.querySelectorAll(".atlas-file")];90 const key = `kepr:atlas:${root.dataset.repo}`;91 const opened = new Set(remembered(key));92 for (const folder of folders) {93 folder.open = opened.has(folder.dataset.path);94 folder.addEventListener("toggle", () => {95 if (filter.value.trim()) return;96 if (folder.open) opened.add(folder.dataset.path);97 else opened.delete(folder.dataset.path);98 remember(key, [...opened]);99 });100 }101 let selected = null;102 const show = (row) => {103 selected?.classList.remove("selected");104 selected = row;105 row.classList.add("selected");106 panel.replaceChildren(row.nextElementSibling.content.cloneNode(true));107 };108 for (const row of root.querySelectorAll(".atlas-row")) {109 row.addEventListener("pointerenter", (event) => {110 if (event.pointerType === "mouse") show(row);111 });112 row.addEventListener("focus", () => show(row));113 }114 filter.addEventListener("input", () => {115 const query = filter.value.trim().toLowerCase();116 for (const file of files) file.hidden = query !== "" && !file.dataset.name.includes(query);117 for (const folder of folders) {118 folder.hidden = query !== "" && !folder.querySelector(".atlas-file:not([hidden])");119 folder.open = query !== "" || opened.has(folder.dataset.path);120 }121 });122 filter.hidden = false;123 panel.hidden = false;124}125126127128function remembered(key) {129 try {130 const value = JSON.parse(localStorage.getItem(key) ?? "[]");131 return Array.isArray(value) && value.every((path) => typeof path === "string") ? value : [];132 } catch {133 return [];134 }135}136137function remember(key, value) {138 try {139 localStorage.setItem(key, JSON.stringify(value));140 } catch {}141}142143timelineTips();144copyButtons();145lineRanges();146atlas();