Files
kate522andClaude Fable 5 38bbd917a9 fix(skeleton): align the homepage tab and sort loaders with the reference 1:1
首页「更多栏目」tab 与「排序方式」切换的骨架占位与参考主题 zibll 有实质差异,
根因不是几何宽度,而是三处结构/契约错位:

1. 骨架块自身结构与参考主题不同。card 分支用了 `<posts>` 元素 + `.post-graphic`
   包裹层(列表变体,带 margin-right:20px),而参考主题 card 骨架是
   `<div class="posts-item card">` 且 `.item-thumbnail` 是直接子元素。
   → card 骨架右侧多出一条真实卡片没有的 20px 沟槽。

2. 骨架块被错误地打上 `ajax-item`。参考主题的占位块没有该类。它会让
   `find('.ajax-item')` / ajax-list 的 `inject()` 把骨架当成真实条目。

3. 排序切换的骨架被预渲染节点顶替。aksrt_render_pagination() 在 .ajax-pag 内
   渲染了一个写死 2 条、写死 list 形状的 .post_ajax_loader;runtime post_ajax()
   会先在容器内 find('.post_ajax_loader'),找到就复用并丢弃调用方按视口动态
   拼出的骨架 → 切排序永远是 2 条 list 骨架,card 布局下也是、与视口宽度无关。
   参考主题 zib_paging() 根本没有 loader 兄弟节点。

改动:
- inc/helpers/posts-renderer/markup.php:aksrt_render_placeholder() 的
  card/list 分支改为与 zib_get_post_placeholder() 逐字等价的结构;去掉
  ajax-item 类、card 分支的 .post-graphic 包裹层,元素改用 <div>。
- inc/helpers/template-tags/pagination-permissions.php:删除预渲染 loader,
  与参考主题 zib_paging() 一致 —— 只渲染翻页控件。
- assets/js/core/pagination.js:找不到骨架节点时构造与 runtime/main.js 同款
  的文章骨架(卡片 8/6/4 条、列表 4/3 条,按视口与参考主题同阈值),
  不再退化为「转圈 + 文案」这个第二套加载语言;仅评论等非文章面保留该兜底。
- assets/js/core/pagination.js:为排序菜单(.ajax-option)与 tab 触发器
  (.post_ajax_trigger)增加避让 —— 它们归 legacy runtime 管线,
  否则一次点击发两个请求(与 ajax-list.js 的既有避让对称)。
- assets/js/runtime/main.js:post_ajax 的显隐由 fadeIn()/hide() 改为
  .is-loading 类切换。前者写 inline display,优先级高于类规则,一旦残留
  骨架永远打不开,正是骨架契约明令禁止的失败模式。

归档/分类筛选面原本另有一套独立加载语言(spinner 遮罩 + opacity 淡出),
本次一并统一到同一条 .post_ajax_loader + .is-loading 契约:
- inc/helpers/aksrt-archive.php:删除 show_loading 标记位与遮罩 DOM,
  改为在分页之后渲染文章骨架。
- assets/js/core/archive.js:showLoading/hideLoading 只切 .is-loading;
  fetchPosts() 清空内容时保留骨架与分页节点(与参考主题 success 只 hide
  不删的生命周期一致),原先会连带删掉骨架。
- assets/css/frontend/layout.css:删除 .archive__ajax-wrap.is-loading
  的 opacity 淡出与 spinner 遮罩规则。
- category.php / tag.php:删除已失效的 show_loading 实参。

验证:php -l 全部通过;php tools/audit-admin-contracts.php errors=0;
契约测试 post-visual(22) / asset-ledger(16) / message-center(8) /
lazyload(7) / frontend-lifecycle(13) / zibll-modern-widgets(10) /
page-display(18) / a11y 全绿;已重跑 build:public 刷新产物。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-04 03:04:02 +08:00

235 lines
8.6 KiB
JavaScript
Raw Permalink 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.
/**
* AKSRT Archive AJAX Filter
*/
(function () {
"use strict";
function initArchiveFilter(root) {
var scope = root || document;
var bar = scope.querySelector(".archive-filter-bar--ajax[data-ajax-archive]");
if (!bar || bar.dataset.aksrtArchiveInit === "1") return;
bar.dataset.aksrtArchiveInit = "1";
var archiveType = bar.dataset.ajaxArchive || "category";
var termId = parseInt(bar.dataset.termId, 10) || 0;
var wrap = scope.querySelector("[data-ajax-wrap]");
if (!wrap) return;
// 归档面与首页共用同一套骨架契约:.post_ajax_loader + .is-loading,
// 不再使用独立 spinner 遮罩 / opacity 淡出。
var loading = wrap.querySelector(".post_ajax_loader");
var REST_BASE = "/wp-json/aksrt/v1/archive/posts";
var emptyText = archiveType === "tag" ? "该标签下暂无文章" : "该分类下暂无文章";
var state = {
filter_cat: "",
filter_tag: "",
custom: {},
sort: "date",
page: 1
};
var activeCat = bar.querySelector("[data-filter-cat].is-active");
if (activeCat) state.filter_cat = activeCat.dataset.filterCat || "";
var activeTag = bar.querySelector("[data-filter-tag].is-active");
if (activeTag) state.filter_tag = activeTag.dataset.filterTag || "";
var activeSort = bar.querySelector("[data-filter-sort].is-active");
if (activeSort) state.sort = activeSort.dataset.filterSort || "date";
// 自定义高级筛选维度:以当前 URL 参数为准(SSR active 状态与 URL 一致)。
if (bar.querySelector("[data-filter-dim]")) {
new URLSearchParams(window.location.search).forEach(function (value, key) {
if (bar.querySelector('[data-filter-dim="' + key + '"]')) {
state.custom[key] = value;
}
});
}
function buildUrl() {
var params = new URLSearchParams();
params.set("archive_type", archiveType);
params.set("term_id", String(termId));
if (state.filter_cat) params.set("filter_cat", state.filter_cat);
if (state.filter_tag) params.set("filter_tag", state.filter_tag);
if (Object.keys(state.custom).length) params.set("custom_filter", JSON.stringify(state.custom));
params.set("sort", state.sort || "date");
params.set("page", String(state.page));
return REST_BASE + "?" + params.toString();
}
function updateActiveChips() {
bar.querySelectorAll("[data-filter-cat]").forEach(function (el) {
el.classList.toggle("is-active", (el.dataset.filterCat || "") === state.filter_cat);
});
bar.querySelectorAll("[data-filter-tag]").forEach(function (el) {
el.classList.toggle("is-active", (el.dataset.filterTag || "") === state.filter_tag);
});
bar.querySelectorAll("[data-filter-sort]").forEach(function (el) {
el.classList.toggle("is-active", (el.dataset.filterSort || "date") === state.sort);
});
bar.querySelectorAll("[data-filter-dim]").forEach(function (el) {
var current = state.custom[el.dataset.filterDim] || "";
el.classList.toggle("is-active", (el.dataset.filterDimValue || "") !== "" && (el.dataset.filterDimValue || "") === current);
});
}
function showLoading() {
if (loading) loading.classList.add("is-loading");
}
function hideLoading() {
if (loading) loading.classList.remove("is-loading");
}
function rebuildEmptyState() {
var noResults = document.createElement("div");
noResults.className = "no-results content-stream__empty";
noResults.innerHTML = "<p>" + emptyText + "</p>";
wrap.appendChild(noResults);
}
function fetchPosts(pushState) {
showLoading();
fetch(buildUrl(), {
method: "GET",
headers: { Accept: "application/json" },
credentials: "same-origin"
})
.then(function (r) {
if (!r.ok) throw new Error("HTTP " + r.status);
return r.json();
})
.then(function (data) {
hideLoading();
if (data.html !== undefined) {
// 只清真实内容;骨架(.post_ajax_loader)与分页控件保留复用 ——
// 与参考主题 post_ajax success 只 hide 不删的生命周期一致。
var keep = function (node) {
return node === loading ||
node.classList.contains("post_ajax_loader") ||
node.classList.contains("ajax-pag");
};
Array.from(wrap.children).forEach(function (child) {
if (!keep(child)) wrap.removeChild(child);
});
if (data.html) {
var temp = document.createElement("div");
temp.innerHTML = data.html;
while (temp.firstChild) wrap.appendChild(temp.firstChild);
} else {
rebuildEmptyState();
}
// legacy 事件顺序:先 post_ajax.ed,再全局 auto_fun 重扫
wrap.dispatchEvent(new CustomEvent("post_ajax.ed", { bubbles: true }));
if (typeof window.auto_fun === "function") window.auto_fun();
}
if (pushState !== false) {
var url = new URL(window.location.href);
url.searchParams.delete("orderby");
url.searchParams.delete("order");
url.searchParams.delete("paged");
url.searchParams.delete("aksrt_filter_cat");
url.searchParams.delete("aksrt_filter_tag");
bar.querySelectorAll("[data-filter-dim]").forEach(function (el) {
url.searchParams.delete(el.dataset.filterDim);
});
if (state.filter_cat) url.searchParams.set("aksrt_filter_cat", state.filter_cat);
if (state.filter_tag) url.searchParams.set("aksrt_filter_tag", state.filter_tag);
Object.keys(state.custom).forEach(function (key) {
url.searchParams.set(key, state.custom[key]);
});
if (state.sort) url.searchParams.set("orderby", state.sort);
if (state.page > 1) url.searchParams.set("paged", String(state.page));
history.pushState({ aksrtArchive: true, state: Object.assign({}, state) }, "", url.toString());
}
})
.catch(function () {
hideLoading();
});
}
bar.addEventListener("click", function (e) {
var chip = e.target.closest("[data-filter-cat], [data-filter-tag], [data-filter-sort], [data-filter-dim]");
if (!chip) return;
e.preventDefault();
var changed = false;
if (chip.dataset.filterCat !== undefined) {
var newCat = chip.dataset.filterCat;
if (newCat !== state.filter_cat) {
state.filter_cat = newCat;
changed = true;
}
}
if (chip.dataset.filterTag !== undefined) {
var newTag = chip.dataset.filterTag;
if (newTag !== state.filter_tag) {
state.filter_tag = newTag;
changed = true;
}
}
if (chip.dataset.filterSort !== undefined) {
var newSort = chip.dataset.filterSort || "date";
if (newSort !== state.sort) {
state.sort = newSort;
changed = true;
}
}
if (chip.dataset.filterDim !== undefined) {
var dimKey = chip.dataset.filterDim;
var dimValue = chip.dataset.filterDimValue || "";
if (dimValue !== (state.custom[dimKey] || "")) {
if (dimValue === "") {
delete state.custom[dimKey];
} else {
state.custom[dimKey] = dimValue;
}
changed = true;
}
}
if (changed) {
state.page = 1;
updateActiveChips();
fetchPosts(true);
}
});
wrap.addEventListener("click", function (e) {
var pageLink = e.target.closest(".pagination a, .nav-links a, a.page-numbers");
if (!pageLink || !wrap.contains(pageLink)) return;
var href = pageLink.getAttribute("href");
if (!href) return;
var match = href.match(/[?&]paged=(\d+)/);
if (match) {
e.preventDefault();
state.page = parseInt(match[1], 10) || 1;
fetchPosts(true);
}
});
window.addEventListener("popstate", function (e) {
if (e.state && e.state.aksrtArchive) {
state = Object.assign(state, e.state.state);
updateActiveChips();
fetchPosts(false);
}
});
}
function init(root) {
initArchiveFilter(root);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", function () { init(); });
} else {
init();
}
// AJAX 注入新内容后由 auto_fun 重扫(bar 级 data 标记保证幂等)。
if (window.jQuery) {
window.jQuery(document).on("auto_fun", function () { init(); });
}
})();