gsdf
This commit is contained in:
+124
-80
@@ -33,10 +33,24 @@
|
||||
--pagination-background: #2b2b2b;
|
||||
--metadata-bg: #2b2b2b;
|
||||
--badge-bg: #171717;
|
||||
--badge-sfw: #02500b;
|
||||
--badge-nsfw: #E10DC3;
|
||||
--badge-nsfl: #660000;
|
||||
--badge-untagged: #555555;
|
||||
--badge-tag: #090909;
|
||||
--posts-meta-bg: #000000b8;
|
||||
--x2154: #215483;
|
||||
}
|
||||
|
||||
html:not([theme]), html[theme=""] {
|
||||
--badge-bg: #171717;
|
||||
--badge-sfw: #02500b;
|
||||
--badge-nsfw: #E10DC3;
|
||||
--badge-nsfl: #660000;
|
||||
--badge-untagged: #555555;
|
||||
--badge-tag: #090909;
|
||||
}
|
||||
|
||||
html, body {
|
||||
touch-action: pan-x pan-y;
|
||||
}
|
||||
@@ -3017,45 +3031,15 @@ body.sidebar-right-hidden #sidebar-drag-zone {
|
||||
transition: background-color 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.sidebar-video-card:hover {
|
||||
.sidebar-video-card:hover,
|
||||
.sidebar-video-card.touch-active {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border-color: rgba(255, 255, 255, 0.12);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.sidebar-video-card:active {
|
||||
transform: scale(0.99);
|
||||
}
|
||||
|
||||
.sidebar-video-card.sidebar-card-swapping {
|
||||
opacity: 0.35;
|
||||
filter: grayscale(0.6);
|
||||
transition: opacity 0.5s ease, filter 0.5s ease;
|
||||
}
|
||||
|
||||
.sidebar-video-card.sidebar-card-entering {
|
||||
animation: sidebarCardEnter 2s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
|
||||
@keyframes sidebarCardEnter {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0.91) translateY(8px);
|
||||
filter: brightness(1.2) contrast(0.95);
|
||||
}
|
||||
40% {
|
||||
opacity: 0.75;
|
||||
transform: scale(0.97) translateY(3px);
|
||||
}
|
||||
75% {
|
||||
opacity: 0.95;
|
||||
transform: scale(0.995) translateY(0.5px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
filter: brightness(1) contrast(1);
|
||||
}
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.sidebar-video-card .sidebar-video-link {
|
||||
@@ -3080,43 +3064,15 @@ body.sidebar-right-hidden #sidebar-drag-zone {
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
transition: transform 0.3s ease;
|
||||
-webkit-user-drag: none;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.sidebar-video-card:hover .sidebar-video-thumb {
|
||||
transform: scale(1.04);
|
||||
}
|
||||
|
||||
.sidebar-video-play-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
color: #fff;
|
||||
font-size: 1.2em;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease, background 0.2s ease;
|
||||
}
|
||||
|
||||
.sidebar-video-play-overlay i {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.65);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-left: 3px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.sidebar-video-card:hover .sidebar-video-play-overlay {
|
||||
opacity: 1;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.sidebar-video-badge {
|
||||
position: absolute;
|
||||
bottom: 6px;
|
||||
@@ -3131,20 +3087,21 @@ body.sidebar-right-hidden #sidebar-drag-zone {
|
||||
color: #fff;
|
||||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.sidebar-video-badge.rating-sfw {
|
||||
background-color: var(--badge-sfw);
|
||||
background-color: var(--badge-sfw, #02500b);
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.sidebar-video-badge.rating-nsfw {
|
||||
background-color: var(--badge-nsfw);
|
||||
background-color: var(--badge-nsfw, #E10DC3);
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.sidebar-video-badge.rating-nsfl {
|
||||
background-color: var(--badge-nsfl);
|
||||
background-color: var(--badge-nsfl, #660000);
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
@@ -3167,6 +3124,7 @@ body.sidebar-right-hidden #sidebar-drag-zone {
|
||||
backdrop-filter: blur(4px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
line-height: 1;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.sidebar-media-placeholder {
|
||||
@@ -3313,11 +3271,6 @@ html[theme='paper'] .sidebar-video-channel {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
html[theme='light'] .sidebar-video-play-overlay,
|
||||
html[theme='paper'] .sidebar-video-play-overlay {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Theme 95 */
|
||||
html[theme='95'] .sidebar-tabs {
|
||||
background: #c0c0c0;
|
||||
@@ -3346,6 +3299,68 @@ html[theme='95'] .sidebar-video-title {
|
||||
font-family: 'MS Sans Serif', sans-serif;
|
||||
}
|
||||
|
||||
/* Sidebar Tag Feed Header & Active Card Highlighting */
|
||||
.sidebar-tag-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
background: rgba(18, 18, 24, 0.95);
|
||||
backdrop-filter: blur(8px);
|
||||
border-bottom: 1px solid var(--border-color, rgba(255, 255, 255, 0.08));
|
||||
margin-bottom: 6px;
|
||||
position: sticky;
|
||||
top: -6px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.sidebar-tag-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 0.85em;
|
||||
font-weight: 700;
|
||||
color: var(--accent, #a78bfa);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sidebar-tag-title i {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.sidebar-tag-count {
|
||||
font-size: 0.85em;
|
||||
font-weight: 500;
|
||||
color: var(--text-muted, #888);
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.sidebar-tag-order-btn {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid var(--border-color, rgba(255, 255, 255, 0.1));
|
||||
color: var(--text-color, #ccc);
|
||||
border-radius: 4px;
|
||||
padding: 3px 8px;
|
||||
font-size: 0.82em;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.sidebar-tag-order-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
color: #fff;
|
||||
border-color: var(--accent, #a78bfa);
|
||||
}
|
||||
|
||||
.sidebar-video-card.active-tag-item {
|
||||
border-color: var(--accent, #a78bfa) !important;
|
||||
}
|
||||
|
||||
#sidebar-activity-container.sidebar-comments-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
@@ -5646,11 +5661,16 @@ div.posts {
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
/* Mobile: Ensure at least 3 columns unless user picked a larger size */
|
||||
/* Mobile: Ensure at least 3 columns for S/M, and at least 2 columns for L/XL */
|
||||
@media (max-width: 600px) {
|
||||
html:not([data-thumb-size="l"]):not([data-thumb-size="xl"]) div.posts {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
html[data-thumb-size="l"] div.posts,
|
||||
html[data-thumb-size="xl"] div.posts {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
div.posts>a:not(.notif-item) {
|
||||
@@ -6476,6 +6496,28 @@ body.layout-modern.sidebar-right-hidden .pagination-container-fluid {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.item_title:has(.info-title-edit-wrap) .item_title_text {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
transition: color 0.15s ease, opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.item_title:has(.info-title-edit-wrap) .item_title_text:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.item_title_text.no-title,
|
||||
.item_title_text em {
|
||||
font-style: italic;
|
||||
color: var(--text-muted, rgba(255, 255, 255, 0.45));
|
||||
}
|
||||
|
||||
.item_title:has(.info-title-edit-wrap) .item_title_text.no-title:hover em,
|
||||
.item_title:has(.info-title-edit-wrap) .item_title_text:hover em {
|
||||
color: var(--accent, #9f0);
|
||||
}
|
||||
|
||||
/* .location {
|
||||
padding-left: 5px;
|
||||
} */
|
||||
@@ -7339,7 +7381,7 @@ span#tags:not(.tags-expanded) .tags-inner>span:nth-child(n+11) {
|
||||
|
||||
.badge-success {
|
||||
color: var(--white);
|
||||
background-color: var(--badge-sfw);
|
||||
background-color: var(--badge-sfw, #02500b);
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
border-radius: 3px;
|
||||
@@ -7349,7 +7391,7 @@ span#tags:not(.tags-expanded) .tags-inner>span:nth-child(n+11) {
|
||||
|
||||
.badge-danger {
|
||||
color: var(--white);
|
||||
background-color: var(--badge-nsfw);
|
||||
background-color: var(--badge-nsfw, #E10DC3);
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
border-radius: 3px;
|
||||
@@ -12982,7 +13024,8 @@ div.posts>a.thumb[data-size="2"] {
|
||||
grid-row: span 2;
|
||||
}
|
||||
|
||||
.thumb>.preview-video {
|
||||
.thumb>.preview-video,
|
||||
.sidebar-video-thumb-wrap>.preview-video {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@@ -12996,7 +13039,8 @@ div.posts>a.thumb[data-size="2"] {
|
||||
transition: opacity 0.2s ease-in;
|
||||
}
|
||||
|
||||
.thumb>.preview-video.playing {
|
||||
.thumb>.preview-video.playing,
|
||||
.sidebar-video-thumb-wrap>.preview-video.playing {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -13562,17 +13606,17 @@ body.layout-modern>.pagewrapper:not(:has(.index-layout-wrapper)):not(:has(.item-
|
||||
}
|
||||
|
||||
#a_toggle.rating-btn.is-sfw {
|
||||
background-color: var(--badge-sfw);
|
||||
background-color: var(--badge-sfw, #02500b);
|
||||
border-color: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
#a_toggle.rating-btn.is-nsfw {
|
||||
background-color: var(--badge-nsfw);
|
||||
background-color: var(--badge-nsfw, #E10DC3);
|
||||
border-color: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
#a_toggle.rating-btn.is-nsfl {
|
||||
background-color: var(--badge-nsfl);
|
||||
background-color: var(--badge-nsfl, #660000);
|
||||
border-color: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
|
||||
@@ -404,21 +404,21 @@
|
||||
}
|
||||
|
||||
.item-rating-option input:checked + .item-rating-label.sfw {
|
||||
background: var(--badge-sfw);
|
||||
background: var(--badge-sfw, #02500b);
|
||||
color: #fff;
|
||||
border-color: var(--badge-sfw);
|
||||
border-color: var(--badge-sfw, #02500b);
|
||||
}
|
||||
|
||||
.item-rating-option input:checked + .item-rating-label.nsfw {
|
||||
background: var(--badge-nsfw);
|
||||
background: var(--badge-nsfw, #E10DC3);
|
||||
color: #fff;
|
||||
border-color: var(--badge-nsfw);
|
||||
border-color: var(--badge-nsfw, #E10DC3);
|
||||
}
|
||||
|
||||
.item-rating-option input:checked + .item-rating-label.nsfl {
|
||||
background: var(--badge-nsfl);
|
||||
background: var(--badge-nsfl, #660000);
|
||||
color: #fff;
|
||||
border-color: var(--badge-nsfl);
|
||||
border-color: var(--badge-nsfl, #660000);
|
||||
}
|
||||
|
||||
/* Visibility Container - Only checked option is colored, unchecked options are gray */
|
||||
|
||||
+115
-20
@@ -3340,8 +3340,18 @@ window.cancelAnimFrame = (function () {
|
||||
// Full page: Sync attributes (CRITICAL for fullscreen and theme state)
|
||||
['theme', 'res'].forEach(attr => {
|
||||
const val = doc.documentElement.getAttribute(attr);
|
||||
if (val !== null) document.documentElement.setAttribute(attr, val);
|
||||
else document.documentElement.removeAttribute(attr);
|
||||
if (attr === 'theme') {
|
||||
if (val && val.trim()) {
|
||||
document.documentElement.setAttribute('theme', val.trim());
|
||||
} else if (!document.documentElement.getAttribute('theme')) {
|
||||
const cookieTheme = document.cookie.match(/(?:^|; )theme=([^;]*)/)?.[1];
|
||||
const fallback = (cookieTheme && decodeURIComponent(cookieTheme)) || window.f0ckDefaultTheme || 'amoled';
|
||||
document.documentElement.setAttribute('theme', fallback);
|
||||
}
|
||||
} else {
|
||||
if (val !== null) document.documentElement.setAttribute(attr, val);
|
||||
else document.documentElement.removeAttribute(attr);
|
||||
}
|
||||
});
|
||||
|
||||
// Sync body classes (CRITICAL for layout triggers)
|
||||
@@ -4837,6 +4847,12 @@ window.cancelAnimFrame = (function () {
|
||||
loadItemAjax(`/user/${encodeURIComponent(wUserHallOwner)}/hall/${encodeURIComponent(wUserHall)}/${targetKey}`, true, { transition: 'fade-zoom' });
|
||||
} else if (wFavsUser) {
|
||||
loadItemAjax(`/user/${encodeURIComponent(wFavsUser)}/favs/${targetKey}`, true, { transition: 'fade-zoom' });
|
||||
} else if (wTagMatch) {
|
||||
const wTag = decodeURIComponent(wTagMatch[1]);
|
||||
loadItemAjax(`/tag/${encodeURIComponent(wTag).replace(/%2C/g, ',').replace(/%20/g, ' ')}/${targetKey}`, true, { transition: 'fade-zoom' });
|
||||
} else if (wHallMatch) {
|
||||
const wHall = decodeURIComponent(wHallMatch[1]);
|
||||
loadItemAjax(`/h/${encodeURIComponent(wHall).replace(/%20/g, ' ')}/${targetKey}`, true, { transition: 'fade-zoom' });
|
||||
} else {
|
||||
loadItemAjax(`/${targetKey}`, true, { transition: 'fade-zoom' });
|
||||
}
|
||||
@@ -10369,6 +10385,8 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
// Sync UI immediately (Before AJAX)
|
||||
if (window.updateMimeLabel) window.updateMimeLabel();
|
||||
|
||||
document.dispatchEvent(new CustomEvent('f0ck:mimeChanged', { detail: { mime: newMime } }));
|
||||
|
||||
// Trigger reload context-aware
|
||||
const fromModal = !!menu.closest('#excluded-tags-overlay');
|
||||
const postsEl = document.querySelector('.posts, .tags-grid');
|
||||
@@ -10656,6 +10674,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (activeThumb) {
|
||||
activeThumb.classList.remove('previewing');
|
||||
activeThumb.classList.remove('touch-active');
|
||||
const card = activeThumb.closest('.sidebar-video-card');
|
||||
if (card) card.classList.remove('previewing');
|
||||
activeThumb = null;
|
||||
}
|
||||
};
|
||||
@@ -10677,7 +10697,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (mode === 'nsfw') shouldBlurThis = blurNsfw;
|
||||
else if (mode === 'nsfl') shouldBlurThis = blurNsfl;
|
||||
else if (mode === 'sfw') shouldBlurThis = blurSfw;
|
||||
else if (mode === 'null' || !mode) shouldBlurThis = blurUntagged;
|
||||
else if (mode === 'null' || mode === 'untagged' || !mode) shouldBlurThis = blurUntagged;
|
||||
|
||||
if (shouldBlurThis && !thumb.classList.contains('revealed')) {
|
||||
return;
|
||||
@@ -10700,6 +10720,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (thumb !== activeThumb) return;
|
||||
|
||||
activeThumb.classList.add('previewing');
|
||||
const card = activeThumb.closest('.sidebar-video-card');
|
||||
if (card) card.classList.add('previewing');
|
||||
|
||||
const video = document.createElement('video');
|
||||
video.src = `/b/${file}`;
|
||||
@@ -10716,11 +10738,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
video.classList.add('playing');
|
||||
};
|
||||
|
||||
thumb.appendChild(video);
|
||||
const container = thumb.classList.contains('sidebar-video-thumb-wrap')
|
||||
? thumb
|
||||
: (thumb.querySelector('.sidebar-video-thumb-wrap') || thumb);
|
||||
container.appendChild(video);
|
||||
activeVideo = video;
|
||||
|
||||
// Note: Hover prefetching of the first 512 KB has been removed to prevent
|
||||
// Chromium cache locks and validation loop stalls on subsequent playback.
|
||||
};
|
||||
|
||||
if (delay === 0) {
|
||||
@@ -10732,7 +10754,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
window.startHoverPreview = startPreview;
|
||||
|
||||
const handleEnter = (e) => {
|
||||
const thumb = e.target.closest('.thumb');
|
||||
let thumb = e.target.closest('.thumb');
|
||||
if (!thumb) {
|
||||
// The playback can ONLY start when exactly hovering class="sidebar-video-thumb"
|
||||
const imgThumb = e.target.closest('.sidebar-video-thumb');
|
||||
if (imgThumb) {
|
||||
thumb = imgThumb.closest('.sidebar-video-thumb-wrap');
|
||||
}
|
||||
}
|
||||
if (thumb && thumb !== activeThumb) {
|
||||
startPreview(thumb, 150);
|
||||
}
|
||||
@@ -10741,7 +10770,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
document.addEventListener('focusin', handleEnter);
|
||||
|
||||
const handleLeave = (e) => {
|
||||
const thumb = e.target.closest('.thumb');
|
||||
const thumb = e.target.closest('.thumb, .sidebar-video-thumb-wrap');
|
||||
if (thumb) {
|
||||
if (e.type === 'mouseout' && e.relatedTarget && thumb.contains(e.relatedTarget)) return;
|
||||
clearPreview();
|
||||
@@ -10757,7 +10786,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
let touchBlockClick = false;
|
||||
|
||||
document.addEventListener('touchstart', (e) => {
|
||||
const thumb = e.target.closest('.thumb');
|
||||
let thumb = e.target.closest('.thumb');
|
||||
if (!thumb) {
|
||||
const imgThumb = e.target.closest('.sidebar-video-thumb');
|
||||
if (imgThumb) {
|
||||
thumb = imgThumb.closest('.sidebar-video-thumb-wrap');
|
||||
}
|
||||
}
|
||||
|
||||
if (thumb) {
|
||||
// Only apply logic for items we want to preview
|
||||
@@ -10782,7 +10817,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
// Capture phase click listener to stop navigation if we just activated
|
||||
document.addEventListener('click', (e) => {
|
||||
if (touchBlockClick) {
|
||||
const thumb = e.target.closest('.thumb');
|
||||
const thumb = e.target.closest('.thumb, .sidebar-video-thumb-wrap');
|
||||
if (thumb && thumb === activeThumb) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
@@ -12768,6 +12803,40 @@ document.addEventListener('click', (e) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Title text click to edit
|
||||
const titleText = e.target.closest('.item_title_text');
|
||||
if (titleText && !document.body.classList.contains('preview-as-user')) {
|
||||
const container = titleText.closest('.item_title');
|
||||
if (container) {
|
||||
const editWrap = container.querySelector('.info-title-edit-wrap');
|
||||
const input = container.querySelector('#info-title-input');
|
||||
if (editWrap && input) {
|
||||
e.preventDefault();
|
||||
titleText.style.display = 'none';
|
||||
editWrap.style.display = 'flex';
|
||||
input.dataset.origVal = input.value;
|
||||
input.focus();
|
||||
if (input.value) {
|
||||
input.select();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close title editor when clicking outside .item_title
|
||||
const openTitleWrap = document.querySelector('.item_title .info-title-edit-wrap');
|
||||
if (openTitleWrap && openTitleWrap.style.display !== 'none' && !e.target.closest('.item_title')) {
|
||||
const container = openTitleWrap.closest('.item_title');
|
||||
const textEl = container?.querySelector('.item_title_text');
|
||||
const input = container?.querySelector('#info-title-input');
|
||||
openTitleWrap.style.display = 'none';
|
||||
if (textEl) textEl.style.display = '';
|
||||
if (input && input.dataset.origVal !== undefined) {
|
||||
input.value = input.dataset.origVal;
|
||||
}
|
||||
}
|
||||
|
||||
// Title save button
|
||||
const saveBtn = e.target.closest('#info-title-save');
|
||||
if (saveBtn) {
|
||||
@@ -12798,13 +12867,26 @@ document.addEventListener('click', (e) => {
|
||||
saveBtn.disabled = false;
|
||||
saveBtn.innerHTML = origIcon;
|
||||
if (data.success) {
|
||||
// Keep input form intact and update its value
|
||||
input.value = data.title || '';
|
||||
const savedTitle = (data.title || '').trim();
|
||||
input.value = savedTitle;
|
||||
input.dataset.origVal = savedTitle;
|
||||
|
||||
// Update any plain-text title display element
|
||||
const textEl = container.querySelector('.item_title_text') || document.querySelector('.item_title_text');
|
||||
if (textEl) {
|
||||
textEl.textContent = data.title || '';
|
||||
if (savedTitle) {
|
||||
textEl.textContent = savedTitle;
|
||||
textEl.classList.remove('no-title');
|
||||
} else {
|
||||
textEl.innerHTML = '<em>Add a title</em>';
|
||||
textEl.classList.add('no-title');
|
||||
}
|
||||
textEl.style.display = '';
|
||||
}
|
||||
|
||||
const editWrap = container.querySelector('.info-title-edit-wrap');
|
||||
if (editWrap) {
|
||||
editWrap.style.display = 'none';
|
||||
}
|
||||
|
||||
// Invalidate cache so navigating next/prev fetches fresh HTML from server
|
||||
@@ -12977,13 +13059,26 @@ document.addEventListener('click', (e) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Handle Enter key on title input
|
||||
// Handle Enter and Escape key on title input
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' && e.target && e.target.id === 'info-title-input') {
|
||||
e.preventDefault();
|
||||
const container = e.target.closest('.item_title') || document;
|
||||
const saveBtn = container.querySelector('#info-title-save') || document.getElementById('info-title-save');
|
||||
if (saveBtn) saveBtn.click();
|
||||
if (e.target && e.target.id === 'info-title-input') {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
const container = e.target.closest('.item_title') || document;
|
||||
const saveBtn = container.querySelector('#info-title-save') || document.getElementById('info-title-save');
|
||||
if (saveBtn) saveBtn.click();
|
||||
} else if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
const container = e.target.closest('.item_title') || document;
|
||||
const editWrap = container.querySelector('.info-title-edit-wrap');
|
||||
const textEl = container.querySelector('.item_title_text');
|
||||
const input = e.target;
|
||||
if (editWrap) editWrap.style.display = 'none';
|
||||
if (textEl) textEl.style.display = '';
|
||||
if (input && input.dataset.origVal !== undefined) {
|
||||
input.value = input.dataset.origVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+456
-32
@@ -1030,26 +1030,53 @@
|
||||
|
||||
let lastBoundMode = typeof window.activeMode !== 'undefined' ? window.activeMode : null;
|
||||
|
||||
const getCurrentMimeFilter = () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const qMime = urlParams.get('mime');
|
||||
if (qMime !== null) return qMime.trim();
|
||||
const cookieMime = document.cookie.split('; ').find(row => row.startsWith('mime='));
|
||||
if (cookieMime) {
|
||||
const val = cookieMime.split('=')[1];
|
||||
if (typeof val === 'string') return decodeURIComponent(val).trim();
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
let lastBoundMime = getCurrentMimeFilter();
|
||||
|
||||
// Handle AJAX item loads
|
||||
document.addEventListener('f0ck:contentLoaded', () => {
|
||||
const currentMode = typeof window.activeMode !== 'undefined' ? window.activeMode : null;
|
||||
const modeChanged = lastBoundMode !== null && lastBoundMode !== currentMode;
|
||||
lastBoundMode = currentMode;
|
||||
|
||||
window.f0ckDebug("Sidebar Activity: Page transition detected", modeChanged ? "(Mode changed)" : "");
|
||||
const currentMime = getCurrentMimeFilter();
|
||||
const mimeChanged = lastBoundMime !== null && lastBoundMime !== currentMime;
|
||||
lastBoundMime = currentMime;
|
||||
|
||||
if (modeChanged) {
|
||||
window._sidebarActivityCache = [];
|
||||
lastRenderedIds = '';
|
||||
currentPage = 1;
|
||||
hasMore = true;
|
||||
loadActivity(false); // Force reload with loading state
|
||||
window.f0ckDebug("Sidebar Activity: Page transition detected", modeChanged ? "(Mode changed)" : "", mimeChanged ? "(Mime changed)" : "");
|
||||
|
||||
if (modeChanged || mimeChanged) {
|
||||
if (modeChanged) {
|
||||
window._sidebarActivityCache = [];
|
||||
lastRenderedIds = '';
|
||||
currentPage = 1;
|
||||
hasMore = true;
|
||||
loadActivity(false); // Force reload with loading state
|
||||
}
|
||||
|
||||
recommendationsLoaded = false;
|
||||
tagFeedLoaded = false;
|
||||
tagFeedNeedsReload = true;
|
||||
const activeTabEl = document.querySelector('.sidebar-tab.active')?.dataset.tab;
|
||||
let savedTab = 'comments';
|
||||
try { savedTab = localStorage.getItem('sidebar_active_tab'); } catch (_) {}
|
||||
if (savedTab === 'videos') {
|
||||
loadVideoRecommendations(false);
|
||||
const currentTabName = activeTabEl || savedTab;
|
||||
|
||||
if (currentTabName === 'videos' || currentTabName === 'recommendations') {
|
||||
loadRecommendations(false);
|
||||
} else if (currentTabName === 'tag' && currentTag) {
|
||||
loadTagFeed(true);
|
||||
}
|
||||
} else {
|
||||
// Immediately render from cache to avoid flicker
|
||||
@@ -1059,6 +1086,27 @@
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('f0ck:mimeChanged', (e) => {
|
||||
const newMime = (e.detail && typeof e.detail.mime !== 'undefined') ? e.detail.mime : getCurrentMimeFilter();
|
||||
window.f0ckDebug("Sidebar Activity: MIME filter changed", newMime);
|
||||
lastBoundMime = newMime;
|
||||
|
||||
recommendationsLoaded = false;
|
||||
tagFeedLoaded = false;
|
||||
tagFeedNeedsReload = true;
|
||||
|
||||
const activeTabEl = document.querySelector('.sidebar-tab.active')?.dataset.tab;
|
||||
let savedTab = 'comments';
|
||||
try { savedTab = localStorage.getItem('sidebar_active_tab'); } catch (_) {}
|
||||
const currentTabName = activeTabEl || savedTab;
|
||||
|
||||
if (currentTabName === 'videos' || currentTabName === 'recommendations') {
|
||||
loadRecommendations(false);
|
||||
} else if (currentTabName === 'tag' && currentTag) {
|
||||
loadTagFeed(true);
|
||||
}
|
||||
});
|
||||
|
||||
// Sync sidebar and comments-list layout on initial page load (Legacy View Only)
|
||||
if (typeof syncSidebarAndComments === 'function') {
|
||||
syncSidebarAndComments();
|
||||
@@ -1074,7 +1122,7 @@
|
||||
let recIoSentinel = null;
|
||||
let recObserver = null;
|
||||
|
||||
const renderVideoCard = (video) => {
|
||||
const renderVideoCard = (video, options = {}) => {
|
||||
const videoKey = (window.f0ckSession?.enable_item_slugs && video.slug) ? video.slug : video.id;
|
||||
const rClass = video.rating_class || 'untagged';
|
||||
const blurNsfw = localStorage.getItem('blurNsfw') === 'true';
|
||||
@@ -1140,7 +1188,7 @@
|
||||
`;
|
||||
} else {
|
||||
thumbContentHtml = `
|
||||
<img src="${thumbUrl}" class="sidebar-video-thumb" alt="${escapeHtml(displayTitle)}" loading="lazy" onerror="this.style.display='none'; if(this.nextElementSibling) this.nextElementSibling.classList.remove('hidden');" />
|
||||
<img src="${thumbUrl}" class="sidebar-video-thumb" alt="${escapeHtml(displayTitle)}" loading="lazy" draggable="false" onerror="this.style.display='none'; if(this.nextElementSibling) this.nextElementSibling.classList.remove('hidden');" />
|
||||
<div class="sidebar-media-placeholder ${isAudio ? 'audio' : ''} hidden">
|
||||
<i class="${overlayIcon}"></i>
|
||||
</div>
|
||||
@@ -1151,12 +1199,18 @@
|
||||
? `<span class="sidebar-personalized-pill" title="Personalized recommendation based on your interests"><i class="fa-solid fa-wand-magic-sparkles"></i> For You</span>`
|
||||
: '';
|
||||
|
||||
const isTagFeed = options.isTagFeed || false;
|
||||
const tagContext = options.tag || null;
|
||||
const targetHref = (isTagFeed && tagContext)
|
||||
? `/tag/${encodeURIComponent(tagContext).replace(/%2C/g, ',').replace(/%20/g, ' ')}/${videoKey}`
|
||||
: `/${videoKey}`;
|
||||
const activeClass = options.isActive ? ' active-tag-item' : '';
|
||||
|
||||
return `
|
||||
<div class="sidebar-video-card" data-id="${video.id}" data-slug="${escapeHtml(video.slug || '')}" data-personalized="${video.personalized ? 'true' : 'false'}">
|
||||
<a href="/${videoKey}" class="sidebar-video-link" data-mode="${rClass}" data-inherit-context="false">
|
||||
<div class="sidebar-video-thumb-wrap">
|
||||
<div class="sidebar-video-card${activeClass}" data-id="${video.id}" data-slug="${escapeHtml(video.slug || '')}" data-file="${escapeHtml(video.dest || '')}" data-mime="${escapeHtml(video.mime || '')}" data-ext="${escapeHtml(ext ? ext.toLowerCase() : '')}" data-mode="${rClass}" data-personalized="${video.personalized ? 'true' : 'false'}">
|
||||
<a href="${targetHref}" class="sidebar-video-link" data-mode="${rClass}" data-inherit-context="false">
|
||||
<div class="sidebar-video-thumb-wrap" data-file="${escapeHtml(video.dest || '')}" data-mime="${escapeHtml(video.mime || '')}" data-ext="${escapeHtml(ext ? ext.toLowerCase() : '')}" data-mode="${rClass}">
|
||||
${thumbContentHtml}
|
||||
<div class="sidebar-video-play-overlay"><i class="${overlayIcon}"></i></div>
|
||||
${formatBadge}
|
||||
<span class="sidebar-video-badge rating-${rClass}">${rClass.toUpperCase()}</span>
|
||||
</div>
|
||||
@@ -1215,7 +1269,9 @@
|
||||
seenRecommendationIds.clear();
|
||||
const mode = typeof window.activeMode !== 'undefined' ? window.activeMode : '';
|
||||
const affParams = getSessionAffinityParams();
|
||||
const res = await fetch(`/api/v2/recommendations?limit=${RECOMMENDATIONS_LIMIT}&mode=${mode}${affParams}`, {
|
||||
const mime = getCurrentMimeFilter();
|
||||
const mimeParam = mime ? `&mime=${encodeURIComponent(mime)}` : '';
|
||||
const res = await fetch(`/api/v2/recommendations?limit=${RECOMMENDATIONS_LIMIT}&mode=${mode}${mimeParam}${affParams}`, {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
});
|
||||
const data = await res.json();
|
||||
@@ -1272,7 +1328,9 @@
|
||||
const mode = typeof window.activeMode !== 'undefined' ? window.activeMode : '';
|
||||
const excludeArr = Array.from(seenRecommendationIds).slice(-100);
|
||||
const affParams = getSessionAffinityParams();
|
||||
const res = await fetch(`/api/v2/recommendations?limit=15&mode=${mode}&exclude_ids=${excludeArr.join(',')}${affParams}`, {
|
||||
const mime = getCurrentMimeFilter();
|
||||
const mimeParam = mime ? `&mime=${encodeURIComponent(mime)}` : '';
|
||||
const res = await fetch(`/api/v2/recommendations?limit=15&mode=${mode}${mimeParam}&exclude_ids=${excludeArr.join(',')}${affParams}`, {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
});
|
||||
const data = await res.json();
|
||||
@@ -1349,13 +1407,335 @@
|
||||
|
||||
const loadVideoRecommendations = loadRecommendations;
|
||||
|
||||
// ── Dedicated Tag Feed (Ordered for Tag Views) ───────────────────────
|
||||
let currentTag = null;
|
||||
let tagOrder = 'desc'; // 'desc' = newest first (default), 'asc' = oldest first (chronological)
|
||||
let tagOffset = 0;
|
||||
let tagTotal = 0;
|
||||
let tagFeedLoaded = false;
|
||||
let tagFeedLoading = false;
|
||||
let tagFeedNeedsReload = false;
|
||||
let tagHasMore = false;
|
||||
let tagSentinel = null;
|
||||
let tagObserver = null;
|
||||
|
||||
const getCurrentTag = () => {
|
||||
const pathMatch = window.location.pathname.match(/^\/tag\/([^/?#]+)/);
|
||||
if (pathMatch) return decodeURIComponent(pathMatch[1]);
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
if (searchParams.get('tag')) return searchParams.get('tag');
|
||||
if (window.currentTag) return window.currentTag;
|
||||
return null;
|
||||
};
|
||||
|
||||
const getCurrentItemIdentifiers = () => {
|
||||
const ids = new Set();
|
||||
|
||||
// 1. From DOM elements on the page (primary/direct)
|
||||
const idElem = document.querySelector('[data-item-id]');
|
||||
if (idElem && idElem.dataset.itemId) {
|
||||
ids.add(String(idElem.dataset.itemId).trim());
|
||||
}
|
||||
const idLink = document.querySelector('.id-link');
|
||||
if (idLink) {
|
||||
if (idLink.dataset.itemId) ids.add(String(idLink.dataset.itemId).trim());
|
||||
const txt = idLink.textContent.trim();
|
||||
if (txt) ids.add(txt);
|
||||
}
|
||||
|
||||
// 2. From URL pathname (/tag/:tag/:slugOrId or /: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 === 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])) {
|
||||
ids.add(decodeURIComponent(segments[0]).trim());
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(ids).filter(Boolean);
|
||||
};
|
||||
|
||||
const getCurrentItemKey = () => {
|
||||
const ids = getCurrentItemIdentifiers();
|
||||
return ids.length > 0 ? ids[0] : null;
|
||||
};
|
||||
|
||||
let isReCenteringFeed = false;
|
||||
|
||||
const highlightActiveTagCard = (scrollIntoView = false) => {
|
||||
const identifiers = getCurrentItemIdentifiers();
|
||||
const container = document.getElementById('sidebar-tag-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 isTagTabActive = document.querySelector('.sidebar-tab.active')?.dataset.tab === 'tag' &&
|
||||
container.style.display !== 'none';
|
||||
if (isTagTabActive) {
|
||||
try {
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const cardRect = matchedCard.getBoundingClientRect();
|
||||
const targetScrollTop = container.scrollTop + (cardRect.top - containerRect.top) - (container.clientHeight / 2) + (cardRect.height / 2);
|
||||
container.scrollTo({ top: Math.max(0, targetScrollTop), behavior: 'smooth' });
|
||||
} catch (_) {
|
||||
matchedCard.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
}
|
||||
} else if (!matchedCard && identifiers.length > 0 && currentTag && !isReCenteringFeed && !tagFeedLoading) {
|
||||
// Active item belongs to current tag context but is not in currently rendered slice
|
||||
// (e.g. after pressing Random in tag). Center feed around this active item!
|
||||
isReCenteringFeed = true;
|
||||
loadTagFeed(true, identifiers[0]).finally(() => {
|
||||
isReCenteringFeed = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let userManuallySelectedNonTagTab = false;
|
||||
|
||||
const updateTagTabVisibility = (forceSwitchToTag = false) => {
|
||||
const tag = getCurrentTag();
|
||||
const tagBtn = document.getElementById('sidebar-tab-tag');
|
||||
if (!tagBtn) return;
|
||||
|
||||
if (tag) {
|
||||
tagBtn.style.display = 'inline-flex';
|
||||
tagBtn.title = `#${tag}`;
|
||||
tagBtn.setAttribute('aria-label', `#${tag}`);
|
||||
|
||||
const titleNameEl = document.querySelector('.sidebar-tag-name');
|
||||
if (titleNameEl) titleNameEl.textContent = `#${tag}`;
|
||||
|
||||
if (currentTag !== tag) {
|
||||
currentTag = tag;
|
||||
tagOffset = 0;
|
||||
tagFeedLoaded = false;
|
||||
tagFeedNeedsReload = true;
|
||||
userManuallySelectedNonTagTab = false;
|
||||
|
||||
// When entering or switching tag view (e.g. /tag/cat), automatically jump to the tag tab
|
||||
switchSidebarTab('tag');
|
||||
} else {
|
||||
if (forceSwitchToTag || (!userManuallySelectedNonTagTab && document.querySelector('.sidebar-tab.active')?.dataset.tab !== 'tag')) {
|
||||
switchSidebarTab('tag');
|
||||
} else {
|
||||
highlightActiveTagCard(true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tagBtn.style.display = 'none';
|
||||
currentTag = null;
|
||||
tagFeedLoaded = false;
|
||||
userManuallySelectedNonTagTab = false;
|
||||
|
||||
const activeTab = document.querySelector('.sidebar-tab.active')?.dataset.tab;
|
||||
if (activeTab === 'tag') {
|
||||
switchSidebarTab('recommendations');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const loadTagFeed = async (reset = false, focusId = null) => {
|
||||
const container = document.getElementById('sidebar-tag-container');
|
||||
const itemsContainer = container?.querySelector('.sidebar-tag-items-container');
|
||||
if (!container || !itemsContainer || tagFeedLoading) return;
|
||||
if (!currentTag) return;
|
||||
|
||||
tagFeedLoading = true;
|
||||
tagFeedNeedsReload = false;
|
||||
|
||||
if (reset) {
|
||||
tagOffset = 0;
|
||||
itemsContainer.innerHTML = `
|
||||
<div class="sidebar-loading-state">
|
||||
<i class="fa-solid fa-circle-notch fa-spin"></i>
|
||||
<span>Loading #${escapeHtml(currentTag)} items...</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
try {
|
||||
const mode = typeof window.activeMode !== 'undefined' ? window.activeMode : '';
|
||||
const mime = getCurrentMimeFilter();
|
||||
let url = `/api/v2/tag-feed?tag=${encodeURIComponent(currentTag)}&order=${tagOrder}&offset=${tagOffset}&limit=20&mode=${mode}`;
|
||||
if (mime) {
|
||||
url += `&mime=${encodeURIComponent(mime)}`;
|
||||
}
|
||||
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 || [];
|
||||
tagTotal = data.total || 0;
|
||||
tagOffset = (data.offset ?? tagOffset) + items.length;
|
||||
tagHasMore = typeof data.hasMore !== 'undefined' ? !!data.hasMore : (tagOffset < tagTotal);
|
||||
|
||||
// Update counter in header
|
||||
const countEl = container.querySelector('.sidebar-tag-count');
|
||||
if (countEl) countEl.textContent = `(${tagTotal})`;
|
||||
|
||||
// Update order button icon and tooltip
|
||||
const orderBtn = container.querySelector('.sidebar-tag-order-btn');
|
||||
if (orderBtn) {
|
||||
if (tagOrder === 'asc') {
|
||||
orderBtn.innerHTML = '<i class="fa-solid fa-arrow-down-1-9"></i>';
|
||||
orderBtn.title = 'Chronological (Oldest first) — Click for Newest first';
|
||||
} else {
|
||||
orderBtn.innerHTML = '<i class="fa-solid fa-arrow-down-9-1"></i>';
|
||||
orderBtn.title = 'Newest first (default) — Click for Oldest first';
|
||||
}
|
||||
}
|
||||
|
||||
if (data.success && items.length > 0) {
|
||||
tagFeedLoaded = 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, { isTagFeed: true, tag: currentTag, isActive });
|
||||
});
|
||||
|
||||
if (reset) {
|
||||
itemsContainer.innerHTML = html;
|
||||
setTimeout(() => highlightActiveTagCard(true), 60);
|
||||
} else {
|
||||
itemsContainer.insertAdjacentHTML('beforeend', html);
|
||||
highlightActiveTagCard(false);
|
||||
}
|
||||
|
||||
initTagSentinel();
|
||||
} 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-solid fa-tag" style="font-size:1.8em;opacity:0.4;margin-bottom:8px;display:block;"></i>
|
||||
No uploads found for #${escapeHtml(currentTag)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[TAG-FEED] Failed to load tag items:', 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 tag items
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} finally {
|
||||
tagFeedLoading = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadMoreTagFeed = async () => {
|
||||
if (tagFeedLoading || !tagHasMore || !currentTag) return;
|
||||
tagOffset += 20;
|
||||
await loadTagFeed(false);
|
||||
};
|
||||
|
||||
const initTagSentinel = () => {
|
||||
const container = document.getElementById('sidebar-tag-container');
|
||||
const itemsContainer = container?.querySelector('.sidebar-tag-items-container');
|
||||
if (!container || !itemsContainer) return;
|
||||
|
||||
if (tagSentinel && tagSentinel.parentNode) {
|
||||
tagSentinel.remove();
|
||||
}
|
||||
|
||||
if (!tagHasMore) return;
|
||||
|
||||
tagSentinel = document.createElement('div');
|
||||
tagSentinel.className = 'sidebar-tag-sentinel';
|
||||
tagSentinel.style.cssText = 'height: 20px; margin: 10px 0;';
|
||||
itemsContainer.appendChild(tagSentinel);
|
||||
|
||||
if (tagObserver) tagObserver.disconnect();
|
||||
|
||||
tagObserver = new IntersectionObserver((entries) => {
|
||||
if (entries[0].isIntersecting && !tagFeedLoading && tagHasMore) {
|
||||
loadMoreTagFeed();
|
||||
}
|
||||
}, {
|
||||
root: container,
|
||||
rootMargin: '200px'
|
||||
});
|
||||
|
||||
tagObserver.observe(tagSentinel);
|
||||
};
|
||||
|
||||
const initTagOrderButton = () => {
|
||||
const container = document.getElementById('sidebar-tag-container');
|
||||
if (!container || container.dataset.orderBound === 'true') return;
|
||||
container.dataset.orderBound = 'true';
|
||||
|
||||
const orderBtn = container.querySelector('.sidebar-tag-order-btn');
|
||||
if (orderBtn) {
|
||||
orderBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
tagOrder = (tagOrder === 'desc') ? 'asc' : 'desc';
|
||||
loadTagFeed(true);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const bindTagContainerEvents = () => {
|
||||
const container = document.getElementById('sidebar-tag-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');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const switchSidebarTab = (tabName) => {
|
||||
const normalizedTab = (tabName === 'videos' || tabName === 'recommendations') ? 'recommendations' : tabName;
|
||||
if (window.clearHoverPreview) {
|
||||
window.clearHoverPreview();
|
||||
}
|
||||
const normalizedTab = (tabName === 'videos' || tabName === 'recommendations')
|
||||
? 'recommendations'
|
||||
: (tabName === 'tag' ? 'tag' : 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;
|
||||
const tTab = (t.dataset.tab === 'videos' || t.dataset.tab === 'recommendations')
|
||||
? 'recommendations'
|
||||
: (t.dataset.tab === 'tag' ? 'tag' : t.dataset.tab);
|
||||
if (tTab === normalizedTab) {
|
||||
t.classList.add('active');
|
||||
} else {
|
||||
@@ -1364,7 +1744,9 @@
|
||||
});
|
||||
|
||||
contents.forEach(c => {
|
||||
const cTab = (c.dataset.tabContent === 'videos' || c.dataset.tabContent === 'recommendations') ? 'recommendations' : c.dataset.tabContent;
|
||||
const cTab = (c.dataset.tabContent === 'videos' || c.dataset.tabContent === 'recommendations')
|
||||
? 'recommendations'
|
||||
: (c.dataset.tabContent === 'tag' ? 'tag' : c.dataset.tabContent);
|
||||
if (cTab === normalizedTab) {
|
||||
c.classList.add('active');
|
||||
c.style.display = 'block';
|
||||
@@ -1382,6 +1764,12 @@
|
||||
if (!recommendationsLoaded) {
|
||||
loadRecommendations();
|
||||
}
|
||||
} else if (normalizedTab === 'tag') {
|
||||
if (tagFeedNeedsReload || !tagFeedLoaded) {
|
||||
loadTagFeed(true);
|
||||
} else {
|
||||
setTimeout(() => highlightActiveTagCard(true), 60);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1392,6 +1780,11 @@
|
||||
const btn = e.target.closest('.sidebar-tab');
|
||||
if (btn && btn.dataset.tab) {
|
||||
e.preventDefault();
|
||||
if (btn.dataset.tab !== 'tag') {
|
||||
userManuallySelectedNonTagTab = true;
|
||||
} else {
|
||||
userManuallySelectedNonTagTab = false;
|
||||
}
|
||||
switchSidebarTab(btn.dataset.tab);
|
||||
}
|
||||
});
|
||||
@@ -1403,13 +1796,20 @@
|
||||
savedTab = localStorage.getItem('sidebar_active_tab') || 'comments';
|
||||
} catch (_) {}
|
||||
|
||||
if (savedTab === 'videos' || savedTab === 'recommendations') {
|
||||
if (getCurrentTag()) {
|
||||
switchSidebarTab('tag');
|
||||
} else if (savedTab === 'videos' || savedTab === 'recommendations') {
|
||||
switchSidebarTab('recommendations');
|
||||
} else {
|
||||
switchSidebarTab(savedTab);
|
||||
}
|
||||
};
|
||||
|
||||
const replaceCardWithNewRandom = async (card) => {
|
||||
if (!card || card.dataset.swapping === 'true') return;
|
||||
if (window.clearHoverPreview) {
|
||||
window.clearHoverPreview();
|
||||
}
|
||||
card.dataset.swapping = 'true';
|
||||
card.classList.add('sidebar-card-swapping');
|
||||
|
||||
@@ -1436,7 +1836,9 @@
|
||||
const excludeArr = Array.from(excludeSet).slice(-120);
|
||||
|
||||
const affParams = getSessionAffinityParams();
|
||||
const res = await fetch(`/api/v2/recommendations?limit=1&mode=${mode}&exclude_ids=${excludeArr.join(',')}${affParams}`, {
|
||||
const mime = getCurrentMimeFilter();
|
||||
const mimeParam = mime ? `&mime=${encodeURIComponent(mime)}` : '';
|
||||
const res = await fetch(`/api/v2/recommendations?limit=1&mode=${mode}${mimeParam}&exclude_ids=${excludeArr.join(',')}${affParams}`, {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
});
|
||||
const data = await res.json();
|
||||
@@ -1458,11 +1860,7 @@
|
||||
const newCard = temp.firstElementChild;
|
||||
|
||||
if (newCard && card.parentNode) {
|
||||
newCard.classList.add('sidebar-card-entering');
|
||||
card.replaceWith(newCard);
|
||||
setTimeout(() => {
|
||||
newCard.classList.remove('sidebar-card-entering');
|
||||
}, 2200);
|
||||
}
|
||||
} else {
|
||||
card.classList.remove('sidebar-card-swapping');
|
||||
@@ -1496,13 +1894,19 @@
|
||||
container.addEventListener('auxclick', handleCardAction);
|
||||
};
|
||||
|
||||
// Reload recommendations when user triggers Random (#random, #nav-random, or 'r' key)
|
||||
// Reload recommendations or sync tag feed when user triggers Random (#random, #nav-random, or 'r' key)
|
||||
const handleRandomAction = () => {
|
||||
recommendationsLoaded = false;
|
||||
let savedTab = 'comments';
|
||||
try { savedTab = localStorage.getItem('sidebar_active_tab'); } catch (_) {}
|
||||
if (savedTab === 'videos' || savedTab === 'recommendations') {
|
||||
loadRecommendations(false);
|
||||
const tag = getCurrentTag();
|
||||
if (tag) {
|
||||
userManuallySelectedNonTagTab = false;
|
||||
switchSidebarTab('tag');
|
||||
} else {
|
||||
let savedTab = 'comments';
|
||||
try { savedTab = localStorage.getItem('sidebar_active_tab'); } catch (_) {}
|
||||
if (savedTab === 'videos' || savedTab === 'recommendations') {
|
||||
loadRecommendations(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1525,10 +1929,17 @@
|
||||
loadActivity(false);
|
||||
|
||||
recommendationsLoaded = false;
|
||||
tagFeedLoaded = false;
|
||||
tagFeedNeedsReload = true;
|
||||
const activeTabEl = document.querySelector('.sidebar-tab.active')?.dataset.tab;
|
||||
let savedTab = 'comments';
|
||||
try { savedTab = localStorage.getItem('sidebar_active_tab'); } catch (_) {}
|
||||
if (savedTab === 'videos' || savedTab === 'recommendations') {
|
||||
const currentTabName = activeTabEl || savedTab;
|
||||
|
||||
if (currentTabName === 'videos' || currentTabName === 'recommendations') {
|
||||
loadRecommendations(false);
|
||||
} else if (currentTabName === 'tag' && currentTag) {
|
||||
loadTagFeed(true);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1573,6 +1984,19 @@
|
||||
const _origInit = init;
|
||||
const initWithScroll = async () => {
|
||||
initSidebarTabs();
|
||||
initTagOrderButton();
|
||||
bindTagContainerEvents();
|
||||
updateTagTabVisibility();
|
||||
document.addEventListener('f0ck:contentLoaded', () => {
|
||||
updateTagTabVisibility();
|
||||
setTimeout(() => highlightActiveTagCard(true), 60);
|
||||
});
|
||||
window.addEventListener('popstate', () => {
|
||||
setTimeout(() => {
|
||||
updateTagTabVisibility();
|
||||
highlightActiveTagCard(true);
|
||||
}, 60);
|
||||
});
|
||||
bindRecommendationEvents();
|
||||
bindRecommendationScrollListener();
|
||||
await _origInit();
|
||||
|
||||
Reference in New Issue
Block a user