This commit is contained in:
2026-09-13 04:54:24 +02:00
parent 340c825019
commit 33c6d0e76d
15 changed files with 425 additions and 104 deletions
+12
View File
@@ -3164,6 +3164,18 @@ body.sidebar-right-hidden #sidebar-drag-zone {
display: none !important;
}
.thumb > .sidebar-media-placeholder {
z-index: 1;
font-size: 2.8em;
border-radius: inherit;
}
.thumb:has(.sidebar-media-placeholder),
.thumb.thumb-fallback:has(.sidebar-media-placeholder) {
opacity: 1 !important;
background-image: none !important;
}
.sidebar-video-details {
display: flex;
margin-top: 8px;
+193 -55
View File
@@ -297,7 +297,7 @@ window.cancelAnimFrame = (function () {
* Forcefully refreshes all thumbnail occurrences for a specific item in the DOM.
* Handles grid items (data-bg), images (src), and the background canvas.
*/
window.refreshItemThumbnails = (itemId, timestamp = Date.now()) => {
window.refreshItemThumbnails = (itemId, timestamp = Date.now(), hasCoverart = undefined) => {
if (!itemId) return;
const idStr = String(itemId);
@@ -340,15 +340,28 @@ window.cancelAnimFrame = (function () {
// Update audio cover in player if viewing this audio item
const audioCover = document.getElementById('f0ck-audio-cover');
if (audioCover && currentId === idStr) {
const coverUrl = `/ca/${idStr}.webp?t=${timestamp}`;
audioCover.src = coverUrl;
const parent = audioCover.parentElement;
if (parent) {
parent.style.background = `url('${coverUrl}') no-repeat center / contain black`;
}
const audioEl = document.querySelector('audio#my-video');
if (audioEl) {
audioEl.setAttribute('poster', coverUrl);
if (hasCoverart === true || (hasCoverart === undefined && audioCover.src && audioCover.src.includes('/ca/'))) {
const coverUrl = `/ca/${idStr}.webp?t=${timestamp}`;
audioCover.src = coverUrl;
const parent = audioCover.parentElement;
if (parent) {
parent.style.background = `url('${coverUrl}') no-repeat center / contain black`;
}
const audioEl = document.querySelector('audio#my-video');
if (audioEl) {
audioEl.setAttribute('poster', coverUrl);
}
} else if (hasCoverart === false) {
const fallbackUrl = '/s/img/audio.webp';
audioCover.src = fallbackUrl;
const parent = audioCover.parentElement;
if (parent) {
parent.style.background = `url('${fallbackUrl}') no-repeat center / contain black`;
}
const audioEl = document.querySelector('audio#my-video');
if (audioEl) {
audioEl.setAttribute('poster', fallbackUrl);
}
}
}
@@ -434,7 +447,19 @@ window.cancelAnimFrame = (function () {
applyThumb(thumb, finalBg);
} else {
img.onload = () => applyThumb(thumb, finalBg);
img.onerror = () => thumb.classList.remove('lazy-thumb');
img.onerror = () => {
const mime = thumb.dataset.mime || '';
if (mime.startsWith('audio/')) {
if (!thumb.querySelector('.sidebar-media-placeholder.audio')) {
const ph = document.createElement('div');
ph.className = 'sidebar-media-placeholder audio';
ph.innerHTML = '<i class="fa-solid fa-music"></i>';
thumb.prepend(ph);
}
thumb.classList.add('thumb-fallback');
}
thumb.classList.remove('lazy-thumb');
};
}
thumb.dataset.lazyObserved = 'true';
}
@@ -463,7 +488,12 @@ window.cancelAnimFrame = (function () {
} else {
const mime = thumb.dataset.mime || '';
if (mime.startsWith('audio/')) {
thumb.style.setProperty('--thumb-bg', `url('/s/img/audio.webp')`);
if (!thumb.querySelector('.sidebar-media-placeholder.audio')) {
const ph = document.createElement('div');
ph.className = 'sidebar-media-placeholder audio';
ph.innerHTML = '<i class="fa-solid fa-music"></i>';
thumb.prepend(ph);
}
thumb.classList.add('thumb-fallback');
}
thumb.classList.remove('lazy-thumb');
@@ -1346,8 +1376,8 @@ window.cancelAnimFrame = (function () {
};
window.openOnaraModal = openOnaraModal;
const updateOnaraActiveItem = (itemid, url) => {
if (!isOnaraActive()) return;
const updateOnaraActiveItem = (itemid, url, forceScroll = false, slug = null) => {
if (!isOnaraActive()) return null;
// Clear onara-active from any and all elements to guarantee only 1 item is selected
document.querySelectorAll('.onara-active').forEach(el => el.classList.remove('onara-active'));
@@ -1358,6 +1388,9 @@ window.cancelAnimFrame = (function () {
targetThumb = document.querySelector(`.posts > a.thumb[href="${parsedPath}"], .posts > a.thumb[href="${url}"]`);
} catch {}
}
if (!targetThumb && slug) {
targetThumb = document.querySelector(`.posts > a.thumb[href$="/${slug}"]`);
}
if (!targetThumb && itemid) {
targetThumb = document.querySelector(`.posts > a.thumb[href$="/${itemid}"], .posts > a.thumb[data-bg*="/${itemid}."]`);
}
@@ -1374,13 +1407,140 @@ window.cancelAnimFrame = (function () {
const vh = window.innerHeight || document.documentElement.clientHeight;
// If any part of the thumbnail is already visible in the viewport, do not scroll
const isVisible = rect.bottom > navbarH && rect.top < vh;
if (!isVisible) {
targetThumb.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'instant' });
if (!isVisible || forceScroll) {
targetThumb.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'instant' });
}
}
return targetThumb;
};
window.updateOnaraActiveItem = updateOnaraActiveItem;
let _onaraSyncSeq = 0;
const syncOnaraBackgroundGrid = async (itemid, url, knownPage = null, slug = null) => {
if (!isOnaraActive()) return;
// Fast path: if target item is already in current background DOM, just highlight and ensure visibility
const existingThumb = updateOnaraActiveItem(itemid, url, false, slug);
if (existingThumb) {
return;
}
const currentSeq = ++_onaraSyncSeq;
const urlObj = new URL(url || window.location.href, window.location.origin);
const feedBasePath = urlObj.pathname.replace(/\/+$/, '').replace(/\/(?:\d+|[a-zA-Z0-9_-]{11})$/, '') || '/';
let targetPage = (typeof knownPage === 'number' && knownPage >= 1) ? knownPage : null;
if (!targetPage) {
try {
const pageParams = new URLSearchParams();
pageParams.set('id', itemid);
if (feedBasePath.startsWith('/tag/')) {
pageParams.set('tag', decodeURIComponent(feedBasePath.replace(/^\/tag\//, '')));
} else if (feedBasePath.startsWith('/h/')) {
pageParams.set('hall', decodeURIComponent(feedBasePath.replace(/^\/h\//, '')));
} else if (feedBasePath.match(/^\/user\/([^/]+)\/hall\/([^/]+)/)) {
const m = feedBasePath.match(/^\/user\/([^/]+)\/hall\/([^/]+)/);
pageParams.set('userHallOwner', decodeURIComponent(m[1]));
pageParams.set('userHall', decodeURIComponent(m[2]));
} else if (feedBasePath.match(/^\/user\/([^/]+)\/favs/)) {
const m = feedBasePath.match(/^\/user\/([^/]+)\/favs/);
pageParams.set('user', decodeURIComponent(m[1]));
pageParams.set('fav', 'true');
} else if (feedBasePath.match(/^\/user\/([^/]+)/)) {
const m = feedBasePath.match(/^\/user\/([^/]+)/);
pageParams.set('user', decodeURIComponent(m[1]));
} else if (feedBasePath === '/favs') {
pageParams.set('fav', 'true');
}
if (typeof window.activeMode !== 'undefined') {
pageParams.set('mode', window.activeMode);
}
const isStrict = window.f0ckSession?.strict_mode || (localStorage.getItem('search_strict') === 'true') || urlObj.searchParams.get('strict') === '1';
if (isStrict) {
pageParams.set('strict', '1');
}
const resp = await fetch(`/api/v2/item-page?${pageParams.toString()}`);
if (resp.ok) {
const pageData = await resp.json();
if (pageData && pageData.success && pageData.page) {
targetPage = pageData.page;
}
}
} catch (e) {
console.warn('[ONARA] Failed to fetch item-page:', e);
}
}
if (currentSeq !== _onaraSyncSeq) return;
if (!targetPage || targetPage < 1) targetPage = 1;
const pagePath = targetPage === 1 ? feedBasePath : (feedBasePath === '/' ? `/p/${targetPage}` : `${feedBasePath}/p/${targetPage}`);
const searchParams = new URLSearchParams(urlObj.search);
const searchStr = searchParams.toString() ? `?${searchParams.toString()}` : '';
const pageUrl = pagePath + searchStr;
try {
const pageRes = await fetch(pageUrl, {
headers: { 'X-Requested-With': 'XMLHttpRequest' }
});
if (currentSeq !== _onaraSyncSeq) return;
if (!pageRes.ok) return;
const html = await pageRes.text();
if (currentSeq !== _onaraSyncSeq) return;
const doc = new DOMParser().parseFromString(html, 'text/html');
const newPosts = doc.querySelector('.posts');
const oldPosts = document.querySelector('.posts');
if (newPosts && oldPosts) {
oldPosts.innerHTML = newPosts.innerHTML;
oldPosts.dataset.currentPage = targetPage;
if (newPosts.dataset.hasMore) {
oldPosts.dataset.hasMore = newPosts.dataset.hasMore;
}
if (typeof window.initLazyLoading === 'function') {
window.initLazyLoading();
}
}
const newPag = doc.querySelector('.pagination-wrapper');
const oldPag = document.querySelector('.pagination-wrapper');
if (newPag && oldPag) {
oldPag.innerHTML = newPag.innerHTML;
}
const newTitle = doc.querySelector('.page-title-wrap, .page-title, .user-profile-header, .hall-header');
const oldTitle = document.querySelector('.page-title-wrap, .page-title, .user-profile-header, .hall-header');
if (newTitle && oldTitle) {
oldTitle.replaceWith(newTitle);
}
window._onaraCurrentGridUrl = pageUrl;
window._onaraReturnUrl = pageUrl;
if (doc.title) {
window._onaraReturnTitle = doc.title;
}
const thumb = updateOnaraActiveItem(itemid, url, true, slug);
if (!thumb && oldPosts) {
const synthThumb = document.createElement('a');
synthThumb.href = url;
synthThumb.className = 'thumb lazy-thumb onara-active';
synthThumb.dataset.bg = `/t/${itemid}.webp`;
synthThumb.dataset.size = '1';
synthThumb.innerHTML = '<div class="thumb-indicators"></div><p></p>';
oldPosts.prepend(synthThumb);
if (typeof window.initLazyLoading === 'function') window.initLazyLoading();
synthThumb.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'instant' });
}
} catch (err) {
console.error('[ONARA] Failed to sync background grid:', err);
}
};
window.syncOnaraBackgroundGrid = syncOnaraBackgroundGrid;
const isItemPath = (pathOrUrl) => {
try {
const p = new URL(pathOrUrl, window.location.origin).pathname;
@@ -4436,6 +4596,10 @@ window.cancelAnimFrame = (function () {
});
}
if (useOnara) {
params.append('onara', '1');
}
if (params.toString() !== '') {
ajaxUrl += (ajaxUrl.includes('?') ? '&' : '?') + params.toString();
}
@@ -4466,7 +4630,8 @@ window.cancelAnimFrame = (function () {
onaraMount.innerHTML = '';
}
_container = onaraMount;
updateOnaraActiveItem(itemid, url);
updateOnaraActiveItem(itemid, url, false, _cachedItem.slug);
syncOnaraBackgroundGrid(itemid, url, _cachedItem.page, _cachedItem.slug);
} else {
_container = document.querySelector('#main .container') || (document.getElementById('main')?.classList.contains('item-view') ? document.getElementById('main') : null);
const _isStructuralPage = !!document.querySelector('.pagewrapper');
@@ -4597,12 +4762,14 @@ window.cancelAnimFrame = (function () {
if (!freshText) return;
let freshHtml = freshText;
let freshSlug = null;
let freshPage = null;
try {
const d = JSON.parse(freshText);
if (d && typeof d.html === 'string') freshHtml = d.html;
if (d && (d.slug || d.item?.slug)) freshSlug = d.slug || d.item?.slug;
if (d && d.page) freshPage = d.page;
} catch(_) {}
itemCacheMap.set(_itemCacheKey, { html: freshHtml, slug: freshSlug, ts: Date.now() });
itemCacheMap.set(_itemCacheKey, { html: freshHtml, slug: freshSlug, page: freshPage, ts: Date.now() });
window.f0ckDebug('[itemCache] Background revalidation complete for', _itemCacheKey);
})
.catch(() => {});
@@ -4629,7 +4796,7 @@ window.cancelAnimFrame = (function () {
- Total Network: ${(tBody - tStart).toFixed(2)}ms
- Content Size: ${(rawText.length / 1024).toFixed(2)} KB`);
let html, paginationHtml, responseSlug = null;
let html, paginationHtml, responseSlug = null, responsePage = null;
try {
// Optimistically try to parse as JSON first
@@ -4643,6 +4810,7 @@ window.cancelAnimFrame = (function () {
html = data.html;
paginationHtml = data.pagination;
responseSlug = data.slug || data.item?.slug || null;
responsePage = data.page || null;
} else {
html = rawText;
}
@@ -4653,7 +4821,7 @@ window.cancelAnimFrame = (function () {
// ── Store in item cache (stale-while-revalidate) ───────────────────────
if (html && !options.noCacheStore) {
itemCacheMap.set(_itemCacheKey, { html, slug: responseSlug, ts: Date.now() });
itemCacheMap.set(_itemCacheKey, { html, slug: responseSlug, page: responsePage, ts: Date.now() });
if (itemCacheMap.size > ITEM_CACHE_MAX) {
// Evict oldest entry
itemCacheMap.delete(itemCacheMap.keys().next().value);
@@ -4673,7 +4841,8 @@ window.cancelAnimFrame = (function () {
onaraMount.innerHTML = '';
}
container = onaraMount;
updateOnaraActiveItem(itemid, url);
updateOnaraActiveItem(itemid, url, false, responseSlug);
syncOnaraBackgroundGrid(itemid, url, responsePage, responseSlug);
} else {
container = document.querySelector('#main .container') || (document.getElementById('main') && document.getElementById('main').classList.contains('item-view') ? document.getElementById('main') : null);
const isStructuralPage = !!document.querySelector('.pagewrapper');
@@ -5121,38 +5290,7 @@ window.cancelAnimFrame = (function () {
// Background grid sync — page number already in response, no extra fetch needed
if (isOnaraActive() && targetId) {
const postsEl = document.querySelector('.posts[data-current-page]');
const currentPage = postsEl ? parseInt(postsEl.dataset.currentPage, 10) : 1;
if (targetPage !== currentPage) {
// Build page URL from current grid base
let gridBase = window._onaraCurrentGridUrl
? window._onaraCurrentGridUrl.replace(/\/p\/\d+/, '').replace(/\/$/, '')
: window.location.pathname
.replace(/\/[a-zA-Z0-9_-]{11}$/, '')
.replace(/\/\d+$/, '')
.replace(/\/p\/\d+/, '')
.replace(/\/$/, '');
const pageUrl = targetPage === 1 ? (gridBase || '/') : `${gridBase}/p/${targetPage}`;
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');
if (newPosts && oldPosts) {
oldPosts.innerHTML = newPosts.innerHTML;
oldPosts.dataset.currentPage = targetPage;
if (typeof window.initLazyLoading === 'function') window.initLazyLoading();
}
window._onaraCurrentGridUrl = pageUrl;
if (typeof updateOnaraActiveItem === 'function') {
updateOnaraActiveItem(targetId, `/${targetKey}`);
}
})
.catch(() => {});
}
syncOnaraBackgroundGrid(targetId, `/${targetKey}`, targetPage, targetKey);
}
} else if (params.has('tag') || params.has('hall') || params.has('user') || params.has('userHall')) {
// Context had no matching items with the active MIME filter — try global random with the same filter
@@ -9110,7 +9248,7 @@ class NotificationSystem {
} else if (data.type === 'rethumb') {
window.f0ckDebug(`[SSE] Rethumb update received for item ${data.data?.item_id}`);
if (data.data && data.data.item_id && window.refreshItemThumbnails) {
window.refreshItemThumbnails(data.data.item_id);
window.refreshItemThumbnails(data.data.item_id, Date.now(), data.data.has_coverart);
}
} else if (data.type === 'new_item') {
window.f0ckDebug(`[SSE] New item received:`, data.data);
@@ -13838,7 +13976,7 @@ document.addEventListener('click', (e) => {
// Cache-bust thumbnails and player media on page
const timestamp = Date.now();
if (typeof window.refreshItemThumbnails === 'function') {
window.refreshItemThumbnails(itemId, timestamp);
window.refreshItemThumbnails(itemId, timestamp, data.has_coverart);
} else {
document.querySelectorAll(`img[src*="/t/${itemId}.webp"]`).forEach(img => {
const url = new URL(img.src, window.location.origin);