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
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. */
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;
}
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;
}
body.layout-modern.sidebar-left-hidden .item-layout-container .item-sidebar-left {
display: none;
body.layout-modern.sidebar-left-hidden .item-layout-container .item-sidebar-left,
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;
}
@@ -3324,6 +3328,27 @@ body.layout-legacy #comments-container.faded-out {
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 {
border-top: 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;
}
.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 {
0% {
opacity: 0;
@@ -19028,9 +19096,9 @@ body.layout-modern #onara-modal .item-layout-container .item-main-content {
margin-right: auto !important;
}
#onara-modal .content: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),
#onara-modal .item_title: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):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):not(.fade-in-zoom):not(.fade-out-zoom) {
animation: contentSlideIn 0.55s cubic-bezier(0.16, 1, 0.3, 1) both !important;
will-change: transform, opacity;
}
+48 -14
View File
@@ -48,18 +48,30 @@ class CommentSystem {
const isHidden = localStorage.getItem('comments_hidden') === 'true';
// Force show if hash is present
if (window.location.hash && window.location.hash.startsWith('#c')) {
this.container.classList.remove('faded-out');
this.container.style.display = 'block';
this.container.classList.remove('faded-out', 'is-hidden');
this.container.style.removeProperty('display');
this.container.style.display = '';
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');
if (layout) layout.classList.remove('sidebar-hidden');
} else if (isHidden && (window.innerWidth >= 1000 || !document.body.classList.contains('layout-modern'))) {
this.container.classList.add('faded-out');
this.container.style.display = 'none';
document.body.classList.add('sidebar-left-hidden');
const sidebar = this.container.closest('.item-sidebar-left');
if (sidebar) {
sidebar.classList.remove('is-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');
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();
// 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();
// Legacy layout: comments are in the normal page flow under .item-main-content
@@ -3959,7 +3976,7 @@ class CommentSystem {
toggleComments() {
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 (typeof window.toggleSidebarLeft === 'function') {
window.toggleSidebarLeft();
@@ -3974,27 +3991,44 @@ class CommentSystem {
] : [];
// 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) {
// SHOW: expand grid first, then slide content in
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 = '';
if (sidebar) {
sidebar.classList.remove('is-hidden');
sidebar.style.removeProperty('display');
sidebar.style.display = '';
}
localStorage.setItem('comments_hidden', 'false');
void this.container.offsetWidth; // force reflow so transition fires
this.container.classList.remove('faded-out');
siblings.forEach(el => el.classList.remove('faded-out'));
} 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');
this.container.classList.add('faded-out');
siblings.forEach(el => el.classList.add('faded-out'));
setTimeout(() => {
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');
document.body.classList.add('sidebar-left-hidden');
document.body.classList.add('sidebar-left-hidden', 'comments-hidden');
}, 300);
}
}
+339 -107
View File
@@ -1135,6 +1135,21 @@ window.cancelAnimFrame = (function () {
};
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) => {
try {
const urlObj = new URL(itemUrl, window.location.origin);
@@ -1142,7 +1157,8 @@ window.cancelAnimFrame = (function () {
path = path.replace(/\/(?:\d+|[a-zA-Z0-9_-]{11})$/, '');
const postsEl = document.querySelector('.posts[data-current-page]');
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;
}
if (!path) path = '/';
@@ -1202,15 +1218,30 @@ window.cancelAnimFrame = (function () {
if (!options.skipHistory) {
const returnUrl = window._onaraReturnUrl || getOnaraBaseUrl();
history.pushState({}, '', returnUrl);
if (window._onaraReturnTitle) {
document.title = window._onaraReturnTitle;
} else if (window.f0ckDomain) {
document.title = window.f0ckDomain;
const returnTitle = window._onaraReturnTitle || window.f0ckDomain || '';
// Always jump directly to the return URL — never step back through stacked item history.
// history.back() would re-open the previous item via popstate; replaceState exits cleanly.
const depth = window._onaraOpenHistoryLength != null
? 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._onaraReturnTitle = null;
window._onaraOpenHistoryLength = null;
// Maintain focus on active item so user TAB navigation seamlessly resumes from current progress
const activeThumb = document.querySelector('.posts > a.thumb.onara-active');
@@ -1806,6 +1837,21 @@ window.cancelAnimFrame = (function () {
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) => {
if (!html) return;
@@ -1813,7 +1859,7 @@ window.cancelAnimFrame = (function () {
const parser = new DOMParser();
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');
if (img) {
const src = img.getAttribute('src');
@@ -1837,20 +1883,9 @@ window.cancelAnimFrame = (function () {
}
}
// 3. Preload Video / Audio Media (fetch initial bytes into browser cache)
const mediaEl = doc.querySelector('video#my-video, audio#my-video, .media-object video, .media-object audio');
if (mediaEl) {
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);
}
}
// Note: Video & Audio binary streams are deliberately NOT fetched via fetch() in JS.
// HTML5 media elements stream natively upon mounting. Fetching multi-megabyte chunks
// in the background exhausts the browser's 6-connection HTTP pool, stalling navigation.
if (_preloadedMediaUrls.size > 150) {
_preloadedMediaUrls.clear();
@@ -1861,11 +1896,20 @@ window.cancelAnimFrame = (function () {
};
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 prevBtn = document.getElementById('prev');
[nextBtn, prevBtn].forEach(btn => {
if (!btn || !btn.href || btn.href.endsWith('#')) return;
if (signal.aborted) return;
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));
@@ -1907,17 +1951,17 @@ window.cancelAnimFrame = (function () {
return;
}
fetch(ajaxUrl, { credentials: 'include', priority: 'low' })
fetch(ajaxUrl, { credentials: 'include', priority: 'low', signal })
.then(r => r.ok ? r.text() : null)
.then(rawText => {
if (!rawText) return;
if (!rawText || signal.aborted) return;
let html = rawText;
try {
const data = JSON.parse(rawText);
if (data && typeof data.html === 'string') html = data.html;
} catch (_) {}
if (html) {
if (html && !signal.aborted) {
itemCacheMap.set(ajaxUrl, { html, ts: Date.now() });
if (itemCacheMap.size > ITEM_CACHE_MAX) {
itemCacheMap.delete(itemCacheMap.keys().next().value);
@@ -1926,10 +1970,26 @@ window.cancelAnimFrame = (function () {
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 elem = document.querySelector("#my-video") || document.querySelector("audio#my-video");
if (elem) {
@@ -1948,6 +2008,7 @@ window.cancelAnimFrame = (function () {
window._onaraReturnUrl = getOnaraBaseUrl();
window._onaraReturnTitle = window.f0ckDomain || 'f0ck';
}
window._onaraCurrentGridUrl = window._onaraReturnUrl;
const pathSegments = window.location.pathname.split('/');
const keySegments = pathSegments.filter(s => /^\d+$/.test(s) || /^[a-zA-Z0-9_-]{11}$/.test(s));
const activeKey = keySegments.length ? keySegments[keySegments.length - 1] : null;
@@ -1957,6 +2018,8 @@ window.cancelAnimFrame = (function () {
window.trackVisit(activeKey);
}
}
} else {
window._onaraCurrentGridUrl = window.location.pathname + window.location.search;
}
};
@@ -1964,11 +2027,7 @@ window.cancelAnimFrame = (function () {
document.addEventListener('DOMContentLoaded', () => {
setupMedia();
initOnaraInitialState();
if (typeof requestIdleCallback === 'function') {
requestIdleCallback(() => prefetchNextPrevItems());
} else {
setTimeout(prefetchNextPrevItems, 500);
}
schedulePrefetch(800);
});
initOnaraInitialState();
@@ -2820,6 +2879,10 @@ window.cancelAnimFrame = (function () {
const loadPageAjax = async (url, replace = true, options = {}) => {
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
if (window.resetGlobalScrollState) window.resetGlobalScrollState();
if (window.hideAllModals) window.hideAllModals();
@@ -2882,7 +2945,7 @@ window.cancelAnimFrame = (function () {
const currentScroll = currentContainer ? currentContainer.scrollTop : window.scrollY;
// 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 || {};
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)
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
requestAnimationFrame(() => {
@@ -3001,6 +3070,7 @@ window.cancelAnimFrame = (function () {
if (window.initSidebarRightToggle) window.initSidebarRightToggle();
if (window.syncNavbarHeight) window.syncNavbarHeight();
window.NotificationSystemInstance?.pollDebounced?.();
window._onaraCurrentGridUrl = urlObj.pathname + urlObj.search;
isNavigating = false;
return; // SKIP FETCH
}
@@ -3185,7 +3255,7 @@ window.cancelAnimFrame = (function () {
});
// 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));
document.body.className = doc.body.className;
@@ -3412,7 +3482,11 @@ window.cancelAnimFrame = (function () {
// UPDATE HISTORY BEFORE INITIALIZING SCRIPTS
if (!options.skipPush && !document.body.classList.contains('onara-modal-open')) {
history.pushState({}, '', url);
if (shouldReplaceHistory) {
history.replaceState({}, '', url);
} else {
history.pushState({}, '', url);
}
}
// Sync sidebar and comments-list layout (Legacy View Only)
@@ -3551,6 +3625,7 @@ window.cancelAnimFrame = (function () {
}, 500);
}
window._onaraCurrentGridUrl = urlObj.pathname + urlObj.search;
return;
}
@@ -3618,7 +3693,11 @@ window.cancelAnimFrame = (function () {
}
}
history.pushState({}, '', pushUrl);
if (shouldReplaceHistory) {
history.replaceState({}, '', pushUrl);
} else {
history.pushState({}, '', pushUrl);
}
}
// Update pagination (CRITICAL: Do this BEFORE initializing scroll so it captures fresh HTML)
@@ -3655,6 +3734,7 @@ window.cancelAnimFrame = (function () {
if (window.initSidebarRightToggle) window.initSidebarRightToggle();
// Sync has-notif highlights after grid loads — handles PWA where visibilitychange doesn't fire
window.NotificationSystemInstance?.pollDebounced?.();
window._onaraCurrentGridUrl = urlObj.pathname + urlObj.search;
// Instant jump to hash (e.g. #c123)
if (hash) {
@@ -3781,6 +3861,7 @@ window.cancelAnimFrame = (function () {
const loadItemAjax = async (url, inheritContext = true, options = {}) => {
if (isNavigating) return;
cancelPrefetch();
// Immediately restore scrollability and hide modals
if (window.resetGlobalScrollState) window.resetGlobalScrollState();
@@ -3793,8 +3874,14 @@ window.cancelAnimFrame = (function () {
const useOnara = isOnaraActive() && (isGridPage || isModalOpen);
if (useOnara && !isModalOpen) {
window._onaraReturnUrl = window.location.href;
if (options.skipPush || isItemPath(window.location.href)) {
window._onaraReturnUrl = getOnaraBaseUrl(url);
} else {
window._onaraReturnUrl = window.location.href;
}
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) ───────────────────
@@ -4000,8 +4087,8 @@ window.cancelAnimFrame = (function () {
const _itemCacheKey = ajaxUrl;
const _cachedItem = !options.skipCache ? itemCacheMap.get(_itemCacheKey) : null;
// Allow slide-out animation to visibly start before content swap
if (options.direction && document.querySelector('.content')) {
// Allow slide-out or fade-out animation to visibly start before content swap
if ((options.direction || options.transition === 'fade-zoom') && document.querySelector('.content')) {
await new Promise(r => setTimeout(r, 180));
}
@@ -4065,13 +4152,39 @@ window.cancelAnimFrame = (function () {
if (_o && _n) _o.replaceWith(_n.cloneNode(true));
});
} 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);
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) {
const _newEls = _container.querySelectorAll('.content, ._204863, .item_title');
_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');
});
} 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(() => {});
if (typeof requestIdleCallback === 'function') {
requestIdleCallback(() => prefetchNextPrevItems());
} else {
setTimeout(prefetchNextPrevItems, 100);
}
schedulePrefetch(600);
return; // Done — served from cache
}
@@ -4279,13 +4388,39 @@ window.cancelAnimFrame = (function () {
}
});
} 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);
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) {
const newEls = container.querySelectorAll('.content, ._204863, .item_title');
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');
});
} 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
@@ -4365,11 +4500,7 @@ window.cancelAnimFrame = (function () {
console.error("updateStrictLinks error:", err);
}
if (typeof requestIdleCallback === 'function') {
requestIdleCallback(() => prefetchNextPrevItems());
} else {
setTimeout(prefetchNextPrevItems, 200);
}
schedulePrefetch(600);
// After comments have rendered (f0ck:contentLoaded above triggers synchronous render),
// 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') {
e.preventDefault();
// In Onara mode with a background grid, pick a random item from the grid so it reflects on the main page
if (isOnaraActive()) {
const gridThumbs = Array.from(document.querySelectorAll('.posts > a:not(.notif-item)'));
if (gridThumbs.length > 0) {
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;
}
}
}
const outEls = document.querySelectorAll('.content, ._204863, .item_title');
outEls.forEach(el => {
el.classList.remove('slide-out-left', 'slide-out-right', 'slide-next', 'slide-prev', 'fade-out-zoom', 'fade-in-zoom');
el.classList.add('fade-out-zoom');
});
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
const canvas = document.getElementById('bg');
@@ -4595,14 +4715,79 @@ window.cancelAnimFrame = (function () {
.then(data => {
if (data.success && data.items && (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) {
loadItemAjax(`/user/${encodeURIComponent(wUserHallOwner)}/hall/${encodeURIComponent(wUserHall)}/${targetKey}`, true);
loadItemAjax(`/user/${encodeURIComponent(wUserHallOwner)}/hall/${encodeURIComponent(wUserHall)}/${targetKey}`, true, { transition: 'fade-zoom' });
} else if (wFavsUser) {
// Preserve /user/:name/favs/:id context so next/prev arrows stay within favs
loadItemAjax(`/user/${encodeURIComponent(wFavsUser)}/favs/${targetKey}`, true);
loadItemAjax(`/user/${encodeURIComponent(wFavsUser)}/favs/${targetKey}`, true, { transition: 'fade-zoom' });
} 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 {
window.location.href = link.href;
@@ -4697,20 +4882,15 @@ window.cancelAnimFrame = (function () {
}
const parts = pathname.split('/').filter(Boolean);
const isItemLink = !pathname.match(/\/p\//) && (
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])))
);
const isItemLink = isItemPath(pathname);
if (isItemLink) {
// Links inside comment bodies or MOTD should not inherit tag/hall/user context
const _inComment = anyLink.closest(".comment, .comment-content, .comment-body, .motd-content");
loadItemAjax(targetUrl, !_inComment);
} else {
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;
}
@@ -5134,6 +5314,9 @@ window.cancelAnimFrame = (function () {
});
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 p = window.location.pathname;
@@ -5158,30 +5341,51 @@ window.cancelAnimFrame = (function () {
// Item detection logic MUST match loadPageAjax/loadItemAjax analysis
// Priorities: Item first, then Special/Grid
const parts = p.split('/').filter(Boolean);
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 isItem = isItemPath(p);
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 === '/';
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 });
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;
}
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 });
} else if (isSpecial || isGridLike) {
if (isOnaraActive()) {
window._onaraCurrentGridUrl = url;
}
loadPageAjax(url, true, { skipPush: true });
} else {
// Fallback
if (isOnaraActive()) {
window._onaraCurrentGridUrl = url;
}
loadPageAjax(url, true, { skipPush: true });
}
});
@@ -5358,17 +5562,32 @@ window.cancelAnimFrame = (function () {
const imageModal = document.getElementById('image-modal');
if (imageModal && imageModal.classList.contains('visible')) return;
const navBtn = event.target.closest('.nav-next, .nav-prev');
const isNavHover = !!navBtn;
const isMediaHover = !isNavHover && !!event.target.closest('.media-object') && !event.target.closest('#favs');
// Yield if scroller/abyss page is active
if (document.body && document.body.classList.contains('scroller-active')) return;
// For media-object, user preference 'wheelNavEnabled' must be enabled.
// For .nav-next / .nav-prev, scrolling always triggers next/prev navigation directly.
if (!isNavHover && (!isMediaHover || localStorage.getItem('wheelNavEnabled') !== 'true')) {
// Don't hijack if user is inside an input, textarea, or contenteditable element
if (event.target && (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA' || event.target.isContentEditable)) {
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();
// Prevent rapid multi-firing while navigating or during throttle cooldown
@@ -7519,11 +7738,20 @@ window.cancelAnimFrame = (function () {
sb.style.setProperty('transition', 'none', 'important');
if (isHidden) {
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.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');
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
sb.style.transform = `translateX(${-sw}px)`;
} else {
@@ -7544,14 +7772,15 @@ window.cancelAnimFrame = (function () {
sb.style.removeProperty('transition');
if (shouldOpen) {
sb.style.removeProperty('transform');
sb.style.removeProperty('display');
localStorage.setItem('comments_hidden', 'false');
document.body.classList.remove('sidebar-left-hidden');
document.body.classList.remove('sidebar-left-hidden', 'comments-hidden');
} else {
// Hide BEFORE removing transform to prevent snap-back flash
sb.style.display = 'none';
sb.style.setProperty('display', 'none', 'important');
layout.classList.add('sidebar-hidden');
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');
}
requestAnimationFrame(() => syncLeftEdgeZone());
@@ -7772,7 +8001,11 @@ window.cancelAnimFrame = (function () {
if (window.innerWidth < 1000) return;
const { sb, layout } = getLS();
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;
prepareDrag(sb, layout, isHidden, sw);
// rAF ensures the initial transform state is painted before the transition starts
@@ -9569,9 +9802,8 @@ class NotificationSystem {
btn.style.visibility = 'visible';
});
if (typeof prefetchNextPrevItems === 'function') {
if (typeof requestIdleCallback === 'function') requestIdleCallback(() => prefetchNextPrevItems());
else setTimeout(prefetchNextPrevItems, 200);
if (typeof schedulePrefetch === 'function') {
schedulePrefetch(800);
}
}
}