/** * AKSRT Page Navigation — v3.0 * * 功能: * - TOC 高亮 + 平滑滚动(文档阅读页) * - 文档导航搜索 + 结果接管(文档导航页) * - 分类切换 AJAX 加载 + 分页(文档导航页) * - 加载状态和分页控件 * * @package AKSRT */ (function () { 'use strict'; var API_BASE = (window.aksrtData && window.aksrtData.restNonce) ? (window.aksrtData.siteUrl || '') + '/wp-json/aksrt/v1' : ''; var NONCE = (window.aksrtData && window.aksrtData.restNonce) || ''; var PAGE_NAVS = { tocItems: [], headings: [], scrollTimer: null, activeCategory: 0, currentPage: 1, totalPages: 1, searchQuery: '', ajaxInFlight: null, init: function () { this.initTocHighlight(); this.initSmoothScroll(); this.initDocSearch(); this.initCategoryChips(); this.initPagination(); }, /* ============================================================ * TOC 高亮(文档阅读页) * ============================================================ */ initTocHighlight: function () { var toc = document.querySelector('.aksrt-doc-toc'); if (!toc) return; this.tocItems = Array.from(toc.querySelectorAll('.aksrt-doc-toc__item a')); if (this.tocItems.length === 0) return; this.headings = this.tocItems.map(function (link) { var id = link.getAttribute('href'); if (id && id.startsWith('#')) { return document.getElementById(id.substring(1)); } return null; }).filter(Boolean); if (this.headings.length === 0) return; var self = this; window.addEventListener('scroll', function () { if (self.scrollTimer) clearTimeout(self.scrollTimer); self.scrollTimer = setTimeout(function () { self.updateTocHighlight(); }, 50); }, { passive: true }); this.updateTocHighlight(); }, updateTocHighlight: function () { var scrollY = window.scrollY + 100; var currentIndex = -1; for (var i = this.headings.length - 1; i >= 0; i--) { if (this.headings[i].offsetTop <= scrollY) { currentIndex = i; break; } } this.tocItems.forEach(function (link, index) { var li = link.parentElement; if (index === currentIndex) { li.classList.add('is-active'); link.classList.add('is-active'); } else { li.classList.remove('is-active'); link.classList.remove('is-active'); } }); }, /* ============================================================ * 平滑滚动 * ============================================================ */ initSmoothScroll: function () { document.addEventListener('click', function (e) { var link = e.target.closest('a[href^="#"]'); if (!link) return; var id = link.getAttribute('href').substring(1); if (!id) return; var target = document.getElementById(id); if (!target) return; e.preventDefault(); var offset = 80; // nav height var top = target.getBoundingClientRect().top + window.scrollY - offset; window.scrollTo({ top: top, behavior: 'smooth' }); }); }, /* ============================================================ * 文档搜索(AJAX 接管) * ============================================================ */ initDocSearch: function () { var searchInput = document.querySelector('.aksrt-doc-search__input'); if (!searchInput) return; var resultsContainer = document.querySelector('.aksrt-doc-search__results'); var takeover = document.querySelector('.aksrt-doc-takeover'); var self = this; var debounceTimer = null; searchInput.addEventListener('input', function () { var query = searchInput.value.trim(); if (debounceTimer) clearTimeout(debounceTimer); if (query.length < 2) { self.clearSearch(resultsContainer, takeover); return; } debounceTimer = setTimeout(function () { self.searchDocs(query, resultsContainer, takeover); }, 300); }); searchInput.addEventListener('keydown', function (e) { if (e.key === 'Escape') { searchInput.value = ''; self.clearSearch(resultsContainer, takeover); } }); }, searchDocs: function (query, resultsContainer, takeover) { if (this.ajaxInFlight) { this.ajaxInFlight.abort(); } var self = this; this.showLoading(takeover || resultsContainer); var url = API_BASE + '/docs/search?q=' + encodeURIComponent(query); this.ajaxInFlight = this.ajaxRequest(url, function (data) { self.hideLoading(takeover || resultsContainer); self.renderSearchResults(data, resultsContainer, takeover, query); }, function () { self.hideLoading(takeover || resultsContainer); }); }, renderSearchResults: function (data, resultsContainer, takeover, query) { var items = data.items || data.data || []; if (items.length === 0) { if (resultsContainer) { resultsContainer.hidden = false; resultsContainer.innerHTML = '
未找到相关文档
'; } return; } var html = '
'; items.forEach(function (item) { html += ''; html += '' + this.highlightQuery(this.escHtml(item.title), query) + ''; if (item.excerpt) { html += '' + this.highlightQuery(this.escHtml(item.excerpt), query) + ''; } html += ''; }.bind(this)); html += '
'; if (takeover) { takeover.innerHTML = html; takeover.hidden = false; } else if (resultsContainer) { resultsContainer.innerHTML = html; resultsContainer.hidden = false; } }, highlightQuery: function (text, query) { if (!query) return text; var escaped = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); return text.replace(new RegExp('(' + escaped + ')', 'gi'), '$1'); }, clearSearch: function (resultsContainer, takeover) { if (takeover) { takeover.hidden = true; takeover.innerHTML = ''; } if (resultsContainer) { resultsContainer.hidden = true; resultsContainer.innerHTML = ''; } // 显示所有卡片 var cards = document.querySelectorAll('.aksrt-doc-index-card'); cards.forEach(function (card) { card.style.display = ''; }); }, showLoading: function (container) { if (!container) return; var existing = container.querySelector('.aksrt-doc-loading'); if (existing) return; var loader = document.createElement('div'); loader.className = 'aksrt-doc-loading'; loader.innerHTML = '
搜索中...'; container.appendChild(loader); }, hideLoading: function (container) { if (!container) return; var loader = container.querySelector('.aksrt-doc-loading'); if (loader) loader.remove(); }, /* ============================================================ * 分类切换 — AJAX 加载 + 分页 * ============================================================ */ initCategoryChips: function () { var toolbar = document.querySelector('.aksrt-doc-toolbar'); if (!toolbar) return; var chips = toolbar.querySelectorAll('.aksrt-doc-toolbar__chip[data-cat-id]'); if (chips.length === 0) return; var self = this; chips.forEach(function (chip) { chip.addEventListener('click', function (e) { e.preventDefault(); var catId = parseInt(chip.getAttribute('data-cat-id') || '0', 10); // 切换激活状态 chips.forEach(function (c) { c.classList.remove('is-active'); }); chip.classList.add('is-active'); self.activeCategory = catId; self.currentPage = 1; self.loadCategoryDocs(catId, 1); }); }); }, loadCategoryDocs: function (catId, page) { if (this.ajaxInFlight) { this.ajaxInFlight.abort(); } var self = this; var grid = document.querySelector('.aksrt-doc-grid'); if (!grid) return; // 显示加载状态 grid.classList.add('is-loading'); var url = API_BASE + '/docs/list?cat_id=' + catId + '&page=' + page + '&per_page=12'; this.ajaxInFlight = this.ajaxRequest(url, function (data) { grid.classList.remove('is-loading'); self.renderCategoryDocs(data, grid); self.updatePagination(data.total || 0, data.pages || 1, page); self.updateMetaCount(data.total || 0); }, function () { grid.classList.remove('is-loading'); }); }, renderCategoryDocs: function (data, grid) { var items = data.items || data.data || []; if (items.length === 0) { grid.innerHTML = '
暂无文档
'; return; } var html = ''; items.forEach(function (item) { html += ''; if (item.thumbnail) { html += ''; } html += '
'; html += '

' + this.escHtml(item.title) + '

'; if (item.excerpt) { html += '

' + this.escHtml(item.excerpt) + '

'; } html += '
'; if (item.child_count) html += '' + item.child_count + ' 篇子文档'; if (item.date) html += '' + this.escHtml(item.date) + ''; html += '
'; }.bind(this)); grid.innerHTML = html; }, updateMetaCount: function (total) { var meta = document.querySelector('.aksrt-doc-toolbar__meta'); if (meta) { meta.textContent = total + ' 篇文档'; } }, /* ============================================================ * 分页控件 * ============================================================ */ initPagination: function () { var self = this; var paginationContainer = document.querySelector('.aksrt-doc-pagination'); if (!paginationContainer) return; paginationContainer.addEventListener('click', function (e) { var btn = e.target.closest('[data-page]'); if (!btn) return; e.preventDefault(); var page = parseInt(btn.getAttribute('data-page'), 10); if (page < 1 || page > self.totalPages || page === self.currentPage) return; self.currentPage = page; self.loadCategoryDocs(self.activeCategory, page); // 滚动到顶部 var grid = document.querySelector('.aksrt-doc-grid'); if (grid) { grid.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }, updatePagination: function (total, totalPages, currentPage) { this.totalPages = totalPages; this.currentPage = currentPage; var container = document.querySelector('.aksrt-doc-pagination'); if (!container) return; if (totalPages <= 1) { container.innerHTML = ''; return; } var html = '
'; // 上一页 if (currentPage > 1) { html += ''; } // 页码 var start = Math.max(1, currentPage - 2); var end = Math.min(totalPages, currentPage + 2); if (start > 1) { html += ''; if (start > 2) html += '...'; } for (var i = start; i <= end; i++) { html += ''; } if (end < totalPages) { if (end < totalPages - 1) html += '...'; html += ''; } // 下一页 if (currentPage < totalPages) { html += ''; } html += '
'; container.innerHTML = html; }, /* ============================================================ * 工具函数 * ============================================================ */ escHtml: function (str) { var div = document.createElement('div'); div.appendChild(document.createTextNode(str || '')); return div.innerHTML; }, filterDocItems: function (query) { var cards = document.querySelectorAll('.aksrt-doc-index-card'); if (cards.length > 0) { cards.forEach(function (card) { var title = card.querySelector('.aksrt-doc-index-card__title'); var desc = card.querySelector('.aksrt-doc-index-card__desc'); var text = (title ? title.textContent : '') + ' ' + (desc ? desc.textContent : ''); if (query === '' || text.toLowerCase().indexOf(query) >= 0) { card.style.display = ''; } else { card.style.display = 'none'; } }); } var sidebarItems = document.querySelectorAll('.aksrt-doc-sidebar__item'); if (sidebarItems.length > 0) { sidebarItems.forEach(function (item) { var link = item.querySelector('.aksrt-doc-sidebar__link'); var text = link ? link.textContent : ''; if (query === '' || text.toLowerCase().indexOf(query) >= 0) { item.style.display = ''; } else { item.style.display = 'none'; } }); } }, ajaxRequest: function (url, onSuccess, onError) { var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); if (NONCE) { xhr.setRequestHeader('X-WP-Nonce', NONCE); } xhr.onreadystatechange = function () { if (xhr.readyState !== 4) return; if (xhr.status >= 200 && xhr.status < 300) { try { var data = JSON.parse(xhr.responseText); if (onSuccess) onSuccess(data); } catch (e) { if (onError) onError(e); } } else { if (onError) onError(new Error('HTTP ' + xhr.status)); } }; xhr.send(); return xhr; } }; jQuery(document).off('auto_fun.aksrt-page-navs').on('auto_fun.aksrt-page-navs', function () { PAGE_NAVS.init(); }); })();