From 15628739b1c5df635bfaf48f71c8c4a1b5cd53a9 Mon Sep 17 00:00:00 2001 From: Kibi Kelburton Date: Sat, 19 Sep 2026 12:50:56 +0200 Subject: [PATCH] fdsafdas --- public/s/js/f0ckm.js | 208 ++++++++++++++++++++++++++-------- public/s/js/scroller.js | 65 ++++++++++- public/s/js/upload.js | 16 +++ public/s/js/v0ck.js | 1 + src/inc/locales/de.json | 1 + src/inc/locales/en.json | 1 + src/inc/locales/nl.json | 1 + src/inc/locales/zange.json | 1 + src/inc/routes/apiv2/tags.mjs | 26 +++-- src/index.mjs | 13 ++- views/scroller.html | 34 +++++- views/snippets/navbar.html | 2 +- 12 files changed, 303 insertions(+), 66 deletions(-) diff --git a/public/s/js/f0ckm.js b/public/s/js/f0ckm.js index a466e31..515ded6 100644 --- a/public/s/js/f0ckm.js +++ b/public/s/js/f0ckm.js @@ -1839,9 +1839,11 @@ window.cancelAnimFrame = (function () { window.applyBackgroundOpacitySettings(window.onaraTuning || null, true); } window._bgModeAutoSwitch = true; - const onaraModeBtn = document.querySelector('#bg-mode-btn-onara'); - if (onaraModeBtn) onaraModeBtn.click(); + if (typeof window.setBgTunerMode === 'function') { + window.setBgTunerMode('onara'); + } window._bgModeAutoSwitch = false; + modal.scrollTop = 0; if (document.querySelector('.index-container') && window.scrollY !== 0) { window.scrollTo(0, 0); } @@ -1878,18 +1880,18 @@ window.cancelAnimFrame = (function () { const scrollOnaraThumbIntoView = (thumb, forceCenter = false) => { if (!thumb) return; const container = thumb.closest('.index-container'); - if (container) { - const cRect = container.getBoundingClientRect(); - const tRect = thumb.getBoundingClientRect(); - const navbarH = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--navbar-h')) || 50; - const vh = window.innerHeight || document.documentElement.clientHeight; - const isVisible = tRect.bottom > navbarH && tRect.top < vh; - if (!isVisible || forceCenter) { + const navbarH = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--navbar-h')) || 50; + const vh = window.innerHeight || document.documentElement.clientHeight; + const tRect = thumb.getBoundingClientRect(); + const isVisible = tRect.bottom > navbarH && tRect.top < vh; + if (!isVisible || forceCenter) { + if (container) { + const cRect = container.getBoundingClientRect(); const targetScrollTop = container.scrollTop + (tRect.top - cRect.top) - (container.clientHeight / 2) + (tRect.height / 2); container.scrollTop = Math.max(0, targetScrollTop); + } else { + thumb.scrollIntoView({ block: forceCenter ? 'center' : 'nearest', inline: 'nearest', behavior: 'instant' }); } - } else { - thumb.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'instant' }); } if (document.querySelector('.index-container') && window.scrollY !== 0) { window.scrollTo(0, 0); @@ -1956,7 +1958,39 @@ window.cancelAnimFrame = (function () { if (extraMeta.dest && !targetThumb.dataset.file) { targetThumb.dataset.file = String(extraMeta.dest).replace(/^\/b\//, ''); } + if (extraMeta.numericId && !targetThumb.dataset.itemId) { + targetThumb.dataset.itemId = String(extraMeta.numericId); + } } + + // Ensure thumbnail image is loaded and rendered immediately + const numericId = targetThumb.dataset.itemId || extraMeta?.numericId || (typeof itemid === 'number' || (typeof itemid === 'string' && /^\d+$/.test(itemid)) ? itemid : null); + let rawBg = targetThumb.dataset.bg || extraMeta?.thumb || (numericId ? `/t/${numericId}.webp` : null); + if (rawBg) { + let bg = rawBg; + const mode = targetThumb.getAttribute('data-mode') || extraMeta?.mode; + const blurNsfw = localStorage.getItem('blurNsfw') === 'true'; + const blurNsfl = localStorage.getItem('blurNsfl') === 'true'; + const blurSfw = localStorage.getItem('blurSfw') === 'true'; + const blurUntagged = localStorage.getItem('blurUntagged') === 'true'; + let shouldBlurThis = false; + if (mode === 'nsfw') shouldBlurThis = blurNsfw; + else if (mode === 'nsfl') shouldBlurThis = blurNsfl; + else if (mode === 'sfw') shouldBlurThis = blurSfw; + else if (mode === 'null' || !mode) shouldBlurThis = blurUntagged; + if (shouldBlurThis && !targetThumb.classList.contains('revealed')) { + bg = bg.replace('.webp', '_blur.webp'); + } + const finalBg = typeof window.applyThumbCacheBust === 'function' ? window.applyThumbCacheBust(bg) : bg; + targetThumb.dataset.bg = finalBg; + targetThumb.style.setProperty('--thumb-bg', `url('${finalBg}')`); + targetThumb.classList.add('loaded'); + targetThumb.classList.remove('lazy-thumb'); + if (window.loadedThumbs) window.loadedThumbs.add(finalBg); + const img = new Image(); + img.src = finalBg; + } + scrollOnaraThumbIntoView(targetThumb, forceScroll); } return targetThumb; @@ -2046,9 +2080,11 @@ window.cancelAnimFrame = (function () { const { numericId, thumb, mode, mime, user, dest } = resolveItemThumbInfo(itemid, slug, extraMeta); const synthThumb = document.createElement('a'); synthThumb.href = url || `/${slug || itemid}`; - synthThumb.className = 'thumb lazy-thumb onara-active onara-synth-thumb loaded'; + synthThumb.className = 'thumb onara-active onara-synth-thumb loaded'; if (numericId) { - synthThumb.dataset.itemId = numericId; + synthThumb.dataset.itemId = String(numericId); + } else if (typeof itemid === 'string' && /^\d+$/.test(itemid)) { + synthThumb.dataset.itemId = itemid; } if (mime) { synthThumb.dataset.mime = mime; @@ -2060,12 +2096,28 @@ window.cancelAnimFrame = (function () { if (dest) { synthThumb.dataset.file = String(dest).replace(/^\/b\//, ''); } - const finalThumbUrl = thumb || (numericId ? `/t/${numericId}.webp` : `/t/${itemid}.webp`); + let baseThumbUrl = thumb || (numericId ? `/t/${numericId}.webp` : `/t/${itemid}.webp`); + const blurNsfw = localStorage.getItem('blurNsfw') === 'true'; + const blurNsfl = localStorage.getItem('blurNsfl') === 'true'; + const blurSfw = localStorage.getItem('blurSfw') === 'true'; + const blurUntagged = localStorage.getItem('blurUntagged') === 'true'; + let shouldBlurThis = false; + if (mode === 'nsfw') shouldBlurThis = blurNsfw; + else if (mode === 'nsfl') shouldBlurThis = blurNsfl; + else if (mode === 'sfw') shouldBlurThis = blurSfw; + else if (mode === 'null' || !mode) shouldBlurThis = blurUntagged; + if (shouldBlurThis) { + baseThumbUrl = baseThumbUrl.replace('.webp', '_blur.webp'); + } + const finalThumbUrl = typeof window.applyThumbCacheBust === 'function' ? window.applyThumbCacheBust(baseThumbUrl) : baseThumbUrl; synthThumb.dataset.bg = finalThumbUrl; synthThumb.setAttribute('data-mode', mode || 'sfw'); synthThumb.dataset.size = '1'; synthThumb.style.setProperty('--thumb-bg', `url('${finalThumbUrl}')`); synthThumb.innerHTML = '

'; + if (window.loadedThumbs) window.loadedThumbs.add(finalThumbUrl); + const img = new Image(); + img.src = finalThumbUrl; return synthThumb; }; @@ -2278,7 +2330,8 @@ window.cancelAnimFrame = (function () { 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]))) + (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]))) || + (parts.length >= 2 && parts[0] === 'favs' && (/^\d+$/.test(parts[parts.length - 1]) || /^[a-zA-Z0-9_-]{11}$/.test(parts[parts.length - 1]))) ); } catch { return false; @@ -2417,8 +2470,9 @@ window.cancelAnimFrame = (function () { window.applyBackgroundOpacitySettings(null, false); } window._bgModeAutoSwitch = true; - const targetModeBtn = document.querySelector((typeof isCurrentItemAudio === 'function' && isCurrentItemAudio()) ? '#bg-mode-btn-audio' : '#bg-mode-btn-standard'); - if (targetModeBtn) targetModeBtn.click(); + if (typeof window.setBgTunerMode === 'function') { + window.setBgTunerMode((typeof isCurrentItemAudio === 'function' && isCurrentItemAudio()) ? 'audio' : 'standard'); + } window._bgModeAutoSwitch = false; if (window.destroyBackgroundInstance) { window.destroyBackgroundInstance(); @@ -2450,8 +2504,9 @@ window.cancelAnimFrame = (function () { window.applyBackgroundOpacitySettings(null, false); } window._bgModeAutoSwitch = true; - const targetModeBtn = document.querySelector((typeof isCurrentItemAudio === 'function' && isCurrentItemAudio()) ? '#bg-mode-btn-audio' : '#bg-mode-btn-standard'); - if (targetModeBtn) targetModeBtn.click(); + if (typeof window.setBgTunerMode === 'function') { + window.setBgTunerMode((typeof isCurrentItemAudio === 'function' && isCurrentItemAudio()) ? 'audio' : 'standard'); + } window._bgModeAutoSwitch = false; if (window.destroyBackgroundInstance) { window.destroyBackgroundInstance(); @@ -5131,15 +5186,15 @@ window.cancelAnimFrame = (function () { window._bgModeAutoSwitch = true; const inOnara = (typeof isCurrentlyInOnara === 'function') ? isCurrentlyInOnara() : document.body.classList.contains('onara-modal-open'); if (inOnara) { - const btn = document.querySelector('#bg-mode-btn-onara'); - if (btn) btn.click(); - else if (typeof window.applyBackgroundOpacitySettings === 'function') { + if (typeof window.setBgTunerMode === 'function') { + window.setBgTunerMode('onara'); + } else if (typeof window.applyBackgroundOpacitySettings === 'function') { window.applyBackgroundOpacitySettings(window.onaraTuning, true); } } else if (elem.tagName === 'AUDIO') { - const btn = document.querySelector('#bg-mode-btn-audio'); - if (btn) btn.click(); - else if (typeof window.applyBackgroundOpacitySettings === 'function') { + if (typeof window.setBgTunerMode === 'function') { + window.setBgTunerMode('audio'); + } else if (typeof window.applyBackgroundOpacitySettings === 'function') { window.applyBackgroundOpacitySettings(window.audioBgTuning, 'audio'); } } else { @@ -5149,9 +5204,9 @@ window.cancelAnimFrame = (function () { mediaAmbientState.bottom = [25, 25, 35]; mediaAmbientState.targetBottom = [25, 25, 35]; mediaAmbientState.hasSampled = false; - const btn = document.querySelector('#bg-mode-btn-standard'); - if (btn) btn.click(); - else if (typeof window.applyBackgroundOpacitySettings === 'function') { + if (typeof window.setBgTunerMode === 'function') { + window.setBgTunerMode('standard'); + } else if (typeof window.applyBackgroundOpacitySettings === 'function') { window.applyBackgroundOpacitySettings(window.audioVisualizerTuning, false); } } @@ -7504,28 +7559,34 @@ window.cancelAnimFrame = (function () { }); }; - panel.querySelector('#bg-mode-btn-standard')?.addEventListener('click', () => { - activeBgTunerMode = 'standard'; + window.setBgTunerMode = (mode) => { + activeBgTunerMode = mode; syncBgSlidersDisplay(); - applyBackgroundOpacitySettings(window.audioVisualizerTuning, false); + if (mode === 'onara') { + applyBackgroundOpacitySettings(window.onaraTuning, true); + } else if (mode === 'audio') { + applyBackgroundOpacitySettings(window.audioBgTuning, 'audio'); + } else { + applyBackgroundOpacitySettings(window.audioVisualizerTuning, false); + } + }; + + panel.querySelector('#bg-mode-btn-standard')?.addEventListener('click', () => { + window.setBgTunerMode('standard'); if (!window._bgModeAutoSwitch && typeof window.flashMessage === 'function') { window.flashMessage('Tuning Standard Background', 1500, 'info'); } }); panel.querySelector('#bg-mode-btn-onara')?.addEventListener('click', () => { - activeBgTunerMode = 'onara'; - syncBgSlidersDisplay(); - applyBackgroundOpacitySettings(window.onaraTuning, true); + window.setBgTunerMode('onara'); if (!window._bgModeAutoSwitch && typeof window.flashMessage === 'function') { window.flashMessage('Tuning Onara Mode Background', 1500, 'info'); } }); panel.querySelector('#bg-mode-btn-audio')?.addEventListener('click', () => { - activeBgTunerMode = 'audio'; - syncBgSlidersDisplay(); - applyBackgroundOpacitySettings(window.audioBgTuning, 'audio'); + window.setBgTunerMode('audio'); if (!window._bgModeAutoSwitch && typeof window.flashMessage === 'function') { window.flashMessage('Tuning Audio Background', 1500, 'info'); } @@ -11019,7 +11080,10 @@ window.cancelAnimFrame = (function () { // Immediately restore scrollability and hide modals if (window.resetGlobalScrollState) window.resetGlobalScrollState(); if (window.hideAllModals) window.hideAllModals(); - if (window.closeOnaraModal) window.closeOnaraModal({ skipHistory: true }); + const willNavigateToItem = typeof isItemPath === 'function' ? isItemPath(url) : false; + if (!isOnaraActive() || !willNavigateToItem) { + if (window.closeOnaraModal) window.closeOnaraModal({ skipHistory: true }); + } isNavigating = true; @@ -14615,6 +14679,7 @@ window.cancelAnimFrame = (function () { const btns = document.querySelectorAll('#nav-search-btn'); let debounceTimer = null; + let closeTimer = null; let highlightIdx = -1; const doSearch = () => { @@ -14753,6 +14818,10 @@ window.cancelAnimFrame = (function () { fetch(`/api/v2/tags/suggest?q=${encodeURIComponent(q)}`).then(r => r.json()).catch(() => ({ success: false })), fetch(`/api/v2/items/suggest?q=${encodeURIComponent(q)}`).then(r => r.json()).catch(() => ({ success: false })) ]); + if (!overlay.classList.contains('visible') || !input.value.trim()) { + suggestions.style.display = 'none'; + return; + } const tagSuggestions = (tagRes.success && tagRes.suggestions) ? tagRes.suggestions.slice(0, 6) : []; const titleSuggestions = (titleRes.success && titleRes.suggestions) ? titleRes.suggestions.slice(0, 4) : []; if (tagSuggestions.length || titleSuggestions.length) { @@ -14797,18 +14866,42 @@ window.cancelAnimFrame = (function () { const savedStrict = localStorage.getItem('search_strict') === 'true'; if (strict) strict.checked = savedStrict; + const clearSearchState = () => { + if (debounceTimer) { + clearTimeout(debounceTimer); + debounceTimer = null; + } + input.value = ''; + suggestions.innerHTML = ''; + suggestions.style.display = 'none'; + highlightIdx = -1; + }; + const toggleSearch = (show) => { if (show) { + if (closeTimer) { + clearTimeout(closeTimer); + closeTimer = null; + } + clearSearchState(); overlay.style.display = 'flex'; // Force reflow overlay.offsetHeight; overlay.classList.add('visible'); - if (window.innerWidth > 768) input.focus(); + input.focus(); } else { + if (closeTimer) { + clearTimeout(closeTimer); + } overlay.classList.remove('visible'); suggestions.style.display = 'none'; - setTimeout(() => { + if (document.activeElement === input) { + input.blur(); + } + closeTimer = setTimeout(() => { overlay.style.display = 'none'; + clearSearchState(); + closeTimer = null; }, 200); } }; @@ -14883,7 +14976,7 @@ window.cancelAnimFrame = (function () { toggleSearch(false); } // "k" to open - if (e.key === 'k' && e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA' && !overlay.classList.contains('visible')) { + if ((e.key === 'k' || e.key === 'K') && e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA' && !e.target.isContentEditable && !overlay.classList.contains('visible')) { e.preventDefault(); toggleSearch(true); } @@ -14935,8 +15028,8 @@ window.cancelAnimFrame = (function () { const titleItems = suggestions.querySelectorAll('.tag-suggestion-item[data-type="title"]'); suggestions.style.display = 'none'; if (tagItems.length === 0 && titleItems.length > 0) { - toggleSearch(false); const q = input.value.trim(); + toggleSearch(false); const target = `/tag/title:${encodeURIComponent(q)}/`; if (typeof loadPageAjax === 'function') { loadPageAjax(target, true); @@ -18839,11 +18932,14 @@ class NotificationSystem { const ghost = document.createElement('a'); ghost.href = `${linkBase}${itemKey}`; ghost.className = 'thumb lazy-thumb filtered-upload-ghost loaded'; + ghost.dataset.itemId = String(data.id); ghost.dataset.file = data.dest; ghost.dataset.mime = data.mime; ghost.dataset.user = data.display_name || data.username; ghost.dataset.mode = mode; - ghost.style.setProperty('--thumb-bg', `url('/t/${data.id}.webp')`); + const ghostBg = typeof window.applyThumbCacheBust === 'function' ? window.applyThumbCacheBust(`/t/${data.id}.webp`) : `/t/${data.id}.webp`; + ghost.dataset.bg = ghostBg; + ghost.style.setProperty('--thumb-bg', `url('${ghostBg}')`); ghost.style.opacity = '0'; ghost.style.transform = 'scale(0.9)'; ghost.innerHTML = ` @@ -18881,19 +18977,37 @@ class NotificationSystem { const nsflId = window.f0ckSession?.nsfl_tag_id || 3; const mode = data.tag_id ? (data.tag_id === 1 ? 'sfw' : (data.tag_id === 2 ? 'nsfw' : (data.tag_id == nsflId ? 'nsfl' : 'null'))) : 'null'; + let baseBg = `/t/${data.id}.webp`; + const blurNsfw = localStorage.getItem('blurNsfw') === 'true'; + const blurNsfl = localStorage.getItem('blurNsfl') === 'true'; + const blurSfw = localStorage.getItem('blurSfw') === 'true'; + const blurUntagged = localStorage.getItem('blurUntagged') === 'true'; + let shouldBlurThis = false; + if (mode === 'nsfw') shouldBlurThis = blurNsfw; + else if (mode === 'nsfl') shouldBlurThis = blurNsfl; + else if (mode === 'sfw') shouldBlurThis = blurSfw; + else if (mode === 'null' || !mode) shouldBlurThis = blurUntagged; + if (shouldBlurThis) { + baseBg = baseBg.replace('.webp', '_blur.webp'); + } + const finalBg = typeof window.applyThumbCacheBust === 'function' ? window.applyThumbCacheBust(baseBg) : baseBg; + const thumb = document.createElement('a'); thumb.href = `${linkBase}${itemKey}`; - thumb.className = 'thumb lazy-thumb'; + thumb.className = 'thumb loaded'; + thumb.dataset.itemId = String(data.id); thumb.dataset.file = data.dest; thumb.dataset.mime = data.mime; thumb.dataset.user = data.display_name || data.username; thumb.dataset.ext = data.mime.split('/')[1].replace('youtube', 'yt').toUpperCase(); thumb.dataset.mode = mode; - thumb.dataset.bg = `/t/${data.id}.webp`; + thumb.dataset.bg = finalBg; thumb.dataset.size = '1'; // New items start with no contributions → tier 1 - thumb.style.setProperty('--thumb-bg', `url('/t/${data.id}.webp')`); - thumb.classList.add('loaded'); + thumb.style.setProperty('--thumb-bg', `url('${finalBg}')`); thumb.style.transform = 'scale(0.9)'; + if (window.loadedThumbs) window.loadedThumbs.add(finalBg); + const img = new Image(); + img.src = finalBg; // Build thumb-indicators (matches server-rendered template structure) let indicatorsHtml = ''; diff --git a/public/s/js/scroller.js b/public/s/js/scroller.js index 07a3306..15cb2f5 100644 --- a/public/s/js/scroller.js +++ b/public/s/js/scroller.js @@ -36,6 +36,8 @@ const filterResetBtn = document.getElementById('filter-reset-btn'); const filterApplyBtn = document.getElementById('filter-apply-btn'); const filterSummary = document.getElementById('filter-active-summary'); + let filterSummaryText = document.getElementById('filter-active-summary-text'); + let filterClearBtn = document.getElementById('filter-active-clear'); const tagInput = document.getElementById('filter-tag-input'); const tagClear = document.getElementById('filter-tag-clear'); const tagSuggestEl = document.getElementById('tag-suggestions'); @@ -3182,10 +3184,59 @@ filterResetBtn.addEventListener('click', () => { pending = { mode: defaultMode, mime: scrollerSingleMime || '', order: 'random', tags: [] }; syncPanelUI(); tagInput.value = ''; tagClear.classList.remove('show'); tagSuggestEl.innerHTML = ''; lastSugg = []; renderActiveTags(); }); + function clearActiveFilters() { + applied = { mode: defaultMode, mime: scrollerSingleMime || '', order: 'random', tags: [], externalUrl: null }; + pending = { ...applied, tags: [] }; + if (externalUrlInput) externalUrlInput.value = ''; + if (galleryOpen) toggleGallery(); + if (chanGalleryBtn) chanGalleryBtn.style.display = 'none'; + if (tagInput) tagInput.value = ''; + if (tagClear) tagClear.classList.remove('show'); + if (tagSuggestEl) tagSuggestEl.innerHTML = ''; + lastSugg = []; + renderActiveTags(); + syncPanelUI(); + renderPresets(); + reloadFeed(); + } + + function setupFilterSummary() { + if (!filterSummary) return; + if (!filterSummaryText || !filterClearBtn) { + if (!filterSummary.querySelector('#filter-active-summary-text')) { + const txt = document.createElement('span'); + txt.id = 'filter-active-summary-text'; + const btn = document.createElement('button'); + btn.id = 'filter-active-clear'; + btn.type = 'button'; + btn.title = (window.f0ckI18n && window.f0ckI18n.clear_filter) || 'Clear filter'; + btn.setAttribute('aria-label', (window.f0ckI18n && window.f0ckI18n.clear_filter) || 'Clear filter'); + btn.innerHTML = ''; + filterSummary.innerHTML = ''; + filterSummary.appendChild(txt); + filterSummary.appendChild(btn); + } + filterSummaryText = document.getElementById('filter-active-summary-text'); + filterClearBtn = document.getElementById('filter-active-clear'); + } + if (filterClearBtn && !filterClearBtn._hasListener) { + filterClearBtn._hasListener = true; + filterClearBtn.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + clearActiveFilters(); + }); + } + } + setupFilterSummary(); + function updateFilterSummary() { + if (!filterSummary) return; + setupFilterSummary(); const is4chan = !!applied.externalUrl; - const isDef = applied.mode === defaultMode && applied.mime === '' && applied.order === 'random' && applied.tags.length === 0 && !is4chan; - filterOpenBtn.classList.toggle('has-filter', !isDef); filterSummary.classList.toggle('show', !isDef); + const isDef = applied.mode === defaultMode && applied.mime === (scrollerSingleMime || '') && applied.order === 'random' && applied.tags.length === 0 && !is4chan; + if (filterOpenBtn) filterOpenBtn.classList.toggle('has-filter', !isDef); + filterSummary.classList.toggle('show', !isDef); if (!isDef) { // Check if current filters exactly match a saved preset const tagsKey = t => [...t].map(s => s.toLowerCase()).sort().join(','); @@ -3195,8 +3246,9 @@ p.order === applied.order && tagsKey(p.tags) === tagsKey(applied.tags) ); + let summaryText = ''; if (matchedPreset && !is4chan) { - filterSummary.textContent = matchedPreset.name; + summaryText = matchedPreset.name; } else { const parts = []; if (is4chan) parts.push('4chan'); @@ -3204,7 +3256,12 @@ if (applied.mime) parts.push(applied.mime); if (applied.order !== 'random') parts.push(applied.order); parts.push(...applied.tags); - filterSummary.textContent = parts.join(' · '); + summaryText = parts.join(' · '); + } + if (filterSummaryText) { + filterSummaryText.textContent = summaryText; + } else { + filterSummary.textContent = summaryText; } } } diff --git a/public/s/js/upload.js b/public/s/js/upload.js index ebe700f..9b00122 100644 --- a/public/s/js/upload.js +++ b/public/s/js/upload.js @@ -3004,6 +3004,22 @@ window.initUploadForm = (selector) => { // URL uploads: redirect or stay based on user preference (pending/async jobs skip redirect) if (!lastData?.pending && !lastData?.manual_approval) { + if (lastData?.itemid && window.NotificationSystemInstance && typeof window.NotificationSystemInstance.handleNewItem === 'function') { + window.NotificationSystemInstance.handleNewItem({ + id: lastData.itemid, + dest: lastData.dest, + mime: lastData.mime, + username: lastData.username || window.f0ckSession?.user || '', + display_name: lastData.display_name || window.f0ckSession?.display_name || null, + tag_id: lastData.tag_id ?? 0, + is_oc: !!lastData.is_oc, + slug: lastData.slug, + visibility: lastData.visibility || 0, + is_album: !!lastData.is_album, + album_count: lastData.album_count || 0 + }); + } + const shouldRedirectToItem = redirectCheckbox ? redirectCheckbox.checked : (localStorage.getItem('upload_redirect_to_item') !== 'false'); diff --git a/public/s/js/v0ck.js b/public/s/js/v0ck.js index d0229c6..1cd8e93 100644 --- a/public/s/js/v0ck.js +++ b/public/s/js/v0ck.js @@ -891,6 +891,7 @@ class v0ck { // Close menu/panel when clicking outside document.addEventListener('click', (e) => { + if (!e.isTrusted) return; const isFlashYankUI = e.target.closest('#flash-yank-ui'); const isInsidePlayer = player.contains(e.target); diff --git a/src/inc/locales/de.json b/src/inc/locales/de.json index e6b7224..35f96c1 100644 --- a/src/inc/locales/de.json +++ b/src/inc/locales/de.json @@ -740,6 +740,7 @@ "settings": "Einstellungen", "filters": "Filter", "volume": "Lautstärke", + "clear_filter": "Filter löschen", "reset_all": "Alles zurücksetzen", "rating": "Bewertung", "all": "Alle", diff --git a/src/inc/locales/en.json b/src/inc/locales/en.json index 3e620b6..aca0f68 100644 --- a/src/inc/locales/en.json +++ b/src/inc/locales/en.json @@ -740,6 +740,7 @@ "settings": "Settings", "filters": "Filters", "volume": "Volume", + "clear_filter": "Clear filter", "reset_all": "Reset all", "rating": "Rating", "all": "All", diff --git a/src/inc/locales/nl.json b/src/inc/locales/nl.json index 9479350..0fd238b 100644 --- a/src/inc/locales/nl.json +++ b/src/inc/locales/nl.json @@ -736,6 +736,7 @@ "settings": "Instellingen", "filters": "Filters", "volume": "Volume", + "clear_filter": "Filter wissen", "reset_all": "Alles resetten", "rating": "Beoordeling", "all": "Alles", diff --git a/src/inc/locales/zange.json b/src/inc/locales/zange.json index 33ca2df..42c19b0 100644 --- a/src/inc/locales/zange.json +++ b/src/inc/locales/zange.json @@ -736,6 +736,7 @@ "settings": "Einstellungen", "filters": "Filter", "volume": "Lautstärke", + "clear_filter": "Filter entfernen", "reset_all": "Alles zurücksetzen", "rating": "Bewertung", "all": "Alle", diff --git a/src/inc/routes/apiv2/tags.mjs b/src/inc/routes/apiv2/tags.mjs index dd91052..e2f147b 100644 --- a/src/inc/routes/apiv2/tags.mjs +++ b/src/inc/routes/apiv2/tags.mjs @@ -323,6 +323,9 @@ export default router => { } } + const nsflTagRow = await db`SELECT id FROM tags WHERE normalized = 'nsfl' LIMIT 1`; + const nsflId = nsflTagRow.length > 0 ? nsflTagRow[0].id : (cfg.nsfl_tag_id || 11517); + try { let tagid = (await db` select id @@ -331,13 +334,21 @@ export default router => { `)?.[0]?.id; if (!tagid) { // create new tag - tagid = (await db` - insert into "tags" ${db({ - tag: tagname - }) - } - returning id - `)[0].id; + try { + tagid = (await db` + insert into "tags" ${db({ + tag: tagname + }) + } + returning id + `)[0].id; + } catch (e) { + tagid = (await db` + select id + from "tags" + where normalized = slugify(${tagname}) + `)?.[0]?.id; + } } if (albumItemId) { @@ -383,6 +394,7 @@ export default router => { `; } } catch (err) { + console.error('[ADD_TAG_ERROR]', err); const isDuplicate = err.code === '23505' || err.constraint?.includes('tags_assign'); return res.json({ success: false, diff --git a/src/index.mjs b/src/index.mjs index 83db888..19b28c7 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -1939,7 +1939,7 @@ process.on('uncaughtException', err => { upload_limit: cfg.main.upload_limit ?? 69, meme_creator: !!cfg.websrv.meme_creator, custom_favicon: cfg.websrv.custom_favicon || "", - custom_brand_image: Array.isArray(cfg.websrv.custom_brand_image) ? cfg.websrv.custom_brand_image[0] : (cfg.websrv.custom_brand_image || ""), + get custom_brand_image() { return getBrandImageUrl(); }, custom_navbar_brand_text: cfg.websrv.custom_navbar_brand_text || "", default_font: cfg.websrv.default_font || "", site_description: cfg.websrv.description || "The webs dumpster", @@ -1999,7 +1999,16 @@ process.on('uncaughtException', err => { return JSON.stringify(cfg.websrv.koepfe || []); } }, - custom_brand_images_json: JSON.stringify(getBrandImageUrl() ? [getBrandImageUrl()] : []), + get custom_brand_images_json() { + const current = getBrandImageUrl(); + if (current) { + if (Array.isArray(cfg.websrv.custom_brand_image) && cfg.websrv.custom_brand_image.length > 1 && cfg.websrv.custom_brand_image.includes(current)) { + return JSON.stringify(cfg.websrv.custom_brand_image); + } + return JSON.stringify([current]); + } + return JSON.stringify([]); + }, allowed_comment_images: cfg.websrv.allowed_comment_images || [], allowed_comment_images_json: JSON.stringify(cfg.websrv.allowed_comment_images || []), paths_images: cfg.websrv.paths?.images || '/b', diff --git a/views/scroller.html b/views/scroller.html index cc4c003..a75ef7f 100644 --- a/views/scroller.html +++ b/views/scroller.html @@ -85,15 +85,35 @@ @media (max-width: 600px) { .topbar-left, .topbar-right { gap: 4px; } .topbar-icon-btn { width: 32px; height: 32px; font-size: .78rem; } + #filter-active-summary { max-width: 140px; font-size: .65rem; padding: 4px 6px 4px 9px; gap: 4px; } + #filter-active-clear { width: 16px; height: 16px; font-size: .65rem; } } #filter-active-summary { display: none; background: rgba(0,0,0,.6); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border: 1px solid var(--accent); border-radius: 50px; - padding: 6px 12px; font-size: .7rem; font-weight: 700; letter-spacing: .03em; - color: var(--accent); max-width: 190px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + padding: 5px 8px 5px 12px; font-size: .7rem; font-weight: 700; letter-spacing: .03em; + color: var(--accent); max-width: 220px; + align-items: center; gap: 6px; + } + #filter-active-summary.show { display: inline-flex; } + #filter-active-summary-text { + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; + } + #filter-active-clear { + background: none; border: none; padding: 0; margin: 0; + color: var(--accent); cursor: pointer; + display: inline-flex; align-items: center; justify-content: center; + width: 18px; height: 18px; border-radius: 50%; + font-size: .72rem; flex-shrink: 0; opacity: .8; + transition: opacity .15s, background .15s, color .15s, transform .12s; + } + #filter-active-clear:hover { + opacity: 1; background: rgba(255,255,255,.18); color: #fff; transform: scale(1.15); + } + #filter-active-clear:active { + transform: scale(0.92); } - #filter-active-summary.show { display: block; } /* ── VOLUME POPUP ─────────────────────────────── */ #volume-popup { @@ -1001,7 +1021,8 @@ no_comments: "{{ t('scroller.no_comments') }}", write_comment: "{{ t('scroller.write_comment') }}", login_required: "{{ t('scroller.login_required') }}", - login_to_comment: "{{ t('scroller.login_to_comment') }}" + login_to_comment: "{{ t('scroller.login_to_comment') }}", + clear_filter: "{{ t('scroller.clear_filter') || 'Clear filter' }}" }; @if(typeof session !== 'undefined' && session) window.scrollerUsername = "{{ session.user || '' }}"; @@ -1059,7 +1080,10 @@
-
+
+ + +
diff --git a/views/snippets/navbar.html b/views/snippets/navbar.html index 4fcba26..cdc058d 100644 --- a/views/snippets/navbar.html +++ b/views/snippets/navbar.html @@ -5,7 +5,7 @@ @if(custom_brand_image) - + @else {{ custom_navbar_brand_text || domain }} @endif