feat: rewrite schema-driven widget system
This commit is contained in:
1 parent
6c6c462df8
commit
ad99ab53ce
28 files changed
+3489
No files matched your search
@@ -0,0 +1,290 @@
|
||||
(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 initSwiper(el) {
|
||||
if (!once(el, 'Swiper') || typeof window.Swiper !== 'function') return;
|
||||
var kind = el.dataset.aksrtSwiper || 'cards';
|
||||
var vertical = kind === 'notice' || el.dataset.direction === 'vertical';
|
||||
var options = {
|
||||
direction: vertical ? 'vertical' : 'horizontal',
|
||||
loop: el.dataset.loop === '1' || kind === 'notice',
|
||||
speed: Number(el.dataset.speed || 500),
|
||||
spaceBetween: Number(el.dataset.space || 16),
|
||||
observer: true,
|
||||
observeParents: true,
|
||||
watchOverflow: true,
|
||||
navigation: {
|
||||
nextEl: el.querySelector('.swiper-button-next'),
|
||||
prevEl: el.querySelector('.swiper-button-prev')
|
||||
},
|
||||
pagination: { el: el.querySelector('.swiper-pagination'), clickable: true }
|
||||
};
|
||||
if (kind === 'scroll' || kind === 'cards') {
|
||||
options.slidesPerView = 'auto';
|
||||
options.freeMode = kind === 'scroll';
|
||||
}
|
||||
if (kind === 'slider') {
|
||||
options.effect = el.dataset.effect || 'slide';
|
||||
options.parallax = !!el.querySelector('[data-swiper-parallax]');
|
||||
}
|
||||
if (!reducedMotion && (el.dataset.autoplay === '1' || kind === 'notice')) {
|
||||
options.autoplay = { delay: Number(el.dataset.interval || 4000), disableOnInteraction: false, pauseOnMouseEnter: true };
|
||||
}
|
||||
try {
|
||||
el.aksrtSwiper = new window.Swiper(el, options);
|
||||
} catch (error) {
|
||||
el.classList.add('is-fallback');
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!once(el, 'Video')) 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;
|
||||
});
|
||||
}
|
||||
if (typeof window.DPlayer !== 'function') return;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
function initToc(toc) {
|
||||
if (!once(toc, 'Toc')) return;
|
||||
var scope = document.querySelector('.article-content, .wp-posts-content, article .entry-content');
|
||||
var headings = scope ? Array.prototype.slice.call(scope.querySelectorAll('h2,h3,h4')) : [];
|
||||
var minimum = Number(toc.dataset.minHeadings || 3);
|
||||
var list = toc.querySelector('.aksrt-widget-toc__list');
|
||||
if (headings.length < minimum || !list) {
|
||||
toc.hidden = true;
|
||||
return;
|
||||
}
|
||||
headings.forEach(function (heading, index) {
|
||||
if (!heading.id) heading.id = 'aksrt-heading-' + (index + 1);
|
||||
var item = document.createElement('li');
|
||||
item.className = 'level-' + heading.tagName.toLowerCase();
|
||||
var link = document.createElement('a');
|
||||
link.href = '#' + heading.id;
|
||||
link.textContent = heading.textContent.trim();
|
||||
link.addEventListener('click', function (event) {
|
||||
event.preventDefault();
|
||||
window.scrollTo({ top: heading.getBoundingClientRect().top + window.scrollY - Number(toc.dataset.scrollOffset || 70), behavior: reducedMotion ? 'auto' : 'smooth' });
|
||||
history.replaceState(null, '', link.href);
|
||||
});
|
||||
item.appendChild(link);
|
||||
list.appendChild(item);
|
||||
});
|
||||
var empty = toc.querySelector('.aksrt-widget-toc__empty');
|
||||
if (empty) empty.remove();
|
||||
var toggle = toc.querySelector('.aksrt-widget-toc__toggle');
|
||||
if (toggle) toggle.addEventListener('click', function () {
|
||||
var expanded = toggle.getAttribute('aria-expanded') === 'true';
|
||||
toggle.setAttribute('aria-expanded', expanded ? 'false' : 'true');
|
||||
toc.classList.toggle('is-collapsed', expanded);
|
||||
});
|
||||
}
|
||||
|
||||
function updateQuote(figure) {
|
||||
if (figure.dataset.source !== 'api' || !config.quoteUrl) return Promise.resolve();
|
||||
var button = figure.querySelector('button');
|
||||
if (button) button.disabled = true;
|
||||
return fetch(config.quoteUrl, { credentials: 'same-origin', headers: { Accept: 'application/json' } })
|
||||
.then(function (response) { if (!response.ok) throw new Error('quote'); return response.json(); })
|
||||
.then(function (data) {
|
||||
if (data && data.text) figure.querySelector('blockquote').textContent = data.text;
|
||||
})
|
||||
.catch(function () {})
|
||||
.finally(function () { if (button) button.disabled = false; });
|
||||
}
|
||||
|
||||
function initQuote(figure) {
|
||||
if (!once(figure, 'Quote')) return;
|
||||
var button = figure.querySelector('button');
|
||||
if (button) button.addEventListener('click', function () { updateQuote(figure); });
|
||||
if (!reducedMotion && figure.dataset.source === 'api') {
|
||||
window.setInterval(function () { if (!document.hidden) updateQuote(figure); }, Math.max(5, Number(figure.dataset.interval || 30)) * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
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 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.dataset.loading = '1';
|
||||
container.classList.add('is-loading');
|
||||
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 oldPagination = container.querySelector('.aksrt-widget-pagination, .aksrt-widget-load-more');
|
||||
if (oldPagination) oldPagination.remove();
|
||||
var fragment = document.createElement('div');
|
||||
fragment.innerHTML = String(data.html || '');
|
||||
if (append) {
|
||||
var target = container.querySelector('.aksrt-widget-posts, .posts-row, .swiper-wrapper') || container;
|
||||
var source = fragment.querySelector('.aksrt-widget-posts, .posts-row, .swiper-wrapper');
|
||||
Array.prototype.slice.call((source || fragment).children).forEach(function (node) { target.appendChild(node); });
|
||||
} else {
|
||||
container.innerHTML = fragment.innerHTML;
|
||||
}
|
||||
if (data.paginate_html) container.insertAdjacentHTML('beforeend', data.paginate_html);
|
||||
container.dataset.page = String(data.page || page);
|
||||
scan(container);
|
||||
})
|
||||
.catch(function () { container.innerHTML = errorMarkup(); })
|
||||
.finally(function () { delete container.dataset.loading; container.classList.remove('is-loading'); });
|
||||
}
|
||||
|
||||
function initItems(container) {
|
||||
if (!once(container, 'Items')) return;
|
||||
if (container.classList.contains('is-loading')) loadItems(container, 1, false);
|
||||
container.addEventListener('click', function (event) {
|
||||
var retry = event.target.closest('[data-widget-retry]');
|
||||
var more = event.target.closest('.aksrt-widget-load-more');
|
||||
var pageButton = event.target.closest('[data-widget-page]');
|
||||
if (retry) loadItems(container, Number(container.dataset.page || 1), false);
|
||||
if (more) loadItems(container, Number(more.dataset.nextPage || 2), true);
|
||||
if (pageButton) loadItems(container, Number(pageButton.dataset.widgetPage || 1), false);
|
||||
});
|
||||
if ('IntersectionObserver' in window) {
|
||||
var observer = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (entry) {
|
||||
var more = container.querySelector('.aksrt-widget-load-more[data-mode="infinite"]');
|
||||
if (entry.isIntersecting && more) loadItems(container, Number(more.dataset.nextPage || 2), true);
|
||||
});
|
||||
}, { rootMargin: '240px' });
|
||||
observer.observe(container);
|
||||
}
|
||||
}
|
||||
|
||||
function initTabs(tabs) {
|
||||
if (!once(tabs, 'Tabs')) return;
|
||||
tabs.addEventListener('click', function (event) {
|
||||
var button = event.target.closest('[data-segment]');
|
||||
if (!button) return;
|
||||
tabs.querySelectorAll('[role="tab"]').forEach(function (tab) { tab.setAttribute('aria-selected', tab === button ? 'true' : 'false'); });
|
||||
var panel = tabs.querySelector('[data-widget-items]');
|
||||
panel.dataset.segment = button.dataset.segment;
|
||||
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('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'); });
|
||||
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);
|
||||
}
|
||||
|
||||
function scan(root) {
|
||||
var scope = root || document;
|
||||
scope.querySelectorAll('[data-aksrt-swiper]').forEach(initSwiper);
|
||||
scope.querySelectorAll('.aksrt-widget-video[data-video-url]').forEach(initVideo);
|
||||
scope.querySelectorAll('.aksrt-widget-toc').forEach(initToc);
|
||||
scope.querySelectorAll('.aksrt-widget-quote').forEach(initQuote);
|
||||
scope.querySelectorAll('[data-widget-items]').forEach(initItems);
|
||||
scope.querySelectorAll('.aksrt-widget-tabs').forEach(initTabs);
|
||||
scope.querySelectorAll('.aksrt-faq').forEach(initFaq);
|
||||
scope.querySelectorAll('.aksrt-feature-intro').forEach(initFeatures);
|
||||
scope.querySelectorAll('[data-count-up]').forEach(initCount);
|
||||
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();
|
||||
})();
|
||||
Reference in new issue
Block a user