Merge branch 'release/v0.6.0' into main

This commit is contained in:
Svilen Markov
2024-08-05 13:26:46 +01:00
committed by GitHub
79 changed files with 2659 additions and 336 deletions

View File

@@ -59,9 +59,9 @@ function setupCarousels() {
const determineSideCutoffsRateLimited = throttledDebounce(determineSideCutoffs, 20, 100);
itemsContainer.addEventListener("scroll", determineSideCutoffsRateLimited);
document.addEventListener("resize", determineSideCutoffsRateLimited);
window.addEventListener("resize", determineSideCutoffsRateLimited);
setTimeout(determineSideCutoffs, 1);
afterContentReady(determineSideCutoffs);
}
}
@@ -103,7 +103,108 @@ function updateRelativeTimeForElements(elements)
if (timestamp === undefined)
continue
element.innerText = relativeTimeSince(timestamp);
element.textContent = relativeTimeSince(timestamp);
}
}
function setupSearchBoxes() {
const searchWidgets = document.getElementsByClassName("search");
if (searchWidgets.length == 0) {
return;
}
for (let i = 0; i < searchWidgets.length; i++) {
const widget = searchWidgets[i];
const defaultSearchUrl = widget.dataset.defaultSearchUrl;
const newTab = widget.dataset.newTab === "true";
const inputElement = widget.getElementsByClassName("search-input")[0];
const bangElement = widget.getElementsByClassName("search-bang")[0];
const bangs = widget.querySelectorAll(".search-bangs > input");
const bangsMap = {};
const kbdElement = widget.getElementsByTagName("kbd")[0];
let currentBang = null;
for (let j = 0; j < bangs.length; j++) {
const bang = bangs[j];
bangsMap[bang.dataset.shortcut] = bang;
}
const handleKeyDown = (event) => {
if (event.key == "Escape") {
inputElement.blur();
return;
}
if (event.key == "Enter") {
const input = inputElement.value.trim();
let query;
let searchUrlTemplate;
if (currentBang != null) {
query = input.slice(currentBang.dataset.shortcut.length + 1);
searchUrlTemplate = currentBang.dataset.url;
} else {
query = input;
searchUrlTemplate = defaultSearchUrl;
}
if (query.length == 0 && currentBang == null) {
return;
}
const url = searchUrlTemplate.replace("!QUERY!", encodeURIComponent(query));
if (newTab && !event.ctrlKey || !newTab && event.ctrlKey) {
window.open(url, '_blank').focus();
} else {
window.location.href = url;
}
return;
}
};
const changeCurrentBang = (bang) => {
currentBang = bang;
bangElement.textContent = bang != null ? bang.dataset.title : "";
}
const handleInput = (event) => {
const value = event.target.value.trim();
if (value in bangsMap) {
changeCurrentBang(bangsMap[value]);
return;
}
const words = value.split(" ");
if (words.length >= 2 && words[0] in bangsMap) {
changeCurrentBang(bangsMap[words[0]]);
return;
}
changeCurrentBang(null);
};
inputElement.addEventListener("focus", () => {
document.addEventListener("keydown", handleKeyDown);
document.addEventListener("input", handleInput);
});
inputElement.addEventListener("blur", () => {
document.removeEventListener("keydown", handleKeyDown);
document.removeEventListener("input", handleInput);
});
document.addEventListener("keydown", (event) => {
if (['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName)) return;
if (event.key != "s") return;
inputElement.focus();
event.preventDefault();
});
kbdElement.addEventListener("mousedown", () => {
requestAnimationFrame(() => inputElement.focus());
});
}
}
@@ -149,6 +250,46 @@ function setupDynamicRelativeTime() {
});
}
function setupGroups() {
const groups = document.getElementsByClassName("widget-type-group");
if (groups.length == 0) {
return;
}
for (let g = 0; g < groups.length; g++) {
const group = groups[g];
const titles = group.getElementsByClassName("widget-header")[0].children;
const tabs = group.getElementsByClassName("widget-group-contents")[0].children;
let current = 0;
for (let t = 0; t < titles.length; t++) {
const title = titles[t];
title.addEventListener("click", () => {
if (t == current) {
return;
}
for (let i = 0; i < titles.length; i++) {
titles[i].classList.remove("widget-group-title-current");
tabs[i].classList.remove("widget-group-content-current");
}
if (current < t) {
tabs[t].dataset.direction = "right";
} else {
tabs[t].dataset.direction = "left";
}
current = t;
title.classList.add("widget-group-title-current");
tabs[t].classList.add("widget-group-content-current");
});
}
}
}
function setupLazyImages() {
const images = document.querySelectorAll("img[loading=lazy]");
@@ -160,22 +301,24 @@ function setupLazyImages() {
image.classList.add("finished-transition");
}
setTimeout(() => {
for (let i = 0; i < images.length; i++) {
const image = images[i];
afterContentReady(() => {
setTimeout(() => {
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);
});
if (image.complete) {
image.classList.add("cached");
setTimeout(() => imageFinishedTransition(image), 1);
} else {
// TODO: also handle error event
image.addEventListener("load", () => {
image.classList.add("loaded");
setTimeout(() => imageFinishedTransition(image), 400);
});
}
}
}
}, 5);
}, 1);
});
}
function attachExpandToggleButton(collapsibleContainer) {
@@ -253,8 +396,6 @@ function setupCollapsibleLists() {
child.classList.add("collapsible-item");
child.style.animationDelay = ((c - collapseAfter) * 20).toString() + "ms";
}
list.classList.add("ready");
}
}
@@ -314,11 +455,10 @@ function setupCollapsibleGrids() {
}
};
setTimeout(() => {
afterContentReady(() => {
cardsPerRow = getCardsPerRow();
resolveCollapsibleItems();
gridElement.classList.add("ready");
}, 1);
});
window.addEventListener("resize", () => {
const newCardsPerRow = getCardsPerRow();
@@ -333,6 +473,118 @@ function setupCollapsibleGrids() {
}
}
const contentReadyCallbacks = [];
function afterContentReady(callback) {
contentReadyCallbacks.push(callback);
}
const weekDayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
function makeSettableTimeElement(element, hourFormat) {
const fragment = document.createDocumentFragment();
const hour = document.createElement('span');
const minute = document.createElement('span');
const amPm = document.createElement('span');
fragment.append(hour, document.createTextNode(':'), minute);
if (hourFormat == '12h') {
fragment.append(document.createTextNode(' '), amPm);
}
element.append(fragment);
return (date) => {
const hours = date.getHours();
if (hourFormat == '12h') {
amPm.textContent = hours < 12 ? 'AM' : 'PM';
hour.textContent = hours % 12 || 12;
} else {
hour.textContent = hours < 10 ? '0' + hours : hours;
}
const minutes = date.getMinutes();
minute.textContent = minutes < 10 ? '0' + minutes : minutes;
};
};
function timeInZone(now, zone) {
let timeInZone;
try {
timeInZone = new Date(now.toLocaleString('en-US', { timeZone: zone }));
} catch (e) {
// TODO: indicate to the user that this is an invalid timezone
console.error(e);
timeInZone = now
}
const diffInHours = Math.round((timeInZone.getTime() - now.getTime()) / 1000 / 60 / 60);
return { time: timeInZone, diffInHours: diffInHours };
}
function setupClocks() {
const clocks = document.getElementsByClassName('clock');
if (clocks.length == 0) {
return;
}
const updateCallbacks = [];
for (var i = 0; i < clocks.length; i++) {
const clock = clocks[i];
const hourFormat = clock.dataset.hourFormat;
const localTimeContainer = clock.querySelector('[data-local-time]');
const localDateElement = localTimeContainer.querySelector('[data-date]');
const localWeekdayElement = localTimeContainer.querySelector('[data-weekday]');
const localYearElement = localTimeContainer.querySelector('[data-year]');
const timeZoneContainers = clock.querySelectorAll('[data-time-in-zone]');
const setLocalTime = makeSettableTimeElement(
localTimeContainer.querySelector('[data-time]'),
hourFormat
);
updateCallbacks.push((now) => {
setLocalTime(now);
localDateElement.textContent = now.getDate() + ' ' + monthNames[now.getMonth()];
localWeekdayElement.textContent = weekDayNames[now.getDay()];
localYearElement.textContent = now.getFullYear();
});
for (var z = 0; z < timeZoneContainers.length; z++) {
const timeZoneContainer = timeZoneContainers[z];
const diffElement = timeZoneContainer.querySelector('[data-time-diff]');
const setZoneTime = makeSettableTimeElement(
timeZoneContainer.querySelector('[data-time]'),
hourFormat
);
updateCallbacks.push((now) => {
const { time, diffInHours } = timeInZone(now, timeZoneContainer.dataset.timeInZone);
setZoneTime(time);
diffElement.textContent = (diffInHours <= 0 ? diffInHours : '+' + diffInHours) + 'h';
});
}
}
const updateClocks = () => {
const now = new Date();
for (var i = 0; i < updateCallbacks.length; i++)
updateCallbacks[i](now);
setTimeout(updateClocks, (60 - now.getSeconds()) * 1000);
};
updateClocks();
}
async function setupPage() {
const pageElement = document.getElementById("page");
const pageContentElement = document.getElementById("page-content");
@@ -340,23 +592,26 @@ async function setupPage() {
pageContentElement.innerHTML = pageContent;
setTimeout(() => {
document.body.classList.add("animate-element-transition");
}, 200);
try {
setupLazyImages();
setupClocks()
setupCarousels();
setupSearchBoxes();
setupCollapsibleLists();
setupCollapsibleGrids();
setupGroups();
setupDynamicRelativeTime();
setupLazyImages();
} finally {
pageElement.classList.add("content-ready");
for (let i = 0; i < contentReadyCallbacks.length; i++) {
contentReadyCallbacks[i]();
}
setTimeout(() => {
document.body.classList.add("page-columns-transitioned");
}, 300);
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", setupPage);
} else {
setupPage();
}
setupPage();