Files
wordpress-theme-aksrt/assets/js/page-navs.js
T

455 lines
15 KiB
JavaScript

/**
* 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 = '<div class="aksrt-doc-search__empty">未找到相关文档</div>';
}
return;
}
var html = '<div class="aksrt-doc-search__list">';
items.forEach(function (item) {
html += '<a class="aksrt-doc-search__item" href="' + this.escHtml(item.url) + '">';
html += '<span class="aksrt-doc-search__item-title">' + this.highlightQuery(this.escHtml(item.title), query) + '</span>';
if (item.excerpt) {
html += '<span class="aksrt-doc-search__item-excerpt">' + this.highlightQuery(this.escHtml(item.excerpt), query) + '</span>';
}
html += '</a>';
}.bind(this));
html += '</div>';
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'), '<mark>$1</mark>');
},
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 = '<div class="aksrt-doc-loading__spinner"></div><span>搜索中...</span>';
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 = '<div class="aksrt-doc-empty">暂无文档</div>';
return;
}
var html = '';
items.forEach(function (item) {
html += '<a class="aksrt-doc-index-card" href="' + this.escHtml(item.url) + '" data-cat-id="' + (item.cat_id || 0) + '">';
if (item.thumbnail) {
html += '<img' + window.aksrtGetLazyImageAttr('lazy_other', item.thumbnail, 'aksrt-doc-index-card__thumb') + ' alt="">';
}
html += '<div class="aksrt-doc-index-card__body">';
html += '<h3 class="aksrt-doc-index-card__title">' + this.escHtml(item.title) + '</h3>';
if (item.excerpt) {
html += '<p class="aksrt-doc-index-card__desc">' + this.escHtml(item.excerpt) + '</p>';
}
html += '<div class="aksrt-doc-index-card__meta">';
if (item.child_count) html += '<span>' + item.child_count + ' 篇子文档</span>';
if (item.date) html += '<span>' + this.escHtml(item.date) + '</span>';
html += '</div></div></a>';
}.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 = '<div class="aksrt-doc-pagination__inner">';
// 上一页
if (currentPage > 1) {
html += '<button class="aksrt-doc-pagination__btn" data-page="' + (currentPage - 1) + '"><i class="fas fa-chevron-left"></i></button>';
}
// 页码
var start = Math.max(1, currentPage - 2);
var end = Math.min(totalPages, currentPage + 2);
if (start > 1) {
html += '<button class="aksrt-doc-pagination__btn" data-page="1">1</button>';
if (start > 2) html += '<span class="aksrt-doc-pagination__ellipsis">...</span>';
}
for (var i = start; i <= end; i++) {
html += '<button class="aksrt-doc-pagination__btn' + (i === currentPage ? ' is-active' : '') + '" data-page="' + i + '">' + i + '</button>';
}
if (end < totalPages) {
if (end < totalPages - 1) html += '<span class="aksrt-doc-pagination__ellipsis">...</span>';
html += '<button class="aksrt-doc-pagination__btn" data-page="' + totalPages + '">' + totalPages + '</button>';
}
// 下一页
if (currentPage < totalPages) {
html += '<button class="aksrt-doc-pagination__btn" data-page="' + (currentPage + 1) + '"><i class="fas fa-chevron-right"></i></button>';
}
html += '</div>';
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;
}
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () { PAGE_NAVS.init(); });
} else {
PAGE_NAVS.init();
}
})();