nativegallery/static/js/routing.js
2025-05-25 15:04:30 +03:00

258 lines
8.1 KiB
JavaScript

document.addEventListener("DOMContentLoaded", () => {
const DEBUG = true;
const cache = new Map();
const CACHE_TTL = 300000; // 5 минут
let loadingTimeout;
const loadedScriptSrcs = new Set();
function reloadExternalScripts(doc) {
const scripts = Array.from(doc.querySelectorAll("script[src]"));
const loadedUrls = new Set(Array.from(document.scripts).map((s) => s.src));
// Загрузка скриптов последовательно
const loadScript = (index) => {
if (index >= scripts.length) return;
const script = scripts[index];
const src = script.src;
if (!loadedUrls.has(src)) {
const newScript = document.createElement("script");
newScript.src = src;
newScript.async = false; // Важно для порядка
// Копируем все атрибуты
Array.from(script.attributes).forEach((attr) => {
newScript.setAttribute(attr.name, attr.value);
});
newScript.onload = () => {
loadedUrls.add(src);
loadScript(index + 1); // Следующий скрипт
};
newScript.onerror = () => {
console.error(`Failed to load: ${src}`);
loadScript(index + 1); // Продолжаем цепочку
};
document.body.appendChild(newScript);
} else {
loadScript(index + 1); // Пропускаем уже загруженный
}
};
loadScript(0);
}
document.querySelectorAll("script[src]").forEach((script) => {
const src = script.getAttribute("src");
if (src) loadedScriptSrcs.add(src);
});
// Текущий путь страницы
let lastPath = location.pathname;
// Элементы интерфейса
const loader = createLoader();
const contentContainers = ["td.main", "#pmain"];
initNavigation();
function initNavigation() {
document.body.addEventListener("click", handleClick);
window.addEventListener("popstate", handlePopState);
}
function handleClick(e) {
const link = e.target.closest("a");
if (link && shouldIntercept(link)) {
e.preventDefault();
navigateTo(link.href);
}
}
function handlePopState() {
navigateTo(window.location.href, true);
}
function shouldIntercept(link) {
try {
const url = new URL(link.href);
return (
url.origin === location.origin && !link.dataset.noAjax && !link.hash
);
} catch {
return false;
}
}
async function navigateTo(url, isHistoryNavigation = false) {
try {
showLoader();
window.commentsCleanup?.();
delete window.commentsInitialized;
const cached = cache.get(url);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
updatePage(cached.html, url, isHistoryNavigation);
return;
}
const html = await fetchContent(url);
cache.set(url, { html, timestamp: Date.now() });
updatePage(html, url, isHistoryNavigation);
} catch (error) {
console.error("Navigation error:", error);
window.location.href = url;
} finally {
hideLoader();
}
}
async function fetchContent(url) {
const response = await fetch(url, {
headers: { "X-Requested-With": "XMLHttpRequest" },
});
return await response.text();
}
function updatePage(html, url, isHistoryNavigation) {
const doc = new DOMParser().parseFromString(html, "text/html");
const newTitle = doc.title;
const newPath = new URL(url).pathname;
// Обновление контента
contentContainers.forEach((selector) => {
const container = document.querySelector(selector);
const newContent = doc.querySelector(selector);
if (container && newContent) {
container.innerHTML = newContent.innerHTML;
}
});
// Обновление pmain: добавление/удаление
const currPmain = document.querySelector("#pmain");
const newPmain = doc.querySelector("#pmain");
if (!currPmain && newPmain) {
document.body.appendChild(newPmain.cloneNode(true));
} else if (currPmain && !newPmain) {
currPmain.remove();
}
// Управление navbar и title-small
const navbar = document.querySelector("#navbard");
const titleSmall = document.querySelector("#title-small");
const isPhoto = /\/photo\/\d+/.test(newPath);
if (isPhoto) {
if (navbar) navbar.style.display = "none";
if (titleSmall) titleSmall.style.display = "";
} else {
if (navbar) navbar.style.display = "";
if (titleSmall) titleSmall.style.display = "none";
}
// Обработка footer: удаляем дубликаты и ставим единственный в конец
const footers = Array.from(document.querySelectorAll("footer"));
if (footers.length > 1) footers.slice(1).forEach((f) => f.remove());
const footer = document.querySelector("footer");
if (footer) document.body.appendChild(footer);
// Обработка td.footer: оставляем только один и помещаем внутрь таблицы в #pmain
const tdFooters = Array.from(document.querySelectorAll("td.footer"));
if (tdFooters.length > 1) tdFooters.slice(1).forEach((td) => td.remove());
const singleTdFooter = document.querySelector("td.footer");
if (singleTdFooter) {
let tableWrapper = document.querySelector("#pmain table.footer-wrapper");
if (!tableWrapper) {
const tbl = document.createElement("table");
tbl.className = "footer-wrapper";
tbl.width = "100%";
tbl.style.marginTop = "30px";
const tbody = document.createElement("tbody");
const tr = document.createElement("tr");
tbody.appendChild(tr);
tbl.appendChild(tbody);
document.querySelector("#pmain").appendChild(tbl);
tableWrapper = tbl;
}
const tr = tableWrapper.querySelector("tr");
tr.innerHTML = "";
tr.appendChild(singleTdFooter);
}
// Обновление истории
if (!isHistoryNavigation) window.history.pushState({}, "", url);
// Обновление title
document.title = newTitle;
// Перезагрузка inline-скриптов
reloadExternalScripts(doc); // Только новые внешние скрипты
reloadInlineScripts(); // Inline-скрипты, кроме Tracy // Инициализация логики
// Прокрутка наверх
window.scrollTo({ top: 0, behavior: "smooth" });
lastPath = newPath;
}
const executedInlineScripts = new Set();
function reloadInlineScripts() {
document.querySelectorAll("script:not([src])").forEach((oldScript) => {
const code = oldScript.textContent.trim();
if (!code || /^Tracy\.Debug\.init/.test(code)) return;
const hash = simpleHash(code);
if (executedInlineScripts.has(hash)) return;
const newScript = document.createElement("script");
Array.from(oldScript.attributes).forEach((attr) =>
newScript.setAttribute(attr.name, attr.value)
);
newScript.textContent = code;
oldScript.parentNode.replaceChild(newScript, oldScript);
executedInlineScripts.add(hash);
});
}
function simpleHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = (hash << 5) - hash + str.charCodeAt(i);
hash |= 0; // Преобразование в 32-битное целое
}
return hash;
}
function createLoader() {
const loader = document.createElement("div");
loader.style = `
position: fixed;
top: 20px;
right: 20px;
padding: 15px;
background: #fff;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
border-radius: 5px;
display: none;
z-index: 9999;
`;
loader.innerHTML = "🔄 Загрузка...";
document.body.appendChild(loader);
return loader;
}
function showLoader() {
clearTimeout(loadingTimeout);
loadingTimeout = setTimeout(() => (loader.style.display = "block"), 300);
}
function hideLoader() {
clearTimeout(loadingTimeout);
loader.style.display = "none";
}
});