补充主题静态资源与归档模板
This commit is contained in:
@@ -0,0 +1,799 @@
|
||||
/**
|
||||
* Homepage Fixed - Interactive Layer (v2.0 Zibll-aligned)
|
||||
*
|
||||
* Unified AJAX protocol for homepage tabs, sort, pagination, and view toggle.
|
||||
* Uses Zibll-style ajaxpager DOM conventions and custom event flow:
|
||||
* post_ajax.before → fetch → post_ajax.ed → aksrt:posts:rendered
|
||||
*
|
||||
* @package AKSRT
|
||||
* @since 4.0.0
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var STORAGE_KEY = 'aksrt_homepage_view_mode';
|
||||
var SORT_QUERY_KEY = 'home_sort';
|
||||
var DEFAULT_SORT = 'modified';
|
||||
var DEFAULT_VIEW = 'list';
|
||||
var REST_PATH = '/wp-json/aksrt/v1/homepage/tab-posts';
|
||||
|
||||
/* ============================================================
|
||||
* Utilities
|
||||
* ============================================================ */
|
||||
|
||||
function safeLocalStorage() {
|
||||
try { return window.localStorage; } catch (e) { return null; }
|
||||
}
|
||||
|
||||
function getViewMode() {
|
||||
var store = safeLocalStorage();
|
||||
if (store) {
|
||||
var saved = store.getItem(STORAGE_KEY);
|
||||
if (saved === 'card' || saved === 'list') return saved;
|
||||
}
|
||||
return getDefaultView();
|
||||
}
|
||||
|
||||
function setViewMode(mode) {
|
||||
var store = safeLocalStorage();
|
||||
if (store) { try { store.setItem(STORAGE_KEY, mode); } catch (e) { /* */ } }
|
||||
}
|
||||
|
||||
function getRenderedPostCount(scope) {
|
||||
if (!scope) return 0;
|
||||
return scope.querySelectorAll('.posts-item, .posts-mini').length;
|
||||
}
|
||||
|
||||
function getHomepagePerPage(panel) {
|
||||
var panelCount = getRenderedPostCount(panel);
|
||||
if (panelCount > 0) return panelCount;
|
||||
|
||||
var configured = window.aksrtData && parseInt(aksrtData.homepagePostsPerPage, 10);
|
||||
if (configured && configured > 0) return configured;
|
||||
|
||||
var latestPanel = document.querySelector('[data-home-tab-panel="latest"]');
|
||||
var latestCount = getRenderedPostCount(latestPanel);
|
||||
return latestCount || 10;
|
||||
}
|
||||
|
||||
function getDefaultSort() {
|
||||
var configured = window.aksrtData && aksrtData.homepageDefaultSort;
|
||||
var allowed = ['modified', 'date', 'downloads', 'views', 'likes', 'comments'];
|
||||
return allowed.indexOf(configured) !== -1 ? configured : DEFAULT_SORT;
|
||||
}
|
||||
|
||||
function getDefaultView() {
|
||||
var configured = window.aksrtData && aksrtData.homepageDefaultView;
|
||||
return configured === 'card' || configured === 'list' ? configured : DEFAULT_VIEW;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
function getRestBaseUrl() {
|
||||
var baseUrl = (window.aksrtData && aksrtData.siteUrl) || '/';
|
||||
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') : 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');
|
||||
links.forEach(function (link) {
|
||||
link.classList.toggle('is-active', link.getAttribute('data-sort') === sort);
|
||||
});
|
||||
// Update dropdown trigger text to match the active item
|
||||
var activeItem = sortbar.querySelector('.aksrt-homepage__sortbar-dropdown-item[data-sort="' + sort + '"]');
|
||||
var trigger = sortbar.querySelector('[data-sort-dropdown-trigger]');
|
||||
if (activeItem && trigger) {
|
||||
trigger.firstChild.textContent = activeItem.textContent;
|
||||
}
|
||||
}
|
||||
|
||||
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 activeBtn = stream.querySelector('.aksrt-homepage__view-btn.is-active');
|
||||
return (activeBtn && activeBtn.getAttribute('data-view-mode')) || getViewMode();
|
||||
}
|
||||
|
||||
function getActivePanel(stream) {
|
||||
return stream.querySelector('.aksrt-homepage__panel.is-active, .aksrt-homepage__panel:not([hidden])');
|
||||
}
|
||||
|
||||
function getPanelTermId(panel) {
|
||||
if (!panel) return 0;
|
||||
var target = panel.getAttribute('data-home-tab-panel');
|
||||
if (target === 'latest') return 0;
|
||||
var match = target ? target.match(/^term-(\d+)$/) : null;
|
||||
return match ? parseInt(match[1], 10) : 0;
|
||||
}
|
||||
|
||||
function getPanelTabOrderby(panel) {
|
||||
if (!panel) return '';
|
||||
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 +
|
||||
'?term_id=' + encodeURIComponent(options.termId || 0) +
|
||||
'&page=' + encodeURIComponent(options.page || 1) +
|
||||
'&per_page=' + encodeURIComponent(options.perPage || 10) +
|
||||
'&sort=' + encodeURIComponent(options.sort || getDefaultSort()) +
|
||||
'&view_mode=' + encodeURIComponent(options.viewMode || getDefaultView()) +
|
||||
'&page_url=' + encodeURIComponent(options.pageUrl || window.location.href) +
|
||||
'&panel=' + encodeURIComponent(options.panel || 'latest') +
|
||||
'&source=' + encodeURIComponent(options.source || 'home') +
|
||||
'&tab_orderby=' + encodeURIComponent(options.tabOrderby || '');
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Zibll-style event dispatching
|
||||
* ============================================================ */
|
||||
|
||||
/**
|
||||
* Dispatch post_ajax.before — fired before AJAX request is sent.
|
||||
* Detail: { termId, page, sort, viewMode, panel, source, url }
|
||||
*/
|
||||
function dispatchBeforeEvent(container, detail) {
|
||||
try {
|
||||
container.dispatchEvent(new CustomEvent('post_ajax.before', {
|
||||
bubbles: true,
|
||||
detail: detail
|
||||
}));
|
||||
} catch (e) { /* */ }
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch post_ajax.ed — fired after AJAX response is injected.
|
||||
* Detail: { termId, page, sort, viewMode, panel, source, total, hasMore }
|
||||
*/
|
||||
function dispatchAfterEvent(container, detail) {
|
||||
try {
|
||||
container.dispatchEvent(new CustomEvent('post_ajax.ed', {
|
||||
bubbles: true,
|
||||
detail: detail
|
||||
}));
|
||||
} catch (e) { /* */ }
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch aksrt:posts:rendered — fired after DOM is hydrated (lazyload etc).
|
||||
* Detail: { viewMode, panel }
|
||||
*/
|
||||
function dispatchRenderedEvent(container, detail) {
|
||||
try {
|
||||
container.dispatchEvent(new CustomEvent('aksrt:posts:rendered', {
|
||||
bubbles: true,
|
||||
detail: detail
|
||||
}));
|
||||
} catch (e) { /* */ }
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Skeleton, lazyload, and hydration
|
||||
* ============================================================ */
|
||||
|
||||
function buildSkeleton(count) {
|
||||
count = count || 3;
|
||||
var wrapper = document.createElement('div');
|
||||
wrapper.className = 'aksrt-homepage__tab-skeleton';
|
||||
for (var i = 0; i < count; i++) {
|
||||
var item = document.createElement('div');
|
||||
item.className = 'aksrt-homepage__tab-skeleton-item';
|
||||
item.innerHTML =
|
||||
'<div class="aksrt-homepage__tab-skeleton-thumb"></div>' +
|
||||
'<div class="aksrt-homepage__tab-skeleton-body">' +
|
||||
'<div class="aksrt-homepage__tab-skeleton-line"></div>' +
|
||||
'<div class="aksrt-homepage__tab-skeleton-line"></div>' +
|
||||
'<div class="aksrt-homepage__tab-skeleton-line"></div>' +
|
||||
'</div>';
|
||||
wrapper.appendChild(item);
|
||||
}
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
function hydrateLazyImages(scope) {
|
||||
var lazyImages = (scope || document).querySelectorAll('img.aksrt-lazy');
|
||||
if (!lazyImages.length) return;
|
||||
|
||||
if (!('IntersectionObserver' in window)) {
|
||||
lazyImages.forEach(function (img) {
|
||||
var src = img.dataset.src;
|
||||
if (src) {
|
||||
img.src = src;
|
||||
img.classList.remove('aksrt-lazy');
|
||||
img.classList.add('aksrt-loaded');
|
||||
}
|
||||
var skeleton = img.previousElementSibling;
|
||||
if (skeleton && skeleton.classList.contains('aksrt-skeleton')) skeleton.remove();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var observer = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (entry) {
|
||||
if (!entry.isIntersecting) return;
|
||||
var img = entry.target;
|
||||
var src = img.dataset.src;
|
||||
if (!src) { observer.unobserve(img); return; }
|
||||
|
||||
var tempImg = new Image();
|
||||
tempImg.onload = function () {
|
||||
img.src = src;
|
||||
img.classList.remove('aksrt-lazy');
|
||||
img.classList.add('aksrt-loaded');
|
||||
var skeleton = img.previousElementSibling;
|
||||
if (skeleton && skeleton.classList.contains('aksrt-skeleton')) skeleton.remove();
|
||||
};
|
||||
tempImg.onerror = function () {
|
||||
img.classList.remove('aksrt-lazy');
|
||||
img.classList.add('aksrt-error');
|
||||
var skeleton = img.previousElementSibling;
|
||||
if (skeleton && skeleton.classList.contains('aksrt-skeleton')) skeleton.remove();
|
||||
};
|
||||
tempImg.src = src;
|
||||
observer.unobserve(img);
|
||||
});
|
||||
}, { rootMargin: '200px 0px', threshold: 0.01 });
|
||||
|
||||
lazyImages.forEach(function (img) { observer.observe(img); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate a panel after AJAX content injection.
|
||||
* Sets data-view, re-binds lazy images, and dispatches events.
|
||||
*/
|
||||
function notifyPostsRendered(panel, viewMode) {
|
||||
var postsContainer = panel.querySelector('.aksrt-homepage__posts, .posts-row');
|
||||
if (postsContainer) {
|
||||
postsContainer.setAttribute('data-view', viewMode || getDefaultView());
|
||||
}
|
||||
|
||||
hydrateLazyImages(panel);
|
||||
dispatchRenderedEvent(panel, {
|
||||
viewMode: viewMode || getDefaultView(),
|
||||
panel: panel.getAttribute('data-home-tab-panel') || ''
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Unified AJAX panel loader
|
||||
* ============================================================ */
|
||||
|
||||
function buildErrorState(panel, requestOptions) {
|
||||
var wrapper = document.createElement('div');
|
||||
wrapper.className = 'aksrt-homepage__tab-error';
|
||||
|
||||
var msg = document.createElement('p');
|
||||
msg.textContent = (window.aksrtData && aksrtData.ajaxLoadError) || '加载失败,请重试';
|
||||
wrapper.appendChild(msg);
|
||||
|
||||
var retry = document.createElement('button');
|
||||
retry.type = 'button';
|
||||
retry.className = 'aksrt-homepage__tab-retry';
|
||||
retry.innerHTML = '<i class="fas fa-redo"></i> ' +
|
||||
((window.aksrtData && aksrtData.ajaxRetry) || '重试');
|
||||
retry.addEventListener('click', function () {
|
||||
requestHomepagePanel(panel, requestOptions);
|
||||
});
|
||||
wrapper.appendChild(retry);
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Core AJAX loader. Shared by tab switch, sort change, pagination, and widget tabs.
|
||||
*
|
||||
* Dispatches the full Zibll-aligned event lifecycle:
|
||||
* 1. post_ajax.before (before fetch)
|
||||
* 2. [inject HTML]
|
||||
* 3. post_ajax.ed (after inject, before hydrate)
|
||||
* 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) || ''
|
||||
};
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
// Show loading skeleton
|
||||
panel.innerHTML = '';
|
||||
panel.appendChild(buildSkeleton(options.perPage > 6 ? 4 : 3));
|
||||
|
||||
function markLoaded() {
|
||||
panel.dataset.loaded = 'true';
|
||||
panel.dataset.loadedPage = String(options.page);
|
||||
panel.dataset.loadedSort = options.sort;
|
||||
panel.dataset.loadedTermId = String(options.termId);
|
||||
}
|
||||
|
||||
function doFetch() {
|
||||
fetch(restUrl, {
|
||||
method: 'GET',
|
||||
headers: { Accept: 'application/json' },
|
||||
credentials: 'same-origin'
|
||||
})
|
||||
.then(function (response) {
|
||||
if (!response.ok) throw new Error('HTTP ' + response.status);
|
||||
return response.json();
|
||||
})
|
||||
.then(function (data) {
|
||||
panel.innerHTML = '';
|
||||
markLoaded();
|
||||
|
||||
if (data && data.html) {
|
||||
// 2. Inject HTML
|
||||
panel.innerHTML = data.html;
|
||||
|
||||
// 3. Post-inject event
|
||||
dispatchAfterEvent(panel, {
|
||||
termId: options.termId,
|
||||
page: options.page,
|
||||
sort: options.sort,
|
||||
viewMode: options.viewMode,
|
||||
panel: options.panel,
|
||||
source: options.source,
|
||||
total: data.total || 0,
|
||||
hasMore: !!data.has_more
|
||||
});
|
||||
|
||||
// 4. Hydrate + rendered event
|
||||
notifyPostsRendered(panel, options.viewMode);
|
||||
|
||||
// 5. Rehydrate dynamic components on new content
|
||||
if (window.AksrtFrontend && typeof window.AksrtFrontend.rehydrate === 'function') {
|
||||
window.AksrtFrontend.rehydrate(panel, 'ajax');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Empty response
|
||||
var empty = document.createElement('div');
|
||||
empty.className = 'no-results';
|
||||
empty.innerHTML = '<p>' + ((window.aksrtData && aksrtData.ajaxEmpty) || '暂无文章') + '</p>';
|
||||
panel.appendChild(empty);
|
||||
|
||||
dispatchAfterEvent(panel, {
|
||||
termId: options.termId,
|
||||
page: options.page,
|
||||
sort: options.sort,
|
||||
viewMode: options.viewMode,
|
||||
panel: options.panel,
|
||||
source: options.source,
|
||||
total: 0,
|
||||
hasMore: false
|
||||
});
|
||||
})
|
||||
.catch(function () {
|
||||
if (retryCount < maxRetries) {
|
||||
retryCount++;
|
||||
window.setTimeout(doFetch, 3000);
|
||||
return;
|
||||
}
|
||||
panel.innerHTML = '';
|
||||
panel.appendChild(buildErrorState(panel, options));
|
||||
});
|
||||
}
|
||||
|
||||
doFetch();
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Tab Navigation
|
||||
* ============================================================ */
|
||||
|
||||
function initHomeTabs(scope) {
|
||||
var tablists = (scope || document).querySelectorAll('[data-aksrt-home-tabs]');
|
||||
|
||||
tablists.forEach(function (list, listIndex) {
|
||||
var stream = list.closest('.aksrt-homepage__stream');
|
||||
if (!stream) return;
|
||||
|
||||
var buttons = Array.from(list.querySelectorAll('[data-home-tab-target]'));
|
||||
var panels = Array.from(stream.querySelectorAll('[data-home-tab-panel]'));
|
||||
|
||||
if (buttons.length < 2 || !panels.length) return;
|
||||
|
||||
function getPanel(button) {
|
||||
var target = button.getAttribute('data-home-tab-target');
|
||||
return panels.find(function (p) {
|
||||
return p.getAttribute('data-home-tab-panel') === target;
|
||||
});
|
||||
}
|
||||
|
||||
function activate(button) {
|
||||
var target = button.getAttribute('data-home-tab-target');
|
||||
var panel = getPanel(button);
|
||||
var termId = getPanelTermId(panel);
|
||||
var currentSort = getCurrentSort(stream);
|
||||
var viewMode = getStreamViewMode(stream);
|
||||
var tabOrderby = button.getAttribute('data-tab-orderby') || getPanelTabOrderby(panel) || '';
|
||||
|
||||
// Toggle active state on tab buttons
|
||||
buttons.forEach(function (item) {
|
||||
var isActive = item === button;
|
||||
var itemWrap = item.closest('.aksrt-homepage__tab-item');
|
||||
if (itemWrap) itemWrap.classList.toggle('is-active', isActive);
|
||||
item.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
||||
item.tabIndex = isActive ? 0 : -1;
|
||||
});
|
||||
|
||||
// Toggle active state on panels (ajaxpager convention)
|
||||
panels.forEach(function (tabPanel) {
|
||||
var isActive = tabPanel === panel;
|
||||
tabPanel.classList.toggle('is-active', isActive);
|
||||
if (isActive) {
|
||||
tabPanel.removeAttribute('hidden');
|
||||
tabPanel.setAttribute('aria-hidden', 'false');
|
||||
} else {
|
||||
tabPanel.setAttribute('hidden', '');
|
||||
tabPanel.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
});
|
||||
|
||||
// Load content if needed (lazy AJAX load like Zibll)
|
||||
// For tab panels, the "loaded sort" check uses tabOrderby, not main stream sort
|
||||
var effectiveSort = tabOrderby || currentSort;
|
||||
if (panel && (panel.dataset.loaded !== 'true' || panel.dataset.loadedSort !== effectiveSort)) {
|
||||
requestHomepagePanel(panel, {
|
||||
termId: termId,
|
||||
page: 1,
|
||||
perPage: getHomepagePerPage(panel),
|
||||
sort: currentSort,
|
||||
viewMode: viewMode,
|
||||
panel: target,
|
||||
source: 'home',
|
||||
tabOrderby: tabOrderby
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Set up ARIA relationships
|
||||
buttons.forEach(function (button) {
|
||||
var target = button.getAttribute('data-home-tab-target');
|
||||
var panel = getPanel(button);
|
||||
var buttonId = 'aksrt-home-tab-' + listIndex + '-' + target;
|
||||
|
||||
button.id = button.id || buttonId;
|
||||
if (panel) {
|
||||
panel.id = panel.id || buttonId + '-panel';
|
||||
panel.setAttribute('aria-labelledby', button.id);
|
||||
button.setAttribute('aria-controls', panel.id);
|
||||
// Add ajaxpager class for Zibll DOM convention
|
||||
panel.classList.add('ajaxpager');
|
||||
}
|
||||
|
||||
button.addEventListener('click', function () { activate(button); });
|
||||
|
||||
button.addEventListener('keydown', function (event) {
|
||||
if (event.key !== 'ArrowRight' && event.key !== 'ArrowLeft') return;
|
||||
event.preventDefault();
|
||||
var direction = event.key === 'ArrowRight' ? 1 : -1;
|
||||
var currentIndex = buttons.indexOf(button);
|
||||
var nextIndex = (currentIndex + direction + buttons.length) % buttons.length;
|
||||
var nextButton = buttons[nextIndex];
|
||||
if (nextButton) {
|
||||
nextButton.focus();
|
||||
activate(nextButton);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Mark server-rendered panels as already loaded
|
||||
panels.forEach(function (panel) {
|
||||
if (!panel.querySelector('.aksrt-homepage__posts, .posts-row')) return;
|
||||
panel.dataset.loaded = 'true';
|
||||
panel.dataset.loadedPage = '1';
|
||||
panel.dataset.loadedSort = getCurrentSort(stream);
|
||||
panel.dataset.loadedTermId = String(getPanelTermId(panel));
|
||||
});
|
||||
|
||||
// Activate initial tab
|
||||
var initialButton = buttons.find(function (b) {
|
||||
return b.getAttribute('aria-selected') === 'true';
|
||||
}) || buttons[0];
|
||||
activate(initialButton);
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Sortbar Dropdown Toggle
|
||||
* ============================================================ */
|
||||
|
||||
function initSortbarDropdown() {
|
||||
document.querySelectorAll('[data-sort-dropdown-trigger]').forEach(function (trigger) {
|
||||
var wrapper = trigger.closest('.aksrt-homepage__sortbar-dropdown');
|
||||
if (!wrapper) return;
|
||||
var menu = wrapper.querySelector('.aksrt-homepage__sortbar-dropdown-menu');
|
||||
if (!menu) return;
|
||||
|
||||
function close() {
|
||||
menu.setAttribute('hidden', '');
|
||||
trigger.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
var isOpen = !menu.hasAttribute('hidden');
|
||||
if (isOpen) {
|
||||
close();
|
||||
} else {
|
||||
menu.removeAttribute('hidden');
|
||||
trigger.setAttribute('aria-expanded', 'true');
|
||||
}
|
||||
}
|
||||
|
||||
trigger.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
toggle();
|
||||
});
|
||||
|
||||
// Close when clicking an item
|
||||
menu.addEventListener('click', function (e) {
|
||||
var item = e.target.closest('.aksrt-homepage__sortbar-dropdown-item');
|
||||
if (!item) return;
|
||||
// Update trigger text
|
||||
trigger.firstChild.textContent = item.textContent;
|
||||
// Update active state
|
||||
menu.querySelectorAll('.aksrt-homepage__sortbar-dropdown-item').forEach(function (el) {
|
||||
el.classList.toggle('is-active', el === item);
|
||||
});
|
||||
close();
|
||||
});
|
||||
|
||||
// Close on outside click
|
||||
document.addEventListener('click', function (e) {
|
||||
if (!wrapper.contains(e.target)) close();
|
||||
});
|
||||
|
||||
// Close on Escape
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Escape' && !menu.hasAttribute('hidden')) {
|
||||
close();
|
||||
trigger.focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Sortbar AJAX
|
||||
* ============================================================ */
|
||||
|
||||
function initSortbar() {
|
||||
document.querySelectorAll('.aksrt-homepage__stream').forEach(function (stream) {
|
||||
var sortbar = stream.querySelector('.aksrt-homepage__sortbar');
|
||||
if (!sortbar || !isSortAjaxEnabled(sortbar)) return;
|
||||
|
||||
sortbar.addEventListener('click', function (event) {
|
||||
var link = event.target.closest('.aksrt-homepage__sortbar-link, .aksrt-homepage__sortbar-dropdown-item');
|
||||
var activePanel = getActivePanel(stream);
|
||||
var sort = link ? link.getAttribute('data-sort') : '';
|
||||
var termId = getPanelTermId(activePanel);
|
||||
|
||||
if (!link || !activePanel || !sort) return;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
setActiveSortLink(sortbar, sort);
|
||||
replaceUrlSort(sort);
|
||||
|
||||
requestHomepagePanel(activePanel, {
|
||||
termId: termId,
|
||||
page: 1,
|
||||
perPage: getHomepagePerPage(activePanel),
|
||||
sort: sort,
|
||||
viewMode: getStreamViewMode(stream),
|
||||
panel: activePanel.getAttribute('data-home-tab-panel') || 'latest',
|
||||
source: 'home'
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Sticky Tabs
|
||||
* ============================================================ */
|
||||
|
||||
function supportsStickyPosition() {
|
||||
if (window.CSS && typeof window.CSS.supports === 'function') {
|
||||
return window.CSS.supports('position', 'sticky') || window.CSS.supports('position', '-webkit-sticky');
|
||||
}
|
||||
var testNode = document.createElement('div');
|
||||
testNode.style.position = 'sticky';
|
||||
return testNode.style.position.indexOf('sticky') !== -1;
|
||||
}
|
||||
|
||||
function getStickyOffset() {
|
||||
var docStyles = window.getComputedStyle(document.documentElement);
|
||||
var offset = parseFloat(docStyles.getPropertyValue('--nav-height') || '60');
|
||||
return isNaN(offset) ? 60 : offset;
|
||||
}
|
||||
|
||||
function initStickyTabs() {
|
||||
if (!isStickyTabsEnabled()) return;
|
||||
|
||||
document.querySelectorAll('.aksrt-homepage__tabs.home-tab-nav-box').forEach(function (tabs) {
|
||||
var parent = tabs.parentNode;
|
||||
var sentinel = tabs.previousElementSibling;
|
||||
var canUseSticky = supportsStickyPosition();
|
||||
var hasObserver = 'IntersectionObserver' in window;
|
||||
var observer = null;
|
||||
|
||||
if (!parent || tabs.getAttribute('data-sticky-tabs') === 'false') return;
|
||||
|
||||
if (!sentinel || !sentinel.classList.contains('aksrt-homepage__tabs-sentinel')) {
|
||||
sentinel = document.createElement('div');
|
||||
sentinel.className = 'aksrt-homepage__tabs-sentinel';
|
||||
parent.insertBefore(sentinel, tabs);
|
||||
}
|
||||
|
||||
function clearAffixBox() {
|
||||
sentinel.style.height = '1px';
|
||||
tabs.style.left = '';
|
||||
tabs.style.width = '';
|
||||
}
|
||||
|
||||
function syncAffixBox() {
|
||||
var rect = sentinel.getBoundingClientRect();
|
||||
sentinel.style.height = tabs.offsetHeight + 'px';
|
||||
tabs.style.left = rect.left + 'px';
|
||||
tabs.style.width = rect.width + 'px';
|
||||
}
|
||||
|
||||
function setStickyState(isStuck) {
|
||||
tabs.classList.toggle('is-stuck', isStuck);
|
||||
if (!canUseSticky) {
|
||||
tabs.classList.toggle('is-affixed', isStuck);
|
||||
if (isStuck) { syncAffixBox(); } else { clearAffixBox(); }
|
||||
return;
|
||||
}
|
||||
tabs.classList.remove('is-affixed');
|
||||
clearAffixBox();
|
||||
}
|
||||
|
||||
function evaluateFallbackState() {
|
||||
setStickyState(sentinel.getBoundingClientRect().top <= getStickyOffset());
|
||||
}
|
||||
|
||||
if (canUseSticky && hasObserver) {
|
||||
var bindObserver = function () {
|
||||
if (observer) observer.disconnect();
|
||||
observer = new IntersectionObserver(function (entries) {
|
||||
setStickyState(!entries[0].isIntersecting);
|
||||
}, { rootMargin: '-' + getStickyOffset() + 'px 0px 0px 0px', threshold: [1] });
|
||||
observer.observe(sentinel);
|
||||
};
|
||||
bindObserver();
|
||||
window.addEventListener('resize', bindObserver);
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', evaluateFallbackState, { passive: true });
|
||||
window.addEventListener('resize', evaluateFallbackState);
|
||||
evaluateFallbackState();
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* View Mode Toggle
|
||||
* ============================================================ */
|
||||
|
||||
function applyViewMode(buttons, postsContainers, mode) {
|
||||
buttons.forEach(function (btn) {
|
||||
var btnMode = btn.getAttribute('data-view-mode');
|
||||
btn.classList.toggle('is-active', btnMode === mode);
|
||||
});
|
||||
Array.prototype.forEach.call(postsContainers, function (container) {
|
||||
container.setAttribute('data-view', mode);
|
||||
});
|
||||
}
|
||||
|
||||
function initViewToggle() {
|
||||
var toggles = document.querySelectorAll('[data-aksrt-view-toggle]');
|
||||
|
||||
toggles.forEach(function (toggle) {
|
||||
var buttons = toggle.querySelectorAll('.aksrt-homepage__view-btn');
|
||||
var stream = toggle.closest('.aksrt-homepage__stream');
|
||||
if (!stream) return;
|
||||
|
||||
function getPostsContainers() {
|
||||
return stream.querySelectorAll('.aksrt-homepage__posts, .posts-row');
|
||||
}
|
||||
|
||||
function getActiveMode() {
|
||||
var activeBtn = toggle.querySelector('.aksrt-homepage__view-btn.is-active');
|
||||
return (activeBtn && activeBtn.getAttribute('data-view-mode')) || getViewMode();
|
||||
}
|
||||
|
||||
applyViewMode(buttons, getPostsContainers(), getViewMode());
|
||||
|
||||
buttons.forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var mode = btn.getAttribute('data-view-mode') || getDefaultView();
|
||||
applyViewMode(buttons, getPostsContainers(), mode);
|
||||
setViewMode(mode);
|
||||
});
|
||||
});
|
||||
|
||||
// Re-apply view mode after AJAX content renders
|
||||
stream.addEventListener('aksrt:posts:rendered', function () {
|
||||
applyViewMode(buttons, getPostsContainers(), getActiveMode());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Init
|
||||
* ============================================================ */
|
||||
|
||||
function init() {
|
||||
initHomeTabs();
|
||||
initSortbarDropdown();
|
||||
initSortbar();
|
||||
initStickyTabs();
|
||||
initViewToggle();
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user