fix(frontend): keep asset and homepage filters in DOM #18

Merged
kate522 merged 1 commits from worktree/fix-asset-homepage-dom into main 2026-09-03 11:34:22 +00:00
23 changed files with 362 additions and 305 deletions
+63 -25
View File
@@ -528,7 +528,18 @@
var item = document.createElement('button');
item.type = 'button';
item.className = 'uc-transfer-user';
item.innerHTML = '<img' + window.aksrtGetLazyImageAttr('lazy_avatar', u.avatar || '', 'uc-transfer-user__avatar', 'sm') + ' alt=""><span>' + (u.display_name || u.user_login) + '</span><span class="muted-2-color em09">@' + (u.user_login || '') + '</span>';
var avatar = document.createElement('img');
avatar.className = 'uc-transfer-user__avatar';
avatar.alt = '';
avatar.src = String(u.avatar || '');
item.appendChild(avatar);
var displayName = document.createElement('span');
displayName.textContent = String(u.display_name || u.user_login || '');
item.appendChild(displayName);
var login = document.createElement('span');
login.className = 'muted-2-color em09';
login.textContent = '@' + String(u.user_login || '');
item.appendChild(login);
item.addEventListener('click', function () {
if (recipientInput) recipientInput.value = String(u.id);
if (selected) selected.textContent = '已选择:' + (u.display_name || u.user_login);
@@ -737,31 +748,58 @@
}
function initAssetTabs() {
var nav = document.querySelectorAll('[data-aksrt-asset-tab]');
if (!nav.length) return;
nav.forEach(function (link) {
if (link.dataset.aksrtAssetTabBound === 'true') return;
link.dataset.aksrtAssetTabBound = 'true';
link.addEventListener('click', function (e) {
e.preventDefault();
var key = link.getAttribute('data-aksrt-asset-tab');
document.querySelectorAll('.uc-asset-tabs__nav [data-aksrt-asset-tab]').forEach(function (a) {
var li = a.closest('li');
if (!li) return;
var aKey = a.getAttribute('data-aksrt-asset-tab');
var on = aKey === key || (key === 'points_date' && aKey === 'points_free');
li.classList.toggle('active', on);
});
var tabs = document.querySelector('.uc-asset-tabs');
if (tabs && typeof tabs.scrollIntoView === 'function') {
tabs.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
document.querySelectorAll('[data-aksrt-asset-pane]').forEach(function (pane) {
var on = pane.getAttribute('data-aksrt-asset-pane') === key;
pane.classList.toggle('is-active', on);
pane.hidden = !on;
});
if (document.documentElement.dataset.aksrtAssetTabsBound === 'true') return;
document.documentElement.dataset.aksrtAssetTabsBound = 'true';
function select(key) {
document.querySelectorAll('.uc-asset-tabs__nav [data-aksrt-asset-tab]').forEach(function (link) {
var li = link.closest('li');
if (!li) return;
var linkKey = link.getAttribute('data-aksrt-asset-tab');
li.classList.toggle('active', linkKey === key || (key === 'points_date' && linkKey === 'points_free'));
});
document.querySelectorAll('[data-aksrt-asset-pane]').forEach(function (pane) {
var active = pane.getAttribute('data-aksrt-asset-pane') === key;
pane.classList.toggle('is-active', active);
pane.hidden = !active;
});
}
function loadPage(key, page) {
var panel = document.querySelector('#panel-credits');
var rest = (window.aksrtData && (window.aksrtData.restUrl || window.aksrtData.apiUrl)) || '';
if (!panel || !rest) return;
panel.classList.add('is-loading');
var url = rest.replace(/\/$/, '') + '/user-center/panel/credits?asset=' + encodeURIComponent(key) + '&page=' + encodeURIComponent(page);
fetch(url, {
credentials: 'same-origin',
headers: { 'X-WP-Nonce': (window.aksrtData && (window.aksrtData.restNonce || window.aksrtData.nonce)) || '' }
}).then(function (response) {
return response.json().then(function (payload) {
if (!response.ok || !payload || !payload.success || !payload.data || typeof payload.data.html !== 'string') throw new Error('asset-page-load-failed');
panel.innerHTML = payload.data.html;
if (window.jQuery) jQuery(document).trigger('post_ajax.ed', [panel]);
if (typeof window.auto_fun === 'function') window.auto_fun();
select(key);
});
}).catch(function () {
notyf('记录加载失败,请重试', 'danger');
}).finally(function () {
panel.classList.remove('is-loading');
});
}
document.addEventListener('click', function (e) {
var pageButton = e.target.closest('[data-aksrt-asset-page]');
if (pageButton) {
e.preventDefault();
loadPage(pageButton.getAttribute('data-aksrt-asset-key') || 'points', parseInt(pageButton.getAttribute('data-aksrt-asset-page') || '1', 10) || 1);
return;
}
var link = e.target.closest('[data-aksrt-asset-tab]');
if (!link) return;
e.preventDefault();
select(link.getAttribute('data-aksrt-asset-tab'));
});
}
+43 -89
View File
@@ -11,7 +11,6 @@
(function () {
'use strict';
var SORT_QUERY_KEY = 'home_sort';
var DEFAULT_SORT = 'modified';
var REST_PATH = '/wp-json/aksrt/v1/homepage/tab-posts';
@@ -19,10 +18,6 @@
* Utilities
* ============================================================ */
function getViewMode() {
return getDefaultView();
}
function getRenderedPostCount(scope) {
if (!scope) return 0;
return scope.querySelectorAll('.posts-item, .posts-mini').length;
@@ -42,7 +37,7 @@
function getDefaultSort() {
var configured = window.aksrtData && aksrtData.homepageDefaultSort;
var allowed = ['modified', 'date', 'downloads', 'views', 'likes', 'comments'];
var allowed = ['modified', 'date', 'views', 'like', 'comment_count', 'favorite', 'rand'];
return allowed.indexOf(configured) !== -1 ? configured : DEFAULT_SORT;
}
@@ -51,12 +46,6 @@
return panel && panel.getAttribute('data-view') === 'card' ? 'card' : 'list';
}
function isSortAjaxEnabled(sortbar) {
var attr = sortbar && sortbar.getAttribute('data-sort-ajax');
if (attr === 'false') return false;
return !(window.aksrtData && aksrtData.homepageSortAjax === false);
}
function isStickyTabsEnabled() {
return !(window.aksrtData && aksrtData.homepageStickyTabs === false);
}
@@ -66,14 +55,11 @@
return baseUrl.replace(/\/?$/, '');
}
function getSortFromUrl() {
return new URLSearchParams(window.location.search).get(SORT_QUERY_KEY) || getDefaultSort();
function getCurrentSort(stream) {
var activeLink = stream ? stream.querySelector('.aksrt-homepage__sortbar-link.is-active, .aksrt-homepage__sortbar-dropdown-item.is-active') : null;
return (activeLink && activeLink.getAttribute('data-sort')) || getDefaultSort();
}
function getCurrentSort(stream) {
var activeLink = stream ? stream.querySelector('.aksrt-homepage__sortbar-link.is-active') : null;
return (activeLink && activeLink.getAttribute('data-sort')) || getSortFromUrl();
}
function setActiveSortLink(sortbar, sort) {
var links = sortbar.querySelectorAll('.aksrt-homepage__sortbar-link, .aksrt-homepage__sortbar-dropdown-item');
@@ -88,20 +74,6 @@
}
}
function normalizeHomepageUrl(url) {
url.searchParams.delete('paged');
url.searchParams.delete('page');
url.pathname = url.pathname.replace(/\/page\/\d+\/?$/, '/');
return url;
}
function replaceUrlSort(sort) {
if (!window.history || !window.history.replaceState) return;
var url = normalizeHomepageUrl(new URL(window.location.href));
url.searchParams.set(SORT_QUERY_KEY, sort);
window.history.replaceState({}, '', url.toString());
}
function getStreamViewMode(stream) {
var panel = getActivePanel(stream);
return panel && panel.getAttribute('data-view') === 'card' ? 'card' : 'list';
@@ -124,12 +96,6 @@
return panel.getAttribute('data-tab-orderby') || '';
}
function getPanelSource(panel) {
var target = panel ? panel.getAttribute('data-home-tab-panel') : '';
if (target === 'latest') return 'home';
return target ? 'home' : 'widget';
}
function buildRestUrl(options) {
options = options || {};
return getRestBaseUrl() + REST_PATH +
@@ -137,9 +103,7 @@
'&page=' + encodeURIComponent(options.page || 1) +
'&per_page=' + encodeURIComponent(options.perPage || 10) +
'&sort=' + encodeURIComponent(options.sort || getDefaultSort()) +
'&page_url=' + encodeURIComponent(options.pageUrl || window.location.href) +
'&panel=' + encodeURIComponent(options.panel || 'latest') +
'&source=' + encodeURIComponent(options.source || 'home') +
'&tab_orderby=' + encodeURIComponent(options.tabOrderby || '');
}
@@ -149,7 +113,7 @@
/**
* Dispatch post_ajax.before — fired before AJAX request is sent.
* Detail: { termId, page, sort, viewMode, panel, source, url }
* Detail: { termId, page, sort, viewMode, panel, url }
*/
function dispatchBeforeEvent(container, detail) {
try {
@@ -162,7 +126,7 @@
/**
* Dispatch post_ajax.ed — fired after AJAX response is injected.
* Detail: { termId, page, sort, viewMode, panel, source, total, hasMore }
* Detail: { termId, page, sort, viewMode, panel, total, hasMore }
*/
function dispatchAfterEvent(container, detail) {
try {
@@ -245,18 +209,16 @@
button.addEventListener('click', function () {
button.disabled = true;
button.textContent = (window.aksrtData && aksrtData.ajaxLoading) || '正在加载...';
requestHomepagePanel(panel, {
termId: options.termId,
page: options.page + 1,
perPage: options.perPage,
sort: options.sort,
viewMode: options.viewMode,
pageUrl: options.pageUrl,
panel: options.panel,
source: options.source,
tabOrderby: options.tabOrderby,
append: true
});
requestHomepagePanel(panel, {
termId: options.termId,
page: options.page + 1,
perPage: options.perPage,
sort: options.sort,
viewMode: options.viewMode,
panel: options.panel,
tabOrderby: options.tabOrderby,
append: true
});
});
footer.appendChild(button);
panel.appendChild(footer);
@@ -293,33 +255,30 @@
* 4. aksrt:posts:rendered (after lazyload rebind)
*/
function requestHomepagePanel(panel, requestOptions) {
var options = {
termId: requestOptions.termId || 0,
page: requestOptions.page || 1,
perPage: requestOptions.perPage || getHomepagePerPage(panel),
sort: requestOptions.sort || getDefaultSort(),
viewMode: requestOptions.viewMode || getDefaultView(),
pageUrl: requestOptions.pageUrl || window.location.href,
panel: requestOptions.panel || panel.getAttribute('data-home-tab-panel') || 'latest',
source: requestOptions.source || getPanelSource(panel),
tabOrderby: requestOptions.tabOrderby || getPanelTabOrderby(panel) || '',
append: requestOptions.append === true
};
var options = {
termId: requestOptions.termId || 0,
page: requestOptions.page || 1,
perPage: requestOptions.perPage || getHomepagePerPage(panel),
sort: requestOptions.sort || getDefaultSort(),
viewMode: requestOptions.viewMode || getDefaultView(),
panel: requestOptions.panel || panel.getAttribute('data-home-tab-panel') || 'latest',
tabOrderby: requestOptions.tabOrderby || getPanelTabOrderby(panel) || '',
append: requestOptions.append === true
};
var restUrl = buildRestUrl(options);
var retryCount = 0;
var maxRetries = 1;
// 1. Pre-request event
dispatchBeforeEvent(panel, {
termId: options.termId,
page: options.page,
sort: options.sort,
viewMode: options.viewMode,
panel: options.panel,
source: options.source,
url: restUrl
});
dispatchBeforeEvent(panel, {
termId: options.termId,
page: options.page,
sort: options.sort,
viewMode: options.viewMode,
panel: options.panel,
url: restUrl
});
// Full refreshes replace the panel; append requests keep existing cards.
if (!options.append) {
@@ -365,11 +324,10 @@
dispatchAfterEvent(panel, {
termId: options.termId,
page: options.page,
sort: options.sort,
viewMode: options.viewMode,
panel: options.panel,
source: options.source,
total: data.total || 0,
sort: options.sort,
viewMode: options.viewMode,
panel: options.panel,
total: data.total || 0,
hasMore: !!data.has_more
});
@@ -393,7 +351,6 @@
sort: options.sort,
viewMode: options.viewMode,
panel: options.panel,
source: options.source,
total: 0,
hasMore: false
});
@@ -477,9 +434,8 @@
perPage: getHomepagePerPage(panel),
sort: currentSort,
viewMode: viewMode,
panel: target,
source: 'home',
tabOrderby: tabOrderby
panel: target,
tabOrderby: tabOrderby
});
}
}
@@ -600,7 +556,7 @@
function initSortbar() {
document.querySelectorAll('.aksrt-homepage__stream').forEach(function (stream) {
var sortbar = stream.querySelector('.aksrt-homepage__sortbar');
if (!sortbar || !isSortAjaxEnabled(sortbar)) return;
if (!sortbar) return;
sortbar.addEventListener('click', function (event) {
var link = event.target.closest('.aksrt-homepage__sortbar-link, .aksrt-homepage__sortbar-dropdown-item');
@@ -612,17 +568,15 @@
event.preventDefault();
setActiveSortLink(sortbar, sort);
replaceUrlSort(sort);
setActiveSortLink(sortbar, sort);
requestHomepagePanel(activePanel, {
requestHomepagePanel(activePanel, {
termId: termId,
page: 1,
perPage: getHomepagePerPage(activePanel),
sort: sort,
viewMode: getStreamViewMode(stream),
panel: activePanel.getAttribute('data-home-tab-panel') || 'latest',
source: 'home'
});
});
});
+2 -2
View File
@@ -81,7 +81,7 @@
html += '<strong class="aksrt-order-detail__hero-amount">' + formatMoney(order) + "</strong></div>";
html += '<div class="aksrt-order-detail__product">';
html += image
? '<img' + window.aksrtGetLazyImageAttr('lazy_posts_thumb', image, 'aksrt-order-detail__thumb') + ' alt="">'
? '<img src="' + escapeHtml(image) + '" class="aksrt-order-detail__thumb" alt="">'
: '<span class="aksrt-order-detail__thumb aksrt-order-detail__thumb--empty"><i class="fa fas fa-book"></i></span>';
html += '<div><strong class="aksrt-order-detail__product-title">' + escapeHtml(title) + "</strong>";
html += '<span class="aksrt-order-detail__product-meta">' + escapeHtml(order.product_type_label || "") + "</span></div></div>";
@@ -179,7 +179,7 @@
function cardHtml(order) {
var image = order.image
? '<img' + window.aksrtGetLazyImageAttr('lazy_posts_thumb', order.image, 'aksrt-orders-page__card-img') + ' alt="">'
? '<img src="' + escapeHtml(order.image) + '" class="aksrt-orders-page__card-img" alt="">'
: '<div class="aksrt-orders-page__card-art" aria-hidden="true"><i class="fa fas fa-book"></i></div>';
var countdown = Number(order.time_left || 0) > 0
? '<span class="aksrt-orders-page__countdown" data-aksrt-order-countdown="' + Number(order.time_left) + '"></span>'
+1 -1
View File
@@ -288,7 +288,7 @@
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 += '<img src="' + this.escHtml(item.thumbnail) + '" class="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>';
+9 -5
View File
@@ -17,10 +17,14 @@
return el;
}
function lazyImage(key, src, className, size) {
var template = document.createElement("template");
template.innerHTML = "<img" + window.aksrtGetLazyImageAttr(key, src, className, size) + ' alt="" decoding="async">';
return template.content.firstElementChild;
function imageNode(src, className) {
if (!src) return null;
var image = document.createElement("img");
image.className = className || "";
image.src = text(src);
image.alt = "";
image.decoding = "async";
return image;
}
function append(parent, child) {
@@ -312,7 +316,7 @@
var levelInfo = levels[String(level)] || {};
var levelHeader = node("div", "aksrt-vip-level-header");
if (levelInfo.img_icon) {
var levelIcon = lazyImage("lazy_other", text(levelInfo.img_icon), "aksrt-vip-level-icon");
var levelIcon = imageNode(text(levelInfo.img_icon), "aksrt-vip-level-icon");
append(levelHeader, levelIcon);
}
append(levelHeader, node("div", "v-name font-bold", group.name || levelInfo.name || "VIP" + level));
+19 -8
View File
@@ -42,8 +42,10 @@
/* ==================== Dashboard 导航 ==================== */
function initDashboardNavigation() {
if (document.documentElement.dataset.aksrtUserCenterNavigationInitialized === 'true') return;
var tabContainer = document.querySelector('.uc-mobile-tabs');
if (!tabContainer) return;
document.documentElement.dataset.aksrtUserCenterNavigationInitialized = 'true';
var tabs = tabContainer.querySelectorAll('[data-tab]');
var sidebarBtns = document.querySelectorAll('.uc-sidebar [data-tab]');
@@ -107,6 +109,7 @@
if (name === 'revenue') allowedParams = ['reward_tab', 'commission_page', 'user_page'];
if (name === 'posts') allowedParams = ['post_status', 'posts_page'];
if (name === 'follow') allowedParams = ['follow_list', 'follow_page'];
if (name === 'credits') allowedParams = ['asset', 'page'];
allowedParams.forEach(function(key) {
if (params.get(key)) url += (url.indexOf('?') === -1 ? '?' : '&') + key + '=' + encodeURIComponent(params.get(key));
});
@@ -166,8 +169,6 @@
function activate(name, updateHistory, forceLoad, queryParams) {
if (!panels[name]) name = defaultTab;
// VIP membership may change outside this page, so never reuse stale HTML.
if (name === 'vip' && !forceLoad) forceLoad = true;
return loadPanel(name, forceLoad, queryParams).then(function() {
switchTo(name);
if (updateHistory) updateUrl(name);
@@ -187,11 +188,11 @@
return tabAliases[name] || name;
}
function updateUrl(name, sourceUrl) {
if (!window.history.replaceState) return;
function updateUrl(name, sourceUrl) {
if (!window.history.pushState) return;
var pathTab = name === 'credits' ? 'balance' : name;
var u = new URL('/user/' + pathTab + '/', window.location.origin);
if (sourceUrl && name !== 'revenue' && name !== 'orders') {
if (sourceUrl && name !== 'revenue' && name !== 'orders' && name !== 'credits') {
var source = new URL(sourceUrl, window.location.href);
source.searchParams.forEach(function(value, key) { u.searchParams.set(key, value); });
}
@@ -249,8 +250,8 @@
name = tabAliases[name] || name;
if (!name || !panels[name]) return;
e.preventDefault();
var hasQuery = url.search !== '';
activate(name, false, hasQuery, url.searchParams).then(function() { updateUrl(name, url.href); });
var hasQuery = url.search !== '' && name !== 'credits';
activate(name, false, hasQuery, name === 'credits' ? null : url.searchParams).then(function() { updateUrl(name, name === 'credits' ? '' : url.href); });
}, true);
var creatorPanel = panels.creator ? panels.creator.panel : null;
if (creatorPanel) {
@@ -269,11 +270,21 @@
});
}
window.addEventListener('popstate', function() {
activate(tabFromUrl(), false, window.location.search !== '', new URLSearchParams(window.location.search));
var current = new URL(window.location.href);
['wallet_page', 'credits_page', 'asset', 'page'].forEach(function (key) { current.searchParams.delete(key); });
if (current.href !== window.location.href && window.history.replaceState) {
window.history.replaceState({}, '', current.pathname + current.search + current.hash);
}
activate(tabFromUrl(), false, false, null);
});
if (drawerTrigger) drawerTrigger.addEventListener('click', openDrawer);
if (drawerOverlay) drawerOverlay.addEventListener('click', closeDrawer);
var initialTab = tabFromUrl();
if (initialTab === 'credits' && window.history && window.history.replaceState) {
var cleanUrl = new URL(window.location.href);
['wallet_page', 'credits_page', 'asset', 'page'].forEach(function (key) { cleanUrl.searchParams.delete(key); });
window.history.replaceState({}, '', cleanUrl.pathname + cleanUrl.search + cleanUrl.hash);
}
if (initialTab === 'orders' && window.location.search && window.history.replaceState) {
window.history.replaceState({}, '', window.location.pathname + window.location.hash);
}
-15
View File
@@ -2258,21 +2258,6 @@ _win.bd.on('click', '[data-toggle="RefreshModal"]', function (e) {
return false;
});
_win.bd.on('click', '.show-order-modal', function (e) {
e.preventDefault();
var _this = jQuery(this);
var order_id = _this.attr('data-order-id');
var is_new = _this.attr('new-modal');
refresh_modal({
class: 'full-sm body-bg',
height: 420,
mobile_from_bottom: true,
touch_close: true,
new: is_new,
remote: _win.ajax_url + '?action=order_details_modal&order_id=' + order_id,
});
});
/**
* 刷新模态框
* @param {object} _config
+1 -1
View File
File diff suppressed because one or more lines are too long
+14 -20
View File
@@ -150,7 +150,7 @@ if (!function_exists('aksrt_homepage_get_sort_mode')) {
$override_sort = get_query_var('aksrt_homepage_sort_override');
$sort = is_string($override_sort) && $override_sort !== ''
? sanitize_key($override_sort)
: (isset($_GET['home_sort']) ? sanitize_key(wp_unslash((string) $_GET['home_sort'])) : $default_sort);
: $default_sort;
return in_array($sort, $allowed, true) ? $sort : $default_sort;
}
@@ -279,19 +279,15 @@ if (!function_exists('aksrt_homepage_render_sortbar')) {
}
$current = aksrt_homepage_get_sort_mode();
$sort_ajax_enabled = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::homepage_sort_ajax_enabled()
: true;
$is_dropdown = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::home_list_sort_dropdown()
: false;
$base_url = remove_query_arg('home_sort', get_pagenum_link(aksrt_homepage_get_current_page()));
$sort_items = aksrt_homepage_get_sort_items();
$all_labels = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::homepage_sort_labels()
: $sort_items;
?>
<div class="aksrt-homepage__sortbar" data-sort-ajax="<?php echo $sort_ajax_enabled ? 'true' : 'false'; ?>" aria-label="<?php esc_attr_e('Homepage Sort', 'aksrt'); ?>">
<div class="aksrt-homepage__sortbar" aria-label="<?php esc_attr_e('Homepage Sort', 'aksrt'); ?>">
<?php if ($is_dropdown) : ?>
<div class="aksrt-homepage__sortbar-dropdown">
<button type="button" class="aksrt-homepage__sortbar-current" data-sort-dropdown-trigger>
@@ -300,26 +296,24 @@ if (!function_exists('aksrt_homepage_render_sortbar')) {
</button>
<div class="aksrt-homepage__sortbar-dropdown-menu" hidden>
<?php foreach ($sort_items as $sort_key => $label) : ?>
<a
class="aksrt-homepage__sortbar-dropdown-item<?php echo $sort_key === $current ? ' is-active' : ''; ?>"
href="<?php echo esc_url(add_query_arg('home_sort', $sort_key, $base_url)); ?>"
data-sort="<?php echo esc_attr($sort_key); ?>"
>
<?php echo esc_html($label); ?>
</a>
<button type="button"
class="aksrt-homepage__sortbar-dropdown-item<?php echo $sort_key === $current ? ' is-active' : ''; ?>"
data-sort="<?php echo esc_attr($sort_key); ?>"
>
<?php echo esc_html($label); ?>
</button>
<?php endforeach; ?>
</div>
</div>
<?php else : ?>
<div class="aksrt-homepage__sortbar-links">
<?php foreach ($sort_items as $sort_key => $label) : ?>
<a
class="aksrt-homepage__sortbar-link<?php echo $sort_key === $current ? ' is-active' : ''; ?>"
href="<?php echo esc_url(add_query_arg('home_sort', $sort_key, $base_url)); ?>"
data-sort="<?php echo esc_attr($sort_key); ?>"
>
<?php echo esc_html($label); ?>
</a>
<button type="button"
class="aksrt-homepage__sortbar-link<?php echo $sort_key === $current ? ' is-active' : ''; ?>"
data-sort="<?php echo esc_attr($sort_key); ?>"
>
<?php echo esc_html($label); ?>
</button>
<?php endforeach; ?>
</div>
<?php endif; ?>
@@ -17,8 +17,11 @@ defined('ABSPATH') || exit;
</h3>
</div>
<?php
$credits_page = max(1, isset($_GET['credits_page']) ? (int) $_GET['credits_page'] : 1);
$log = \AKSRT\Ledger\LedgerQuery::get_instance()->history(['user_id' => $user_id, 'asset' => 'points', 'page' => $credits_page, 'per_page' => 10]);
$asset_request = is_array($asset_request ?? null) ? $asset_request : [];
$credits_page = ($asset_request['asset'] ?? '') === 'points'
? max(1, (int) ($asset_request['page'] ?? 1))
: 1;
$log = \AKSRT\Ledger\LedgerQuery::get_instance()->history(['user_id' => $user_id, 'asset' => 'points', 'page' => $credits_page, 'per_page' => 10]);
if (!empty($log['items'])) :
?>
<div class="uc-transactions__list">
@@ -55,8 +58,8 @@ defined('ABSPATH') || exit;
</div>
<?php
$total_pages = (int) ceil((int) ($log['total'] ?? 0) / 10);
if ($total_pages > 1) {
aksrt_render_profile_pagination($total_pages, $credits_page, 'credits_page');
if ($total_pages > 1 && function_exists('aksrt_render_asset_pagination')) {
aksrt_render_asset_pagination($total_pages, $credits_page, 'points');
}
?>
<?php else : ?>
@@ -0,0 +1,73 @@
<?php
/**
* User center balance transaction list.
*
* Expects extracted $context vars.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
$asset_request = is_array($asset_request ?? null) ? $asset_request : [];
$wallet_page = ($asset_request['asset'] ?? '') === 'balance'
? max(1, (int) ($asset_request['page'] ?? 1))
: 1;
$wlog = \AKSRT\Ledger\LedgerQuery::get_instance()->history([
'user_id' => (int) ($user_id ?? 0),
'asset' => 'balance',
'page' => $wallet_page,
'per_page' => 10,
]);
?>
<div class="uc-transactions uc-transactions--wallet">
<div class="uc-transactions__header">
<h3 class="uc-transactions__title">
<i class="fa fas fa-list-ul" aria-hidden="true"></i>
<?php esc_html_e('余额记录', 'aksrt'); ?>
</h3>
</div>
<?php if (!empty($wlog['items'])) : ?>
<div class="uc-transactions__list">
<?php foreach ($wlog['items'] as $row) :
$delta = (int) $row['delta_minor'];
$delta_class = $delta >= 0 ? 'is-positive' : 'is-negative';
$delta_txt = ($delta > 0 ? '+' : '-') . \AKSRT\Ledger\AssetAmount::from_minor(\AKSRT\Ledger\AssetType::BALANCE, abs($delta))->formatted();
$record_note = (string) $row['reason'] === 'points_exchange'
? (string) $row['ref_key']
: (string) ($row['remark'] ?? '');
if (in_array((string) $row['reason'], ['withdraw_hold', 'withdraw_refund'], true)) {
$record_note = (string) ($row['withdrawal_number'] ?? '');
}
?>
<div class="uc-transactions__item">
<div class="uc-transactions__icon <?php echo esc_attr($delta_class); ?>">
<i class="fa fas fa-<?php echo $delta >= 0 ? 'arrow-up' : 'arrow-down'; ?>" aria-hidden="true"></i>
</div>
<div class="uc-transactions__info">
<div class="uc-transactions__reason">
<?php echo esc_html(aksrt_asset_reason_label((string) $row['reason'])); ?>
<?php if ($record_note !== '') : ?>
<span class="uc-transactions__remark">(<?php echo esc_html($record_note); ?>)</span>
<?php endif; ?>
</div>
<div class="uc-transactions__time"><?php echo esc_html(get_date_from_gmt((string) $row['created_at'], 'Y-m-d H:i')); ?></div>
</div>
<div class="uc-transactions__amount">
<span class="uc-transactions__delta <?php echo esc_attr($delta_class); ?>"><?php echo esc_html($delta_txt); ?></span>
</div>
</div>
<?php endforeach; ?>
</div>
<?php
$total_pages = (int) ceil((int) ($wlog['total'] ?? 0) / 10);
if ($total_pages > 1 && function_exists('aksrt_render_asset_pagination')) {
aksrt_render_asset_pagination($total_pages, $wallet_page, 'balance');
}
?>
<?php else : ?>
<div class="uc-transactions__empty">
<p><?php esc_html_e('还没有任何余额流水', 'aksrt'); ?></p>
</div>
<?php endif; ?>
</div>
@@ -234,65 +234,5 @@ endif; ?>
<?php endif; ?>
<?php if (empty($skip_wallet_ledger)) : ?>
<div class="uc-transactions uc-transactions--wallet">
<div class="uc-transactions__header">
<h3 class="uc-transactions__title">
<i class="fa fas fa-list-ul" aria-hidden="true"></i>
<?php esc_html_e('余额记录', 'aksrt'); ?>
</h3>
</div>
<?php
$wallet_page = max(1, isset($_GET['wallet_page']) ? (int) $_GET['wallet_page'] : 1);
$wlog = \AKSRT\Ledger\LedgerQuery::get_instance()->history(['user_id' => (int) $user_id, 'asset' => 'balance', 'page' => $wallet_page, 'per_page' => 10]);
if (!empty($wlog['items'])) :
?>
<div class="uc-transactions__list">
<?php foreach ($wlog['items'] as $row) :
$delta = (int) $row['delta_minor'];
$delta_class = $delta >= 0 ? 'is-positive' : 'is-negative';
$delta_txt = ($delta > 0 ? '+' : '-') . \AKSRT\Ledger\AssetAmount::from_minor(\AKSRT\Ledger\AssetType::BALANCE, abs($delta))->formatted();
$record_note = in_array((string) $row['reason'], ['withdraw_hold', 'withdraw_refund'], true)
? (string) ($row['withdrawal_number'] ?? '')
: (string) ($row['remark'] ?? '');
?>
<div class="uc-transactions__item">
<div class="uc-transactions__icon <?php echo esc_attr($delta_class); ?>">
<i class="fa fas fa-<?php echo $delta >= 0 ? 'arrow-up' : 'arrow-down'; ?>" aria-hidden="true"></i>
</div>
<div class="uc-transactions__info">
<div class="uc-transactions__reason">
<?php echo esc_html(aksrt_asset_reason_label((string) $row['reason'])); ?>
<?php if ($record_note !== '') : ?>
<span class="uc-transactions__remark">(<?php echo esc_html($record_note); ?>)</span>
<?php endif; ?>
</div>
<div class="uc-transactions__time"><?php echo esc_html(get_date_from_gmt((string) $row['created_at'], 'Y-m-d H:i')); ?></div>
</div>
<div class="uc-transactions__amount">
<span class="uc-transactions__delta <?php echo esc_attr($delta_class); ?>"><?php echo esc_html($delta_txt); ?></span>
<span class="uc-transactions__balance">
<?php
printf(
esc_html__('余额 %s', 'aksrt'),
esc_html(\AKSRT\Ledger\AssetAmount::from_minor(\AKSRT\Ledger\AssetType::BALANCE, (int) $row['balance_after_minor'])->formatted())
);
?>
</span>
</div>
</div>
<?php endforeach; ?>
</div>
<?php
$total_pages = (int) ceil((int) ($wlog['total'] ?? 0) / 10);
if ($total_pages > 1 && function_exists('aksrt_render_profile_pagination')) {
aksrt_render_profile_pagination($total_pages, $wallet_page, 'wallet_page');
}
?>
<?php else : ?>
<div class="uc-transactions__empty">
<div class="uc-transactions__empty-icon"><i class="fa fas fa-money-bill" aria-hidden="true"></i></div>
<p><?php esc_html_e('还没有任何余额流水', 'aksrt'); ?></p>
</div>
<?php endif; ?>
</div>
<?php endif; // skip_wallet_ledger ?>
<?php require __DIR__ . '/credits-wallet-ledger.php'; ?>
<?php endif; ?>
@@ -44,8 +44,12 @@ if ($show_wallet || $show_credits) {
// Tabbed ledgers (legacy record tabs).
if ($show_wallet || $show_credits) :
$active_record = $show_wallet ? 'balance' : 'points';
$show_tasks = $show_credits && class_exists('\AKSRT\Credits\PointsFree') && \AKSRT\Credits\PointsFree::enabled();
$asset_request = is_array($asset_request ?? null) ? $asset_request : [];
$requested_asset = in_array(($asset_request['asset'] ?? ''), ['balance', 'points'], true) ? $asset_request['asset'] : '';
$active_record = $requested_asset !== ''
? $requested_asset
: ($show_wallet ? 'balance' : 'points');
?>
<div class="uc-asset-tabs">
<ul class="uc-asset-tabs__nav list-inline tab-nav-theme font-bold">
@@ -68,58 +72,7 @@ if ($show_wallet || $show_credits) :
<div class="uc-asset-tabs__content tab-content">
<?php if ($show_wallet) : ?>
<div class="tab-pane<?php echo $active_record === 'balance' ? ' is-active' : ''; ?>" id="record_tab_balance" data-aksrt-asset-pane="balance"<?php echo $active_record === 'balance' ? '' : ' hidden'; ?>>
<?php
$skip_wallet_ledger = false;
// Render only the ledger portion by reusing balance log markup.
$user_id = (int) ($user_id ?? 0);
$wallet_page = max(1, isset($_GET['wallet_page']) ? (int) $_GET['wallet_page'] : 1);
$wlog = \AKSRT\Ledger\LedgerQuery::get_instance()->history(['user_id' => $user_id, 'asset' => 'balance', 'page' => $wallet_page, 'per_page' => 10]);
?>
<div class="uc-transactions uc-transactions--wallet">
<?php if (!empty($wlog['items'])) : ?>
<div class="uc-transactions__list">
<?php foreach ($wlog['items'] as $row) :
$delta = (int) $row['delta_minor'];
$delta_class = $delta >= 0 ? 'is-positive' : 'is-negative';
$delta_txt = ($delta > 0 ? '+' : '-') . \AKSRT\Ledger\AssetAmount::from_minor(\AKSRT\Ledger\AssetType::BALANCE, abs($delta))->formatted();
$record_note = (string) $row['reason'] === 'points_exchange'
? (string) $row['ref_key']
: (string) $row['remark'];
if (in_array((string) $row['reason'], ['withdraw_hold', 'withdraw_refund'], true)) {
$record_note = (string) ($row['withdrawal_number'] ?? '');
}
?>
<div class="uc-transactions__item">
<div class="uc-transactions__icon <?php echo esc_attr($delta_class); ?>">
<i class="fa fas fa-<?php echo $delta >= 0 ? 'arrow-up' : 'arrow-down'; ?>" aria-hidden="true"></i>
</div>
<div class="uc-transactions__info">
<div class="uc-transactions__reason">
<?php echo esc_html(aksrt_asset_reason_label((string) $row['reason'])); ?>
<?php if ($record_note !== '') : ?>
<span class="uc-transactions__remark">(<?php echo esc_html($record_note); ?>)</span>
<?php endif; ?>
</div>
<div class="uc-transactions__time"><?php echo esc_html(get_date_from_gmt((string) $row['created_at'], 'Y-m-d H:i')); ?></div>
</div>
<div class="uc-transactions__amount">
<span class="uc-transactions__delta <?php echo esc_attr($delta_class); ?>"><?php echo esc_html($delta_txt); ?></span>
</div>
</div>
<?php endforeach; ?>
</div>
<?php
$total_pages = (int) ceil((int) ($wlog['total'] ?? 0) / 10);
if ($total_pages > 1 && function_exists('aksrt_render_profile_pagination')) {
aksrt_render_profile_pagination($total_pages, $wallet_page, 'wallet_page');
}
?>
<?php else : ?>
<div class="uc-transactions__empty">
<p><?php esc_html_e('还没有任何余额流水', 'aksrt'); ?></p>
</div>
<?php endif; ?>
</div>
<?php require $dir . '/credits-wallet-ledger.php'; ?>
</div>
<?php endif; ?>
<?php if ($show_credits) : ?>
-2
View File
@@ -114,9 +114,7 @@ class GlobalController extends ControllerBase {
'page' => ['type' => 'integer', 'default' => 1, 'sanitize_callback' => 'absint'],
'per_page' => ['type' => 'integer', 'default' => 10, 'sanitize_callback' => 'absint'],
'sort' => ['type' => 'string', 'default' => 'modified', 'sanitize_callback' => 'sanitize_text_field'],
'page_url' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'esc_url_raw'],
'panel' => ['type' => 'string', 'default' => 'latest', 'sanitize_callback' => 'sanitize_key'],
'source' => ['type' => 'string', 'default' => 'home', 'sanitize_callback' => 'sanitize_key'],
'tab_orderby' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_key'],
],
]);
-1
View File
@@ -479,7 +479,6 @@ trait ScriptsPart1c {
'commentPaginationMode' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_comment_pagination_mode() : 'numbered',
'homepagePostsPerPage' => function_exists('aksrt_homepage_get_tab_posts_per_page') ? aksrt_homepage_get_tab_posts_per_page() : (int) get_option('posts_per_page'),
'homepageDefaultSort' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::homepage_default_sort() : 'modified',
'homepageSortAjax' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::homepage_sort_ajax_enabled() : true,
'homepageStickyTabs' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::homepage_sticky_tabs_enabled() : true,
'homePostsListEnabled' => !class_exists('\AKSRT\Settings\Settings') || \AKSRT\Settings\Settings::home_posts_list_enabled(),
'homeListSortEnabled' => !class_exists('\AKSRT\Settings\Settings') || \AKSRT\Settings\Settings::home_list_sort_enabled(),
-4
View File
@@ -175,10 +175,6 @@ class HomeCms {
return $settings['home_list1_orderby_option']['lists'][0] ?? 'modified';
}
public static function homepage_sort_ajax_enabled(): bool {
return true;
}
public static function homepage_sticky_tabs_enabled(): bool {
return true;
}
-7
View File
@@ -68,13 +68,6 @@ trait HomepageReader {
return HomeCms::homepage_default_sort();
}
/**
* Whether the homepage sortbar should refresh through AJAX.
*/
public static function homepage_sort_ajax_enabled(): bool {
return HomeCms::homepage_sort_ajax_enabled();
}
/**
* Whether the homepage tabs should use sticky/affix behaviour.
*/
+24 -1
View File
@@ -46,6 +46,16 @@ class Center {
return preg_match('/^[a-z0-9_-]+$/', (string) $value) === 1;
},
],
'asset' => [
'type' => 'string',
'default' => '',
'sanitize_callback' => 'sanitize_key',
],
'page' => [
'type' => 'integer',
'default' => 1,
'sanitize_callback' => 'absint',
],
],
]);
}
@@ -68,6 +78,17 @@ class Center {
return new \WP_Error('aksrt_user_center_context', __('用户中心数据加载失败', 'aksrt'), ['status' => 500]);
}
$asset_request = [];
if ($tab === 'credits') {
$asset = sanitize_key((string) $request->get_param('asset'));
$asset = in_array($asset, ['balance', 'points'], true) ? $asset : '';
$asset_request = [
'asset' => $asset,
'page' => max(1, min(100, (int) $request->get_param('page'))),
];
$context['asset_request'] = $asset_request;
}
$income_request = [];
if ($tab === 'creator') {
$income_request = [
@@ -104,7 +125,9 @@ class Center {
'reward_tab' => $revenue_request['reward_tab'] ?? '',
'creator_income' => $income_request,
'commission_page' => $revenue_request['commission_page'] ?? 1,
'user_page' => $revenue_request['user_page'] ?? 1,
'user_page' => $revenue_request['user_page'] ?? 1,
'asset' => $asset_request['asset'] ?? '',
'asset_page' => $asset_request['page'] ?? 1,
],
]);
}
+42
View File
@@ -40,6 +40,48 @@ if (!function_exists('aksrt_render_profile_pagination')) {
}
}
if (!function_exists('aksrt_render_asset_pagination')) {
/**
* Render user asset pagination without URL state.
*/
function aksrt_render_asset_pagination(int $total_pages, int $current, string $asset): void {
if ($total_pages <= 1) {
return;
}
$current = max(1, min($total_pages, $current));
$pages = array_values(array_unique(array_filter([
1,
$current - 1,
$current,
$current + 1,
$total_pages,
], static fn(int $page): bool => $page >= 1 && $page <= $total_pages)));
sort($pages);
echo '<nav class="profile-pagination" aria-label="' . esc_attr__('分页', 'aksrt') . '">';
if ($current > 1) {
echo '<button type="button" class="page-numbers prev" data-aksrt-asset-page="' . (int) ($current - 1) . '" data-aksrt-asset-key="' . esc_attr($asset) . '"><span aria-hidden="true">&lsaquo;</span><span class="screen-reader-text">' . esc_html__('上一页', 'aksrt') . '</span></button>';
}
$previous = 0;
foreach ($pages as $page) {
if ($previous > 0 && $page > $previous + 1) {
echo '<span class="page-numbers dots">&hellip;</span>';
}
if ($page === $current) {
echo '<span class="page-numbers current" aria-current="page">' . (int) $page . '</span>';
} else {
echo '<button type="button" class="page-numbers" data-aksrt-asset-page="' . (int) $page . '" data-aksrt-asset-key="' . esc_attr($asset) . '">' . (int) $page . '</button>';
}
$previous = $page;
}
if ($current < $total_pages) {
echo '<button type="button" class="page-numbers next" data-aksrt-asset-page="' . (int) ($current + 1) . '" data-aksrt-asset-key="' . esc_attr($asset) . '"><span aria-hidden="true">&rsaquo;</span><span class="screen-reader-text">' . esc_html__('下一页', 'aksrt') . '</span></button>';
}
echo '</nav>';
}
}
if (!function_exists('aksrt_render_profile_empty')) {
/**
* 渲染空状态卡片
@@ -78,16 +78,32 @@ test('user asset submissions send idempotency keys', () => {
})
test('profile asset records show order references and only the changed amount', () => {
for (const file of ['profile-credits.php', 'credits-transactions.php']) {
for (const file of ['credits-transactions.php', 'credits-wallet-ledger.php']) {
const source = fs.readFileSync(path.join(root, 'inc', 'helpers', 'user-center-renderers', file), 'utf8')
assert.match(source, /\$record_note\s*=\s*\(string\) \$row\['reason'\] === 'points_exchange'[\s\S]*?\(string\) \$row\['ref_key'\]/)
assert.doesNotMatch(source, /uc-transactions__balance|交易 #%d|\$row\['fee_minor'\]|\$row\['ref_id'\]/)
}
})
test('asset record tabs and pagination stay in the DOM without URL state', () => {
const profile = fs.readFileSync(path.join(root, 'inc', 'helpers', 'user-center-renderers', 'profile-credits.php'), 'utf8')
const points = fs.readFileSync(path.join(root, 'inc', 'helpers', 'user-center-renderers', 'credits-transactions.php'), 'utf8')
const wallet = fs.readFileSync(path.join(root, 'inc', 'helpers', 'user-center-renderers', 'credits-wallet-ledger.php'), 'utf8')
const credits = fs.readFileSync(path.join(root, 'assets', 'js', 'credits.js'), 'utf8')
const center = fs.readFileSync(path.join(root, 'src', 'User', 'Center.php'), 'utf8')
assert.match(profile, /data-aksrt-asset-tab="balance"/)
assert.match(profile, /data-aksrt-asset-tab="points"/)
assert.match(points, /aksrt_render_asset_pagination\([^;]+, 'points'\)/)
assert.match(wallet, /aksrt_render_asset_pagination\([^;]+, 'balance'\)/)
assert.doesNotMatch(profile + points + wallet, /\$_GET\[['"](?:wallet_page|credits_page)['"]\]/)
assert.match(credits, /data-aksrt-asset-page/)
assert.match(credits, /user-center\/panel\/credits\?asset=/)
assert.match(center, /'asset'\s*=>\s*\[/)
assert.match(center, /\$context\['asset_request'\]/)
})
test('core asset paths never use floating-point money helpers', () => {
const files = [
'src/Ledger/AssetAmount.php',
'src/Ledger/LedgerCommand.php',
'src/Ledger/LedgerService.php',
'src/Credits/BalanceService.php',
@@ -39,13 +39,13 @@ test('native and front-end surfaces expose immutable business numbers', () => {
assert.match(read('src/Admin/Native/PayWithdrawals.php'), /提现单号/)
assert.match(read('src/Payment/UserOrders.php'), /\$view\['flow_number'\]/)
assert.match(read('assets/js/order-center.js'), /\["流水号", order\.flow_number \|\| "—"\]/)
assert.match(read('inc/helpers/user-center-renderers/credits-wallet.php'), /withdrawal_number/)
assert.match(read('inc/helpers/user-center-renderers/credits-wallet-ledger.php'), /withdrawal_number/)
})
test('balance history renders withdrawal numbers from the committed flow', () => {
const query = read('src/Ledger/LedgerQuery.php')
const profile = read('inc/helpers/user-center-renderers/profile-credits.php')
const wallet = read('inc/helpers/user-center-renderers/credits-wallet.php')
const profile = read('inc/helpers/user-center-renderers/credits-wallet-ledger.php')
const wallet = read('inc/helpers/user-center-renderers/credits-wallet.php') + read('inc/helpers/user-center-renderers/credits-wallet-ledger.php')
assert.match(query, /LEFT JOIN \{\$flow_table\} f ON f\.ledger_transaction_id = t\.id/)
assert.match(query, /'withdrawal_number'\s*=>\s*\(string\)/)
for (const source of [profile, wallet]) {
@@ -99,6 +99,29 @@ test('routed modules use one namespaced auto_fun owner and no registry', () => {
}
});
test('user center navigation initializes once across auto_fun rescans', () => {
const source = read('assets/js/routes/user-center.js');
const initAt = source.indexOf('function initDashboardNavigation()');
const guardAt = source.indexOf('aksrtUserCenterNavigationInitialized', initAt);
const initialActivateAt = source.indexOf('activate(initialTab, false)', initAt);
assert.ok(initAt >= 0);
assert.ok(guardAt > initAt && guardAt < initialActivateAt);
assert.match(source.slice(initAt, initialActivateAt), /if \(document\.documentElement\.dataset\.aksrtUserCenterNavigationInitialized === 'true'\) return;/);
assert.match(source.slice(initAt, initialActivateAt), /document\.documentElement\.dataset\.aksrtUserCenterNavigationInitialized = 'true';/);
assert.doesNotMatch(source, /name === 'vip' && !forceLoad\) forceLoad = true/);
});
test('homepage sort refreshes one DOM panel without URL state', () => {
const source = read('assets/js/homepage-fixed.js')
const homepage = read('inc/helpers/aksrt-homepage.php')
const controller = read('src/Admin/GlobalController.php')
assert.match(source, /event\.preventDefault\(\)/)
assert.match(source, /requestHomepagePanel\(activePanel, \{/)
assert.doesNotMatch(source, /home_sort|replaceState|location\.search|data-sort-ajax|homepageSortAjax/)
assert.doesNotMatch(homepage, /home_sort|data-sort-ajax|add_query_arg\(['"]home_sort['"]|homepage_sort_ajax_enabled/)
assert.doesNotMatch(controller, /'page_url'\s*=>|'source'\s*=>/)
})
test('HTML fragment injection announces post_ajax before auto_fun', () => {
for (const file of ['assets/js/core/pagination.js', 'assets/js/author.js', 'assets/js/credits.js', 'assets/js/routes/user-center.js']) {
const source = read(file);
@@ -157,6 +157,18 @@ test('lazySizes has one auto_fun integration owner and loader boots main once',
assert.match(runtime, /var cases = jQuery\('\.vanilla-tilt:not\(\.is-initialized\)'\);[\s\S]*?if \(!cases\.length\)[\s\S]*?tbquire\(\['tilt'\]/);
assert.doesNotMatch(pagination, /IntersectionObserver|new Image\(/);
assert.doesNotMatch(widgets, /IntersectionObserver/);
const dynamicSources = [
read('assets/js/order-center.js'),
read('assets/js/credits.js'),
read('assets/js/page-navs.js'),
read('assets/js/pay-vip.js'),
loader,
].join('\n');
const legacyDynamicImageApi = 'aksrt' + 'GetLazyImageAttr';
assert.equal(dynamicSources.includes(legacyDynamicImageApi), false);
assert.match(read('assets/js/order-center.js'), /<img src="' \+ escapeHtml\(image\)/);
assert.match(read('assets/js/page-navs.js'), /<img src="' \+ this\.escHtml\(item\.thumbnail\)/);
assert.match(read('assets/js/pay-vip.js'), /document\.createElement\("img"\)/);
});
test('post lists have no second lazy switch and keep legacy fallback thumbnails', () => {