Files
wordpress-theme-aksrt/assets/widgets/frontend.js
T
kate522 8aa116ec43 fix(theme): align reference layouts and restore passing contracts
Align homepage and category discovery, modern widgets, admin controls, quotes, and comments with the reference theme. Remove superseded paths and add rendering and behavior coverage. Update stale asset and finance contracts; all 760 Node tests and 139 widget assertions pass.
2026-10-29 19:16:03 +08:00

337 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.
(function () {
'use strict';
var config = window.aksrtWidgets || {};
var reducedMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
function once(el, key) {
var name = 'aksrt' + key;
if (el.dataset[name]) return false;
el.dataset[name] = '1';
return true;
}
function moveTabFocus(event, root) {
var current = event.target.closest('[role="tab"]');
if (!current) return;
var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
var index = tabs.indexOf(current);
var next = index;
if (event.key === 'ArrowRight') next = (index + 1) % tabs.length;
else if (event.key === 'ArrowLeft') next = (index - 1 + tabs.length) % tabs.length;
else if (event.key === 'Home') next = 0;
else if (event.key === 'End') next = tabs.length - 1;
else return;
event.preventDefault();
tabs[next].focus();
tabs[next].click();
}
function videoType(url) {
if (/\.m3u8(?:\?|$)/i.test(url)) return 'hls';
if (/\.flv(?:\?|$)/i.test(url)) return 'flv';
if (/\.mpd(?:\?|$)/i.test(url)) return 'dash';
return 'auto';
}
function initVideo(el) {
// 幂等标记落在 data-aksrt-widget-video;data-aksrt-video 是
// AksrtVideoPlayer 的自动初始化契约,被扫到会在容器里再挂一个无源播放器。
if (!once(el, 'WidgetVideo')) return;
var fallback = el.querySelector('video');
if (fallback) {
fallback.addEventListener('error', function () {
var message = el.querySelector('.aksrt-widget-media-error');
if (message) message.hidden = false;
});
}
function mountPlayer(event) {
if (typeof window.DPlayer !== 'function' || el.aksrtPlayer) return;
if (event) event.preventDefault();
var holder = document.createElement('div');
holder.className = 'aksrt-widget-video__player';
el.insertBefore(holder, fallback || el.firstChild);
try {
el.aksrtPlayer = new window.DPlayer({
container: holder,
autoplay: el.dataset.autoplay === '1' && !reducedMotion,
video: {
url: el.dataset.videoUrl || '',
pic: el.dataset.videoPoster || '',
type: videoType(el.dataset.videoUrl || '')
}
});
if (el.dataset.muted === '1' && el.aksrtPlayer.video) el.aksrtPlayer.video.muted = true;
if (fallback) fallback.hidden = true;
} catch (error) {
holder.remove();
}
}
if (fallback && fallback.classList.contains('lazyload')) {
fallback.addEventListener('lazybeforeunveil', mountPlayer, { once: true });
} else {
mountPlayer();
}
}
function initQuote(figure) {
if (!once(figure, 'Quote')) return;
if (window.AksrtYiyan) window.AksrtYiyan.init(figure);
}
function errorMarkup() {
var i18n = config.i18n || {};
return '<div class="aksrt-widget-request-error" role="alert"><span>' + (i18n.error || '加载失败,请重试。') + '</span><button type="button" data-widget-retry>' + (i18n.retry || '重试') + '</button></div>';
}
function itemsUrl(widget, instance, page, segment) {
var base = String(config.restUrl || '').replace(/\/$/, '');
var url = base + '/' + encodeURIComponent(widget) + '/items?instance=' + encodeURIComponent(instance) + '&page=' + encodeURIComponent(page);
if (segment !== undefined && segment !== '') url += '&segment=' + encodeURIComponent(segment);
return url;
}
function loaderOf(container) {
if (!container) return null;
return container.querySelector('.post_ajax_loader');
}
function fallbackLoader() {
var text = (window.aksrtData && aksrtData.ajaxLoading) || '加载中...';
var node = document.createElement('div');
node.className = 'post_ajax_loader';
node.setAttribute('role', 'status');
node.innerHTML = '<div class="mb20"><div class="text-center muted-2-color mt20"><i class="loading mr10"></i>' + text + '</div></div>';
return node;
}
function setBusy(container, busy) {
if (!container) return;
if (busy) {
container.dataset.loading = '1';
container.setAttribute('aria-busy', 'true');
// 骨架开关走 .is-loading 类(modal-runtime.css),不写 inline display。
var loader = loaderOf(container) || container.appendChild(fallbackLoader());
loader.classList.add('is-loading');
} else {
delete container.dataset.loading;
container.setAttribute('aria-busy', 'false');
var loader = loaderOf(container);
if (loader) loader.classList.remove('is-loading');
}
}
// 参考主题 post_ajax 的 beforeSend 语义:骨架每次请求都挪到列表末尾
// (最后一个条目之后、分页控件之前),请求成功后只隐藏而不删除,
// 下一轮翻页复用同一个节点。
function anchorLoader(container) {
var pag = container.querySelector(':scope > .ajax-pag');
var loader = loaderOf(container);
if (!loader) return null;
if (pag) {
pag.parentNode.insertBefore(loader, pag);
} else {
container.appendChild(loader);
}
return loader;
}
function loadItems(container, page, append) {
var owner = container.closest('[data-widget]') || container;
var widget = owner.dataset.widget || container.dataset.widget;
var instance = owner.dataset.instance || container.dataset.instance;
var segment = container.dataset.segment || owner.dataset.segment || '';
if (!widget || !instance || !config.restUrl || container.dataset.loading === '1') return;
setBusy(container, true);
anchorLoader(container);
return fetch(itemsUrl(widget, instance, page, segment), { credentials: 'same-origin', headers: { Accept: 'application/json' } })
.then(function (response) { if (!response.ok) throw new Error('items'); return response.json(); })
.then(function (data) {
var fragment = document.createElement('div');
fragment.innerHTML = String(data.html || '');
if (append) {
// 追加方向为底部:新条目插到骨架之前,骨架始终贴住最后一张卡片。
var anchor = loaderOf(container) || container.querySelector(':scope > .ajax-pag');
Array.prototype.slice.call(fragment.children).forEach(function (node) {
if (anchor && anchor.parentNode === container) {
container.insertBefore(node, anchor);
} else {
container.appendChild(node);
}
});
} else {
// 替换时保留骨架节点本身(参考主题 success 后只 hide() 不删除)。
var loader = loaderOf(container);
Array.prototype.slice.call(container.children).forEach(function (child) {
if (child !== loader && !child.classList.contains('ajax-pag')) child.remove();
});
Array.prototype.slice.call(fragment.children).forEach(function (node) { container.appendChild(node); });
}
Array.prototype.slice.call(container.querySelectorAll(':scope > .ajax-pag')).forEach(function (node) { node.remove(); });
if (data.paginate_html) container.insertAdjacentHTML('beforeend', data.paginate_html);
container.dataset.page = String(data.page || page);
container.dataset.loaded = '1';
anchorLoader(container);
scan(container);
})
.catch(function () {
var loader = loaderOf(container);
if (loader && loader.parentNode === container) {
loader.insertAdjacentHTML('beforebegin', errorMarkup());
} else {
container.insertAdjacentHTML('afterbegin', errorMarkup());
}
})
.finally(function () { setBusy(container, false); });
}
function initItems(container) {
if (!once(container, 'Items')) return;
container.addEventListener('click', function (event) {
var retry = event.target.closest('[data-widget-retry]');
var trigger = event.target.closest('[data-next-page]');
var pageLink = event.target.closest('.page-numbers[data-widget-page]');
if (retry) {
loadItems(container, Number(container.dataset.page || 1), false);
return;
}
if (trigger) {
// data-widget-ias marks the hidden reveal trigger the runtime clicks
// when the lazy container scrolls into view; it reloads page one.
event.preventDefault();
if (trigger.dataset.widgetIas === '1') loadItems(container, 1, false);
else loadItems(container, Number(trigger.dataset.nextPage || 2), true);
return;
}
if (pageLink) {
event.preventDefault();
loadItems(container, Number(pageLink.dataset.widgetPage || 1), false);
}
});
}
function initTabs(tabs) {
if (!once(tabs, 'Tabs')) return;
tabs.addEventListener('keydown', function (event) { moveTabFocus(event, tabs); });
tabs.addEventListener('click', function (event) {
var button = event.target.closest('[role="tab"][data-segment]');
if (!button) return;
event.preventDefault();
tabs.querySelectorAll('[role="tab"]').forEach(function (tab) {
tab.setAttribute('aria-selected', tab === button ? 'true' : 'false');
tab.setAttribute('tabindex', tab === button ? '0' : '-1');
if (tab.parentElement) tab.parentElement.classList.toggle('active', tab === button);
});
tabs.querySelectorAll('.tab-content > .tab-pane').forEach(function (item) {
var active = item.id === button.getAttribute('aria-controls');
item.classList.toggle('active', active); item.classList.toggle('in', active);
});
var panel = tabs.querySelector('[data-widget-items][data-segment="' + button.dataset.segment + '"]');
if (!panel) panel = tabs.querySelector('[data-widget-items]');
if (!panel) return;
panel.dataset.segment = button.dataset.segment;
panel.setAttribute('aria-labelledby', button.id);
if (panel.dataset.loaded !== '1') loadItems(panel, 1, false);
});
}
function initFaq(faq) {
if (!once(faq, 'Faq')) return;
faq.addEventListener('click', function (event) {
var button = event.target.closest('.aksrt-faq__item > button');
if (!button) return;
var expanded = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', expanded ? 'false' : 'true');
var panel = document.getElementById(button.getAttribute('aria-controls'));
if (panel) panel.hidden = expanded;
});
var search = faq.querySelector('input[type="search"]');
if (search) search.addEventListener('input', function () {
var needle = search.value.trim().toLowerCase();
faq.querySelectorAll('.aksrt-faq__item').forEach(function (item) { item.hidden = needle && String(item.dataset.search || '').toLowerCase().indexOf(needle) === -1; });
});
}
function initFeatures(features) {
if (!once(features, 'Features')) return;
features.addEventListener('keydown', function (event) { moveTabFocus(event, features); });
features.addEventListener('click', function (event) {
var button = event.target.closest('[data-feature-index]');
if (!button) return;
var index = button.dataset.featureIndex;
features.querySelectorAll('[data-feature-index]').forEach(function (tab) {
tab.setAttribute('aria-selected', tab === button ? 'true' : 'false');
tab.setAttribute('tabindex', tab === button ? '0' : '-1');
});
features.querySelectorAll('[data-feature-panel]').forEach(function (panel) { panel.hidden = panel.dataset.featurePanel !== index; });
});
}
function initCount(el) {
if (!once(el, 'Count') || reducedMotion) return;
var raw = el.dataset.countUp || '';
var match = raw.match(/-?[0-9]+(?:\.[0-9]+)?/);
if (!match) return;
var target = Number(match[0]);
var prefix = raw.slice(0, match.index);
var suffix = raw.slice(match.index + match[0].length);
var start = performance.now();
function frame(now) {
var progress = Math.min(1, (now - start) / 900);
el.textContent = prefix + Math.round(target * (1 - Math.pow(1 - progress, 3))) + suffix;
if (progress < 1) requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
}
/* 幻灯片加图文视频卡片:以左侧幻灯片实际高度重算右侧卡片比例。
* 合作伙伴滚动由运行时 main.js 初始化(.zib-partners-scroll 唯一所有者)。 */
function measureSliderGraphic(widget) {
if (once(widget, 'Combo')) return;
var slider = widget.querySelector('.slider-col .zib-slider') || widget.querySelector('.slider-col');
var covers = widget.querySelector('.cover-col');
if (!slider || !covers) return;
var row = Number(widget.dataset.row || 2);
var col = Number(widget.dataset.col || 2);
var gap = 10;
function update() {
var height = slider.offsetHeight;
var width = covers.offsetWidth;
if (!height || !width) return;
var ratio = (((height - (col - 1) * gap) / col) / ((width - (row - 1) * gap) / row)) * 100;
covers.style.setProperty('--cover-ratio', ratio.toFixed(2) + '%');
}
update();
window.addEventListener('resize', update, { passive: true });
if ('ResizeObserver' in window) new ResizeObserver(update).observe(slider);
}
function scan(root) {
var scope = root || document;
scope.querySelectorAll('.aksrt-widget-video[data-video-url]').forEach(initVideo);
scope.querySelectorAll('.aksrt-widget-quote').forEach(initQuote);
scope.querySelectorAll('[data-widget-items]').forEach(initItems);
scope.querySelectorAll('.aksrt-widget-tabs, .widget-tab-post').forEach(initTabs);
scope.querySelectorAll('.aksrt-faq').forEach(initFaq);
scope.querySelectorAll('.aksrt-feature-intro').forEach(initFeatures);
scope.querySelectorAll('[data-count-up]').forEach(initCount);
scope.querySelectorAll('.widget-slider-graphic').forEach(measureSliderGraphic);
if (typeof window.VanillaTilt !== 'undefined') scope.querySelectorAll('[data-aksrt-tilt]').forEach(function (el) { if (once(el, 'Tilt')) window.VanillaTilt.init(el); });
}
function ready() {
scan(document);
if ('MutationObserver' in window) {
new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) { mutation.addedNodes.forEach(function (node) { if (node.nodeType === 1) scan(node); }); });
}).observe(document.body, { childList: true, subtree: true });
}
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', ready);
else ready();
// AJAX 注入新内容后由 auto_fun 重扫(once() data 标记保证幂等)。
if (window.jQuery) window.jQuery(document).on('auto_fun', function () { scan(document); });
})();