386 lines
17 KiB
JavaScript
386 lines
17 KiB
JavaScript
(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(page, append) {
|
||
var i18n = config.i18n || {};
|
||
return '<div class="aksrt-widget-request-error" role="alert"><span>' + (i18n.error || '加载失败,请重试。') + '</span><button type="button" data-widget-retry data-retry-page="' + page + '" data-retry-append="' + (append ? '1' : '0') + '">' + (i18n.retry || '重试') + '</button></div>';
|
||
}
|
||
|
||
function itemsUrl(widget, instance, page, segment) {
|
||
var url = new URL(config.restUrl, window.location.href);
|
||
var path = '/' + encodeURIComponent(widget) + '/items';
|
||
if (url.searchParams.has('rest_route')) {
|
||
url.searchParams.set('rest_route', url.searchParams.get('rest_route').replace(/\/$/, '') + path);
|
||
} else {
|
||
url.pathname = url.pathname.replace(/\/$/, '') + path;
|
||
}
|
||
url.searchParams.set('instance', instance);
|
||
url.searchParams.set('page', page);
|
||
if (segment !== undefined && segment !== '') url.searchParams.set('segment', segment);
|
||
return url.href;
|
||
}
|
||
|
||
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');
|
||
}
|
||
}
|
||
|
||
// 追加时骨架紧跟已有条目;替换时先清空旧页,骨架留在新页的位置。
|
||
// 请求成功后隐藏并保留同一个骨架节点,供下一轮请求复用。
|
||
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;
|
||
container.querySelectorAll(':scope > .aksrt-widget-request-error').forEach(function (error) { error.remove(); });
|
||
setBusy(container, true);
|
||
var previous = [];
|
||
if (!append) {
|
||
// 对齐参考主题 beforeSend:数字分页与跳页先移除旧内容,而非等待响应。
|
||
// 暂存原节点,只在请求失败时恢复内容和分页控件。
|
||
var loader = loaderOf(container);
|
||
Array.prototype.slice.call(container.children).forEach(function (child) {
|
||
if (child === loader) return;
|
||
previous.push(child);
|
||
child.remove();
|
||
});
|
||
}
|
||
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 || '');
|
||
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);
|
||
}
|
||
});
|
||
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);
|
||
previous = [];
|
||
scan(container);
|
||
if (window.jQuery) window.jQuery(document).trigger('auto_fun');
|
||
})
|
||
.catch(function () {
|
||
var loader = loaderOf(container);
|
||
previous.forEach(function (node) { container.insertBefore(node, loader); });
|
||
anchorLoader(container);
|
||
if (loader && loader.parentNode === container) {
|
||
loader.insertAdjacentHTML('beforebegin', errorMarkup(page, append));
|
||
} else {
|
||
container.insertAdjacentHTML('afterbegin', errorMarkup(page, append));
|
||
}
|
||
})
|
||
.finally(function () { setBusy(container, false); });
|
||
}
|
||
|
||
function initItems(container) {
|
||
if (!once(container, 'Items')) return;
|
||
if (container.hasAttribute('data-widget-lazy') && container.dataset.loaded !== '1') {
|
||
container.addEventListener('lazybeforeunveil', function (event) {
|
||
if (event.target === container && container.dataset.loaded !== '1') loadItems(container, 1, false);
|
||
});
|
||
// main.js loads asynchronously through loader_js. Its lazySizes scan
|
||
// may finish before this client binds, so consume an unveiled pager.
|
||
if (!container.classList.contains('lazyload')) loadItems(container, 1, false);
|
||
}
|
||
function jumpTo(input) {
|
||
var page = Math.min(Number(input.max), Math.floor(Number(input.value)));
|
||
if (!Number.isFinite(page) || page < 1 || page === Number(container.dataset.page || 1)) {
|
||
input.focus();
|
||
return;
|
||
}
|
||
loadItems(container, page, false);
|
||
}
|
||
container.addEventListener('input', function (event) {
|
||
if (!event.target.matches('[data-widget-jump]')) return;
|
||
event.stopPropagation();
|
||
var page = Math.floor(Number(event.target.value));
|
||
event.target.value = page > 0 ? String(Math.min(Number(event.target.max), page)) : '';
|
||
});
|
||
container.addEventListener('keydown', function (event) {
|
||
if (event.key !== 'Enter' || !event.target.matches('[data-widget-jump]')) return;
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
jumpTo(event.target);
|
||
});
|
||
container.addEventListener('click', function (event) {
|
||
var jump = event.target.closest('[data-widget-jump-go]');
|
||
if (jump) {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
jumpTo(jump.parentElement.querySelector('[data-widget-jump]'));
|
||
return;
|
||
}
|
||
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) {
|
||
event.preventDefault();
|
||
loadItems(container, Number(retry.dataset.retryPage || 1), retry.dataset.retryAppend === '1');
|
||
return;
|
||
}
|
||
if (trigger) {
|
||
event.preventDefault();
|
||
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();
|
||
var nav = button.closest('[role="tablist"]');
|
||
if (nav && nav.scrollWidth > nav.clientWidth) {
|
||
var tabBounds = button.getBoundingClientRect();
|
||
var navBounds = nav.getBoundingClientRect();
|
||
nav.scrollTo({ left: nav.scrollLeft + tabBounds.left - navBounds.left + tabBounds.width / 2 - nav.clientWidth / 2, behavior: reducedMotion ? 'auto' : 'smooth' });
|
||
}
|
||
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) return;
|
||
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);
|
||
if (scope.matches && scope.matches('[data-widget-items]')) initItems(scope);
|
||
scope.querySelectorAll('[data-widget-items]').forEach(initItems);
|
||
scope.querySelectorAll('.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); });
|
||
})();
|