/* * @Author: iowen * @Author URI: https://www.iowen.cn/ * @Date: 2024-01-03 00:15:11 * @LastEditors: iowen * @LastEditTime: 2024-01-13 11:06:29 * @FilePath: \onenavsub\assets\js\sub-app.js * @Description: 自定义主题js */ jQuery(document).ready(function($) { // History Tool Implementation // 0. Inject Styles var historyStyles = ` `; $('head').append(historyStyles); // 1. Insert HTML Structure var historyHtml = ` `; // Insert before .nav-login if it exists, otherwise append to .header-tools var $headerTools = $('.nav.header-tools'); var $navLogin = $headerTools.find('.nav-login'); if ($navLogin.length > 0) { $navLogin.before(historyHtml); } else { $headerTools.append(historyHtml); } // 2. Hover Behavior var $historyLi = $('.nav-history'); var $subMenu = $historyLi.find('.sub-menu'); // Helper functions for state management function clearAllHoverStates() { $('.aside-item').removeClass('hover-state').removeClass('is-expanded'); $('.ioui-aside').removeClass('is-expanded'); } function setHoverState($element) { if ($element && $element.length) { $element.addClass('hover-state'); $element.addClass('is-expanded'); $element.closest('.ioui-aside').addClass('is-expanded'); } } function removeHoverState($element) { if ($element && $element.length) { $element.removeClass('hover-state'); $element.removeClass('is-expanded'); $element.closest('.ioui-aside').removeClass('is-expanded'); } } // Initial render renderHistory(); $historyLi.hover(function() { renderHistory(); }); // 3. Save History on Click $(document).on('click', '.sites-item a, .io-lh-item, .footprint-card a, a.footprint-card', function(e) { var $link = $(this); // Ensure we have the anchor if (!$link.is('a')) $link = $link.closest('a'); var url = $link.attr('href'); // Ignore internal links or javascript: if (!url || url.indexOf('javascript:') === 0 || url.indexOf('#') === 0) return; var title, iconSrc; var $card = $link.closest('.sites-item, .io-lh-item, .footprint-card'); if ($card.length > 0) { // Try to find title title = $card.find('.item-title').text().trim(); if (!title) title = $card.find('h3').text().trim(); // Fallback for some themes if (!title) title = $link.attr('title'); // Fallback to link title // Try to find icon var $icon = $card.find('.sites-icon, .io-lh-icon img, .item-header img, img.avatar'); if ($icon.length > 0) { iconSrc = $icon.attr('src') || $icon.attr('data-src'); } } else { // Fallback if card container not found but link clicked (e.g. direct class on a) title = $link.attr('title') || $link.text().trim(); var $icon = $link.find('img'); if ($icon.length > 0) { iconSrc = $icon.attr('src'); } } if (!title) return; var history = JSON.parse(localStorage.getItem('user_history') || '[]'); // Remove existing entry for same URL history = history.filter(h => h.url !== url); // Add new entry history.unshift({ title: title, url: url, icon: iconSrc, time: new Date().getTime() }); // Limit to 10 if (history.length > 10) history = history.slice(0, 10); localStorage.setItem('user_history', JSON.stringify(history)); }); // 4. Render History Function function renderHistory() { var history = JSON.parse(localStorage.getItem('user_history') || '[]'); var html = ''; if (history.length === 0) { html = '
暂无浏览记录
'; } else { history.forEach(function(item) { // Use theme card structure var iconHtml = item.icon ? `` : `
`; html += `
${iconHtml}

${item.title}

`; }); } $('.history-list').html(html); } // 5. Delete Single History Item $(document).on('click', '.history-delete', function(e) { e.preventDefault(); e.stopPropagation(); var url = $(this).data('url'); var history = JSON.parse(localStorage.getItem('user_history') || '[]'); history = history.filter(h => h.url !== url); localStorage.setItem('user_history', JSON.stringify(history)); renderHistory(); }); // 6. Clear History $(document).on('click', '.history-clear', function(e) { e.preventDefault(); e.stopPropagation(); if(confirm('确定要清空浏览记录吗?')) { localStorage.removeItem('user_history'); renderHistory(); } }); // --- Custom Sidebar Features --- // 1. Enhanced Floating Sub-Menu var $customPopup = $('
').appendTo('body'); var subMenuTimer; var $currentTrigger = null; // Store current trigger element // Disable default sidebar title tooltip to avoid conflict $('.aside-item a').attr('title', ''); $(document).on('mouseenter', '.aside-item', function() { // 手机端不显示悬浮子菜单 if ($(window).width() <= 768) return; var $this = $(this); var $sub = $this.find('.aside-sub'); clearTimeout(subMenuTimer); // Remove hover state from ALL previous triggers to ensure clean state clearAllHoverStates(); // Remove hover state from previous trigger if exists if ($currentTrigger && $currentTrigger[0] !== $this[0]) { removeHoverState($currentTrigger); } $currentTrigger = $this; // Only show if there are sub-items if ($sub.length && $sub.children().length > 0) { // Add hover state immediately for sync feel setHoverState($this); // Store trigger reference $customPopup.data('trigger', $this); // Populate popup $customPopup.find('ul').html($sub.html()); // Calculate Position var rect = this.getBoundingClientRect(); // Show momentarily to measure height $customPopup.css({display: 'flex', opacity: 0}); var popupHeight = $customPopup.outerHeight(); var popupWidth = $customPopup.outerWidth(); var top, left; // Check if Sidebar Fixed Bottom Mode is active if ($('body').hasClass('sidebar-fixed-bottom')) { // Bottom Mode: Popup above // Calculate based on the specific triggering item var parentCenterX = rect.left + (rect.width / 2); left = parentCenterX - (popupWidth / 2); // rect.top is the top of the icon relative to the viewport. // We want popup to be above it by 15px (调整间距适配新的内边距). top = rect.top - popupHeight - 15; var winWidth = $(window).width(); // Boundary checks to keep it on screen horizontally if (left + popupWidth > winWidth - 10) { left = winWidth - popupWidth - 10; } if (left < 10) left = 10; // Remove left margin which is for sidebar mode $customPopup.css({'margin-left': '0'}); } else { // Sidebar Mode: Popup to right // Vertical Centering Calculation // Center of parent item var parentCenterY = rect.top + (rect.height / 2); // Top position for popup to be centered top = parentCenterY - (popupHeight / 2); left = rect.right - 17; // Overlap border (moved left by 10px from -1) var winHeight = $(window).height(); // Boundary checks if (top + popupHeight > winHeight - 10) { top = winHeight - popupHeight - 10; } if (top < 10) top = 10; // Reset margin $customPopup.css({'margin-left': '10px'}); } // Ensure we're setting fixed position coordinates relative to viewport $customPopup.css({ position: 'fixed', top: top + 'px', left: left + 'px', opacity: 1 // Instant visibility }).addClass('show'); // Removed delay for instant appearance } else { $customPopup.removeClass('show').hide(); $currentTrigger = null; } }).on('mouseleave', '.aside-item', function(e) { // Check if moving to popup - improved detection var toElement = e.relatedTarget; var $toElement = $(toElement); // More robust check: if moving to popup or any of its children if ($toElement.closest('.custom-sub-popup').length > 0 || $toElement.hasClass('custom-sub-popup') || $customPopup.is(':hover')) { return; } subMenuTimer = setTimeout(function() { if (!$customPopup.is(':hover')) { $customPopup.removeClass('show').hide(); if ($currentTrigger) { removeHoverState($currentTrigger); $currentTrigger = null; } } }, 50); }); $customPopup.on('mouseenter', function() { clearTimeout(subMenuTimer); // Keep parent highlighted using stored reference var $trigger = $(this).data('trigger'); if ($trigger) { setHoverState($trigger); } // Fallback if ($currentTrigger) { setHoverState($currentTrigger); } }).on('mouseleave', function(e) { // Check if moving back to sidebar item - improved detection var toElement = e.relatedTarget; var $toElement = $(toElement); // More robust check: if moving to aside item or any of its children if ($toElement.closest('.aside-item').length > 0 || $toElement.hasClass('aside-item') || $toElement.closest('.ioui-aside').length > 0) { return; } $customPopup.removeClass('show').hide(); // Remove parent highlight var $trigger = $(this).data('trigger'); if ($trigger) { removeHoverState($trigger); } if ($currentTrigger) { removeHoverState($currentTrigger); $currentTrigger = null; } }); // Handle clicks on sub-menu items for scroll navigation $customPopup.on('click', 'a', function(e) { var $this = $(this); var href = $this.attr('href'); // Priority 1: Check for .ajax-tab-trigger class and data-target if ($this.hasClass('ajax-tab-trigger')) { var target = $this.data('target'); // #tab-xxx if (target) { // Find the corresponding slider-li that controls this tab // The slider-li has data-target="#tab-xxx" // Also check for standard bootstrap tabs just in case var $tabBtn = $('.slider-tab li[data-target="' + target + '"], .nav-tabs a[href="' + target + '"], .nav-tabs a[data-target="' + target + '"]'); if ($tabBtn.length) { e.preventDefault(); // Trigger click to switch tab // We don't check for .active because some themes might need to re-trigger to fix layout or load ajax $tabBtn.first().trigger('click'); // Scroll to parent container var $container = $tabBtn.closest('.content-card, .tab-content'); if ($container.length == 0) $container = $(target).closest('.content-card, .tab-content'); if ($container.length) { // Add a small delay to allow tab switch to complete (rendering content) setTimeout(function(){ $('html, body').animate({ scrollTop: $container.offset().top - 80 }, 300); }, 50); } return; } } } // Priority 2: Fallback logic - direct tab trigger click // Check if it's an anchor link for current page if (href && href.indexOf('#') !== -1) { var anchor = href.substring(href.indexOf('#')); // Check if element exists on page if ($(anchor).length) { // Determine if this is a Tab Trigger (li) or Tab Pane (div) // Based on io-home.php, the sub-menu link points to the Tab Trigger ID (e.g. #term-123-456) var $target = $(anchor); // If the target is the Tab Trigger (li) if ($target.is('li') && ($target.hasClass('slider-li') || $target.attr('data-target'))) { e.preventDefault(); // Check if tab is already active if (!$target.hasClass('active')) { $target.trigger('click'); // Switch tab } // Scroll to parent container var $container = $target.closest('.content-card'); if ($container.length) { $('html, body').animate({ scrollTop: $container.offset().top - 80 }, 500); } return; } // Fallback: If logic above failed but we still want to try switching tab // Maybe the link points to something else? // Standard scroll behavior for non-tab links e.preventDefault(); $('html, body').animate({ scrollTop: $(anchor).offset().top - 80 }, 500); } } }); // 2. Strict ScrollSpy (IntersectionObserver) if ('IntersectionObserver' in window) { var observerOptions = { root: null, rootMargin: '-10% 0px -80% 0px', // Trigger when element is near top (10% from top) threshold: 0 }; var observer = new IntersectionObserver(function(entries) { entries.forEach(function(entry) { if (entry.isIntersecting) { var id = entry.target.id; if (id) { var $link = $('.aside-item a[href="#' + id + '"]'); if ($link.length) { // Remove active class from all items (override main.js) $('.aside-item').removeClass('active active-custom'); // Add active class to current $link.parent().addClass('active-custom'); // Also add 'active' to keep compatibility, but we might need to fight main.js $link.parent().addClass('active'); } } } }); }, observerOptions); // Observe all sections linked in sidebar $('.aside-item a[href^="#term-"]').each(function() { var href = $(this).attr('href'); if (href && href.length > 1) { var $target = $(href); if ($target.length) { observer.observe($target[0]); } } }); // Optional: Conflict resolution with main.js // main.js updates on scroll. We can force our state on scroll too? // Or just trust IntersectionObserver is frequent enough or css !important handles it. // We added .active-custom styles in CSS to ensure it looks active. } // --- Bottom Floating Navigation Logic (Migrated from PHP) --- // Handles slider, scroll arrows, and responsive icon-only mode // Use a function to initialize this logic, so we can retry if elements aren't ready function initBottomNav() { if (!document.body.classList.contains("sidebar-fixed-bottom")) return; const asideUl = document.querySelector(".ioui-aside .aside-ul"); const slider = document.querySelector(".aside-slider"); const items = document.querySelectorAll(".ioui-aside .aside-item"); const asideCard = document.querySelector(".ioui-aside .aside-card"); // If elements are missing, maybe theme JS hasn't rendered them yet. // But asideUl usually exists. Check specifically for what we need. if (asideUl && items.length && asideCard) { // Prevent double initialization if (asideCard.querySelector('.elastic-bg')) return; // --- DOM Restructuring for Scroll Support --- // Create Elastic Background Layer const elasticBg = document.createElement("div"); elasticBg.className = "elastic-bg"; // Create Content Layer const contentLayer = document.createElement("div"); contentLayer.className = "nav-content-layer"; // Create Scroll Container const scrollWrapper = document.createElement("div"); scrollWrapper.className = "nav-scroll-wrapper"; // Create Left Arrow const leftArrow = document.createElement("div"); leftArrow.className = "nav-arrow nav-arrow-left"; leftArrow.innerHTML = ''; // Create Right Arrow const rightArrow = document.createElement("div"); rightArrow.className = "nav-arrow nav-arrow-right"; rightArrow.innerHTML = ''; // Move elements into wrapper // Original structure: asideCard > [slider, asideUl] // New structure: asideCard > [elasticBg, contentLayer > [leftArrow, scrollWrapper > [slider, asideUl], rightArrow]] // Handle slider if it exists, otherwise just handle UL const hasSlider = slider && slider.parentNode === asideCard; // Append elements to content layer contentLayer.appendChild(leftArrow); contentLayer.appendChild(scrollWrapper); contentLayer.appendChild(rightArrow); // Move slider if it exists and is direct child if (hasSlider) { scrollWrapper.appendChild(slider); } // Move UL if (asideUl.parentNode === asideCard) { scrollWrapper.appendChild(asideUl); } else { console.warn("Bottom Nav: asideUl is not a direct child of asideCard. Trying to move anyway."); scrollWrapper.appendChild(asideUl); } // Append layers to asideCard asideCard.appendChild(elasticBg); asideCard.appendChild(contentLayer); // --- Scroll Logic --- function checkScroll() { const scrollLeft = scrollWrapper.scrollLeft; const scrollWidth = scrollWrapper.scrollWidth; const clientWidth = scrollWrapper.clientWidth; // Show left arrow if scrolled right if (scrollLeft > 10) { leftArrow.classList.add("show"); } else { leftArrow.classList.remove("show"); } // Show right arrow if there is content to scroll // Added 1px buffer to fix float precision issues if (scrollWidth > clientWidth && scrollLeft < scrollWidth - clientWidth - 2) { rightArrow.classList.add("show"); } else { rightArrow.classList.remove("show"); } } // Check on load and resize // Use setTimeout to ensure layout is calculated after DOM manipulation setTimeout(checkScroll, 100); window.addEventListener("resize", checkScroll); scrollWrapper.addEventListener("scroll", checkScroll); // Add observer for element size changes (more robust than just window resize) if ('ResizeObserver' in window) { const resizeObserver = new ResizeObserver(() => { checkScroll(); }); resizeObserver.observe(scrollWrapper); resizeObserver.observe(asideUl); } // Arrow Click Events const scrollAmount = 200; leftArrow.addEventListener("click", function() { scrollWrapper.scrollBy({ left: -scrollAmount, behavior: "smooth" }); }); rightArrow.addEventListener("click", function() { scrollWrapper.scrollBy({ left: scrollAmount, behavior: "smooth" }); }); // --- End Scroll Logic --- // --- Slider Logic --- function updateSlider(target) { if (!target) return; const left = target.offsetLeft; const width = target.offsetWidth; slider.style.left = left + "px"; slider.style.width = width + "px"; } // Helper function to get active item function getActiveItem() { return document.querySelector(".ioui-aside .aside-item.active"); } // Initialize slider position to active item const activeItem = getActiveItem(); if (activeItem) { // Wait for layout to be stable setTimeout(() => { updateSlider(activeItem); slider.style.display = "block"; }, 100); } items.forEach(item => { item.addEventListener("mouseenter", function() { slider.style.display = "block"; updateSlider(this); }); }); asideUl.addEventListener("mouseleave", function() { // Return to active item on mouse leave const currentActive = getActiveItem(); if (currentActive) { updateSlider(currentActive); } else { slider.style.display = "none"; } }); // --- Responsive Text Hiding Logic (760px breakpoint) --- function handleResponsiveLayout() { // Check current width const isSmallScreen = window.innerWidth <= 760; // Only toggle if state changes to avoid flickering or conflict with other scripts if (isSmallScreen) { if (!document.body.classList.contains('aside-min')) { document.body.classList.add('aside-min'); } } else { if (document.body.classList.contains('aside-min')) { document.body.classList.remove('aside-min'); } } // Re-check scroll arrows after layout change setTimeout(checkScroll, 300); // Wait for transition } // Initial check handleResponsiveLayout(); // Listen for resize window.addEventListener('resize', handleResponsiveLayout); } } // Try to init immediately initBottomNav(); // And try again after a short delay in case DOM wasn't fully ready (e.g. if loaded via AJAX or footer script delay) setTimeout(initBottomNav, 500); // 网址详情页标签展开收起功能 function initTermsListToggle() { const termsContainers = document.querySelectorAll('.terms-list-container'); termsContainers.forEach(container => { const wrapper = container.querySelector('.terms-list-wrapper'); const toggle = container.querySelector('.terms-list-toggle'); const termsList = container.querySelector('.terms-list'); if (!wrapper || !toggle || !termsList) return; // 计算是否需要显示展开按钮 function checkOverflow() { const wrapperWidth = wrapper.offsetWidth; const contentWidth = termsList.scrollWidth; // 如果内容宽度大于容器宽度,显示展开按钮 if (contentWidth > wrapperWidth) { toggle.style.display = 'flex'; wrapper.classList.add('has-overflow'); } else { toggle.style.display = 'none'; wrapper.classList.remove('has-overflow'); } } // 展开/收起功能 toggle.addEventListener('click', function() { const isExpanded = wrapper.classList.contains('expanded'); if (isExpanded) { // 收起 wrapper.classList.remove('expanded'); toggle.classList.remove('expanded'); } else { // 展开 wrapper.classList.add('expanded'); toggle.classList.add('expanded'); } }); // 初始化检查 checkOverflow(); // 窗口大小改变时重新检查 window.addEventListener('resize', checkOverflow); // 延迟检查,确保DOM完全加载 setTimeout(checkOverflow, 100); }); } // 初始化标签展开收起功能 initTermsListToggle(); // 如果页面通过AJAX加载,延迟初始化 document.addEventListener('DOMContentLoaded', function() { setTimeout(initTermsListToggle, 500); }); }); https://qinggongju.com/wp-sitemap-posts-post-1.xmlhttps://qinggongju.com/wp-sitemap-posts-page-1.xmlhttps://qinggongju.com/wp-sitemap-posts-sites-1.xmlhttps://qinggongju.com/wp-sitemap-posts-bulletin-1.xmlhttps://qinggongju.com/wp-sitemap-taxonomies-category-1.xmlhttps://qinggongju.com/wp-sitemap-taxonomies-post_tag-1.xmlhttps://qinggongju.com/wp-sitemap-taxonomies-sitetag-1.xmlhttps://qinggongju.com/wp-sitemap-taxonomies-sitetag-2.xmlhttps://qinggongju.com/wp-sitemap-taxonomies-favorites-1.xmlhttps://qinggongju.com/wp-sitemap-users-1.xml