123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- function throttledDebounce(callback, maxDebounceTimes, debounceDelay) {
- let debounceTimeout;
- let timesDebounced = 0;
- return function () {
- if (timesDebounced == maxDebounceTimes) {
- clearTimeout(debounceTimeout);
- timesDebounced = 0;
- callback();
- return;
- }
- clearTimeout(debounceTimeout);
- timesDebounced++;
- debounceTimeout = setTimeout(() => {
- timesDebounced = 0;
- callback();
- }, debounceDelay);
- };
- };
- async function fetchPageContents (pageSlug) {
- // TODO: handle non 200 status codes/time outs
- // TODO: add retries
- const response = await fetch(`/api/pages/${pageSlug}/content/`);
- const content = await response.text();
- return content;
- }
- function setupCarousels() {
- const carouselElements = document.getElementsByClassName("carousel-container");
- for (let i = 0; i < carouselElements.length; i++) {
- const carousel = carouselElements[i];
- carousel.classList.add("show-right-cutoff");
- const itemsContainer = carousel.getElementsByClassName("carousel-items-container")[0];
- const determineSideCutoffs = () => {
- if (itemsContainer.scrollLeft != 0) {
- carousel.classList.add("show-left-cutoff");
- } else {
- carousel.classList.remove("show-left-cutoff");
- }
- if (Math.ceil(itemsContainer.scrollLeft) + itemsContainer.clientWidth < itemsContainer.scrollWidth) {
- carousel.classList.add("show-right-cutoff");
- } else {
- carousel.classList.remove("show-right-cutoff");
- }
- }
- const determineSideCutoffsRateLimited = throttledDebounce(determineSideCutoffs, 20, 100);
- itemsContainer.addEventListener("scroll", determineSideCutoffsRateLimited);
- document.addEventListener("resize", determineSideCutoffsRateLimited);
- determineSideCutoffs();
- }
- }
- const minuteInSeconds = 60;
- const hourInSeconds = minuteInSeconds * 60;
- const dayInSeconds = hourInSeconds * 24;
- const monthInSeconds = dayInSeconds * 30;
- const yearInSeconds = monthInSeconds * 12;
- function relativeTimeSince(timestamp) {
- const delta = Math.round((Date.now() / 1000) - timestamp);
- if (delta < minuteInSeconds) {
- return "1m";
- }
- if (delta < hourInSeconds) {
- return Math.floor(delta / minuteInSeconds) + "m";
- }
- if (delta < dayInSeconds) {
- return Math.floor(delta / hourInSeconds) + "h";
- }
- if (delta < monthInSeconds) {
- return Math.floor(delta / dayInSeconds) + "d";
- }
- if (delta < yearInSeconds) {
- return Math.floor(delta / monthInSeconds) + "mo";
- }
- return Math.floor(delta / yearInSeconds) + "y";
- }
- function updateRelativeTimeForElements(elements)
- {
- for (let i = 0; i < elements.length; i++)
- {
- const element = elements[i];
- const timestamp = element.dataset.dynamicRelativeTime;
- if (timestamp === undefined)
- continue
- element.innerText = relativeTimeSince(timestamp);
- }
- }
- function setupDynamicRelativeTime() {
- const elements = document.querySelectorAll("[data-dynamic-relative-time]");
- const updateInterval = 60 * 1000;
- let lastUpdateTime = Date.now();
- const updateElementsAndTimestamp = () => {
- updateRelativeTimeForElements(elements);
- lastUpdateTime = Date.now();
- };
- const scheduleRepeatingUpdate = () => setInterval(updateElementsAndTimestamp, updateInterval);
- if (document.hidden === undefined) {
- scheduleRepeatingUpdate();
- return;
- }
- let timeout = scheduleRepeatingUpdate();
- document.addEventListener("visibilitychange", () => {
- if (document.hidden) {
- clearTimeout(timeout);
- return;
- }
- const delta = Date.now() - lastUpdateTime;
- if (delta >= updateInterval) {
- updateElementsAndTimestamp();
- timeout = scheduleRepeatingUpdate();
- return;
- }
- timeout = setTimeout(() => {
- updateElementsAndTimestamp();
- timeout = scheduleRepeatingUpdate();
- }, updateInterval - delta);
- });
- }
- function setupLazyImages() {
- const images = document.querySelectorAll("img[loading=lazy]");
- if (images.length == 0) {
- return;
- }
- function imageFinishedTransition(image) {
- image.classList.add("finished-transition");
- }
- for (let i = 0; i < images.length; i++) {
- const image = images[i];
- if (image.complete) {
- image.classList.add("cached");
- setTimeout(() => imageFinishedTransition(image), 5);
- } else {
- // TODO: also handle error event
- image.addEventListener("load", () => {
- image.classList.add("loaded");
- setTimeout(() => imageFinishedTransition(image), 500);
- });
- }
- }
- }
- async function setupPage() {
- const pageElement = document.getElementById("page");
- const pageContents = await fetchPageContents(pageData.slug);
- pageElement.innerHTML = pageContents;
- setTimeout(() => {
- document.body.classList.add("animate-element-transition");
- }, 150);
- setTimeout(setupLazyImages, 5);
- setupCarousels();
- setupDynamicRelativeTime();
- }
- if (document.readyState === "loading") {
- document.addEventListener("DOMContentLoaded", setupPage);
- } else {
- setupPage();
- }
|