diff --git a/public/s/js/f0ckm.js b/public/s/js/f0ckm.js index a9e39ec..7ea5518 100644 --- a/public/s/js/f0ckm.js +++ b/public/s/js/f0ckm.js @@ -4727,7 +4727,7 @@ window.cancelAnimFrame = (function () { ]; const getNavXdTier = (s) => { s = +s; - if (s < 5) return 0; + if (s < 1) return 0; if (s < 200) return 1; if (s < 1000) return 2; if (s < 100000) return 3; @@ -4879,7 +4879,7 @@ window.cancelAnimFrame = (function () { // 2. Any grid thumbnail indicators for that item (on any page) const XD_TIER_META = [ null, - { cls: 'xd-tier-1', label: 'xD', min: 5 }, + { cls: 'xd-tier-1', label: 'xD', min: 1 }, { cls: 'xd-tier-2', label: 'xDD', min: 200 }, { cls: 'xd-tier-3', label: 'xDDD', min: 1000 }, { cls: 'xd-tier-4', label: 'xDDDD', min: 100000 }, @@ -4887,7 +4887,7 @@ window.cancelAnimFrame = (function () { ]; const getXdTierFromScore = (score) => { - if (score < 5) return 0; + if (score < 1) return 0; if (score < 200) return 1; if (score < 1000) return 2; if (score < 100000) return 3; diff --git a/public/s/js/scroller.js b/public/s/js/scroller.js index ca37be5..dcd4580 100644 --- a/public/s/js/scroller.js +++ b/public/s/js/scroller.js @@ -1553,24 +1553,13 @@ const rBadge = slide.querySelector('.scroll-rating[data-item-id]'); if (rBadge) { if (window.scrollerIsMod || item.local_id) rBadge.classList.add('can-cycle'); - rBadge.addEventListener('click', async e => { + rBadge.addEventListener('click', e => { if (Date.now() - speedEndedAt < 200) return; // ignore click if we just ended a speed-hold e.stopPropagation(); const slideEl = rBadge.closest('.scroll-slide'); const id = slideEl?.dataset.localId || rBadge.dataset.itemId; if (!id || isNaN(id)) { showShareToast('Rehost first to change rating'); return; } - try { - const resp = await fetch(`/api/v2/tags/${id}/cycle-rating`, { - method: 'PUT', - headers: { 'x-csrf-token': window.scrollerCsrf || '' } - }); - const data = await resp.json(); - if (data.success) { - rBadge.className = `scroll-rating ${data.rating_class}${window.scrollerIsMod ? ' can-cycle' : ''}`; - rBadge.textContent = data.rating_label; - rBadge.dataset.rating = data.rating_class; - } - } catch {} + cycleRatingOptimistic(rBadge, id, false); }); } @@ -1932,6 +1921,73 @@ } } + function cycleRatingOptimistic(badge, id, showToast = false) { + if (!badge || !id || isNaN(id)) return; + + let currentRating = badge.dataset.rating || ''; + if (!currentRating) { + if (badge.classList.contains('sfw')) currentRating = 'sfw'; + else if (badge.classList.contains('nsfw')) currentRating = 'nsfw'; + else if (badge.classList.contains('nsfl')) currentRating = 'nsfl'; + } + + let nextRating = 'sfw'; + if (currentRating === 'sfw') nextRating = 'nsfw'; + else if (currentRating === 'nsfw') nextRating = 'nsfl'; + + const mapping = { + sfw: { label: 'SFW', cls: 'sfw', toast: '🛡 SFW' }, + nsfw: { label: 'NSFW', cls: 'nsfw', toast: '🔥 NSFW' }, + nsfl: { label: 'NSFL', cls: 'nsfl', toast: '💀 NSFL' } + }; + + const info = mapping[nextRating]; + + const oldClassName = badge.className; + const oldTextContent = badge.textContent; + const oldDatasetRating = badge.dataset.rating; + + // Track active request ID to ignore out-of-order race conditions on rapid keypresses + const reqId = (badge._lastCycleReqId || 0) + 1; + badge._lastCycleReqId = reqId; + + // Optimistically apply new state + badge.className = `scroll-rating ${info.cls}${window.scrollerIsMod ? ' can-cycle' : ''}`; + badge.textContent = info.label; + badge.dataset.rating = info.cls; + + if (showToast) { + showShareToast(info.toast); + } + + fetch(`/api/v2/tags/${id}/cycle-rating`, { + method: 'PUT', + headers: { 'x-csrf-token': window.scrollerCsrf || '' } + }) + .then(r => r.json()) + .then(data => { + if (badge._lastCycleReqId !== reqId) return; // ignore stale responses + if (!data.success) { + revert(); + return; + } + // Verify we match actual server result + badge.className = `scroll-rating ${data.rating_class}${window.scrollerIsMod ? ' can-cycle' : ''}`; + badge.textContent = data.rating_label; + badge.dataset.rating = data.rating_class; + }) + .catch(() => { + if (badge._lastCycleReqId !== reqId) return; // ignore stale responses + revert(); + }); + + function revert() { + badge.className = oldClassName; + badge.textContent = oldTextContent; + badge.dataset.rating = oldDatasetRating; + showShareToast('⚠️ Failed to update rating'); + } + } function reloadFeed() { clearCache(); @@ -3143,26 +3199,12 @@ else if (e.key === 'p' || e.key === 'P') { e.preventDefault(); if (!currentSlide || !window.scrollerLoggedIn) return; - const itemId = currentSlide.dataset.id; + const itemId = currentSlide.dataset.localId || currentSlide.dataset.id; if (!itemId) return; - fetch(`/api/v2/tags/${itemId}/cycle-rating`, { - method: 'PUT', - headers: { 'x-csrf-token': window.scrollerCsrf || '' } - }) - .then(r => r.json()) - .then(data => { - if (!data.success) return; - // Update the badge on the slide immediately - const badge = currentSlide.querySelector('.scroll-rating[data-item-id]'); - if (badge) { - badge.className = `scroll-rating ${data.rating_class}${window.scrollerIsMod ? ' can-cycle' : ''}`; - badge.textContent = data.rating_label; - badge.dataset.rating = data.rating_class; - } - const labels = { sfw: '🛡 SFW', nsfw: '🔥 NSFW', nsfl: '💀 NSFL' }; - showShareToast(labels[data.rating_class] ?? data.rating_label); - }) - .catch(() => {}); + const badge = currentSlide.querySelector('.scroll-rating[data-item-id]'); + if (badge) { + cycleRatingOptimistic(badge, itemId, true); + } } else if (e.key === 'l' || e.key === 'L') { e.preventDefault(); if (currentSlide) toggleFav(currentSlide); } else if (e.key === 'i' || e.key === 'I') { e.preventDefault(); if (currentSlide) openTagBar(currentSlide.dataset.id); } diff --git a/public/s/js/settings.js b/public/s/js/settings.js index 8d34184..6e61912 100644 --- a/public/s/js/settings.js +++ b/public/s/js/settings.js @@ -1346,7 +1346,7 @@ const getXdTier = (score) => { score = +score; - if (score < 5) return 0; + if (score < 1) return 0; if (score < 200) return 1; if (score < 1000) return 2; if (score < 100000) return 3; diff --git a/src/inc/routeinc/f0cklib.mjs b/src/inc/routeinc/f0cklib.mjs index 97fb4ed..36fd34a 100644 --- a/src/inc/routeinc/f0cklib.mjs +++ b/src/inc/routeinc/f0cklib.mjs @@ -86,11 +86,11 @@ const computeXdScore = (comments) => { }; const xdScoreMeta = (score) => { - if (score < 5) return { tier: 0, label: '' }; + if (score < 1) return { tier: 0, label: '' }; if (score < 200) return { tier: 1, label: 'xD' }; if (score < 1000) return { tier: 2, label: 'xDD' }; if (score < 100000) return { tier: 3, label: 'xDDD' }; - if (score < 200000000) return { tier: 4, label: 'xDDDD' }; + if (score < 20000000) return { tier: 4, label: 'xDDDD' }; return { tier: 5, label: 'xDDDDD+' }; };