Files
wordpress-theme-aksrt/assets/js/core/pagination.js
T
kate522andClaude Opus 5 35bb96e82f refactor: rename the theme brand and every identifier from AKSRT to Jio
Full-project rename. `git grep -i aksrt` is now empty apart from the
DBMigrator 3.3.0 changelog note that documents the rename itself.

Code identifiers
- PHP namespace AKSRT\ -> Jio\ (composer PSR-4 + 378 declarations)
- global constants AKSRT_* -> JIO_* (VERSION/DIR/URI, PAY_TYPE_*, CAPTCHA_*)
- procedural functions, hooks, options, meta keys, table names,
  cookies, query vars, ajax actions and nonces: aksrt_ -> jio_
- text domain aksrt -> jio; Theme Name -> Jio
- JS globals: aksrtData/aksrtAdminData/... -> jioData/jioAdminData/...,
  window.AKSRTCaptcha & friends -> window.JioCaptcha, Aksrt* -> Jio*
- Vue components Aks*.vue -> Jio*.vue (15 files renamed + imports)
- CSS classes/data-attrs/custom properties: aksrt- -> jio-
- REST namespace aksrt/v1 -> jio/v1 (incl. payment gateway callbacks)

Storage layer
- DB tables {prefix}aksrt_* -> {prefix}jio_*; SCHEMA_VERSION 3.2.0 -> 3.3.0
  so dbDelta creates the new names on install
- widget id_base aksrt_widget_* -> jio_widget_*
- uninstall.php drop list rebuilt from the actual migrator tables
  (dropped 3 stale entries, added the 17 missing ones)

Removed
- hardcoded https://api.aksrt.com/version.json default update endpoint;
  get_endpoint() now reads the 「更新端点」 setting and do_check() no-ops
  when it is empty

Files renamed: inc/helpers/aksrt-*.php -> jio-*.php (54),
assets/admin/src/components/form/Aks*.vue -> Jio*.vue (15).
Build artifacts regenerated: assets/css/build, assets/js/runtime,
assets/admin/build.

Verified: audit-psr4-paths errors=0, audit-admin-contracts errors=0
(warnings=56, unchanged baseline), 813/813 widget-system tests,
44/44 phpunit, php -l clean across all PHP.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-01 20:05:04 +08:00

357 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Jio AJAX Pagination & Lazy Load
*/
(function () {
"use strict";
function hasAjaxConfig() {
return !!(window.jioData && jioData.ajaxUrl && jioData.nonce);
}
function initAjaxPagination(root) {
var containers = (root || document).querySelectorAll("[data-jio-pagination], .jio-comment-pagination");
if (!containers.length) return;
var triggerText = (window.jioData && jioData.ajaxTrigger) || "加载更多";
var nomoreText = (window.jioData && jioData.ajaxNomore) || "没有更多了";
var errorText = (window.jioData && jioData.ajaxLoadError) || "加载失败,请重试";
containers.forEach(function (container) {
if (container.dataset.jioPaginationBound === "1") return;
container.dataset.jioPaginationBound = "1";
var mode = container.dataset.mode || "load_more";
var nextPage = parseInt(container.dataset.nextPage, 10) || 2;
var maxPages = parseInt(container.dataset.maxPages, 10) || 1;
var isComment = container.classList.contains("jio-comment-pagination");
var postId = container.dataset.postId || "";
var commentOrder = container.dataset.commentOrder || "newest";
var loading = false;
var failed = false;
var clickTarget = container.querySelector(".next-page.ajax-next, .ias-btn") || container;
var loadMoreBtn = container.querySelector("[data-jio-load-more]") || clickTarget;
var successfulAutoLoads = 0;
var iasMax = isComment
? ((window.jioData && typeof jioData.commentIasMax !== "undefined") ? parseInt(jioData.commentIasMax, 10) : 0)
: ((window.jioData && typeof jioData.iasMax !== "undefined") ? parseInt(jioData.iasMax, 10) : 0);
// legacy 契约:分页容器与列表同处一个 .ajaxpager 内(首页 tab pane、归档流),
// 此时追加目标就是列表容器本身,新卡片插到分页控件之前。
var listContainer = container.closest(".ajaxpager, .posts-row");
var target = container.previousElementSibling;
if (listContainer && container.parentNode === listContainer) {
target = listContainer;
}
if (isComment) {
var commentList = container.closest("#comment-list, .commentlist, ol.commentlist") ||
document.getElementById("comment-list");
if (commentList) target = commentList;
}
if (!target && container.parentElement) {
target = container.parentElement.querySelector(".post-grid, .comment-list, .commentlist, .content-stream__list");
}
// 文章骨架:与 runtime/main.js 的 .ajax-next 处理器、jio_render_placeholder()
// 同源同款(1:1 对齐参考主题 zib_get_post_placeholder)。刻意不写 ajax-item:
// 否则会被 inject() / find('.ajax-item') 当成真实条目删除或定位。
var CARD_ITEM =
'<div class="posts-item card"><div class="item-thumbnail">' +
'<div class="radius8 item-thumbnail placeholder"></div> </div>' +
'<div class="item-body "> <h2 class="item-excerpt placeholder t1 item-heading"></h2>' +
' <p class="mt10 placeholder k2"></p><i class="flex jsb"><i class="placeholder s1"></i>' +
'<i class="placeholder s1 ml10"></i></i></div></div>';
var LIST_ITEM =
'<div class="posts-item list flex"><div class="post-graphic">' +
'<div class="radius8 item-thumbnail placeholder"></div> </div>' +
'<div class="item-body flex xx flex1 jsb"> <p class="placeholder t1"></p>' +
' <h4 class="item-excerpt placeholder k1"></h4><p class="placeholder k2"></p>' +
'<i><i class="placeholder s1"></i><i class="placeholder s1 ml10"></i></i></div></div>';
// 视口阈值与参考主题 .ajax-next 处理器一致:宽 + 无侧栏 → 8,宽 + 有侧栏 → 6,
// 窄屏 → 4;list 模式宽屏 4、窄屏 3。
function skeletonCount(style) {
var wide = window.innerWidth > 766;
if (style === "card") {
if (!wide) return 4;
return window.innerWidth > 992 && !document.querySelector(".sidebar") ? 8 : 6;
}
return wide ? 4 : 3;
}
// 从列表首张真实卡片判断形状;无卡片时退化为 list。
function postSkeleton() {
var first = target && target.querySelector
? target.querySelector(".posts-item.card, .posts-item.list")
: null;
var isCard = !!first && /(^|\s)card(\s|$)/.test(first.className || "");
var tpl = isCard ? CARD_ITEM : LIST_ITEM;
var out = "";
for (var i = 0, n = skeletonCount(isCard ? "card" : "list"); i < n; i++) {
out += tpl;
}
return '<div class="mb20">' + out + '</div>';
}
// 兜底(非文章列表面,例如评论区没有 .posts-item 时):转圈 + 文案,
// 绝不用按钮自身文字来表达加载态。
function fallbackLoader() {
var text = (window.jioData && jioData.ajaxLoading) || "加载中...";
return (
'<div class="mb20"><div class="text-center muted-2-color mt20">' +
'<i class="loading mr10"></i>' + text +
'</div></div>'
);
}
function showSkeleton() {
if (!target) return;
var loader = target.querySelector(".post_ajax_loader");
if (!loader) {
loader = document.createElement("div");
loader.className = "post_ajax_loader";
loader.innerHTML = postSkeleton() || fallbackLoader();
}
// 参考主题 beforeSend:骨架每次翻页都挪到列表末尾(分页控件之前),
// 紧贴最后一张卡片、位于「加载更多」按钮上方。
target.insertBefore(
loader,
container.parentNode === target ? container : null
);
loader.classList.add("is-loading");
}
function hideSkeleton() {
if (!target) return;
var loader = target.querySelector(".post_ajax_loader");
if (loader) {
loader.classList.remove("is-loading");
}
}
function stopInfinite(markNomore) {
container.classList.remove("ias-pending");
if (markNomore && loadMoreBtn) {
loadMoreBtn.innerHTML = nomoreText;
loadMoreBtn.classList.add("ajax-nomore");
var nextWrap = loadMoreBtn.closest(".next-page");
if (nextWrap) nextWrap.classList.add("no-more");
loadMoreBtn.style.pointerEvents = "none";
} else if (loadMoreBtn && !loadMoreBtn.classList.contains("ajax-nomore")) {
if (failed) {
loadMoreBtn.textContent = errorText;
} else {
loadMoreBtn.innerHTML = triggerText;
}
loadMoreBtn.style.pointerEvents = "";
}
}
function markFailed(message) {
failed = true;
stopInfinite(false);
if (loadMoreBtn) {
loadMoreBtn.textContent = message || errorText;
loadMoreBtn.classList.remove("ajax-nomore");
loadMoreBtn.style.pointerEvents = "";
}
}
function loadNextPage(fromAuto) {
if (loading || failed || nextPage > maxPages || !target) return Promise.resolve(false);
if (!hasAjaxConfig()) {
markFailed(errorText);
return Promise.resolve(false);
}
loading = true;
// 加载态的呈现完全交给列表末尾的骨架(参考主题 post_ajax beforeSend
// 只操作 .post_ajax_loader,不触碰「加载更多」按钮自身的文字)。
showSkeleton();
var action = isComment ? "jio_load_more_comments" : "jio_load_more_posts";
var queryContext = container.dataset.queryContext || "";
var queryVars = container.dataset.queryVars || "";
var renderConfig = container.dataset.renderConfig || "";
var body = new URLSearchParams();
body.set("action", action);
body.set("nonce", jioData.nonce);
body.set("page", String(nextPage));
if (queryContext) {
body.set("query_context", queryContext);
}
if (!isComment && queryVars) {
body.set("query_vars", queryVars);
}
if (!isComment && renderConfig) {
body.set("render_config", renderConfig);
}
if (isComment && postId) {
body.set("post_id", postId);
body.set("comment_order", commentOrder);
var authorOnly = container.dataset.authorOnly || "0";
if (authorOnly === "1") body.set("author_only", "1");
}
return fetch(jioData.ajaxUrl, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
credentials: "same-origin",
body: body.toString(),
})
.then(function (r) {
if (!r.ok) {
throw new Error("HTTP " + r.status);
}
return r.text().then(function (text) {
try {
return JSON.parse(text);
} catch (err) {
throw new Error("Invalid JSON");
}
});
})
.then(function (res) {
if (!(res && res.success && res.data && typeof res.data.html === "string")) {
var msg = res && res.data && res.data.message ? res.data.message : errorText;
markFailed(msg);
return false;
}
var temp = document.createElement("div");
temp.innerHTML = res.data.html;
var newItems = temp.children;
while (newItems.length > 0) {
if (container.parentNode === target) {
target.insertBefore(newItems[0], container);
} else {
target.appendChild(newItems[0]);
}
}
// legacy 事件顺序:先 post_ajax.ed,再全局 auto_fun 重扫
target.dispatchEvent(new CustomEvent("post_ajax.ed", { bubbles: true }));
if (typeof window.auto_fun === "function") window.auto_fun();
nextPage++;
container.dataset.nextPage = String(nextPage);
if (fromAuto) {
successfulAutoLoads++;
}
if (nextPage > maxPages || !res.data.has_more) {
stopInfinite(true);
if (!loadMoreBtn) {
container.innerHTML = '<span class="ajax-nomore">' + nomoreText + '</span>';
}
}
return true;
})
.catch(function () {
markFailed(errorText);
return false;
})
.finally(function () {
loading = false;
hideSkeleton();
if (loadMoreBtn && !loadMoreBtn.classList.contains("ajax-nomore") && !failed) {
loadMoreBtn.innerHTML = triggerText;
if (mode === "infinite") {
var lazyTrigger = clickTarget.closest(".next-page") || clickTarget;
if (iasMax > 0 && successfulAutoLoads >= iasMax) {
container.classList.remove("ias-pending");
lazyTrigger.classList.remove("lazyload", "lazyloaded", "lazyloading");
lazyTrigger.removeAttribute("lazyload-action");
} else {
lazyTrigger.classList.remove("lazyloaded", "lazyloading");
lazyTrigger.classList.add("lazyload");
if (window.lazySizes && lazySizes.loader) {
lazySizes.loader.checkElems();
}
}
}
}
});
}
if (loadMoreBtn) {
clickTarget.addEventListener("click", function(e) {
// 排序菜单(.ajax-option)与 tab pane 触发器(.post_ajax_trigger)归 legacy
// runtime post_ajax() 管线;本引擎避开,否则一次点击发两个请求。
if (e.target.closest && e.target.closest(".ajax-option, .post_ajax_trigger")) {
return;
}
e.preventDefault();
if (loadMoreBtn.dataset.jumpComments === "1") {
var jumpInput = container.querySelector(".jump-input");
if (!jumpInput) return;
var targetPage = parseInt(jumpInput.value, 10) || 1;
var max = parseInt(jumpInput.dataset.maxPages || String(maxPages), 10) || maxPages;
targetPage = Math.max(1, Math.min(max, targetPage));
// 评论跳页:本地 DOM 刷新,地址栏不携带 ? 参数。
if (window.JioComments && typeof window.JioComments.loadPage === "function") {
e.preventDefault();
window.JioComments.loadPage(targetPage);
return;
}
window.location.hash = "comments";
window.location.reload();
return;
}
if (loadMoreBtn.dataset.jumpArchive === "1") {
var archiveJumpInput = container.querySelector(".jump-input");
if (!archiveJumpInput) return;
var archiveTargetPage = parseInt(archiveJumpInput.value, 10) || 1;
var archiveMax = parseInt(archiveJumpInput.dataset.maxPages || String(maxPages), 10) || maxPages;
archiveTargetPage = Math.max(1, Math.min(archiveMax, archiveTargetPage));
var archiveUrl = new URL(window.location.href);
archiveUrl.searchParams.set(loadMoreBtn.dataset.pageVar || "paged", String(archiveTargetPage));
window.location.href = archiveUrl.toString();
return;
}
var fromAuto = clickTarget.dataset.lazyAuto === "1";
delete clickTarget.dataset.lazyAuto;
if (fromAuto && iasMax > 0 && successfulAutoLoads >= iasMax) {
container.classList.remove("ias-pending");
return;
}
// Manual click can recover from a previous auto-load failure.
failed = false;
loadNextPage(fromAuto);
});
}
// The runtime lazySizes dispatches lazybeforeunveil natively but follows up
// with a jQuery-simulated click, which native listeners never receive.
// Route the unveil trigger through the same click pipeline.
if (container.classList.contains("lazyload") || clickTarget.classList.contains("lazyload")) {
container.addEventListener("lazybeforeunveil", function (e) {
if (e.target !== container && e.target !== clickTarget) return;
clickTarget.dataset.lazyAuto = "1";
clickTarget.click();
});
}
if (mode === "infinite") {
if ((isComment && window.jioData && jioData.commentIasAuto === false) || !hasAjaxConfig()) {
var autoTrigger = clickTarget.closest(".next-page") || clickTarget;
autoTrigger.classList.remove("lazyload");
autoTrigger.removeAttribute("lazyload-action");
return;
}
container.classList.add("ias-pending");
}
});
}
function init(root) {
initAjaxPagination(root);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", function () { init(); });
} else {
init();
}
// AJAX 注入新内容后由 auto_fun 重扫(container 级 data 标记保证幂等)。
if (window.jQuery) {
window.jQuery(document).on("auto_fun", function () { init(); });
}
})();