/*
* @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 += `
`;
});
}
$('.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