111
This commit is contained in:
277
public/js/friends-page-handler.js
Normal file
277
public/js/friends-page-handler.js
Normal file
@@ -0,0 +1,277 @@
|
||||
// 友情链接页面处理脚本
|
||||
// 此脚本作为全局脚本加载,不受 Swup 页面切换影响
|
||||
|
||||
(() => {
|
||||
console.log("[Friends Global] Script loaded");
|
||||
|
||||
// 使用全局变量存储状态
|
||||
if (typeof window.friendsPageState === "undefined") {
|
||||
window.friendsPageState = {
|
||||
initialized: false,
|
||||
eventListeners: [],
|
||||
mutationObserver: null,
|
||||
copySuccessText: "已复制", // 默认值,会被页面覆盖
|
||||
};
|
||||
}
|
||||
|
||||
// 初始化函数
|
||||
function initFriendsPage() {
|
||||
console.log("[Friends Global] initFriendsPage called");
|
||||
|
||||
var searchInput = document.getElementById("friend-search");
|
||||
var friendsGrid = document.getElementById("friends-grid");
|
||||
var noResults = document.getElementById("no-results");
|
||||
|
||||
// 如果关键元素不存在,直接返回
|
||||
if (!searchInput || !friendsGrid || !noResults) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var tagFilters = document.querySelectorAll(".filter-tag");
|
||||
var friendCards = document.querySelectorAll(".friend-card");
|
||||
var copyButtons = document.querySelectorAll(".copy-link-btn");
|
||||
|
||||
console.log("[Friends Global] Found elements:", {
|
||||
cards: friendCards.length,
|
||||
filters: tagFilters.length,
|
||||
copyButtons: copyButtons.length,
|
||||
});
|
||||
|
||||
// 从页面获取复制成功文本
|
||||
var copySuccessTextElement = document.getElementById(
|
||||
"friends-copy-success-text",
|
||||
);
|
||||
if (copySuccessTextElement) {
|
||||
window.friendsPageState.copySuccessText =
|
||||
copySuccessTextElement.textContent;
|
||||
}
|
||||
|
||||
// 清理旧的事件监听器
|
||||
if (window.friendsPageState.eventListeners.length > 0) {
|
||||
console.log(
|
||||
"[Friends Global] Cleaning",
|
||||
window.friendsPageState.eventListeners.length,
|
||||
"old listeners",
|
||||
);
|
||||
for (var i = 0; i < window.friendsPageState.eventListeners.length; i++) {
|
||||
var listener = window.friendsPageState.eventListeners[i];
|
||||
var element = listener[0];
|
||||
var type = listener[1];
|
||||
var handler = listener[2];
|
||||
if (element && element.removeEventListener) {
|
||||
element.removeEventListener(type, handler);
|
||||
}
|
||||
}
|
||||
window.friendsPageState.eventListeners = [];
|
||||
}
|
||||
|
||||
var currentTag = "all";
|
||||
var searchTerm = "";
|
||||
|
||||
// 过滤函数
|
||||
function filterFriends() {
|
||||
var visibleCount = 0;
|
||||
for (var i = 0; i < friendCards.length; i++) {
|
||||
var card = friendCards[i];
|
||||
var title = (card.getAttribute("data-title") || "").toLowerCase();
|
||||
var desc = (card.getAttribute("data-desc") || "").toLowerCase();
|
||||
var tags = card.getAttribute("data-tags") || "";
|
||||
|
||||
var matchesSearch =
|
||||
!searchTerm ||
|
||||
title.indexOf(searchTerm) >= 0 ||
|
||||
desc.indexOf(searchTerm) >= 0;
|
||||
var matchesTag =
|
||||
currentTag === "all" || tags.split(",").indexOf(currentTag) >= 0;
|
||||
|
||||
if (matchesSearch && matchesTag) {
|
||||
card.style.display = "";
|
||||
visibleCount++;
|
||||
} else {
|
||||
card.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
if (visibleCount === 0) {
|
||||
noResults.classList.remove("hidden");
|
||||
friendsGrid.classList.add("hidden");
|
||||
} else {
|
||||
noResults.classList.add("hidden");
|
||||
friendsGrid.classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索功能
|
||||
var searchHandler = (e) => {
|
||||
searchTerm = e.target.value.toLowerCase();
|
||||
filterFriends();
|
||||
};
|
||||
searchInput.addEventListener("input", searchHandler);
|
||||
window.friendsPageState.eventListeners.push([
|
||||
searchInput,
|
||||
"input",
|
||||
searchHandler,
|
||||
]);
|
||||
|
||||
// 标签筛选
|
||||
for (var i = 0; i < tagFilters.length; i++) {
|
||||
((button) => {
|
||||
var clickHandler = () => {
|
||||
// 更新选中状态
|
||||
for (var j = 0; j < tagFilters.length; j++) {
|
||||
var btn = tagFilters[j];
|
||||
btn.classList.remove("active");
|
||||
}
|
||||
button.classList.add("active");
|
||||
|
||||
currentTag = button.getAttribute("data-tag") || "all";
|
||||
filterFriends();
|
||||
};
|
||||
button.addEventListener("click", clickHandler);
|
||||
window.friendsPageState.eventListeners.push([
|
||||
button,
|
||||
"click",
|
||||
clickHandler,
|
||||
]);
|
||||
})(tagFilters[i]);
|
||||
}
|
||||
|
||||
// 复制链接功能
|
||||
for (var i = 0; i < copyButtons.length; i++) {
|
||||
((button) => {
|
||||
var clickHandler = () => {
|
||||
var url = button.getAttribute("data-url");
|
||||
if (!url) return;
|
||||
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard
|
||||
.writeText(url)
|
||||
.then(() => {
|
||||
var originalHTML = button.innerHTML;
|
||||
button.innerHTML =
|
||||
'<div class="flex items-center gap-1"><svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg><span class="text-xs">' +
|
||||
window.friendsPageState.copySuccessText +
|
||||
"</span></div>";
|
||||
button.classList.add("text-green-500");
|
||||
setTimeout(() => {
|
||||
button.innerHTML = originalHTML;
|
||||
button.classList.remove("text-green-500");
|
||||
}, 2000);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("[Friends Global] Copy failed:", err);
|
||||
});
|
||||
}
|
||||
};
|
||||
button.addEventListener("click", clickHandler);
|
||||
window.friendsPageState.eventListeners.push([
|
||||
button,
|
||||
"click",
|
||||
clickHandler,
|
||||
]);
|
||||
})(copyButtons[i]);
|
||||
}
|
||||
|
||||
window.friendsPageState.initialized = true;
|
||||
console.log(
|
||||
"[Friends Global] ✅ Initialization complete with",
|
||||
window.friendsPageState.eventListeners.length,
|
||||
"listeners",
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 带重试的初始化
|
||||
function tryInit(retries) {
|
||||
retries = retries || 0;
|
||||
if (initFriendsPage()) {
|
||||
console.log("[Friends Global] Init succeeded");
|
||||
return;
|
||||
}
|
||||
if (retries < 5) {
|
||||
setTimeout(() => {
|
||||
tryInit(retries + 1);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// MutationObserver 监听 DOM 变化
|
||||
function setupMutationObserver() {
|
||||
if (window.friendsPageState.mutationObserver) {
|
||||
window.friendsPageState.mutationObserver.disconnect();
|
||||
}
|
||||
|
||||
window.friendsPageState.mutationObserver = new MutationObserver(
|
||||
(mutations) => {
|
||||
var shouldInit = false;
|
||||
for (var i = 0; i < mutations.length; i++) {
|
||||
var mutation = mutations[i];
|
||||
if (mutation.addedNodes && mutation.addedNodes.length > 0) {
|
||||
for (var j = 0; j < mutation.addedNodes.length; j++) {
|
||||
var node = mutation.addedNodes[j];
|
||||
if (node.nodeType === 1) {
|
||||
if (
|
||||
node.id === "friends-grid" ||
|
||||
node.id === "friend-search" ||
|
||||
(node.querySelector && node.querySelector("#friends-grid"))
|
||||
) {
|
||||
shouldInit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (shouldInit) break;
|
||||
}
|
||||
|
||||
if (shouldInit) {
|
||||
console.log("[Friends Global] DOM mutation detected");
|
||||
window.friendsPageState.initialized = false;
|
||||
setTimeout(() => {
|
||||
tryInit();
|
||||
}, 50);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
window.friendsPageState.mutationObserver.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
|
||||
// 页面加载时初始化
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
console.log("[Friends Global] DOMContentLoaded");
|
||||
tryInit();
|
||||
});
|
||||
} else {
|
||||
tryInit();
|
||||
}
|
||||
|
||||
// 启动 MutationObserver
|
||||
setupMutationObserver();
|
||||
|
||||
// 监听所有可能的页面切换事件
|
||||
var events = [
|
||||
"swup:contentReplaced",
|
||||
"swup:pageView",
|
||||
"astro:page-load",
|
||||
"astro:after-swap",
|
||||
];
|
||||
|
||||
for (var i = 0; i < events.length; i++) {
|
||||
((eventName) => {
|
||||
document.addEventListener(eventName, () => {
|
||||
console.log("[Friends Global] Event:", eventName);
|
||||
window.friendsPageState.initialized = false;
|
||||
setTimeout(() => {
|
||||
tryInit();
|
||||
}, 100);
|
||||
});
|
||||
})(events[i]);
|
||||
}
|
||||
|
||||
console.log("[Friends Global] All listeners registered");
|
||||
})();
|
||||
185
public/js/global-timeline-init.js
Normal file
185
public/js/global-timeline-init.js
Normal file
@@ -0,0 +1,185 @@
|
||||
// 全局时间线功能初始化脚本
|
||||
// 该脚本会在每次页面加载或导航后初始化时间线功能
|
||||
|
||||
(() => {
|
||||
// 存储已绑定事件的元素,防止重复绑定
|
||||
const boundElements = new Set();
|
||||
|
||||
// 处理鼠标滚轮事件的函数
|
||||
function handleWheel(e) {
|
||||
// 检查是否为水平滚动容器
|
||||
if (!this.classList.contains("timeline-horizontal-scroll")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 阻止默认的垂直滚动
|
||||
if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {
|
||||
e.preventDefault();
|
||||
// 将垂直滚动转换为水平滚动
|
||||
this.scrollLeft += e.deltaY;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化时间线滚动功能
|
||||
function initTimelineScroll() {
|
||||
// 检查是否在时间线页面
|
||||
const isTimelinePage = document.querySelector(
|
||||
".timeline-horizontal-scroll",
|
||||
);
|
||||
if (!isTimelinePage) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找所有时间线水平滚动容器
|
||||
var scrollContainers = document.querySelectorAll(
|
||||
".timeline-horizontal-scroll",
|
||||
);
|
||||
|
||||
scrollContainers.forEach((scrollContainer) => {
|
||||
// 移除之前可能添加的事件监听器,防止重复绑定
|
||||
scrollContainer.removeEventListener("wheel", handleWheel);
|
||||
|
||||
// 检测是否为桌面端
|
||||
var isDesktop = window.matchMedia("(min-width: 768px)").matches;
|
||||
|
||||
if (isDesktop) {
|
||||
// 添加新的事件监听器
|
||||
scrollContainer.addEventListener("wheel", handleWheel, {
|
||||
passive: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化时间线节点动画
|
||||
function initTimelineAnimations() {
|
||||
// 检查是否在时间线页面
|
||||
const isTimelinePage = document.querySelector(".timeline-node");
|
||||
if (!isTimelinePage) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加时间线节点动画效果
|
||||
var timelineNodes = document.querySelectorAll(".timeline-node");
|
||||
timelineNodes.forEach((node, index) => {
|
||||
// 添加延迟,使动画依次出现
|
||||
node.style.animationDelay = `${index * 0.2}s`;
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化悬停效果
|
||||
function initHoverEffects() {
|
||||
// 确保悬停效果正常工作
|
||||
var hoverElements = document.querySelectorAll(".hover\\:shadow-lg");
|
||||
hoverElements.forEach((element) => {
|
||||
// 强制重新计算样式以确保悬停效果正常
|
||||
element.style.transform = "translateZ(0)";
|
||||
});
|
||||
}
|
||||
|
||||
// 全局初始化函数
|
||||
function globalInit() {
|
||||
// 初始化所有时间线功能
|
||||
initTimelineScroll();
|
||||
initTimelineAnimations();
|
||||
initHoverEffects();
|
||||
}
|
||||
|
||||
// 清理函数
|
||||
function cleanup() {
|
||||
// 清理已绑定的元素
|
||||
boundElements.clear();
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", globalInit);
|
||||
} else {
|
||||
// DOM 已经加载完成
|
||||
globalInit();
|
||||
}
|
||||
|
||||
// 监听页面加载事件
|
||||
window.addEventListener("load", () => {
|
||||
setTimeout(globalInit, 100);
|
||||
});
|
||||
|
||||
// 监听浏览器前进/后退事件
|
||||
window.addEventListener("pageshow", (event) => {
|
||||
if (event.persisted) {
|
||||
setTimeout(globalInit, 100);
|
||||
}
|
||||
});
|
||||
|
||||
// 监听可能的DOM变化
|
||||
if (typeof MutationObserver !== "undefined") {
|
||||
var observer = new MutationObserver((mutations) => {
|
||||
var shouldInit = false;
|
||||
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.type === "childList") {
|
||||
// 检查是否有新的时间线元素被添加
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
if (node.nodeType === 1) {
|
||||
// 检查直接添加的节点
|
||||
if (
|
||||
node.classList &&
|
||||
(node.classList.contains("timeline-horizontal-scroll") ||
|
||||
node.classList.contains("timeline-node"))
|
||||
) {
|
||||
shouldInit = true;
|
||||
}
|
||||
// 检查节点内的子元素
|
||||
if (node.querySelectorAll) {
|
||||
var timelineElements = node.querySelectorAll(
|
||||
".timeline-horizontal-scroll, .timeline-node",
|
||||
);
|
||||
if (timelineElements.length > 0) {
|
||||
shouldInit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (shouldInit) {
|
||||
setTimeout(globalInit, 50);
|
||||
}
|
||||
});
|
||||
|
||||
// 观察整个文档的变化
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
|
||||
// 如果使用 SWUP,监听其事件
|
||||
if (typeof window.Swup !== "undefined") {
|
||||
document.addEventListener("swup:contentReplaced", () => {
|
||||
setTimeout(globalInit, 100);
|
||||
});
|
||||
|
||||
document.addEventListener("swup:pageView", () => {
|
||||
setTimeout(globalInit, 100);
|
||||
});
|
||||
|
||||
document.addEventListener("swup:transitionStart", () => {
|
||||
cleanup();
|
||||
});
|
||||
}
|
||||
|
||||
// 监听 Astro 导航事件
|
||||
document.addEventListener("astro:page-load", () => {
|
||||
setTimeout(globalInit, 100);
|
||||
});
|
||||
|
||||
document.addEventListener("astro:after-swap", () => {
|
||||
setTimeout(globalInit, 100);
|
||||
});
|
||||
|
||||
// 暴露全局函数
|
||||
window.globalTimelineInit = globalInit;
|
||||
window.cleanupTimeline = cleanup;
|
||||
})();
|
||||
98
public/js/simple-timeline-scroll.js
Normal file
98
public/js/simple-timeline-scroll.js
Normal file
@@ -0,0 +1,98 @@
|
||||
// 简单直接的时间线滚动处理脚本
|
||||
// 该脚本使用最简单的方式实现时间线滚动功能
|
||||
|
||||
(() => {
|
||||
// 简单的滚动处理函数
|
||||
function handleSimpleScroll(e) {
|
||||
// 检查是否为时间线滚动容器
|
||||
if (!this.classList.contains("timeline-horizontal-scroll")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检测是否为桌面端
|
||||
var isDesktop = window.matchMedia("(min-width: 768px)").matches;
|
||||
if (!isDesktop) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否主要是垂直滚动
|
||||
if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {
|
||||
// 阻止默认行为
|
||||
e.preventDefault();
|
||||
// 将垂直滚动转换为水平滚动
|
||||
this.scrollLeft += e.deltaY;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化函数
|
||||
function initSimpleScroll() {
|
||||
// 查找所有时间线水平滚动容器
|
||||
var scrollContainers = document.querySelectorAll(
|
||||
".timeline-horizontal-scroll",
|
||||
);
|
||||
|
||||
// 为每个容器添加滚动事件监听器
|
||||
scrollContainers.forEach((container) => {
|
||||
// 移除之前的事件监听器
|
||||
container.removeEventListener("wheel", handleSimpleScroll);
|
||||
// 添加新的事件监听器
|
||||
container.addEventListener("wheel", handleSimpleScroll, {
|
||||
passive: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", initSimpleScroll);
|
||||
} else {
|
||||
// DOM 已经加载完成
|
||||
initSimpleScroll();
|
||||
}
|
||||
|
||||
// 监听页面加载事件
|
||||
window.addEventListener("load", () => {
|
||||
setTimeout(initSimpleScroll, 100);
|
||||
});
|
||||
|
||||
// 监听浏览器前进/后退事件
|
||||
window.addEventListener("pageshow", (event) => {
|
||||
if (event.persisted) {
|
||||
setTimeout(initSimpleScroll, 100);
|
||||
}
|
||||
});
|
||||
|
||||
// 如果使用 SWUP,监听其事件
|
||||
if (typeof window.Swup !== "undefined") {
|
||||
document.addEventListener("swup:contentReplaced", () => {
|
||||
setTimeout(initSimpleScroll, 100);
|
||||
});
|
||||
|
||||
document.addEventListener("swup:pageView", () => {
|
||||
setTimeout(initSimpleScroll, 100);
|
||||
});
|
||||
}
|
||||
|
||||
// 监听 Astro 导航事件
|
||||
document.addEventListener("astro:page-load", () => {
|
||||
setTimeout(initSimpleScroll, 100);
|
||||
});
|
||||
|
||||
document.addEventListener("astro:after-swap", () => {
|
||||
setTimeout(initSimpleScroll, 100);
|
||||
});
|
||||
|
||||
// 使用 MutationObserver 监听 DOM 变化
|
||||
if (typeof MutationObserver !== "undefined") {
|
||||
var observer = new MutationObserver(() => {
|
||||
// 每次 DOM 变化时都重新初始化
|
||||
setTimeout(initSimpleScroll, 50);
|
||||
});
|
||||
|
||||
// 观察整个文档的变化
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
})();
|
||||
66
public/js/swup-timeline-handler.js
Normal file
66
public/js/swup-timeline-handler.js
Normal file
@@ -0,0 +1,66 @@
|
||||
// SWUP 时间线处理脚本
|
||||
// 该脚本专门处理 SWUP 页面过渡后的时间线功能初始化
|
||||
|
||||
(() => {
|
||||
// 初始化时间线功能的函数
|
||||
function initTimelineFeatures() {
|
||||
// 检查是否在时间线页面
|
||||
if (!window.location.pathname.includes("/timeline")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用全局时间线滚动初始化函数(如果存在)
|
||||
if (typeof window.initTimelineScrollGlobal === "function") {
|
||||
setTimeout(window.initTimelineScrollGlobal, 50);
|
||||
}
|
||||
|
||||
// 初始化时间线节点动画
|
||||
var timelineNodes = document.querySelectorAll(".timeline-node");
|
||||
timelineNodes.forEach((node, index) => {
|
||||
// 添加延迟,使动画依次出现
|
||||
node.style.animationDelay = `${index * 0.2}s`;
|
||||
});
|
||||
|
||||
// 确保悬停效果正常工作
|
||||
var hoverElements = document.querySelectorAll(".hover\\:shadow-lg");
|
||||
hoverElements.forEach((element) => {
|
||||
// 强制重新计算样式以确保悬停效果正常
|
||||
element.style.transform = "translateZ(0)";
|
||||
});
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
initTimelineFeatures();
|
||||
});
|
||||
|
||||
// 如果使用 SWUP,监听其事件
|
||||
if (typeof window.Swup !== "undefined") {
|
||||
// 监听内容替换事件
|
||||
document.addEventListener("swup:contentReplaced", () => {
|
||||
// 延迟执行以确保 DOM 完全更新
|
||||
setTimeout(initTimelineFeatures, 100);
|
||||
});
|
||||
|
||||
// 监听页面视图事件
|
||||
document.addEventListener("swup:pageView", () => {
|
||||
setTimeout(initTimelineFeatures, 100);
|
||||
});
|
||||
}
|
||||
|
||||
// 监听 Astro 导航事件
|
||||
document.addEventListener("astro:page-load", () => {
|
||||
setTimeout(initTimelineFeatures, 100);
|
||||
});
|
||||
|
||||
document.addEventListener("astro:after-swap", () => {
|
||||
setTimeout(initTimelineFeatures, 100);
|
||||
});
|
||||
|
||||
// 监听浏览器前进/后退事件
|
||||
window.addEventListener("pageshow", (event) => {
|
||||
if (event.persisted) {
|
||||
setTimeout(initTimelineFeatures, 100);
|
||||
}
|
||||
});
|
||||
})();
|
||||
172
public/js/timeline-scroll-handler.js
Normal file
172
public/js/timeline-scroll-handler.js
Normal file
@@ -0,0 +1,172 @@
|
||||
// 全局时间线横向滚动处理脚本
|
||||
// 该脚本处理所有带有 timeline-horizontal-scroll 类的元素的鼠标滚轮横向滚动
|
||||
|
||||
(() => {
|
||||
// 存储已绑定事件的元素,防止重复绑定
|
||||
const boundElements = new Set();
|
||||
|
||||
// 处理鼠标滚轮事件的函数
|
||||
function handleWheel(e) {
|
||||
// 检查是否为水平滚动容器
|
||||
if (!this.classList.contains("timeline-horizontal-scroll")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 阻止默认的垂直滚动
|
||||
if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {
|
||||
e.preventDefault();
|
||||
// 将垂直滚动转换为水平滚动
|
||||
this.scrollLeft += e.deltaY;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化滚动功能的函数
|
||||
function initTimelineScroll() {
|
||||
// 查找所有时间线水平滚动容器
|
||||
var scrollContainers = document.querySelectorAll(
|
||||
".timeline-horizontal-scroll",
|
||||
);
|
||||
|
||||
scrollContainers.forEach((scrollContainer) => {
|
||||
// 检查元素是否已经绑定过事件
|
||||
if (boundElements.has(scrollContainer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检测是否为桌面端
|
||||
var isDesktop = window.matchMedia("(min-width: 768px)").matches;
|
||||
|
||||
if (isDesktop) {
|
||||
// 移除之前可能添加的事件监听器,防止重复绑定
|
||||
scrollContainer.removeEventListener("wheel", handleWheel);
|
||||
|
||||
// 添加新的事件监听器
|
||||
scrollContainer.addEventListener("wheel", handleWheel, {
|
||||
passive: false,
|
||||
});
|
||||
|
||||
// 标记元素已绑定事件
|
||||
boundElements.add(scrollContainer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 清理函数,用于页面切换时清理已绑定的元素
|
||||
function cleanupBoundElements() {
|
||||
boundElements.clear();
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
initTimelineScroll();
|
||||
|
||||
// 在DOM更新后再次初始化(处理SWUP等SPA场景)
|
||||
setTimeout(initTimelineScroll, 100);
|
||||
});
|
||||
|
||||
// 监听可能的DOM变化
|
||||
if (typeof MutationObserver !== "undefined") {
|
||||
var observer = new MutationObserver((mutations) => {
|
||||
var shouldInit = false;
|
||||
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.type === "childList") {
|
||||
// 检查是否有新的时间线元素被添加
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
if (node.nodeType === 1) {
|
||||
// 检查直接添加的节点
|
||||
if (node.classList?.contains("timeline-horizontal-scroll")) {
|
||||
shouldInit = true;
|
||||
}
|
||||
// 检查节点内的子元素
|
||||
if (node.querySelectorAll) {
|
||||
var timelineElements = node.querySelectorAll(
|
||||
".timeline-horizontal-scroll",
|
||||
);
|
||||
if (timelineElements.length > 0) {
|
||||
shouldInit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (shouldInit) {
|
||||
setTimeout(initTimelineScroll, 50);
|
||||
}
|
||||
});
|
||||
|
||||
// 观察整个文档的变化
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
|
||||
// 监听页面导航事件
|
||||
window.addEventListener("load", () => {
|
||||
cleanupBoundElements();
|
||||
setTimeout(initTimelineScroll, 100);
|
||||
});
|
||||
|
||||
// 处理浏览器前进/后退导航
|
||||
window.addEventListener("pageshow", (event) => {
|
||||
if (event.persisted) {
|
||||
cleanupBoundElements();
|
||||
setTimeout(initTimelineScroll, 100);
|
||||
}
|
||||
});
|
||||
|
||||
// 如果使用SWUP,监听其事件
|
||||
if (typeof window.Swup !== "undefined") {
|
||||
// 监听 SWUP 开始过渡事件
|
||||
document.addEventListener("swup:transitionStart", () => {
|
||||
cleanupBoundElements();
|
||||
});
|
||||
|
||||
// 监听 SWUP 内容替换事件
|
||||
document.addEventListener("swup:contentReplaced", () => {
|
||||
cleanupBoundElements();
|
||||
// 延迟初始化以确保DOM完全更新
|
||||
setTimeout(initTimelineScroll, 50);
|
||||
});
|
||||
|
||||
// 监听 SWUP 页面视图事件
|
||||
document.addEventListener("swup:pageView", () => {
|
||||
cleanupBoundElements();
|
||||
setTimeout(initTimelineScroll, 100);
|
||||
});
|
||||
}
|
||||
|
||||
// 增强对 Astro 导航的支持
|
||||
document.addEventListener("astro:page-load", () => {
|
||||
cleanupBoundElements();
|
||||
setTimeout(initTimelineScroll, 100);
|
||||
});
|
||||
|
||||
document.addEventListener("astro:after-swap", () => {
|
||||
cleanupBoundElements();
|
||||
setTimeout(initTimelineScroll, 100);
|
||||
});
|
||||
|
||||
// 暴露初始化函数到全局作用域,供其他脚本调用
|
||||
window.initTimelineScrollGlobal = initTimelineScroll;
|
||||
window.cleanupTimelineScroll = cleanupBoundElements;
|
||||
|
||||
// 立即执行一次初始化,确保在脚本加载时就绑定事件
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", initTimelineScroll);
|
||||
} else {
|
||||
// DOM 已经加载完成
|
||||
initTimelineScroll();
|
||||
}
|
||||
|
||||
// 如果页面已经加载完成,立即初始化
|
||||
if (
|
||||
document.readyState === "complete" ||
|
||||
document.readyState === "interactive"
|
||||
) {
|
||||
setTimeout(initTimelineScroll, 10);
|
||||
}
|
||||
})();
|
||||
133
public/js/umami-share.js
Normal file
133
public/js/umami-share.js
Normal file
@@ -0,0 +1,133 @@
|
||||
((global) => {
|
||||
const cacheKey = "umami-share-cache";
|
||||
const cacheTTL = 3600_000; // 1h
|
||||
|
||||
/**
|
||||
* 获取网站统计数据
|
||||
* @param {string} baseUrl - Umami Cloud API基础URL
|
||||
* @param {string} apiKey - API密钥
|
||||
* @param {string} websiteId - 网站ID
|
||||
* @returns {Promise<object>} 网站统计数据
|
||||
*/
|
||||
async function fetchWebsiteStats(baseUrl, apiKey, websiteId) {
|
||||
// 检查缓存
|
||||
const cached = localStorage.getItem(cacheKey);
|
||||
if (cached) {
|
||||
try {
|
||||
const parsed = JSON.parse(cached);
|
||||
if (Date.now() - parsed.timestamp < cacheTTL) {
|
||||
return parsed.value;
|
||||
}
|
||||
} catch {
|
||||
localStorage.removeItem(cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
const currentTimestamp = Date.now();
|
||||
const statsUrl = `${baseUrl}/v1/websites/${websiteId}/stats?startAt=0&endAt=${currentTimestamp}`;
|
||||
|
||||
const res = await fetch(statsUrl, {
|
||||
headers: {
|
||||
"x-umami-api-key": apiKey,
|
||||
},
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("获取网站统计数据失败");
|
||||
}
|
||||
|
||||
const stats = await res.json();
|
||||
|
||||
// 缓存结果
|
||||
localStorage.setItem(
|
||||
cacheKey,
|
||||
JSON.stringify({ timestamp: Date.now(), value: stats }),
|
||||
);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取特定页面的统计数据
|
||||
* @param {string} baseUrl - Umami Cloud API基础URL
|
||||
* @param {string} apiKey - API密钥
|
||||
* @param {string} websiteId - 网站ID
|
||||
* @param {string} urlPath - 页面路径
|
||||
* @param {number} startAt - 开始时间戳
|
||||
* @param {number} endAt - 结束时间戳
|
||||
* @returns {Promise<object>} 页面统计数据
|
||||
*/
|
||||
async function fetchPageStats(
|
||||
baseUrl,
|
||||
apiKey,
|
||||
websiteId,
|
||||
urlPath,
|
||||
startAt = 0,
|
||||
endAt = Date.now(),
|
||||
) {
|
||||
const statsUrl = `${baseUrl}/v1/websites/${websiteId}/stats?startAt=${startAt}&endAt=${endAt}&path=${encodeURIComponent(urlPath)}`;
|
||||
|
||||
const res = await fetch(statsUrl, {
|
||||
headers: {
|
||||
"x-umami-api-key": apiKey,
|
||||
},
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("获取页面统计数据失败");
|
||||
}
|
||||
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Umami 网站统计数据
|
||||
* @param {string} baseUrl - Umami Cloud API基础URL
|
||||
* @param {string} apiKey - API密钥
|
||||
* @param {string} websiteId - 网站ID
|
||||
* @returns {Promise<object>} 网站统计数据
|
||||
*/
|
||||
global.getUmamiWebsiteStats = async (baseUrl, apiKey, websiteId) => {
|
||||
try {
|
||||
return await fetchWebsiteStats(baseUrl, apiKey, websiteId);
|
||||
} catch (err) {
|
||||
throw new Error(`获取Umami统计数据失败: ${err.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取特定页面的 Umami 统计数据
|
||||
* @param {string} baseUrl - Umami Cloud API基础URL
|
||||
* @param {string} apiKey - API密钥
|
||||
* @param {string} websiteId - 网站ID
|
||||
* @param {string} urlPath - 页面路径
|
||||
* @param {number} startAt - 开始时间戳(可选)
|
||||
* @param {number} endAt - 结束时间戳(可选)
|
||||
* @returns {Promise<object>} 页面统计数据
|
||||
*/
|
||||
global.getUmamiPageStats = async (
|
||||
baseUrl,
|
||||
apiKey,
|
||||
websiteId,
|
||||
urlPath,
|
||||
startAt,
|
||||
endAt,
|
||||
) => {
|
||||
try {
|
||||
return await fetchPageStats(
|
||||
baseUrl,
|
||||
apiKey,
|
||||
websiteId,
|
||||
urlPath,
|
||||
startAt,
|
||||
endAt,
|
||||
);
|
||||
} catch (err) {
|
||||
throw new Error(`获取Umami页面统计数据失败: ${err.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
global.clearUmamiShareCache = () => {
|
||||
localStorage.removeItem(cacheKey);
|
||||
};
|
||||
})(window);
|
||||
Reference in New Issue
Block a user