gfds
This commit is contained in:
+279
-16
@@ -1272,14 +1272,19 @@
|
||||
|
||||
const isTagFeed = options.isTagFeed || false;
|
||||
const tagContext = options.tag || null;
|
||||
const isFavsFeed = options.isFavsFeed || false;
|
||||
const favsUser = options.favsUser || null;
|
||||
const isStrict = isStrictMode();
|
||||
const strictSuffix = (isTagFeed && isStrict) ? '?strict=1' : '';
|
||||
const targetSubHash = video.target_subf0ck_slug ? `#${video.target_subf0ck_slug}` : '';
|
||||
const targetHref = (isTagFeed && tagContext)
|
||||
? `/tag/${encodeURIComponent(tagContext).replace(/%2C/g, ',').replace(/%20/g, ' ')}/${videoKey}${strictSuffix}${targetSubHash}`
|
||||
: `/${videoKey}${targetSubHash}`;
|
||||
const targetHref = (isFavsFeed && favsUser)
|
||||
? `/user/${encodeURIComponent(favsUser)}/favs/${videoKey}${targetSubHash}`
|
||||
: (isTagFeed && tagContext)
|
||||
? `/tag/${encodeURIComponent(tagContext).replace(/%2C/g, ',').replace(/%20/g, ' ')}/${videoKey}${strictSuffix}${targetSubHash}`
|
||||
: `/${videoKey}${targetSubHash}`;
|
||||
const activeClass = options.isActive ? ' active-tag-item' : '';
|
||||
|
||||
|
||||
return `
|
||||
<div class="sidebar-video-card${activeClass}" data-id="${video.id}" data-slug="${escapeHtml(video.slug || '')}" data-file="${escapeHtml(activeDest)}" data-mime="${escapeHtml(activeMime)}" data-ext="${escapeHtml(ext ? ext.toLowerCase() : '')}" data-mode="${rClass}" data-target-subf0ck="${escapeHtml(video.target_subf0ck_slug || '')}" data-personalized="${video.personalized ? 'true' : 'false'}"${devScoreAttr}>
|
||||
<a href="${targetHref}" class="sidebar-video-link" data-mode="${rClass}" data-inherit-context="false">
|
||||
@@ -1554,12 +1559,22 @@
|
||||
if (txt) ids.add(txt);
|
||||
}
|
||||
|
||||
// 2. From URL pathname (/tag/:tag/:slugOrId or /:slugOrId)
|
||||
// 2. From URL pathname (/tag/:tag/:slugOrId, /user/:user/favs/:slugOrId, /user/:user/:slugOrId, /:slugOrId)
|
||||
const segments = window.location.pathname.split('/').filter(Boolean);
|
||||
if (segments.length >= 2 && segments[0] === 'tag') {
|
||||
if (segments[2]) {
|
||||
ids.add(decodeURIComponent(segments[2]).trim());
|
||||
}
|
||||
} else if (segments.length >= 4 && segments[0] === 'user' && segments[2] === 'favs') {
|
||||
// /user/:user/favs/:itemid
|
||||
if (segments[3] !== 'p') {
|
||||
ids.add(decodeURIComponent(segments[3]).trim());
|
||||
}
|
||||
} else if (segments.length >= 3 && segments[0] === 'user' && segments[2] !== 'favs') {
|
||||
// /user/:user/:itemid (non-favs)
|
||||
if (segments[2] !== 'p') {
|
||||
ids.add(decodeURIComponent(segments[2]).trim());
|
||||
}
|
||||
} else if (segments.length === 1) {
|
||||
const forbidden = ['s', 'b', 't', 'ca', 'a', 'login', 'register', 'settings', 'about', 'terms', 'rules', 'api', 'logout', 'auth', 'admin', 'mod', 'comments', 'notifications', 'feed', 'upload', 'tags', 'halls', 'ranking', 'abyss', 'random', 'scroller', 'p'];
|
||||
if (!forbidden.includes(segments[0])) {
|
||||
@@ -1567,6 +1582,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return Array.from(ids).filter(Boolean);
|
||||
};
|
||||
|
||||
@@ -1840,22 +1856,253 @@
|
||||
card.classList.add('active-tag-item');
|
||||
}
|
||||
});
|
||||
container.addEventListener('scroll', () => {
|
||||
if (tagFeedLoading || !tagHasMore || !currentTag) return;
|
||||
const nearBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 250;
|
||||
if (nearBottom) {
|
||||
loadMoreTagFeed();
|
||||
}
|
||||
}, { passive: true });
|
||||
};
|
||||
|
||||
// ── Dedicated Favs Feed (mirrors Tag Feed for /user/x/favs context) ─────
|
||||
let currentFavsUser = null;
|
||||
let favsOffset = 0;
|
||||
let favsTotal = 0;
|
||||
let favsFeedLoaded = false;
|
||||
let favsFeedLoading = false;
|
||||
let favsFeedNeedsReload = false;
|
||||
let favsHasMore = false;
|
||||
let favsSentinel = null;
|
||||
let favsObserver = null;
|
||||
|
||||
const getCurrentFavsUser = () => {
|
||||
// Match /user/someuser/favs or /user/someuser/favs/itemid
|
||||
const m = window.location.pathname.match(/^\/user\/([^/?#]+)\/favs/);
|
||||
if (m) return decodeURIComponent(m[1]);
|
||||
return null;
|
||||
};
|
||||
|
||||
let isReCenteringFavsFeed = false;
|
||||
|
||||
const highlightActiveFavsCard = (scrollIntoView = false) => {
|
||||
const identifiers = getCurrentItemIdentifiers();
|
||||
const container = document.getElementById('sidebar-favs-container');
|
||||
if (!container) return;
|
||||
|
||||
let matchedCard = null;
|
||||
container.querySelectorAll('.sidebar-video-card').forEach(card => {
|
||||
const cardId = card.dataset.id ? String(card.dataset.id).trim() : '';
|
||||
const cardSlug = card.dataset.slug ? String(card.dataset.slug).trim() : '';
|
||||
const isMatch = identifiers.length > 0 && (
|
||||
(cardId && identifiers.includes(cardId)) ||
|
||||
(cardSlug && identifiers.includes(cardSlug))
|
||||
);
|
||||
if (isMatch) {
|
||||
card.classList.add('active-tag-item');
|
||||
if (!matchedCard) matchedCard = card;
|
||||
} else {
|
||||
card.classList.remove('active-tag-item');
|
||||
}
|
||||
});
|
||||
|
||||
if (matchedCard && scrollIntoView) {
|
||||
const isFavsTabActive = document.querySelector('.sidebar-tab.active')?.dataset.tab === 'favs' &&
|
||||
container.style.display !== 'none';
|
||||
if (isFavsTabActive) {
|
||||
try {
|
||||
const cr = container.getBoundingClientRect();
|
||||
const mr = matchedCard.getBoundingClientRect();
|
||||
container.scrollTo({ top: Math.max(0, container.scrollTop + (mr.top - cr.top) - (container.clientHeight / 2) + (mr.height / 2)), behavior: 'smooth' });
|
||||
} catch (_) {
|
||||
matchedCard.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
}
|
||||
} else if (scrollIntoView && !matchedCard && identifiers.length > 0 && currentFavsUser && !isReCenteringFavsFeed && !favsFeedLoading) {
|
||||
isReCenteringFavsFeed = true;
|
||||
loadFavsFeed(true, identifiers[0]).finally(() => {
|
||||
isReCenteringFavsFeed = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const loadFavsFeed = async (reset = false, focusId = null) => {
|
||||
const container = document.getElementById('sidebar-favs-container');
|
||||
const itemsContainer = container?.querySelector('.sidebar-favs-items-container');
|
||||
if (!container || !itemsContainer || favsFeedLoading) return;
|
||||
if (!currentFavsUser) return;
|
||||
|
||||
favsFeedLoading = true;
|
||||
favsFeedNeedsReload = false;
|
||||
|
||||
if (reset) {
|
||||
favsOffset = 0;
|
||||
itemsContainer.innerHTML = `
|
||||
<div class="sidebar-loading-state">
|
||||
<i class="fa-solid fa-circle-notch fa-spin"></i>
|
||||
<span>Loading favorites...</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
try {
|
||||
const mode = typeof window.activeMode !== 'undefined' ? window.activeMode : '';
|
||||
let url = `/api/v2/favs-feed?user=${encodeURIComponent(currentFavsUser)}&order=desc&offset=${favsOffset}&limit=20&mode=${mode}`;
|
||||
if (focusId) url += `&focus_id=${encodeURIComponent(focusId)}`;
|
||||
|
||||
const res = await fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } });
|
||||
const data = await res.json();
|
||||
const items = data.items || [];
|
||||
favsTotal = data.total || 0;
|
||||
favsOffset = (data.offset ?? favsOffset) + items.length;
|
||||
favsHasMore = typeof data.hasMore !== 'undefined' ? !!data.hasMore : (favsOffset < favsTotal);
|
||||
|
||||
// Update header
|
||||
const countEl = container.querySelector('.sidebar-favs-count');
|
||||
if (countEl) countEl.textContent = `(${favsTotal})`;
|
||||
|
||||
const nameEl = container.querySelector('.sidebar-favs-name');
|
||||
if (nameEl) nameEl.textContent = currentFavsUser + "'s favs";
|
||||
|
||||
if (data.success && items.length > 0) {
|
||||
favsFeedLoaded = true;
|
||||
const identifiers = getCurrentItemIdentifiers();
|
||||
let html = '';
|
||||
items.forEach(v => {
|
||||
const vId = String(v.id).trim();
|
||||
const vSlug = String(v.slug || '').trim();
|
||||
const isActive = identifiers.length > 0 && (
|
||||
(vId && identifiers.includes(vId)) ||
|
||||
(vSlug && identifiers.includes(vSlug))
|
||||
);
|
||||
html += renderVideoCard(v, { isFavsFeed: true, favsUser: currentFavsUser, isActive });
|
||||
});
|
||||
|
||||
if (reset) {
|
||||
itemsContainer.innerHTML = html;
|
||||
setTimeout(() => highlightActiveFavsCard(true), 60);
|
||||
} else {
|
||||
itemsContainer.insertAdjacentHTML('beforeend', html);
|
||||
highlightActiveFavsCard(false);
|
||||
}
|
||||
initFavsSentinel();
|
||||
} else if (reset) {
|
||||
itemsContainer.innerHTML = `
|
||||
<div class="sidebar-empty-state" style="text-align:center;padding:30px 15px;color:var(--text-muted,#888);font-size:0.88em;">
|
||||
<i class="fa-regular fa-heart" style="font-size:1.8em;opacity:0.4;margin-bottom:8px;display:block;"></i>
|
||||
No favorites found
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[FAVS-FEED] Failed:', err);
|
||||
if (reset) {
|
||||
itemsContainer.innerHTML = `<div class="sidebar-empty-state" style="text-align:center;padding:25px;color:var(--danger,#ff4444);font-size:0.88em;">Failed to load favorites</div>`;
|
||||
}
|
||||
} finally {
|
||||
favsFeedLoading = false;
|
||||
}
|
||||
};
|
||||
|
||||
const initFavsSentinel = () => {
|
||||
const container = document.getElementById('sidebar-favs-container');
|
||||
const itemsContainer = container?.querySelector('.sidebar-favs-items-container');
|
||||
if (!container || !itemsContainer) return;
|
||||
|
||||
if (favsSentinel && favsSentinel.parentNode) favsSentinel.remove();
|
||||
if (favsObserver) { favsObserver.disconnect(); favsObserver = null; }
|
||||
if (!favsHasMore) return;
|
||||
|
||||
favsSentinel = document.createElement('div');
|
||||
favsSentinel.className = 'sidebar-tag-sentinel';
|
||||
favsSentinel.style.cssText = 'height: 20px; margin: 10px 0;';
|
||||
itemsContainer.appendChild(favsSentinel);
|
||||
|
||||
favsObserver = new IntersectionObserver((entries) => {
|
||||
if (entries[0].isIntersecting && !favsFeedLoading && favsHasMore) {
|
||||
loadFavsFeed(false);
|
||||
}
|
||||
}, {
|
||||
root: container,
|
||||
rootMargin: '200px'
|
||||
});
|
||||
favsObserver.observe(favsSentinel);
|
||||
};
|
||||
|
||||
|
||||
const updateFavsTabVisibility = (forceSwitchToFavs = false) => {
|
||||
const user = getCurrentFavsUser();
|
||||
const favsBtn = document.getElementById('sidebar-tab-favs');
|
||||
if (!favsBtn) return;
|
||||
|
||||
if (user) {
|
||||
favsBtn.style.display = 'inline-flex';
|
||||
favsBtn.title = `${user}'s favs`;
|
||||
|
||||
if (currentFavsUser !== user) {
|
||||
currentFavsUser = user;
|
||||
favsOffset = 0;
|
||||
favsFeedLoaded = false;
|
||||
favsFeedNeedsReload = true;
|
||||
switchSidebarTab('favs');
|
||||
} else {
|
||||
if (forceSwitchToFavs || document.querySelector('.sidebar-tab.active')?.dataset.tab !== 'favs') {
|
||||
switchSidebarTab('favs');
|
||||
} else {
|
||||
highlightActiveFavsCard(true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
favsBtn.style.display = 'none';
|
||||
currentFavsUser = null;
|
||||
favsFeedLoaded = false;
|
||||
|
||||
const activeTab = document.querySelector('.sidebar-tab.active')?.dataset.tab;
|
||||
if (activeTab === 'favs') {
|
||||
// Fall back to tag tab if available, else recommendations
|
||||
if (getCurrentTag()) {
|
||||
switchSidebarTab('tag');
|
||||
} else {
|
||||
switchSidebarTab('recommendations');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const bindFavsContainerEvents = () => {
|
||||
const container = document.getElementById('sidebar-favs-container');
|
||||
if (!container || container.dataset.eventsBound === 'true') return;
|
||||
container.dataset.eventsBound = 'true';
|
||||
container.addEventListener('click', (e) => {
|
||||
const card = e.target.closest('.sidebar-video-card');
|
||||
if (card) {
|
||||
container.querySelectorAll('.sidebar-video-card').forEach(c => c.classList.remove('active-tag-item'));
|
||||
card.classList.add('active-tag-item');
|
||||
}
|
||||
});
|
||||
container.addEventListener('scroll', () => {
|
||||
if (favsFeedLoading || !favsHasMore || !currentFavsUser) return;
|
||||
const nearBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 250;
|
||||
if (nearBottom) {
|
||||
loadFavsFeed(false);
|
||||
}
|
||||
}, { passive: true });
|
||||
};
|
||||
|
||||
const switchSidebarTab = (tabName) => {
|
||||
if (window.clearHoverPreview) {
|
||||
window.clearHoverPreview();
|
||||
}
|
||||
const normalizedTab = (tabName === 'videos' || tabName === 'recommendations')
|
||||
? 'recommendations'
|
||||
: (tabName === 'tag' ? 'tag' : tabName);
|
||||
const normalizedTab = (tabName === 'videos' || tabName === 'recommendations')
|
||||
? 'recommendations'
|
||||
: (tabName === 'tag' ? 'tag' : (tabName === 'favs' ? 'favs' : tabName));
|
||||
const tabs = document.querySelectorAll('.sidebar-tab');
|
||||
const contents = document.querySelectorAll('.sidebar-tab-content');
|
||||
|
||||
tabs.forEach(t => {
|
||||
const tTab = (t.dataset.tab === 'videos' || t.dataset.tab === 'recommendations')
|
||||
? 'recommendations'
|
||||
: (t.dataset.tab === 'tag' ? 'tag' : t.dataset.tab);
|
||||
const tTab = (t.dataset.tab === 'videos' || t.dataset.tab === 'recommendations')
|
||||
? 'recommendations'
|
||||
: (t.dataset.tab === 'tag' ? 'tag' : (t.dataset.tab === 'favs' ? 'favs' : t.dataset.tab));
|
||||
if (tTab === normalizedTab) {
|
||||
t.classList.add('active');
|
||||
} else {
|
||||
@@ -1864,9 +2111,9 @@
|
||||
});
|
||||
|
||||
contents.forEach(c => {
|
||||
const cTab = (c.dataset.tabContent === 'videos' || c.dataset.tabContent === 'recommendations')
|
||||
? 'recommendations'
|
||||
: (c.dataset.tabContent === 'tag' ? 'tag' : c.dataset.tabContent);
|
||||
const cTab = (c.dataset.tabContent === 'videos' || c.dataset.tabContent === 'recommendations')
|
||||
? 'recommendations'
|
||||
: (c.dataset.tabContent === 'tag' ? 'tag' : (c.dataset.tabContent === 'favs' ? 'favs' : c.dataset.tabContent));
|
||||
if (cTab === normalizedTab) {
|
||||
c.classList.add('active');
|
||||
c.style.display = 'block';
|
||||
@@ -1890,6 +2137,12 @@
|
||||
} else {
|
||||
setTimeout(() => highlightActiveTagCard(true), 60);
|
||||
}
|
||||
} else if (normalizedTab === 'favs') {
|
||||
if (favsFeedNeedsReload || !favsFeedLoaded) {
|
||||
loadFavsFeed(true);
|
||||
} else {
|
||||
setTimeout(() => highlightActiveFavsCard(true), 60);
|
||||
}
|
||||
}
|
||||
};
|
||||
window.switchSidebarTab = switchSidebarTab;
|
||||
@@ -1901,7 +2154,7 @@
|
||||
const btn = e.target.closest('.sidebar-tab');
|
||||
if (btn && btn.dataset.tab) {
|
||||
e.preventDefault();
|
||||
if (btn.dataset.tab !== 'tag') {
|
||||
if (btn.dataset.tab !== 'tag' && btn.dataset.tab !== 'favs') {
|
||||
userManuallySelectedNonTagTab = true;
|
||||
} else {
|
||||
userManuallySelectedNonTagTab = false;
|
||||
@@ -1917,7 +2170,9 @@
|
||||
savedTab = localStorage.getItem('sidebar_active_tab') || 'comments';
|
||||
} catch (_) {}
|
||||
|
||||
if (getCurrentTag()) {
|
||||
if (getCurrentFavsUser()) {
|
||||
switchSidebarTab('favs');
|
||||
} else if (getCurrentTag()) {
|
||||
switchSidebarTab('tag');
|
||||
} else if (savedTab === 'videos' || savedTab === 'recommendations') {
|
||||
switchSidebarTab('recommendations');
|
||||
@@ -2154,15 +2409,23 @@
|
||||
initSidebarTabs();
|
||||
initTagOrderButton();
|
||||
bindTagContainerEvents();
|
||||
bindFavsContainerEvents();
|
||||
updateTagTabVisibility();
|
||||
updateFavsTabVisibility();
|
||||
document.addEventListener('f0ck:contentLoaded', () => {
|
||||
updateTagTabVisibility();
|
||||
setTimeout(() => highlightActiveTagCard(true), 60);
|
||||
updateFavsTabVisibility();
|
||||
setTimeout(() => {
|
||||
highlightActiveTagCard(true);
|
||||
highlightActiveFavsCard(true);
|
||||
}, 60);
|
||||
});
|
||||
window.addEventListener('popstate', () => {
|
||||
setTimeout(() => {
|
||||
updateTagTabVisibility();
|
||||
updateFavsTabVisibility();
|
||||
highlightActiveTagCard(true);
|
||||
highlightActiveFavsCard(true);
|
||||
}, 60);
|
||||
});
|
||||
bindRecommendationEvents();
|
||||
|
||||
Reference in New Issue
Block a user