This commit is contained in:
2026-09-11 17:59:01 +02:00
parent 90791333f7
commit d97c095635
6 changed files with 503 additions and 131 deletions
+76 -8
View File
@@ -2777,19 +2777,23 @@ body.sidebar-right-hidden #sidebar-drag-zone {
/* Body-level left-sidebar hide set by blocking inline script before first paint /* Body-level left-sidebar hide set by blocking inline script before first paint
to prevent the sidebar from flashing visible. Mirrors the .sidebar-hidden rules above to prevent the sidebar from flashing visible. Mirrors the .sidebar-hidden rules above
but activates from the body class so it works before the container element exists. */ but activates from the body class so it works before the container element exists. */
body.layout-modern.sidebar-left-hidden .item-layout-container { body.layout-modern.sidebar-left-hidden .item-layout-container,
body.layout-modern.comments-hidden .item-layout-container {
grid-template-columns: 0px minmax(0, 1fr) 400px; grid-template-columns: 0px minmax(0, 1fr) 400px;
} }
body.layout-modern.sidebar-left-hidden.sidebar-right-hidden .item-layout-container { body.layout-modern.sidebar-left-hidden.sidebar-right-hidden .item-layout-container,
body.layout-modern.comments-hidden.sidebar-right-hidden .item-layout-container {
grid-template-columns: 0px minmax(0, 1fr) 0px; grid-template-columns: 0px minmax(0, 1fr) 0px;
} }
body.layout-modern.sidebar-left-hidden .item-layout-container .item-sidebar-left { body.layout-modern.sidebar-left-hidden .item-layout-container .item-sidebar-left,
display: none; body.layout-modern.comments-hidden .item-layout-container .item-sidebar-left {
display: none !important;
} }
body.layout-modern.sidebar-left-hidden .item-layout-container .item-main-content { body.layout-modern.sidebar-left-hidden .item-layout-container .item-main-content,
body.layout-modern.comments-hidden .item-layout-container .item-main-content {
grid-column: 2; grid-column: 2;
} }
@@ -3324,6 +3328,27 @@ body.layout-legacy #comments-container.faded-out {
pointer-events: none; pointer-events: none;
} }
/* Full hidden state for comments & left sidebar — overrides flex !important so no plopping or ghost spacing occurs */
body.sidebar-left-hidden #comments-container,
body.comments-hidden #comments-container,
body.layout-legacy.sidebar-left-hidden #comments-container,
body.layout-legacy.comments-hidden #comments-container,
body.layout-legacy .item-layout-container.sidebar-hidden #comments-container,
body.layout-modern.sidebar-left-hidden #comments-container,
body.layout-modern.comments-hidden #comments-container,
body.layout-modern.sidebar-left-hidden .item-sidebar-left,
body.layout-modern.comments-hidden .item-sidebar-left,
body.layout-modern .item-layout-container.sidebar-hidden .item-sidebar-left,
body.sidebar-left-hidden #onara-modal #comments-container,
body.comments-hidden #onara-modal #comments-container,
body.sidebar-left-hidden #onara-modal .item-sidebar-left,
body.comments-hidden #onara-modal .item-sidebar-left,
#onara-modal #comments-container.is-hidden,
#comments-container.is-hidden,
.item-sidebar-left.is-hidden {
display: none !important;
}
body.layout-legacy .comment-input { body.layout-legacy .comment-input {
border-top: 1px solid var(--nav-border-color); border-top: 1px solid var(--nav-border-color);
border-left: 1px solid var(--nav-border-color); border-left: 1px solid var(--nav-border-color);
@@ -5772,6 +5797,49 @@ body.layout-modern.sidebar-right-hidden .pagination-container-fluid {
will-change: transform, opacity; will-change: transform, opacity;
} }
.content.fade-in-zoom,
._204863.fade-in-zoom,
.item_title.fade-in-zoom,
#onara-modal .content.fade-in-zoom,
#onara-modal ._204863.fade-in-zoom,
#onara-modal .item_title.fade-in-zoom {
animation: contentFadeInZoom 0.35s cubic-bezier(0.16, 1, 0.3, 1) both !important;
will-change: transform, opacity;
}
.content.fade-out-zoom,
._204863.fade-out-zoom,
.item_title.fade-out-zoom,
#onara-modal .content.fade-out-zoom,
#onara-modal ._204863.fade-out-zoom,
#onara-modal .item_title.fade-out-zoom {
animation: contentFadeOutZoom 0.18s cubic-bezier(0.3, 0, 0.8, 1) forwards !important;
pointer-events: none;
will-change: transform, opacity;
}
@keyframes contentFadeInZoom {
0% {
opacity: 0;
transform: scale(1.03);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes contentFadeOutZoom {
0% {
opacity: 1;
transform: scale(1);
}
100% {
opacity: 0;
transform: scale(0.97);
}
}
@keyframes contentSlideIn { @keyframes contentSlideIn {
0% { 0% {
opacity: 0; opacity: 0;
@@ -19028,9 +19096,9 @@ body.layout-modern #onara-modal .item-layout-container .item-main-content {
margin-right: auto !important; margin-right: auto !important;
} }
#onara-modal .content:not(.slide-next):not(.slide-prev):not(.slide-out-left):not(.slide-out-right), #onara-modal .content:not(.slide-next):not(.slide-prev):not(.slide-out-left):not(.slide-out-right):not(.fade-in-zoom):not(.fade-out-zoom),
#onara-modal ._204863:not(.slide-next):not(.slide-prev):not(.slide-out-left):not(.slide-out-right), #onara-modal ._204863:not(.slide-next):not(.slide-prev):not(.slide-out-left):not(.slide-out-right):not(.fade-in-zoom):not(.fade-out-zoom),
#onara-modal .item_title:not(.slide-next):not(.slide-prev):not(.slide-out-left):not(.slide-out-right) { #onara-modal .item_title:not(.slide-next):not(.slide-prev):not(.slide-out-left):not(.slide-out-right):not(.fade-in-zoom):not(.fade-out-zoom) {
animation: contentSlideIn 0.55s cubic-bezier(0.16, 1, 0.3, 1) both !important; animation: contentSlideIn 0.55s cubic-bezier(0.16, 1, 0.3, 1) both !important;
will-change: transform, opacity; will-change: transform, opacity;
} }
+48 -14
View File
@@ -48,18 +48,30 @@ class CommentSystem {
const isHidden = localStorage.getItem('comments_hidden') === 'true'; const isHidden = localStorage.getItem('comments_hidden') === 'true';
// Force show if hash is present // Force show if hash is present
if (window.location.hash && window.location.hash.startsWith('#c')) { if (window.location.hash && window.location.hash.startsWith('#c')) {
this.container.classList.remove('faded-out'); this.container.classList.remove('faded-out', 'is-hidden');
this.container.style.display = 'block'; this.container.style.removeProperty('display');
this.container.style.display = '';
localStorage.setItem('comments_hidden', 'false'); localStorage.setItem('comments_hidden', 'false');
document.body.classList.remove('sidebar-left-hidden'); document.body.classList.remove('sidebar-left-hidden', 'comments-hidden');
const layout = this.container.closest('.item-layout-container'); const layout = this.container.closest('.item-layout-container');
if (layout) layout.classList.remove('sidebar-hidden'); if (layout) layout.classList.remove('sidebar-hidden');
} else if (isHidden && (window.innerWidth >= 1000 || !document.body.classList.contains('layout-modern'))) { const sidebar = this.container.closest('.item-sidebar-left');
this.container.classList.add('faded-out'); if (sidebar) {
this.container.style.display = 'none'; sidebar.classList.remove('is-hidden');
document.body.classList.add('sidebar-left-hidden'); sidebar.style.removeProperty('display');
sidebar.style.display = '';
}
} else if (isHidden) {
this.container.classList.add('faded-out', 'is-hidden');
this.container.style.setProperty('display', 'none', 'important');
document.body.classList.add('sidebar-left-hidden', 'comments-hidden');
const layout = this.container.closest('.item-layout-container'); const layout = this.container.closest('.item-layout-container');
if (layout) layout.classList.add('sidebar-hidden'); if (layout) layout.classList.add('sidebar-hidden');
const sidebar = this.container.closest('.item-sidebar-left');
if (sidebar) {
sidebar.classList.add('is-hidden');
sidebar.style.setProperty('display', 'none', 'important');
}
} }
} }
@@ -3878,7 +3890,12 @@ class CommentSystem {
e.preventDefault(); e.preventDefault();
// If comments are hidden, show them first // If comments are hidden, show them first
const isHidden = cs.container.classList.contains('faded-out') || cs.container.style.display === 'none'; const isHidden = cs.container.classList.contains('faded-out') ||
cs.container.classList.contains('is-hidden') ||
cs.container.style.display === 'none' ||
document.body.classList.contains('sidebar-left-hidden') ||
document.body.classList.contains('comments-hidden') ||
localStorage.getItem('comments_hidden') === 'true';
if (isHidden) cs.toggleComments(); if (isHidden) cs.toggleComments();
// Legacy layout: comments are in the normal page flow under .item-main-content // Legacy layout: comments are in the normal page flow under .item-main-content
@@ -3959,7 +3976,7 @@ class CommentSystem {
toggleComments() { toggleComments() {
if (!this.container) return; if (!this.container) return;
if (document.body.classList.contains('layout-modern')) { if (document.body.classList.contains('layout-modern') && !document.body.classList.contains('onara-modal-open')) {
if (window.innerWidth < 1000) return; if (window.innerWidth < 1000) return;
if (typeof window.toggleSidebarLeft === 'function') { if (typeof window.toggleSidebarLeft === 'function') {
window.toggleSidebarLeft(); window.toggleSidebarLeft();
@@ -3974,27 +3991,44 @@ class CommentSystem {
] : []; ] : [];
// Check if currently hidden (or slid out) // Check if currently hidden (or slid out)
const isHidden = this.container.classList.contains('faded-out') || this.container.style.display === 'none'; const isHidden = this.container.classList.contains('faded-out') ||
this.container.classList.contains('is-hidden') ||
this.container.style.display === 'none' ||
document.body.classList.contains('sidebar-left-hidden') ||
document.body.classList.contains('comments-hidden') ||
localStorage.getItem('comments_hidden') === 'true';
if (isHidden) { if (isHidden) {
// SHOW: expand grid first, then slide content in // SHOW: expand grid first, then slide content in
if (layout) layout.classList.remove('sidebar-hidden'); if (layout) layout.classList.remove('sidebar-hidden');
document.body.classList.remove('sidebar-left-hidden'); document.body.classList.remove('sidebar-left-hidden', 'comments-hidden');
this.container.classList.remove('is-hidden');
this.container.style.removeProperty('display');
this.container.style.display = ''; this.container.style.display = '';
if (sidebar) {
sidebar.classList.remove('is-hidden');
sidebar.style.removeProperty('display');
sidebar.style.display = '';
}
localStorage.setItem('comments_hidden', 'false'); localStorage.setItem('comments_hidden', 'false');
void this.container.offsetWidth; // force reflow so transition fires void this.container.offsetWidth; // force reflow so transition fires
this.container.classList.remove('faded-out'); this.container.classList.remove('faded-out');
siblings.forEach(el => el.classList.remove('faded-out')); siblings.forEach(el => el.classList.remove('faded-out'));
} else { } else {
// HIDE: slide content out first, then collapse grid // HIDE: mark state immediately so quick navigations stay hidden, fade out content, then collapse
localStorage.setItem('comments_hidden', 'true'); localStorage.setItem('comments_hidden', 'true');
this.container.classList.add('faded-out'); this.container.classList.add('faded-out');
siblings.forEach(el => el.classList.add('faded-out')); siblings.forEach(el => el.classList.add('faded-out'));
setTimeout(() => { setTimeout(() => {
if (!this.container.classList.contains('faded-out')) return; if (!this.container.classList.contains('faded-out')) return;
this.container.style.display = 'none'; this.container.classList.add('is-hidden');
this.container.style.setProperty('display', 'none', 'important');
if (sidebar) {
sidebar.classList.add('is-hidden');
sidebar.style.setProperty('display', 'none', 'important');
}
if (layout) layout.classList.add('sidebar-hidden'); if (layout) layout.classList.add('sidebar-hidden');
document.body.classList.add('sidebar-left-hidden'); document.body.classList.add('sidebar-left-hidden', 'comments-hidden');
}, 300); }, 300);
} }
} }
+336 -104
View File
@@ -1135,6 +1135,21 @@ window.cancelAnimFrame = (function () {
}; };
window.updateOnaraActiveItem = updateOnaraActiveItem; window.updateOnaraActiveItem = updateOnaraActiveItem;
const isItemPath = (pathOrUrl) => {
try {
const p = new URL(pathOrUrl, window.location.origin).pathname;
const parts = p.split('/').filter(Boolean);
return !p.match(/\/p\//) && (
/^\/\d+/.test(p) ||
/^\/[a-zA-Z0-9_-]{11}(?:[?#]|$)/.test(p) ||
(parts.length >= 3 && (parts[0] === 'tag' || parts[0] === 'user' || parts[0] === 'h') && (/^\d+$/.test(parts[parts.length - 1]) || /^[a-zA-Z0-9_-]{11}$/.test(parts[parts.length - 1])))
);
} catch {
return false;
}
};
window.isItemPath = isItemPath;
const getOnaraBaseUrl = (itemUrl = window.location.href) => { const getOnaraBaseUrl = (itemUrl = window.location.href) => {
try { try {
const urlObj = new URL(itemUrl, window.location.origin); const urlObj = new URL(itemUrl, window.location.origin);
@@ -1142,7 +1157,8 @@ window.cancelAnimFrame = (function () {
path = path.replace(/\/(?:\d+|[a-zA-Z0-9_-]{11})$/, ''); path = path.replace(/\/(?:\d+|[a-zA-Z0-9_-]{11})$/, '');
const postsEl = document.querySelector('.posts[data-current-page]'); const postsEl = document.querySelector('.posts[data-current-page]');
const currentPage = postsEl ? parseInt(postsEl.dataset.currentPage, 10) : 1; const currentPage = postsEl ? parseInt(postsEl.dataset.currentPage, 10) : 1;
if (currentPage > 1 && !path.includes('/p/')) { const currentGridPath = window._onaraCurrentGridUrl ? new URL(window._onaraCurrentGridUrl, window.location.origin).pathname.replace(/\/p\/\d+/, '').replace(/\/+$/, '') : '';
if (currentPage > 1 && !path.includes('/p/') && (!currentGridPath || currentGridPath === path)) {
path = (path ? path : '') + '/p/' + currentPage; path = (path ? path : '') + '/p/' + currentPage;
} }
if (!path) path = '/'; if (!path) path = '/';
@@ -1202,15 +1218,30 @@ window.cancelAnimFrame = (function () {
if (!options.skipHistory) { if (!options.skipHistory) {
const returnUrl = window._onaraReturnUrl || getOnaraBaseUrl(); const returnUrl = window._onaraReturnUrl || getOnaraBaseUrl();
history.pushState({}, '', returnUrl); const returnTitle = window._onaraReturnTitle || window.f0ckDomain || '';
if (window._onaraReturnTitle) { // Always jump directly to the return URL — never step back through stacked item history.
document.title = window._onaraReturnTitle; // history.back() would re-open the previous item via popstate; replaceState exits cleanly.
} else if (window.f0ckDomain) { const depth = window._onaraOpenHistoryLength != null
document.title = window.f0ckDomain; ? history.length - window._onaraOpenHistoryLength
: 0;
if (depth > 0) {
// Suppress popstate re-navigation while we jump out
window._onaraSuppressPopstate = true;
history.go(-depth);
// history.go is async — replace the landing state after it fires
setTimeout(() => {
history.replaceState({}, '', returnUrl);
document.title = returnTitle;
window._onaraSuppressPopstate = false;
}, 50);
} else {
history.replaceState({}, '', returnUrl);
document.title = returnTitle;
} }
} }
window._onaraReturnUrl = null; window._onaraReturnUrl = null;
window._onaraReturnTitle = null; window._onaraReturnTitle = null;
window._onaraOpenHistoryLength = null;
// Maintain focus on active item so user TAB navigation seamlessly resumes from current progress // Maintain focus on active item so user TAB navigation seamlessly resumes from current progress
const activeThumb = document.querySelector('.posts > a.thumb.onara-active'); const activeThumb = document.querySelector('.posts > a.thumb.onara-active');
@@ -1806,6 +1837,21 @@ window.cancelAnimFrame = (function () {
const _preloadedMediaUrls = new Set(); const _preloadedMediaUrls = new Set();
let _prefetchAbortController = null;
let _prefetchDebounceTimer = null;
const cancelPrefetch = () => {
if (_prefetchDebounceTimer) {
clearTimeout(_prefetchDebounceTimer);
_prefetchDebounceTimer = null;
}
if (_prefetchAbortController) {
try {
_prefetchAbortController.abort();
} catch (_) {}
_prefetchAbortController = null;
}
};
const preloadMediaFromHtml = (html) => { const preloadMediaFromHtml = (html) => {
if (!html) return; if (!html) return;
@@ -1813,7 +1859,7 @@ window.cancelAnimFrame = (function () {
const parser = new DOMParser(); const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html'); const doc = parser.parseFromString(html, 'text/html');
// 1. Preload Image // 1. Preload Image only (standard image pipeline, non-blocking)
const img = doc.querySelector('#f0ck-image, .embed-responsive-image img, .media-object img'); const img = doc.querySelector('#f0ck-image, .embed-responsive-image img, .media-object img');
if (img) { if (img) {
const src = img.getAttribute('src'); const src = img.getAttribute('src');
@@ -1837,20 +1883,9 @@ window.cancelAnimFrame = (function () {
} }
} }
// 3. Preload Video / Audio Media (fetch initial bytes into browser cache) // Note: Video & Audio binary streams are deliberately NOT fetched via fetch() in JS.
const mediaEl = doc.querySelector('video#my-video, audio#my-video, .media-object video, .media-object audio'); // HTML5 media elements stream natively upon mounting. Fetching multi-megabyte chunks
if (mediaEl) { // in the background exhausts the browser's 6-connection HTTP pool, stalling navigation.
const mediaSrc = mediaEl.getAttribute('src');
if (mediaSrc && !mediaSrc.startsWith('blob:') && !_preloadedMediaUrls.has(mediaSrc)) {
_preloadedMediaUrls.add(mediaSrc);
fetch(mediaSrc, {
headers: { Range: 'bytes=0-2097151' }, // 2MB initial chunk preload
credentials: 'include',
priority: 'low'
}).catch(() => {});
window.f0ckDebug('[prefetch] Preloaded media buffer:', mediaSrc);
}
}
if (_preloadedMediaUrls.size > 150) { if (_preloadedMediaUrls.size > 150) {
_preloadedMediaUrls.clear(); _preloadedMediaUrls.clear();
@@ -1861,11 +1896,20 @@ window.cancelAnimFrame = (function () {
}; };
const prefetchNextPrevItems = () => { const prefetchNextPrevItems = () => {
// Abort prior in-flight prefetch requests
if (_prefetchAbortController) {
try { _prefetchAbortController.abort(); } catch (_) {}
}
_prefetchAbortController = new AbortController();
const signal = _prefetchAbortController.signal;
// Prioritize next item (most frequent user journey), then prev
const nextBtn = document.getElementById('next'); const nextBtn = document.getElementById('next');
const prevBtn = document.getElementById('prev'); const prevBtn = document.getElementById('prev');
[nextBtn, prevBtn].forEach(btn => { [nextBtn, prevBtn].forEach(btn => {
if (!btn || !btn.href || btn.href.endsWith('#')) return; if (!btn || !btn.href || btn.href.endsWith('#')) return;
if (signal.aborted) return;
const pathSegments = new URL(btn.href, window.location.origin).pathname.split('/'); const pathSegments = new URL(btn.href, window.location.origin).pathname.split('/');
const keySegments = pathSegments.filter(s => /^\d+$/.test(s) || /^[a-zA-Z0-9_-]{11}$/.test(s)); const keySegments = pathSegments.filter(s => /^\d+$/.test(s) || /^[a-zA-Z0-9_-]{11}$/.test(s));
@@ -1907,17 +1951,17 @@ window.cancelAnimFrame = (function () {
return; return;
} }
fetch(ajaxUrl, { credentials: 'include', priority: 'low' }) fetch(ajaxUrl, { credentials: 'include', priority: 'low', signal })
.then(r => r.ok ? r.text() : null) .then(r => r.ok ? r.text() : null)
.then(rawText => { .then(rawText => {
if (!rawText) return; if (!rawText || signal.aborted) return;
let html = rawText; let html = rawText;
try { try {
const data = JSON.parse(rawText); const data = JSON.parse(rawText);
if (data && typeof data.html === 'string') html = data.html; if (data && typeof data.html === 'string') html = data.html;
} catch (_) {} } catch (_) {}
if (html) { if (html && !signal.aborted) {
itemCacheMap.set(ajaxUrl, { html, ts: Date.now() }); itemCacheMap.set(ajaxUrl, { html, ts: Date.now() });
if (itemCacheMap.size > ITEM_CACHE_MAX) { if (itemCacheMap.size > ITEM_CACHE_MAX) {
itemCacheMap.delete(itemCacheMap.keys().next().value); itemCacheMap.delete(itemCacheMap.keys().next().value);
@@ -1926,8 +1970,24 @@ window.cancelAnimFrame = (function () {
preloadMediaFromHtml(html); preloadMediaFromHtml(html);
} }
}) })
.catch(() => {}); .catch(err => {
if (err.name !== 'AbortError') {
window.f0ckDebug('[prefetch] Prefetch error or cancelled:', err.message);
}
}); });
});
};
const schedulePrefetch = (delay = 600) => {
cancelPrefetch();
_prefetchDebounceTimer = setTimeout(() => {
_prefetchDebounceTimer = null;
if (typeof requestIdleCallback === 'function') {
requestIdleCallback(() => prefetchNextPrevItems(), { timeout: 1500 });
} else {
prefetchNextPrevItems();
}
}, delay);
}; };
const setupMedia = () => { const setupMedia = () => {
@@ -1948,6 +2008,7 @@ window.cancelAnimFrame = (function () {
window._onaraReturnUrl = getOnaraBaseUrl(); window._onaraReturnUrl = getOnaraBaseUrl();
window._onaraReturnTitle = window.f0ckDomain || 'f0ck'; window._onaraReturnTitle = window.f0ckDomain || 'f0ck';
} }
window._onaraCurrentGridUrl = window._onaraReturnUrl;
const pathSegments = window.location.pathname.split('/'); const pathSegments = window.location.pathname.split('/');
const keySegments = pathSegments.filter(s => /^\d+$/.test(s) || /^[a-zA-Z0-9_-]{11}$/.test(s)); const keySegments = pathSegments.filter(s => /^\d+$/.test(s) || /^[a-zA-Z0-9_-]{11}$/.test(s));
const activeKey = keySegments.length ? keySegments[keySegments.length - 1] : null; const activeKey = keySegments.length ? keySegments[keySegments.length - 1] : null;
@@ -1957,6 +2018,8 @@ window.cancelAnimFrame = (function () {
window.trackVisit(activeKey); window.trackVisit(activeKey);
} }
} }
} else {
window._onaraCurrentGridUrl = window.location.pathname + window.location.search;
} }
}; };
@@ -1964,11 +2027,7 @@ window.cancelAnimFrame = (function () {
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
setupMedia(); setupMedia();
initOnaraInitialState(); initOnaraInitialState();
if (typeof requestIdleCallback === 'function') { schedulePrefetch(800);
requestIdleCallback(() => prefetchNextPrevItems());
} else {
setTimeout(prefetchNextPrevItems, 500);
}
}); });
initOnaraInitialState(); initOnaraInitialState();
@@ -2820,6 +2879,10 @@ window.cancelAnimFrame = (function () {
const loadPageAjax = async (url, replace = true, options = {}) => { const loadPageAjax = async (url, replace = true, options = {}) => {
if (isNavigating) return; if (isNavigating) return;
const fromOnaraModal = document.body.classList.contains('onara-modal-open') ||
(document.getElementById('onara-modal') && document.getElementById('onara-modal').style.display !== 'none');
const shouldReplaceHistory = options.replaceHistory ?? (fromOnaraModal && !options.skipPush);
// Immediately restore scrollability and hide modals // Immediately restore scrollability and hide modals
if (window.resetGlobalScrollState) window.resetGlobalScrollState(); if (window.resetGlobalScrollState) window.resetGlobalScrollState();
if (window.hideAllModals) window.hideAllModals(); if (window.hideAllModals) window.hideAllModals();
@@ -2882,7 +2945,7 @@ window.cancelAnimFrame = (function () {
const currentScroll = currentContainer ? currentContainer.scrollTop : window.scrollY; const currentScroll = currentContainer ? currentContainer.scrollTop : window.scrollY;
// Save scroll position for the current page before we leave it (only for new navigations) // Save scroll position for the current page before we leave it (only for new navigations)
if (!options.skipPush) { if (!options.skipPush && !shouldReplaceHistory) {
const currentState = history.state || {}; const currentState = history.state || {};
history.replaceState({ ...currentState, scroll: currentScroll }, document.title, window.location.href); history.replaceState({ ...currentState, scroll: currentScroll }, document.title, window.location.href);
} }
@@ -2977,7 +3040,13 @@ window.cancelAnimFrame = (function () {
} }
// Update URL bar (only for explicit clicks, not browser back/forward which already did it) // Update URL bar (only for explicit clicks, not browser back/forward which already did it)
if (!options.skipPush && !document.body.classList.contains('onara-modal-open')) history.pushState({}, '', url); if (!options.skipPush && !document.body.classList.contains('onara-modal-open')) {
if (shouldReplaceHistory) {
history.replaceState({}, '', url);
} else {
history.pushState({}, '', url);
}
}
// Restore scroll position // Restore scroll position
requestAnimationFrame(() => { requestAnimationFrame(() => {
@@ -3001,6 +3070,7 @@ window.cancelAnimFrame = (function () {
if (window.initSidebarRightToggle) window.initSidebarRightToggle(); if (window.initSidebarRightToggle) window.initSidebarRightToggle();
if (window.syncNavbarHeight) window.syncNavbarHeight(); if (window.syncNavbarHeight) window.syncNavbarHeight();
window.NotificationSystemInstance?.pollDebounced?.(); window.NotificationSystemInstance?.pollDebounced?.();
window._onaraCurrentGridUrl = urlObj.pathname + urlObj.search;
isNavigating = false; isNavigating = false;
return; // SKIP FETCH return; // SKIP FETCH
} }
@@ -3185,7 +3255,7 @@ window.cancelAnimFrame = (function () {
}); });
// Sync body classes (CRITICAL for layout triggers) // Sync body classes (CRITICAL for layout triggers)
const persistClasses = ['sidebar-right-hidden', 'bg-active']; const persistClasses = ['sidebar-right-hidden', 'bg-active', 'sidebar-left-hidden', 'comments-hidden'];
const currentlyActive = persistClasses.filter(c => document.body.classList.contains(c)); const currentlyActive = persistClasses.filter(c => document.body.classList.contains(c));
document.body.className = doc.body.className; document.body.className = doc.body.className;
@@ -3412,8 +3482,12 @@ window.cancelAnimFrame = (function () {
// UPDATE HISTORY BEFORE INITIALIZING SCRIPTS // UPDATE HISTORY BEFORE INITIALIZING SCRIPTS
if (!options.skipPush && !document.body.classList.contains('onara-modal-open')) { if (!options.skipPush && !document.body.classList.contains('onara-modal-open')) {
if (shouldReplaceHistory) {
history.replaceState({}, '', url);
} else {
history.pushState({}, '', url); history.pushState({}, '', url);
} }
}
// Sync sidebar and comments-list layout (Legacy View Only) // Sync sidebar and comments-list layout (Legacy View Only)
if (document.body.classList.contains('legacy-view')) { if (document.body.classList.contains('legacy-view')) {
@@ -3551,6 +3625,7 @@ window.cancelAnimFrame = (function () {
}, 500); }, 500);
} }
window._onaraCurrentGridUrl = urlObj.pathname + urlObj.search;
return; return;
} }
@@ -3618,8 +3693,12 @@ window.cancelAnimFrame = (function () {
} }
} }
if (shouldReplaceHistory) {
history.replaceState({}, '', pushUrl);
} else {
history.pushState({}, '', pushUrl); history.pushState({}, '', pushUrl);
} }
}
// Update pagination (CRITICAL: Do this BEFORE initializing scroll so it captures fresh HTML) // Update pagination (CRITICAL: Do this BEFORE initializing scroll so it captures fresh HTML)
if (data.pagination) { if (data.pagination) {
@@ -3655,6 +3734,7 @@ window.cancelAnimFrame = (function () {
if (window.initSidebarRightToggle) window.initSidebarRightToggle(); if (window.initSidebarRightToggle) window.initSidebarRightToggle();
// Sync has-notif highlights after grid loads — handles PWA where visibilitychange doesn't fire // Sync has-notif highlights after grid loads — handles PWA where visibilitychange doesn't fire
window.NotificationSystemInstance?.pollDebounced?.(); window.NotificationSystemInstance?.pollDebounced?.();
window._onaraCurrentGridUrl = urlObj.pathname + urlObj.search;
// Instant jump to hash (e.g. #c123) // Instant jump to hash (e.g. #c123)
if (hash) { if (hash) {
@@ -3781,6 +3861,7 @@ window.cancelAnimFrame = (function () {
const loadItemAjax = async (url, inheritContext = true, options = {}) => { const loadItemAjax = async (url, inheritContext = true, options = {}) => {
if (isNavigating) return; if (isNavigating) return;
cancelPrefetch();
// Immediately restore scrollability and hide modals // Immediately restore scrollability and hide modals
if (window.resetGlobalScrollState) window.resetGlobalScrollState(); if (window.resetGlobalScrollState) window.resetGlobalScrollState();
@@ -3793,8 +3874,14 @@ window.cancelAnimFrame = (function () {
const useOnara = isOnaraActive() && (isGridPage || isModalOpen); const useOnara = isOnaraActive() && (isGridPage || isModalOpen);
if (useOnara && !isModalOpen) { if (useOnara && !isModalOpen) {
if (options.skipPush || isItemPath(window.location.href)) {
window._onaraReturnUrl = getOnaraBaseUrl(url);
} else {
window._onaraReturnUrl = window.location.href; window._onaraReturnUrl = window.location.href;
}
window._onaraReturnTitle = document.title; window._onaraReturnTitle = document.title;
// Record history depth so we can jump past all stacked item entries on close
window._onaraOpenHistoryLength = history.length;
} }
// ── Scroller-active cleanup (same as loadPageAjax) ─────────────────── // ── Scroller-active cleanup (same as loadPageAjax) ───────────────────
@@ -4000,8 +4087,8 @@ window.cancelAnimFrame = (function () {
const _itemCacheKey = ajaxUrl; const _itemCacheKey = ajaxUrl;
const _cachedItem = !options.skipCache ? itemCacheMap.get(_itemCacheKey) : null; const _cachedItem = !options.skipCache ? itemCacheMap.get(_itemCacheKey) : null;
// Allow slide-out animation to visibly start before content swap // Allow slide-out or fade-out animation to visibly start before content swap
if (options.direction && document.querySelector('.content')) { if ((options.direction || options.transition === 'fade-zoom') && document.querySelector('.content')) {
await new Promise(r => setTimeout(r, 180)); await new Promise(r => setTimeout(r, 180));
} }
@@ -4065,13 +4152,39 @@ window.cancelAnimFrame = (function () {
if (_o && _n) _o.replaceWith(_n.cloneNode(true)); if (_o && _n) _o.replaceWith(_n.cloneNode(true));
}); });
} else { } else {
const isCommentsHidden = localStorage.getItem('comments_hidden') === 'true';
if (isCommentsHidden) {
document.body.classList.add('sidebar-left-hidden', 'comments-hidden');
} else {
document.body.classList.remove('sidebar-left-hidden', 'comments-hidden');
}
_container.insertAdjacentHTML('beforeend', _html); _container.insertAdjacentHTML('beforeend', _html);
if (isCommentsHidden) {
const _newLayout = _container.querySelector('.item-layout-container');
if (_newLayout) _newLayout.classList.add('sidebar-hidden');
const _newComments = _container.querySelector('#comments-container');
if (_newComments) {
_newComments.classList.add('faded-out', 'is-hidden');
_newComments.style.setProperty('display', 'none', 'important');
}
const _newLeftSb = _container.querySelector('.item-sidebar-left');
if (_newLeftSb) {
_newLeftSb.classList.add('is-hidden');
_newLeftSb.style.setProperty('display', 'none', 'important');
}
}
if (options.direction) { if (options.direction) {
const _newEls = _container.querySelectorAll('.content, ._204863, .item_title'); const _newEls = _container.querySelectorAll('.content, ._204863, .item_title');
_newEls.forEach(el => { _newEls.forEach(el => {
el.classList.remove('slide-next', 'slide-prev', 'slide-out-left', 'slide-out-right'); el.classList.remove('slide-next', 'slide-prev', 'slide-out-left', 'slide-out-right', 'fade-out-zoom', 'fade-in-zoom');
el.classList.add(options.direction === 'next' ? 'slide-next' : 'slide-prev'); el.classList.add(options.direction === 'next' ? 'slide-next' : 'slide-prev');
}); });
} else if (options.transition === 'fade-zoom') {
const _newEls = _container.querySelectorAll('.content, ._204863, .item_title');
_newEls.forEach(el => {
el.classList.remove('slide-next', 'slide-prev', 'slide-out-left', 'slide-out-right', 'fade-out-zoom', 'fade-in-zoom');
el.classList.add('fade-in-zoom');
});
} }
} }
@@ -4135,11 +4248,7 @@ window.cancelAnimFrame = (function () {
}) })
.catch(() => {}); .catch(() => {});
if (typeof requestIdleCallback === 'function') { schedulePrefetch(600);
requestIdleCallback(() => prefetchNextPrevItems());
} else {
setTimeout(prefetchNextPrevItems, 100);
}
return; // Done — served from cache return; // Done — served from cache
} }
@@ -4279,13 +4388,39 @@ window.cancelAnimFrame = (function () {
} }
}); });
} else { } else {
const isCommentsHidden = localStorage.getItem('comments_hidden') === 'true';
if (isCommentsHidden) {
document.body.classList.add('sidebar-left-hidden', 'comments-hidden');
} else {
document.body.classList.remove('sidebar-left-hidden', 'comments-hidden');
}
container.insertAdjacentHTML('beforeend', html); container.insertAdjacentHTML('beforeend', html);
if (isCommentsHidden) {
const newLayout = container.querySelector('.item-layout-container');
if (newLayout) newLayout.classList.add('sidebar-hidden');
const newComments = container.querySelector('#comments-container');
if (newComments) {
newComments.classList.add('faded-out', 'is-hidden');
newComments.style.setProperty('display', 'none', 'important');
}
const newLeftSb = container.querySelector('.item-sidebar-left');
if (newLeftSb) {
newLeftSb.classList.add('is-hidden');
newLeftSb.style.setProperty('display', 'none', 'important');
}
}
if (options.direction) { if (options.direction) {
const newEls = container.querySelectorAll('.content, ._204863, .item_title'); const newEls = container.querySelectorAll('.content, ._204863, .item_title');
newEls.forEach(el => { newEls.forEach(el => {
el.classList.remove('slide-next', 'slide-prev', 'slide-out-left', 'slide-out-right'); el.classList.remove('slide-next', 'slide-prev', 'slide-out-left', 'slide-out-right', 'fade-out-zoom', 'fade-in-zoom');
el.classList.add(options.direction === 'next' ? 'slide-next' : 'slide-prev'); el.classList.add(options.direction === 'next' ? 'slide-next' : 'slide-prev');
}); });
} else if (options.transition === 'fade-zoom') {
const newEls = container.querySelectorAll('.content, ._204863, .item_title');
newEls.forEach(el => {
el.classList.remove('slide-next', 'slide-prev', 'slide-out-left', 'slide-out-right', 'fade-out-zoom', 'fade-in-zoom');
el.classList.add('fade-in-zoom');
});
} }
// Note: Prefetching video src immediately after DOM injection has been removed // Note: Prefetching video src immediately after DOM injection has been removed
@@ -4365,11 +4500,7 @@ window.cancelAnimFrame = (function () {
console.error("updateStrictLinks error:", err); console.error("updateStrictLinks error:", err);
} }
if (typeof requestIdleCallback === 'function') { schedulePrefetch(600);
requestIdleCallback(() => prefetchNextPrevItems());
} else {
setTimeout(prefetchNextPrevItems, 200);
}
// After comments have rendered (f0ck:contentLoaded above triggers synchronous render), // After comments have rendered (f0ck:contentLoaded above triggers synchronous render),
// constrain the comments-list height via direct measurement (flex chain unreliable in AJAX). // constrain the comments-list height via direct measurement (flex chain unreliable in AJAX).
@@ -4496,25 +4627,14 @@ window.cancelAnimFrame = (function () {
if (link.id === 'random' || link.id === 'nav-random') { if (link.id === 'random' || link.id === 'nav-random') {
e.preventDefault(); e.preventDefault();
// In Onara mode with a background grid, pick a random item from the grid so it reflects on the main page const outEls = document.querySelectorAll('.content, ._204863, .item_title');
if (isOnaraActive()) { outEls.forEach(el => {
const gridThumbs = Array.from(document.querySelectorAll('.posts > a:not(.notif-item)')); el.classList.remove('slide-out-left', 'slide-out-right', 'slide-next', 'slide-prev', 'fade-out-zoom', 'fade-in-zoom');
if (gridThumbs.length > 0) { el.classList.add('fade-out-zoom');
const pool = gridThumbs.filter(t => !t.classList.contains('onara-active')); });
const chosen = (pool.length > 0 ? pool : gridThumbs)[Math.floor(Math.random() * (pool.length > 0 ? pool : gridThumbs).length)];
if (chosen) {
document.querySelectorAll('.onara-active').forEach(el => el.classList.remove('onara-active'));
chosen.classList.add('onara-active');
if (document.activeElement && document.activeElement !== chosen && typeof document.activeElement.blur === 'function') {
document.activeElement.blur();
}
loadItemAjax(chosen.href, true);
return;
}
}
}
stopMedia(); // Abort media BEFORE fetching random item // Pause media without stripping src so it doesn't flash black before fetch returns
document.querySelectorAll('video, audio').forEach(m => { try { m.pause(); } catch (e) {} });
// Fade out background canvas for smooth transition // Fade out background canvas for smooth transition
const canvas = document.getElementById('bg'); const canvas = document.getElementById('bg');
@@ -4595,14 +4715,79 @@ window.cancelAnimFrame = (function () {
.then(data => { .then(data => {
if (data.success && data.items && (data.items.slug || data.items.id)) { if (data.success && data.items && (data.items.slug || data.items.id)) {
const targetKey = data.items.slug || data.items.id; const targetKey = data.items.slug || data.items.id;
// Navigate in the same context (user hall, favs, tag, etc.) const targetId = data.items.id;
// Navigate immediately — don't wait for grid sync
if (wUserHall && wUserHallOwner) { if (wUserHall && wUserHallOwner) {
loadItemAjax(`/user/${encodeURIComponent(wUserHallOwner)}/hall/${encodeURIComponent(wUserHall)}/${targetKey}`, true); loadItemAjax(`/user/${encodeURIComponent(wUserHallOwner)}/hall/${encodeURIComponent(wUserHall)}/${targetKey}`, true, { transition: 'fade-zoom' });
} else if (wFavsUser) { } else if (wFavsUser) {
// Preserve /user/:name/favs/:id context so next/prev arrows stay within favs loadItemAjax(`/user/${encodeURIComponent(wFavsUser)}/favs/${targetKey}`, true, { transition: 'fade-zoom' });
loadItemAjax(`/user/${encodeURIComponent(wFavsUser)}/favs/${targetKey}`, true);
} else { } else {
loadItemAjax(`/${targetKey}`, true); loadItemAjax(`/${targetKey}`, true, { transition: 'fade-zoom' });
}
// Background grid sync for Onara — fire and forget, user never waits for this
if (isOnaraActive() && targetId) {
const gridThumbs = Array.from(document.querySelectorAll('.posts > a:not(.notif-item)'));
const alreadyInGrid = gridThumbs.some(a => {
const k = a.pathname.replace(/\/$/, '').split('/').pop();
return k == targetId || (data.items.slug && k === data.items.slug);
});
console.log('[random-gridsync] isOnaraActive:', true, 'alreadyInGrid:', alreadyInGrid, 'targetId:', targetId, '_onaraCurrentGridUrl:', window._onaraCurrentGridUrl);
if (!alreadyInGrid) {
const pageParams = new URLSearchParams(params);
pageParams.set('id', targetId);
console.log('[random-gridsync] fetching item-page:', `/api/v2/item-page?${pageParams}`);
fetch(`/api/v2/item-page?${pageParams}`)
.then(r => r.json())
.then(pd => {
console.log('[random-gridsync] item-page response:', pd);
if (!pd.success || !pd.page) return;
const targetPage = pd.page;
const postsEl = document.querySelector('.posts[data-current-page]');
const currentPage = postsEl ? parseInt(postsEl.dataset.currentPage, 10) : 1;
console.log('[random-gridsync] targetPage:', targetPage, 'currentPage:', currentPage, '_onaraCurrentGridUrl:', window._onaraCurrentGridUrl);
if (targetPage === currentPage) return;
let gridBase;
if (window._onaraCurrentGridUrl) {
// Strip trailing slash — root '/' becomes '' so '/p/N' builds correctly
gridBase = window._onaraCurrentGridUrl.replace(/\/p\/\d+/, '').replace(/\/$/, '');
} else {
gridBase = window.location.pathname
.replace(/\/[a-zA-Z0-9_-]{11}$/, '')
.replace(/\/\d+$/, '')
.replace(/\/p\/\d+/, '')
.replace(/\/$/, '');
}
const pageUrl = targetPage === 1 ? (gridBase || '/') : `${gridBase}/p/${targetPage}`;
console.log('[random-gridsync] fetching grid page:', pageUrl);
fetch(pageUrl)
.then(r => r.text())
.then(html => {
const doc = new DOMParser().parseFromString(html, 'text/html');
const newPosts = doc.querySelector('.posts');
const oldPosts = document.querySelector('.posts');
console.log('[random-gridsync] grid swap: newPosts found:', !!newPosts, 'oldPosts found:', !!oldPosts);
if (newPosts && oldPosts) {
oldPosts.innerHTML = newPosts.innerHTML;
oldPosts.dataset.currentPage = targetPage;
if (typeof window.initLazyLoading === 'function') window.initLazyLoading();
}
window._onaraCurrentGridUrl = pageUrl;
// Highlight after grid swapped
if (typeof updateOnaraActiveItem === 'function') {
updateOnaraActiveItem(targetId, `/${targetKey}`);
}
})
.catch((e) => { console.warn('[random-gridsync] grid fetch failed', e); });
})
.catch((e) => { console.warn('[random-gridsync] item-page fetch failed', e); });
}
} else {
console.log('[random-gridsync] skipped: isOnaraActive=', isOnaraActive(), 'targetId=', targetId);
} }
} else { } else {
window.location.href = link.href; window.location.href = link.href;
@@ -4697,20 +4882,15 @@ window.cancelAnimFrame = (function () {
} }
const parts = pathname.split('/').filter(Boolean); const parts = pathname.split('/').filter(Boolean);
const isItemLink = !pathname.match(/\/p\//) && ( const isItemLink = isItemPath(pathname);
pathname.match(/^\/\d+/) ||
pathname.match(/^\/[a-zA-Z0-9_-]{11}(?:[?#]|$)/) ||
(parts.length >= 3 && parts[0] === 'tag' && (/^\d+$/.test(parts[parts.length - 1]) || /^[a-zA-Z0-9_-]{11}$/.test(parts[parts.length - 1]))) ||
(parts.length >= 3 && parts[0] === 'user' && (/^\d+$/.test(parts[parts.length - 1]) || /^[a-zA-Z0-9_-]{11}$/.test(parts[parts.length - 1]))) ||
(parts.length >= 3 && parts[0] === 'h' && (/^\d+$/.test(parts[parts.length - 1]) || /^[a-zA-Z0-9_-]{11}$/.test(parts[parts.length - 1])))
);
if (isItemLink) { if (isItemLink) {
// Links inside comment bodies or MOTD should not inherit tag/hall/user context // Links inside comment bodies or MOTD should not inherit tag/hall/user context
const _inComment = anyLink.closest(".comment, .comment-content, .comment-body, .motd-content"); const _inComment = anyLink.closest(".comment, .comment-content, .comment-body, .motd-content");
loadItemAjax(targetUrl, !_inComment); loadItemAjax(targetUrl, !_inComment);
} else { } else {
const isBrand = anyLink.classList.contains('navbar-brand'); const isBrand = anyLink.classList.contains('navbar-brand');
loadPageAjax(targetUrl, true, { bypassCache: isBrand }); const fromModal = document.body.classList.contains('onara-modal-open') || !!anyLink.closest('#onara-modal');
loadPageAjax(targetUrl, true, { bypassCache: isBrand, replaceHistory: fromModal });
} }
return false; return false;
} }
@@ -5134,6 +5314,9 @@ window.cancelAnimFrame = (function () {
}); });
window.addEventListener('popstate', (e) => { window.addEventListener('popstate', (e) => {
// Swallow popstate events fired during Onara close (history.go(-N) unwinding)
if (window._onaraSuppressPopstate) return;
const url = window.location.href; const url = window.location.href;
const p = window.location.pathname; const p = window.location.pathname;
@@ -5158,30 +5341,51 @@ window.cancelAnimFrame = (function () {
// Item detection logic MUST match loadPageAjax/loadItemAjax analysis // Item detection logic MUST match loadPageAjax/loadItemAjax analysis
// Priorities: Item first, then Special/Grid // Priorities: Item first, then Special/Grid
const parts = p.split('/').filter(Boolean); const isItem = isItemPath(p);
const isItem = !p.match(/\/p\//) && (
p.match(/^\/\d+/) ||
p.match(/^\/[a-zA-Z0-9_-]{11}(?:[?#]|$)/) ||
(parts.length >= 3 && parts[0] === 'tag' && (/^\d+$/.test(parts[parts.length - 1]) || /^[a-zA-Z0-9_-]{11}$/.test(parts[parts.length - 1]))) ||
(parts.length >= 3 && parts[0] === 'user' && (/^\d+$/.test(parts[parts.length - 1]) || /^[a-zA-Z0-9_-]{11}$/.test(parts[parts.length - 1]))) ||
(parts.length >= 3 && parts[0] === 'h' && (/^\d+$/.test(parts[parts.length - 1]) || /^[a-zA-Z0-9_-]{11}$/.test(parts[parts.length - 1])))
);
const isSpecial = p.startsWith('/notifications') || p.startsWith('/tags') || p.startsWith('/user/') || p.startsWith('/subscriptions') || p.startsWith('/ranking'); const isSpecial = p.startsWith('/notifications') || p.startsWith('/tags') || p.startsWith('/user/') || p.startsWith('/subscriptions') || p.startsWith('/ranking');
const isGridLike = url.match(/\/p\/\d+/) || url.match(/[?&]page=\d+/) || p === '/'; const isGridLike = url.match(/\/p\/\d+/) || url.match(/[?&]page=\d+/) || p === '/';
window.f0ckDebug("[popstate] Navigation to:", url, { isItem, isSpecial, isGridLike }); window.f0ckDebug("[popstate] Navigation to:", url, { isItem, isSpecial, isGridLike });
if (document.body.classList.contains('onara-modal-open') && !isItem) { const isOnaraModalCurrentlyOpen = document.body.classList.contains('onara-modal-open') ||
(document.getElementById('onara-modal') && document.getElementById('onara-modal').style.display !== 'none');
if (isOnaraModalCurrentlyOpen && !isItem) {
closeOnaraModal({ skipHistory: true }); closeOnaraModal({ skipHistory: true });
if (isOnaraActive()) {
const currentGrid = window._onaraCurrentGridUrl || (document.querySelector('.posts') ? (document.querySelector('.posts').dataset.currentUrl || '/') : '/');
const targetPath = new URL(url, window.location.origin).pathname;
const currentPath = new URL(currentGrid, window.location.origin).pathname;
if (currentPath !== targetPath) {
window._onaraCurrentGridUrl = url;
loadPageAjax(url, true, { skipPush: true });
}
}
return; return;
} }
if (isItem) { if (isItem) {
if (isOnaraActive()) {
const expectedBaseUrl = getOnaraBaseUrl(url);
const currentGrid = window._onaraCurrentGridUrl || (document.querySelector('.posts') ? (document.querySelector('.posts').dataset.currentUrl || '/') : '/');
const expectedPath = new URL(expectedBaseUrl, window.location.origin).pathname;
const currentPath = new URL(currentGrid, window.location.origin).pathname;
if (currentPath !== expectedPath) {
window._onaraCurrentGridUrl = expectedBaseUrl;
loadPageAjax(expectedBaseUrl, true, { skipPush: true });
}
}
loadItemAjax(url, true, { skipPush: true }); loadItemAjax(url, true, { skipPush: true });
} else if (isSpecial || isGridLike) { } else if (isSpecial || isGridLike) {
if (isOnaraActive()) {
window._onaraCurrentGridUrl = url;
}
loadPageAjax(url, true, { skipPush: true }); loadPageAjax(url, true, { skipPush: true });
} else { } else {
// Fallback // Fallback
if (isOnaraActive()) {
window._onaraCurrentGridUrl = url;
}
loadPageAjax(url, true, { skipPush: true }); loadPageAjax(url, true, { skipPush: true });
} }
}); });
@@ -5358,17 +5562,32 @@ window.cancelAnimFrame = (function () {
const imageModal = document.getElementById('image-modal'); const imageModal = document.getElementById('image-modal');
if (imageModal && imageModal.classList.contains('visible')) return; if (imageModal && imageModal.classList.contains('visible')) return;
const navBtn = event.target.closest('.nav-next, .nav-prev'); // Yield if scroller/abyss page is active
const isNavHover = !!navBtn; if (document.body && document.body.classList.contains('scroller-active')) return;
const isMediaHover = !isNavHover && !!event.target.closest('.media-object') && !event.target.closest('#favs');
// For media-object, user preference 'wheelNavEnabled' must be enabled. // Don't hijack if user is inside an input, textarea, or contenteditable element
// For .nav-next / .nav-prev, scrolling always triggers next/prev navigation directly. if (event.target && (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA' || event.target.isContentEditable)) {
if (!isNavHover && (!isMediaHover || localStorage.getItem('wheelNavEnabled') !== 'true')) {
return; return;
} }
// Always prevent default page scroll when scrolling over nav-next / nav-prev or media const isShiftWheel = event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey;
const navBtn = event.target.closest('.nav-next, .nav-prev');
const isNavHover = !!navBtn;
const isMediaHover = !isNavHover && !isShiftWheel && !!event.target.closest('.media-object') && !event.target.closest('#favs');
// Trigger conditions:
// 1. Shift + scrollwheel shortcut anywhere on post view
// 2. Hovering over .nav-next / .nav-prev
// 3. Hovering over media-object when 'wheelNavEnabled' preference is true
if (!isShiftWheel && !isNavHover && (!isMediaHover || localStorage.getItem('wheelNavEnabled') !== 'true')) {
return;
}
// Only intercept if item navigation buttons exist on the page
const hasNav = !!document.querySelector("#next:not([href='#']), .nav-next:not([href='#']), #prev:not([href='#']), .nav-prev:not([href='#'])");
if (!hasNav) return;
// Always prevent default page scroll when shortcut or target is matched
event.preventDefault(); event.preventDefault();
// Prevent rapid multi-firing while navigating or during throttle cooldown // Prevent rapid multi-firing while navigating or during throttle cooldown
@@ -7519,11 +7738,20 @@ window.cancelAnimFrame = (function () {
sb.style.setProperty('transition', 'none', 'important'); sb.style.setProperty('transition', 'none', 'important');
if (isHidden) { if (isHidden) {
layout.classList.remove('sidebar-hidden'); layout.classList.remove('sidebar-hidden');
document.body.classList.remove('sidebar-left-hidden'); document.body.classList.remove('sidebar-left-hidden', 'comments-hidden');
sb.style.removeProperty('display');
sb.style.display = ''; sb.style.display = '';
sb.querySelectorAll('.faded-out').forEach(el => el.classList.remove('faded-out')); sb.classList.remove('is-hidden');
sb.querySelectorAll('.faded-out, .is-hidden').forEach(el => {
el.classList.remove('faded-out', 'is-hidden');
el.style.removeProperty('display');
});
const c = sb.querySelector('#comments-container'); const c = sb.querySelector('#comments-container');
if (c) { c.classList.remove('faded-out'); c.style.display = ''; } if (c) {
c.classList.remove('faded-out', 'is-hidden');
c.style.removeProperty('display');
c.style.display = '';
}
void sb.offsetWidth; // commit display + class change before setting transform void sb.offsetWidth; // commit display + class change before setting transform
sb.style.transform = `translateX(${-sw}px)`; sb.style.transform = `translateX(${-sw}px)`;
} else { } else {
@@ -7544,14 +7772,15 @@ window.cancelAnimFrame = (function () {
sb.style.removeProperty('transition'); sb.style.removeProperty('transition');
if (shouldOpen) { if (shouldOpen) {
sb.style.removeProperty('transform'); sb.style.removeProperty('transform');
sb.style.removeProperty('display');
localStorage.setItem('comments_hidden', 'false'); localStorage.setItem('comments_hidden', 'false');
document.body.classList.remove('sidebar-left-hidden'); document.body.classList.remove('sidebar-left-hidden', 'comments-hidden');
} else { } else {
// Hide BEFORE removing transform to prevent snap-back flash // Hide BEFORE removing transform to prevent snap-back flash
sb.style.display = 'none'; sb.style.setProperty('display', 'none', 'important');
layout.classList.add('sidebar-hidden'); layout.classList.add('sidebar-hidden');
localStorage.setItem('comments_hidden', 'true'); localStorage.setItem('comments_hidden', 'true');
document.body.classList.add('sidebar-left-hidden'); document.body.classList.add('sidebar-left-hidden', 'comments-hidden');
sb.style.removeProperty('transform'); sb.style.removeProperty('transform');
} }
requestAnimationFrame(() => syncLeftEdgeZone()); requestAnimationFrame(() => syncLeftEdgeZone());
@@ -7772,7 +8001,11 @@ window.cancelAnimFrame = (function () {
if (window.innerWidth < 1000) return; if (window.innerWidth < 1000) return;
const { sb, layout } = getLS(); const { sb, layout } = getLS();
if (!sb || !layout) return; if (!sb || !layout) return;
const isHidden = layout.classList.contains('sidebar-hidden'); const isHidden = layout.classList.contains('sidebar-hidden') ||
document.body.classList.contains('sidebar-left-hidden') ||
document.body.classList.contains('comments-hidden') ||
localStorage.getItem('comments_hidden') === 'true';
localStorage.setItem('comments_hidden', isHidden ? 'false' : 'true');
const sw = isHidden ? Math.round(Math.min(400, window.innerWidth * 0.30)) : sb.offsetWidth; const sw = isHidden ? Math.round(Math.min(400, window.innerWidth * 0.30)) : sb.offsetWidth;
prepareDrag(sb, layout, isHidden, sw); prepareDrag(sb, layout, isHidden, sw);
// rAF ensures the initial transform state is painted before the transition starts // rAF ensures the initial transform state is painted before the transition starts
@@ -9569,9 +9802,8 @@ class NotificationSystem {
btn.style.visibility = 'visible'; btn.style.visibility = 'visible';
}); });
if (typeof prefetchNextPrevItems === 'function') { if (typeof schedulePrefetch === 'function') {
if (typeof requestIdleCallback === 'function') requestIdleCallback(() => prefetchNextPrevItems()); schedulePrefetch(800);
else setTimeout(prefetchNextPrevItems, 200);
} }
} }
} }
+38 -1
View File
@@ -570,6 +570,43 @@ export default router => {
res.end("api lol"); res.end("api lol");
}); });
// Lightweight page-lookup for Onara grid sync.
// Called async after random navigation — user never waits for this.
group.get(/\/item-page$/, async (req, res) => {
const id = parseInt(req.url.qs.id, 10) || 0;
if (!id) return res.json({ success: false, page: 1 });
const tag = req.url.qs.tag || null;
const hall = req.url.qs.hall || null;
const userHall = req.url.qs.userHall || null;
const userHallOwner= req.url.qs.userHallOwner|| null;
const user = req.url.qs.user || null;
const mime = req.url.qs.mime || null;
const isFav = req.url.qs.fav === 'true';
const isStrict = req.url.qs.strict === '1';
const mode = req.mode ?? 0;
const ratingsRaw = req.cookies.ratings;
const ratingsArr = ratingsRaw ? decodeURIComponent(ratingsRaw).split(/[|,]/).filter(r => ['sfw','nsfw','nsfl','untagged'].includes(r)) : null;
try {
const page = await f0cklib.getItemPage({
targetItemId: id,
user, tag, hall, userHall, userHallOwner, mime,
fav: isFav,
mode,
ratings: ratingsArr && ratingsArr.length > 0 ? ratingsArr : null,
strict: isStrict,
session: !!req.session,
exclude: req.session?.excluded_tags || [],
user_id: req.session?.id,
is_admin: req.session?.admin
});
return res.json({ success: true, page });
} catch (e) {
return res.json({ success: false, page: 1 });
}
});
group.get(/\/random(\/user\/.+|\/image|\/video|\/audio)?$/, async (req, res) => { group.get(/\/random(\/user\/.+|\/image|\/video|\/audio)?$/, async (req, res) => {
const pathParts = req.url.pathname.split('/'); const pathParts = req.url.pathname.split('/');
// /api/v2/random/user/name -> segments are ["", "api", "v2", "random", "user", "name"] // /api/v2/random/user/name -> segments are ["", "api", "v2", "random", "user", "name"]
@@ -629,7 +666,7 @@ export default router => {
} }
const isYouTube = item.mime === 'video/youtube'; const isYouTube = item.mime === 'video/youtube';
const ytSrcRegex = /(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\/?\/?\?(?:\S*?&?v=))|youtu\.be\/)([a-zA-Z0-9_-]{6,11})/i; const ytSrcRegex = /(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\/?\/?\?(?:\S*?&v=))|youtu\.be\/)([a-zA-Z0-9_-]{6,11})/i;
let ytDest = item.dest; let ytDest = item.dest;
if (isYouTube && (!ytDest || !ytDest.startsWith('yt:'))) { if (isYouTube && (!ytDest || !ytDest.startsWith('yt:'))) {
const m = item.src && item.src.match(ytSrcRegex); const m = item.src && item.src.match(ytSrcRegex);
+1 -1
View File
@@ -98,7 +98,7 @@
</head> </head>
<body class="@if(session)@if(session.use_new_layout)layout-modern@else layout-legacy @endif@else @if(default_layout === 'legacy')layout-legacy@else layout-modern@endif @endif @if(private_society && !session)private-gate-active@endif @if(is_onara_item) onara-modal-open @endif"> <body class="@if(session)@if(session.use_new_layout)layout-modern@else layout-legacy @endif@else @if(default_layout === 'legacy')layout-legacy@else layout-modern@endif @endif @if(private_society && !session)private-gate-active@endif @if(is_onara_item) onara-modal-open @endif">
<script>var sRH = localStorage.getItem('sidebarRightHidden'); if(sRH==='true' || (sRH===null && @if(hide_sidebar_default) true @else false @endif)) document.body.classList.add('sidebar-right-hidden'); if(localStorage.getItem('comments_hidden')==='true')document.body.classList.add('sidebar-left-hidden');(function(){var d=window.devicePixelRatio||1;console.log('[DPR] devicePixelRatio='+d);var t='dpr-1x';if(d>=1.55)t='dpr-high';else if(d>=1.39)t='dpr-150';else if(d>=1.26)t='dpr-130';else if(d>=1.20)t='dpr-120';else if(d>=1.05)t='dpr-110';document.documentElement.setAttribute('data-dpr',t);})();</script> <script>var sRH = localStorage.getItem('sidebarRightHidden'); if(sRH==='true' || (sRH===null && @if(hide_sidebar_default) true @else false @endif)) document.body.classList.add('sidebar-right-hidden'); if(localStorage.getItem('comments_hidden')==='true')document.body.classList.add('sidebar-left-hidden', 'comments-hidden');(function(){var d=window.devicePixelRatio||1;console.log('[DPR] devicePixelRatio='+d);var t='dpr-1x';if(d>=1.55)t='dpr-high';else if(d>=1.39)t='dpr-150';else if(d>=1.26)t='dpr-130';else if(d>=1.20)t='dpr-120';else if(d>=1.05)t='dpr-110';document.documentElement.setAttribute('data-dpr',t);})();</script>
@if(!private_society || session) @if(!private_society || session)
<canvas class="hidden-xs" id="bg"></canvas> <canvas class="hidden-xs" id="bg"></canvas>
@endif @endif
+1
View File
@@ -319,6 +319,7 @@
<div class="shortcut-item"><span><kbd></kbd> <kbd></kbd></span><span>{{ t('shortcuts.next_prev') }}</span></div> <div class="shortcut-item"><span><kbd></kbd> <kbd></kbd></span><span>{{ t('shortcuts.next_prev') }}</span></div>
<div class="shortcut-item"><span><kbd></kbd> <kbd></kbd></span><span>{{ t('shortcuts.volume') }}</span></div> <div class="shortcut-item"><span><kbd></kbd> <kbd></kbd></span><span>{{ t('shortcuts.volume') }}</span></div>
<div class="shortcut-item"><span><kbd>Scroll</kbd></span><span>{{ t('shortcuts.scroll_nav') }}</span></div> <div class="shortcut-item"><span><kbd>Scroll</kbd></span><span>{{ t('shortcuts.scroll_nav') }}</span></div>
<div class="shortcut-item"><span><kbd>Shift</kbd> + <kbd>Scroll</kbd></span><span>{{ t('shortcuts.next_prev') }}</span></div>
<div class="shortcut-item"><span><kbd>,</kbd> <kbd>.</kbd></span><span>{{ t('shortcuts.frame_skip') }}</span></div> <div class="shortcut-item"><span><kbd>,</kbd> <kbd>.</kbd></span><span>{{ t('shortcuts.frame_skip') }}</span></div>
<div class="shortcut-item"><span><kbd>?</kbd></span><span>{{ t('shortcuts.show_help') }}</span></div> <div class="shortcut-item"><span><kbd>?</kbd></span><span>{{ t('shortcuts.show_help') }}</span></div>
</div> </div>