feat(Radio): - Redesigned Queue modal, added drag & drop capabilities - Added stream quality selector, currently offering: AAC @ 128kbps, AAC @ 320kbps & FLAC (lossless) fix(middleware): Import API_URL from config and remove hardcoded API_URL definition security(api): Enhance discord image and video caching with improved signature verification and error handling, updated image proxy to include production checks for signing secret
199 lines
6.0 KiB
JavaScript
199 lines
6.0 KiB
JavaScript
(function () {
|
|
const scriptEl = document.currentScript;
|
|
const API_URL = scriptEl?.dataset?.apiUrl;
|
|
|
|
window.toggleTheme = () => {
|
|
const currentTheme = document.documentElement.getAttribute("data-theme");
|
|
const newTheme = currentTheme === "dark" ? "light" : "dark";
|
|
|
|
// Toggle the dark class for Tailwind
|
|
if (newTheme === "dark") {
|
|
document.documentElement.classList.add("dark");
|
|
} else {
|
|
document.documentElement.classList.remove("dark");
|
|
}
|
|
|
|
// Dispatch event for astro-themes and other listeners
|
|
document.dispatchEvent(new CustomEvent("set-theme", { detail: newTheme }));
|
|
};
|
|
|
|
window.handleLogout = async () => {
|
|
try {
|
|
await fetch(`${API_URL}/auth/logout`, {
|
|
method: "POST",
|
|
credentials: "include",
|
|
});
|
|
} catch (error) {
|
|
console.error("Logout failed", error);
|
|
} finally {
|
|
window.location.href = "/";
|
|
}
|
|
};
|
|
|
|
function setLenisEnabled(enabled) {
|
|
const lenis = window.__lenis;
|
|
if (!lenis) return;
|
|
if (enabled) {
|
|
lenis.start();
|
|
} else {
|
|
lenis.stop();
|
|
}
|
|
}
|
|
|
|
function initMobileMenu() {
|
|
const menuBtn = document.getElementById("mobile-menu-btn");
|
|
const mobileMenu = document.getElementById("mobile-menu");
|
|
const menuIcon = document.getElementById("menu-icon");
|
|
const closeIcon = document.getElementById("close-icon");
|
|
|
|
if (!menuBtn || !mobileMenu || !menuIcon || !closeIcon) {
|
|
return;
|
|
}
|
|
|
|
const newMenuBtn = menuBtn.cloneNode(true);
|
|
menuBtn.parentNode.replaceChild(newMenuBtn, menuBtn);
|
|
|
|
newMenuBtn.addEventListener("click", (e) => {
|
|
e.stopPropagation();
|
|
const isOpen = mobileMenu.classList.contains("open");
|
|
|
|
if (isOpen) {
|
|
mobileMenu.classList.remove("open");
|
|
menuIcon.style.display = "block";
|
|
closeIcon.style.display = "none";
|
|
setLenisEnabled(true);
|
|
} else {
|
|
mobileMenu.classList.add("open");
|
|
menuIcon.style.display = "none";
|
|
closeIcon.style.display = "block";
|
|
setLenisEnabled(false);
|
|
}
|
|
});
|
|
|
|
const mobileLinks = mobileMenu.querySelectorAll("a");
|
|
mobileLinks.forEach((link) => {
|
|
link.addEventListener("click", () => {
|
|
mobileMenu.classList.remove("open");
|
|
menuIcon.style.display = "block";
|
|
closeIcon.style.display = "none";
|
|
setLenisEnabled(true);
|
|
});
|
|
});
|
|
|
|
const closeHandler = (e) => {
|
|
if (!mobileMenu.contains(e.target) && !newMenuBtn.contains(e.target)) {
|
|
if (mobileMenu.classList.contains("open")) {
|
|
mobileMenu.classList.remove("open");
|
|
menuIcon.style.display = "block";
|
|
closeIcon.style.display = "none";
|
|
setLenisEnabled(true);
|
|
}
|
|
}
|
|
};
|
|
|
|
document.removeEventListener("click", closeHandler);
|
|
document.addEventListener("click", closeHandler);
|
|
}
|
|
|
|
function initDropdownExternalLinks() {
|
|
// Close desktop dropdown when external links are clicked
|
|
const dropdownLinks = document.querySelectorAll('.nav-dropdown a[target="_blank"]');
|
|
dropdownLinks.forEach((link) => {
|
|
link.addEventListener("click", () => {
|
|
// Blur the parent nav item to close the CSS hover-based dropdown
|
|
const navItem = link.closest('.nav-item--has-children');
|
|
if (navItem) {
|
|
const parentLink = navItem.querySelector(':scope > a');
|
|
if (parentLink) {
|
|
parentLink.blur();
|
|
}
|
|
// Force close by temporarily disabling pointer events
|
|
navItem.style.pointerEvents = 'none';
|
|
setTimeout(() => {
|
|
navItem.style.pointerEvents = '';
|
|
}, 100);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function initDesktopDropdownToggle() {
|
|
// Desktop: click parent to toggle dropdown open/closed
|
|
const desktopNav = document.querySelector('nav .hidden.md\\:flex');
|
|
if (!desktopNav) return;
|
|
|
|
const parentItems = desktopNav.querySelectorAll('.nav-item--has-children');
|
|
parentItems.forEach((navItem) => {
|
|
const parentLink = navItem.querySelector(':scope > a');
|
|
if (!parentLink) return;
|
|
|
|
parentLink.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
const isOpen = navItem.classList.contains('dropdown-open');
|
|
|
|
// Close any other open dropdowns
|
|
parentItems.forEach((item) => item.classList.remove('dropdown-open'));
|
|
|
|
if (!isOpen) {
|
|
navItem.classList.add('dropdown-open');
|
|
}
|
|
});
|
|
});
|
|
|
|
// Close dropdown when clicking outside
|
|
document.addEventListener('click', (e) => {
|
|
if (!desktopNav.contains(e.target)) {
|
|
parentItems.forEach((item) => item.classList.remove('dropdown-open'));
|
|
}
|
|
});
|
|
}
|
|
|
|
function initMobileSubmenus() {
|
|
const mobileMenu = document.getElementById("mobile-menu");
|
|
if (!mobileMenu) return;
|
|
|
|
// Find all parent links that have a sibling mobile-subnav
|
|
const parentLinks = mobileMenu.querySelectorAll('a:has(+ .mobile-subnav)');
|
|
|
|
parentLinks.forEach((link) => {
|
|
const subnav = link.nextElementSibling;
|
|
const caret = link.querySelector('.mobile-caret');
|
|
|
|
if (!subnav || !subnav.classList.contains('mobile-subnav')) return;
|
|
|
|
// Clone to remove existing listeners
|
|
const newLink = link.cloneNode(true);
|
|
link.parentNode.replaceChild(newLink, link);
|
|
|
|
const newCaret = newLink.querySelector('.mobile-caret');
|
|
|
|
newLink.addEventListener('click', (e) => {
|
|
// Toggle subnav open/closed on click
|
|
e.preventDefault();
|
|
subnav.classList.toggle('open');
|
|
if (newCaret) newCaret.classList.toggle('open');
|
|
});
|
|
});
|
|
}
|
|
|
|
const ready = () => {
|
|
initMobileMenu();
|
|
initDropdownExternalLinks();
|
|
initDesktopDropdownToggle();
|
|
initMobileSubmenus();
|
|
};
|
|
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", ready, { once: true });
|
|
} else {
|
|
ready();
|
|
}
|
|
|
|
document.addEventListener("astro:page-load", () => {
|
|
initMobileMenu();
|
|
initDropdownExternalLinks();
|
|
initDesktopDropdownToggle();
|
|
initMobileSubmenus();
|
|
});
|
|
})();
|