From 990f4635348bcfd8c112a4d807a80c0d517c3dee Mon Sep 17 00:00:00 2001 From: Kibi Kelburton Date: Mon, 21 Sep 2026 00:02:48 +0200 Subject: [PATCH] hip --- public/s/css/f0ckm.css | 47 +++ public/s/js/admin.js | 50 ++- public/s/js/f0ckm.js | 485 +++++++++++++++++++++---- public/s/js/user.js | 50 ++- src/inc/routeinc/f0cklib.mjs | 2 +- src/inc/routes/chan.mjs | 37 +- src/inc/routes/external.mjs | 55 ++- src/inc/routes/notifications.mjs | 12 +- src/inc/routes/toptags.mjs | 2 +- views/admin.html | 6 + views/chan/thread.html | 44 ++- views/item-partial-legacy.html | 22 +- views/item-partial-modern.html | 18 +- views/snippets/footer.html | 2 +- views/snippets/header.html | 2 +- views/snippets/navbar.html | 4 +- views/snippets/notifications-list.html | 20 +- 17 files changed, 690 insertions(+), 168 deletions(-) diff --git a/public/s/css/f0ckm.css b/public/s/css/f0ckm.css index 80026d9..cff124b 100644 --- a/public/s/css/f0ckm.css +++ b/public/s/css/f0ckm.css @@ -7331,6 +7331,53 @@ body.layout-modern.sidebar-right-hidden .pagination-container-fluid { text-decoration: underline; } +.chan-rehost-action-btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 4px 16px; + min-width: 100px; + box-sizing: border-box; + white-space: nowrap; + flex-shrink: 0; + border-radius: 6px; + font-size: 0.8rem; + font-weight: 600; + font-family: inherit; + background: rgba(74, 222, 128, 0.15); + color: #4ade80; + border: 1px solid rgba(74, 222, 128, 0.3); + cursor: pointer; + text-decoration: none; + transition: all 0.15s ease; +} + +.chan-rehost-action-btn:hover { + background: rgba(74, 222, 128, 0.28); + border-color: rgba(74, 222, 128, 0.55); + color: #4ade80; + text-decoration: none; +} + +.chan-rehost-action-btn:active { + transform: scale(0.98); +} + +.chan-rehost-action-btn.rehosted { + background: rgba(74, 222, 128, 0.2); + border-color: rgba(74, 222, 128, 0.4); + color: #4ade80; + min-width: auto; + padding: 4px 12px; +} + +.chan-rehost-action-btn.loading { + opacity: 0.75; + cursor: wait; + pointer-events: none; +} + .item_title, .item-title, .item-itle { diff --git a/public/s/js/admin.js b/public/s/js/admin.js index 391bcba..414b670 100644 --- a/public/s/js/admin.js +++ b/public/s/js/admin.js @@ -293,17 +293,34 @@ return; } const favoBtns = document.querySelectorAll("#a_favo"); + if (!favoBtns.length) return; const firstFavoBtn = favoBtns[0]; const chanRehostBtn = document.querySelector('#chan-item-rehost-btn'); const isChan = firstFavoBtn?.dataset?.isChan === 'true' || !!chanRehostBtn; const chanUrl = firstFavoBtn?.dataset?.chanUrl || chanRehostBtn?.dataset?.url; let localId = firstFavoBtn?.dataset?.localId; + const wasAlreadyFav = favoBtns[0].classList.contains('fa-solid') && !favoBtns[0].classList.contains('fa-spinner'); + + const setFavoUi = (isFav) => { + favoBtns.forEach(btn => { + btn.classList.remove('fa-spinner', 'fa-spin'); + btn.classList.add('iconset', 'fa-heart'); + btn.classList.toggle('fa-solid', isFav); + btn.classList.toggle('fa-regular', !isFav); + }); + }; + + const setFavoLoading = () => { + favoBtns.forEach(btn => { + btn.classList.remove('fa-heart'); + btn.classList.add('iconset', 'fa-solid', 'fa-spinner', 'fa-spin'); + }); + }; + // If viewing un-rehosted 4chan post, auto-rehost first if (isChan && !localId && chanUrl) { - favoBtns.forEach(btn => { - btn.className = 'iconset fa-solid fa-spinner fa-spin'; - }); + setFavoLoading(); try { const csrfToken = window.f0ckSession?.csrf_token || ''; const rehostResp = await fetch('/api/v2/scroller/rehost', { @@ -331,6 +348,15 @@ const infoEl = document.querySelector("#a_info"); if (infoEl) infoEl.dataset.itemId = localId; + const timeEl = document.querySelector('time.timeago'); + if (timeEl) { + const nowIso = new Date().toISOString(); + timeEl.dataset.iso = nowIso; + const fullDate = typeof window.f0ckFormatDateFull === 'function' ? window.f0ckFormatDateFull(nowIso) : new Date().toLocaleString(); + timeEl.setAttribute('tooltip', fullDate); + timeEl.textContent = (window.f0ckTimeAgo ? window.f0ckTimeAgo(nowIso) : (window.f0ckI18n?.timeago_just_now || 'just now')); + } + if (chanRehostBtn) { chanRehostBtn.classList.add('rehosted'); chanRehostBtn.outerHTML = ` @@ -345,9 +371,7 @@ } } catch (err) { console.error('[CHAN-FAV-ADMIN] Auto-rehost error:', err); - favoBtns.forEach(btn => { - btn.className = 'iconset fa-regular fa-heart'; - }); + setFavoUi(wasAlreadyFav); if (typeof window.flashMessage === 'function') { window.flashMessage('Failed to favorite post', 3000, 'error'); } @@ -356,11 +380,12 @@ } const ctx = getContext(); - if (!ctx) return; + if (!ctx) { + setFavoUi(wasAlreadyFav); + return; + } const postid = localId ? Number(localId) : ctx.postid; - const wasAlreadyFav = favoBtns.length > 0 && favoBtns[0].classList.contains('fa-solid'); - try { const res = await post('/api/v2/togglefav', { postid: postid, @@ -373,10 +398,7 @@ // Authoritative server state takes priority; fallback to inverting previous UI state const isNowFav = (res.favorited !== undefined) ? !!res.favorited : !wasAlreadyFav; - favoBtns.forEach(favoBtn => { - favoBtn.classList.toggle('fa-solid', isNowFav); - favoBtn.classList.toggle('fa-regular', !isNowFav); - }); + setFavoUi(isNowFav); const favcontainer = document.querySelector('#favs'); if (favcontainer) { @@ -446,12 +468,14 @@ window.flashMessage((window.f0ckI18n && (isNowFav ? window.f0ckI18n.fav_added : window.f0ckI18n.fav_removed)) || (isNowFav ? 'ADDED TO FAVORITES' : 'REMOVED FROM FAVORITES')); if (navigator.vibrate) navigator.vibrate(50); } else { + setFavoUi(wasAlreadyFav); const errMsg = (res && (res.msg || res.error)) || 'Anonymous favoriting is disabled.'; if (typeof window.flashMessage === 'function') { window.flashMessage(errMsg, 3000, 'error'); } } } catch (err) { + setFavoUi(wasAlreadyFav); if (typeof window.flashMessage === 'function') { window.flashMessage('Failed to update favorite.', 3000, 'error'); } diff --git a/public/s/js/f0ckm.js b/public/s/js/f0ckm.js index f2bd1ca..48675e9 100644 --- a/public/s/js/f0ckm.js +++ b/public/s/js/f0ckm.js @@ -3476,7 +3476,15 @@ window.cancelAnimFrame = (function () { } const locEl = document.querySelector('.location'); if (locEl && !locEl.textContent.includes('#')) { - locEl.textContent = locEl.textContent.replace(/#.*$/, '') + '#' + initialHash; + const lastLink = locEl.querySelector('a:last-of-type'); + if (lastLink) { + lastLink.textContent = lastLink.textContent.replace(/#.*$/, '') + '#' + initialHash; + if (lastLink.href) { + lastLink.href = lastLink.href.replace(/#.*$/, '') + '#' + initialHash; + } + } else { + locEl.textContent = locEl.textContent.replace(/#.*$/, '') + '#' + initialHash; + } } const foundIdx = albumData.findIndex(item => String(item.slug || '') === initialHash || @@ -3600,7 +3608,15 @@ window.cancelAnimFrame = (function () { } const locEl = document.querySelector('.location'); if (locEl && subKey) { - locEl.textContent = locEl.textContent.replace(/#.*$/, '') + '#' + subKey; + const lastLink = locEl.querySelector('a:last-of-type'); + if (lastLink) { + lastLink.textContent = lastLink.textContent.replace(/#.*$/, '') + '#' + subKey; + if (lastLink.href) { + lastLink.href = lastLink.href.replace(/#.*$/, '') + '#' + subKey; + } + } else { + locEl.textContent = locEl.textContent.replace(/#.*$/, '') + '#' + subKey; + } } // Pause any playing audio or video @@ -11023,6 +11039,50 @@ window.cancelAnimFrame = (function () { if (_navEl && window.ResizeObserver) { new ResizeObserver(window.syncNavbarHeight).observe(_navEl); } + const _existingNavbarLogo = document.getElementById('navbar-logo'); + if (_existingNavbarLogo && !_existingNavbarLogo.complete) { + _existingNavbarLogo.addEventListener('load', window.syncNavbarHeight); + } + + window.updateBrandImage = (url) => { + const logos = document.querySelectorAll('#navbar-logo'); + if (url) { + window.f0ckBrandImages = [url]; + if (logos.length > 0) { + logos.forEach(el => { + el.src = url; + el.style.display = ''; + el.onload = () => { if (window.syncNavbarHeight) window.syncNavbarHeight(); }; + }); + } else { + document.querySelectorAll('a.navbar-brand').forEach(brandLink => { + brandLink.querySelectorAll('.f0ck').forEach(s => s.style.display = 'none'); + const img = document.createElement('img'); + img.id = 'navbar-logo'; + img.src = url; + img.alt = window.f0ckDomain || document.title; + img.style.cssText = 'max-height:40px;vertical-align:middle;max-width:180px;width:auto;'; + img.onload = () => { if (window.syncNavbarHeight) window.syncNavbarHeight(); }; + brandLink.prepend(img); + }); + } + } else { + window.f0ckBrandImages = []; + logos.forEach(el => el.remove()); + document.querySelectorAll('a.navbar-brand').forEach(brandLink => { + const f0ckSpan = brandLink.querySelector('.f0ck'); + if (f0ckSpan) { + f0ckSpan.style.display = ''; + } else { + const span = document.createElement('span'); + span.className = 'f0ck'; + span.textContent = window.f0ckCustomBrandText || window.f0ckDomain || ''; + brandLink.appendChild(span); + } + }); + } + if (window.syncNavbarHeight) window.syncNavbarHeight(); + }; // Sidebar state init — apply persisted hidden/visible state on load window.initSidebarRightToggle = () => { @@ -11152,7 +11212,10 @@ window.cancelAnimFrame = (function () { const itemIdMatch = url.match(/\/([a-zA-Z0-9_-]{11}|\d+)(?:\?.*|#.*)?$/); if (itemIdMatch) { const itemId = itemIdMatch[1]; - document.querySelectorAll(`a.thumb.has-notif[href$="/${itemId}"], a.thumb.has-notif[data-item-id="${itemId}"]`).forEach(el => { + if (window.NotificationSystemInstance) { + window.NotificationSystemInstance.markItemRead(itemId); + } + document.querySelectorAll(`a.thumb.has-notif[href$="/${itemId}"], a.thumb.has-notif[data-item-id="${itemId}"], a.lazy-thumb.has-notif[data-item-id="${itemId}"]`).forEach(el => { el.classList.remove('has-notif'); }); } @@ -11265,11 +11328,12 @@ window.cancelAnimFrame = (function () { // Restore scroll position requestAnimationFrame(() => { + window.scrollTo(0, 0); + document.documentElement.scrollTop = 0; + document.body.scrollTop = 0; const ic = document.querySelector('.index-container'); if (ic && cache.scroll !== undefined) { ic.scrollTo(0, cache.scroll); - } else { - window.scrollTo(0, cache.scroll || 0); } }); @@ -11283,8 +11347,9 @@ window.cancelAnimFrame = (function () { window.updateVisitIndicators(); window.initLazyLoading(); if (window.initSidebarRightToggle) window.initSidebarRightToggle(); - if (window.syncNavbarHeight) window.syncNavbarHeight(); - window.NotificationSystemInstance?.pollDebounced?.(); + if (window.NotificationSystemInstance) { + window.NotificationSystemInstance.poll(); + } window._onaraCurrentGridUrl = urlObj.pathname + urlObj.search; isNavigating = false; return; // SKIP FETCH @@ -11342,6 +11407,11 @@ window.cancelAnimFrame = (function () { indexWrapper.className = 'index-layout-wrapper'; indexWrapper.innerHTML = '
'; + // Reset window scroll to 0 before appending indexWrapper so :has(.index-container) doesn't lock a scrolled offset + window.scrollTo(0, 0); + document.documentElement.scrollTop = 0; + document.body.scrollTop = 0; + main.innerHTML = ''; main.appendChild(indexWrapper); @@ -11764,7 +11834,10 @@ window.cancelAnimFrame = (function () { // SCROLL TO TOP ON EVERY NEW NAVIGATION if (!options.skipScroll) { window.scrollTo(0, 0); + document.documentElement.scrollTop = 0; + document.body.scrollTop = 0; } + if (window.syncNavbarHeight) window.syncNavbarHeight(); // Re-init activity feed if applicable if (document.getElementById('activity-container')) { @@ -11891,15 +11964,20 @@ window.cancelAnimFrame = (function () { if (options.skipPush && history.state && history.state.scroll !== undefined) { // Back/Forward Navigation: Restore saved scroll position requestAnimationFrame(() => { + window.scrollTo(0, 0); + document.documentElement.scrollTop = 0; + document.body.scrollTop = 0; const ic = document.querySelector('.index-container'); if (ic) ic.scrollTo(0, history.state.scroll); else window.scrollTo(0, history.state.scroll); }); } else if (replace) { // New Navigation: Reset to top + window.scrollTo(0, 0); + document.documentElement.scrollTop = 0; + document.body.scrollTop = 0; const ic = document.querySelector('.index-container'); if (ic) ic.scrollTo(0, 0); - else window.scrollTo(0, 0); } // Trigger fade-in @@ -11981,6 +12059,7 @@ window.cancelAnimFrame = (function () { // Notify extensions and re-init systems (e.g. CommentSystem) document.dispatchEvent(new Event('f0ck:contentLoaded')); if (window.initSidebarRightToggle) window.initSidebarRightToggle(); + if (window.syncNavbarHeight) window.syncNavbarHeight(); // Sync has-notif highlights after grid loads — handles PWA where visibilitychange doesn't fire window.NotificationSystemInstance?.pollDebounced?.(); window._onaraCurrentGridUrl = urlObj.pathname + urlObj.search; @@ -12237,6 +12316,14 @@ window.cancelAnimFrame = (function () { return; } const itemid = keySegments.pop(); + if (itemid) { + if (window.NotificationSystemInstance) { + window.NotificationSystemInstance.markItemRead(itemid); + } + document.querySelectorAll(`a.thumb.has-notif[data-item-id="${itemid}"], a.thumb.has-notif[href$="/${itemid}"], a.lazy-thumb.has-notif[data-item-id="${itemid}"]`).forEach(el => { + el.classList.remove('has-notif'); + }); + } // Extract context from Target URL first let tag = null, user = null, isFavs = false, mime = null, hall = null, userHall = null, userHallOwner = null, tagger = null; @@ -12427,6 +12514,11 @@ window.cancelAnimFrame = (function () { const _ic = main.querySelector('.index-container'); const _ntc = _iw || _ic; if (_ntc && _isIndexPage) { + if (itemid) { + _ntc.querySelectorAll(`a.thumb.has-notif[data-item-id="${itemid}"], a.thumb.has-notif[href$="/${itemid}"], a.lazy-thumb.has-notif[data-item-id="${itemid}"]`).forEach(el => { + el.classList.remove('has-notif'); + }); + } const _ck = window.location.pathname + window.location.search; const _pw = document.querySelector('.pagination-wrapper'); gridCacheMap.set(_ck, { node: _ntc, scroll: currentScroll, pagination: _pw ? _pw.innerHTML : '' }); @@ -12707,6 +12799,11 @@ window.cancelAnimFrame = (function () { const nodeToCache = indexWrapper || indexContainer; if (nodeToCache && isIndexPage) { + if (itemid) { + nodeToCache.querySelectorAll(`a.thumb.has-notif[data-item-id="${itemid}"], a.thumb.has-notif[href$="/${itemid}"], a.lazy-thumb.has-notif[data-item-id="${itemid}"]`).forEach(el => { + el.classList.remove('has-notif'); + }); + } const cacheKey = window.location.pathname + window.location.search; const paginationWrapper = document.querySelector('.pagination-wrapper'); gridCacheMap.set(cacheKey, { @@ -13021,6 +13118,13 @@ window.cancelAnimFrame = (function () { const thumbnail = target.closest('.posts > a:not(.notif-item)'); if (thumbnail && !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey) { e.preventDefault(); + if (thumbnail.classList.contains('has-notif')) { + thumbnail.classList.remove('has-notif'); + } + const clickedId = thumbnail.dataset.itemId || thumbnail.getAttribute('data-item-id') || thumbnail.getAttribute('href')?.match(/\/([a-zA-Z0-9_-]{11}|\d+)(?:[?#]|$)/)?.[1]; + if (clickedId && window.NotificationSystemInstance) { + window.NotificationSystemInstance.markItemRead(clickedId); + } if (isOnaraActive()) { const wasOpen = document.body.classList.contains('onara-modal-open'); document.querySelectorAll('.onara-active').forEach(el => el.classList.remove('onara-active')); @@ -13459,10 +13563,13 @@ window.cancelAnimFrame = (function () { const ratingGroup = modal.querySelector('#chan-rehost-rating-group'); let selectedRating = 'untagged'; + let userSelectedRating = false; + let userEditedTags = false; ratingGroup.addEventListener('click', (e) => { const pill = e.target.closest('.chan-rehost-pill'); if (!pill) return; + userSelectedRating = true; ratingGroup.querySelectorAll('.chan-rehost-pill').forEach(b => b.classList.remove('active')); pill.classList.add('active'); selectedRating = pill.dataset.rating; @@ -13471,19 +13578,23 @@ window.cancelAnimFrame = (function () { const tagsInput = modal.querySelector('#chan-rehost-tags-input'); const commentInput = modal.querySelector('#chan-rehost-comment-input'); - // Pre-fill current rating and tags if available + tagsInput.addEventListener('input', () => { + userEditedTags = true; + }); + + // Pre-fill current rating and tags if available (don't overwrite if user already interacted) fetch(`/api/v2/scroller/rehost/details/${itemId}`) .then(r => r.json()) .then(data => { if (data && data.success) { - if (data.rating) { + if (data.rating && data.rating !== 'untagged' && !userSelectedRating) { selectedRating = data.rating; ratingGroup.querySelectorAll('.chan-rehost-pill').forEach(b => { b.classList.toggle('active', b.dataset.rating === data.rating); }); } - if (data.tags && data.tags.length) { - tagsInput.value = data.tags.join(', '); + if (data.tags && data.tags.length && !userEditedTags && !tagsInput.value) { + tagsInput.value = data.tags.map(t => (typeof t === 'string' ? t : t.tag)).join(', '); } } }).catch(() => {}); @@ -13507,6 +13618,122 @@ window.cancelAnimFrame = (function () { if (e.target === modal) closeModal(); }; + const updateDomAfterSave = (resData) => { + try { + const finalRating = resData.rating || selectedRating || 'untagged'; + const finalTags = Array.isArray(resData.tags) ? resData.tags : []; + + // 1. Update #tags container + const tagsContainer = document.querySelector('#tags'); + if (tagsContainer) { + tagsContainer.dataset.itemId = itemId; + tagsContainer.dataset.canManage = 'true'; + + const ratingBadges = { + sfw: { cls: 'badge-success is-sfw', label: 'SFW' }, + nsfw: { cls: 'badge-warning is-nsfw', label: 'NSFW' }, + nsfl: { cls: 'badge-danger is-nsfl', label: 'NSFL' }, + untagged: { cls: 'badge-untagged is-untagged', label: '?' } + }; + const rMeta = ratingBadges[finalRating] || ratingBadges.untagged; + + let ratingEl = tagsContainer.querySelector('.rating-tag'); + if (!ratingEl) { + ratingEl = document.createElement('span'); + tagsContainer.prepend(ratingEl); + } + ratingEl.className = `badge ${rMeta.cls} rating-tag can-cycle`; + ratingEl.dataset.itemId = itemId; + ratingEl.dataset.rating = finalRating; + ratingEl.innerHTML = `${rMeta.label}`; + + // Remove previous tag badges and re-render new ones + tagsContainer.querySelectorAll('.tag-badge').forEach(el => el.remove()); + finalTags.forEach(t => { + const tagName = typeof t === 'string' ? t : (t.tag || ''); + const tagNorm = typeof t === 'string' ? tagName.toLowerCase().replace(/[^a-z0-9_-]/g, '') : (t.normalized || tagName); + const tagBadge = (typeof t === 'object' && t.badge) ? t.badge : 'badge-light'; + const tagId = (typeof t === 'object' && t.id) ? t.id : ''; + + const span = document.createElement('span'); + span.className = `badge ${tagBadge} tag-badge`; + if (tagId) span.dataset.tagId = tagId; + span.dataset.tag = tagName; + span.dataset.tagNormalized = tagNorm; + + const a = document.createElement('a'); + a.href = `/tag/${encodeURIComponent(tagNorm)}`; + a.className = 'tag-name'; + a.textContent = tagName; + span.appendChild(a); + + tagsContainer.appendChild(span); + }); + } + + // 2. Update .item-layout-container + const layoutContainer = document.querySelector('.item-layout-container'); + if (layoutContainer) { + layoutContainer.dataset.itemId = itemId; + } + + // 3. Update .media-object mode + const mediaObj = document.querySelector('.media-object'); + if (mediaObj) { + mediaObj.dataset.mode = finalRating; + } + + // 4. Update .location breadcrumb + const locEl = document.querySelector('.location'); + if (locEl && !locEl.querySelector('.is-rehosted')) { + const sep = document.createElement('span'); + sep.className = 'location-sep'; + sep.style.cssText = 'opacity: 0.6; margin: 0 4px;'; + sep.textContent = '→'; + + const a = document.createElement('a'); + a.href = `/${itemId}`; + a.className = 'location-link is-rehosted'; + a.title = `View rehosted post #${itemId} on f0ckm`; + a.style.cssText = 'color: #4ade80; font-weight: 700;'; + a.innerHTML = ` #${itemId}`; + + locEl.appendChild(sep); + locEl.appendChild(a); + } + + // 5. Update #a_favo + const favBtn = document.querySelector('#a_favo'); + if (favBtn) { + favBtn.dataset.itemId = itemId; + favBtn.dataset.localId = itemId; + } + + // 6. Update #chan-item-rehost-btn if present + const rehostBtn = document.querySelector('#chan-item-rehost-btn'); + if (rehostBtn) { + rehostBtn.outerHTML = ` + + + #${itemId} + + + + `; + } + + // 7. Invalidate caches + if (typeof window.invalidateItemCache === 'function') { + window.invalidateItemCache(itemId); + window.invalidateItemCache(window.location.pathname); + } + } catch (err) { + console.error('[REHOST-MODAL] DOM update error:', err); + } + }; + const saveDetails = async (andView = false) => { const saveBtn = modal.querySelector('.chan-rehost-modal-save'); const saveViewBtn = modal.querySelector('.chan-rehost-modal-save-view'); @@ -13535,12 +13762,13 @@ window.cancelAnimFrame = (function () { window.showToast('Details saved for #' + itemId + '!'); } closeModal(); + updateDomAfterSave(resData); if (typeof onSave === 'function') { onSave(resData); } if (andView) { if (typeof window.loadItemAjax === 'function') { - window.loadItemAjax('/' + itemId); + window.loadItemAjax('/' + itemId, true, { skipCache: true }); } else { window.location.href = '/' + itemId; } @@ -13602,9 +13830,19 @@ window.cancelAnimFrame = (function () { if (data.success && data.item_id) { btn.classList.remove('loading'); btn.classList.add('rehosted'); + + const timeEl = document.querySelector('time.timeago'); + if (timeEl) { + const nowIso = new Date().toISOString(); + timeEl.dataset.iso = nowIso; + const fullDate = typeof window.f0ckFormatDateFull === 'function' ? window.f0ckFormatDateFull(nowIso) : new Date().toLocaleString(); + timeEl.setAttribute('tooltip', fullDate); + timeEl.textContent = (window.f0ckTimeAgo ? window.f0ckTimeAgo(nowIso) : (window.f0ckI18n?.timeago_just_now || 'just now')); + } + btn.outerHTML = ` - + #${data.item_id} '; + + const postCard = document.getElementById('p' + postNo) || btn.closest('.chan-post-card'); + if (postCard) { + const timeEl = postCard.querySelector('.chan-post-time, .timeago'); + if (timeEl) { + const nowIso = new Date().toISOString(); + timeEl.dataset.iso = nowIso; + const fullDate = typeof window.f0ckFormatDateFull === 'function' ? window.f0ckFormatDateFull(nowIso) : new Date().toLocaleString(); + timeEl.setAttribute('tooltip', fullDate); + timeEl.setAttribute('title', fullDate); + timeEl.textContent = (window.f0ckTimeAgo ? window.f0ckTimeAgo(nowIso) : (window.f0ckI18n?.timeago_just_now || 'just now')); + } + } + if (typeof window.showToast === 'function') { window.showToast(data.repost ? 'Already on site — linked to existing post' : 'Rehosted successfully!'); } @@ -696,6 +710,7 @@ const editBtn = e.target.closest('.chan-rehost-edit-btn'); if (editBtn) { e.preventDefault(); + e.stopImmediatePropagation(); const itemId = editBtn.dataset.itemId; if (itemId && typeof window.openChanRehostModal === 'function') { window.openChanRehostModal({ itemId: Number(itemId), isPlayerView: false }); @@ -762,14 +777,25 @@ } } // Add thumb badge if missing - const postCard = favBtn.closest('.chan-post'); - const thumbLink = postCard?.querySelector('.chan-thumb-link'); - if (thumbLink && !thumbLink.querySelector('.chan-thumb-rehost-badge')) { - const badge = document.createElement('span'); - badge.className = 'chan-thumb-rehost-badge'; - badge.title = 'Rehosted on f0ckm as Post #' + localId; - badge.innerHTML = ' #' + localId; - thumbLink.appendChild(badge); + const postCard = document.getElementById('p' + postNo) || favBtn.closest('.chan-post-card'); + if (postCard) { + const thumbLink = postCard.querySelector('.chan-thumb-link'); + if (thumbLink && !thumbLink.querySelector('.chan-thumb-rehost-badge')) { + const badge = document.createElement('span'); + badge.className = 'chan-thumb-rehost-badge'; + badge.title = 'Rehosted on f0ckm as Post #' + localId; + badge.innerHTML = ' #' + localId; + thumbLink.appendChild(badge); + } + const timeEl = postCard.querySelector('.chan-post-time, .timeago'); + if (timeEl) { + const nowIso = new Date().toISOString(); + timeEl.dataset.iso = nowIso; + const fullDate = typeof window.f0ckFormatDateFull === 'function' ? window.f0ckFormatDateFull(nowIso) : new Date().toLocaleString(); + timeEl.setAttribute('tooltip', fullDate); + timeEl.setAttribute('title', fullDate); + timeEl.textContent = (window.f0ckTimeAgo ? window.f0ckTimeAgo(nowIso) : (window.f0ckI18n?.timeago_just_now || 'just now')); + } } } else { throw new Error(rehostData.msg || 'Rehost failed'); diff --git a/views/item-partial-legacy.html b/views/item-partial-legacy.html index faf01ce..97d5111 100644 --- a/views/item-partial-legacy.html +++ b/views/item-partial-legacy.html @@ -1,12 +1,12 @@ {{-- LEGACY LAYOUT — 2-column (main + fixed right sidebar) --}} {{-- Used by: members with use_new_layout = false --}} -
+
{{-- MAIN CONTENT: media + navigation + metadata + comments --}}
-
@if(item.is_chan)/4/{{ item.external_board }}/{{ item.external_tid }}/{{ item.external_id }}@if(item.local_id)→ #{{ item.local_id }}@endif@else{{ link.mainDisplay || link.main }}{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}{{ link.suffix }}{{ item.requested_subf0ck_slug ? ('#' + item.requested_subf0ck_slug) : '' }}@endif
+
@if(item.is_chan)/4/{{ item.external_board }}/{{ item.external_tid }}/{{ item.external_id }}@if(item.local_id)→ #{{ item.local_id }}@endif@elseif(tmp && tmp.tag)/tag/{{ tmp.tag }}/{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}{{ item.requested_subf0ck_slug ? ('#' + item.requested_subf0ck_slug) : '' }}@else{{ link.mainDisplay || link.main }}{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}{{ link.suffix }}{{ item.requested_subf0ck_slug ? ('#' + item.requested_subf0ck_slug) : '' }}@endif
@if(enable_item_title) @@ -14,7 +14,7 @@ @if(item.title){!! item.title !!}@elseif(can_manage_item)Add a title@endif @if(can_manage_item) @@ -129,11 +129,11 @@ @if(item.is_chan) @if(item.local_id) - #{{ item.local_id }} + #{{ item.local_id }} @else - + @endif @endif @if(session) @@ -190,9 +190,9 @@ @if(!user_alternative_infobox && item.is_chan) — /{{ item.external_board }}/{{ item.external_tid }} @if(item.local_id) - #{{ item.local_id }} + #{{ item.local_id }} @else - + @endif @endif @@ -201,17 +201,17 @@ @endif - + @if(!item.is_sfw && !item.is_nsfw && !item.is_nsfl) - + ? @endif @if(typeof item.tags !== "undefined") @each(item.tags as tag) @if(tag.normalized === 'sfw' || tag.normalized === 'nsfw' || tag.normalized === 'nsfl') - + {!! tag.tag !!} @else @@ -256,7 +256,7 @@ @if(enable_comments && (session || !hide_comments_from_public))
diff --git a/views/item-partial-modern.html b/views/item-partial-modern.html index 935b8a5..45f34fa 100644 --- a/views/item-partial-modern.html +++ b/views/item-partial-modern.html @@ -1,6 +1,6 @@ {{-- MODERN LAYOUT — 3-column grid --}} {{-- Used by: guests (no session) + members with use_new_layout = true --}} -
+
{{-- LEFT SIDEBAR: comments + tags --}}
@@ -15,7 +15,7 @@ @if(enable_comments && (session || !hide_comments_from_public))
@@ -40,16 +40,16 @@ @endif @elseif(n.type === 'report') - +
thumb
@@ -33,7 +33,7 @@
@elseif(n.type === 'deny') - +
thumb
@@ -47,7 +47,7 @@
@elseif(n.type === 'item_deleted') - +
thumb
@@ -61,7 +61,7 @@
@elseif(n.type === 'upload_comment') - +
thumbnail
@@ -72,7 +72,7 @@
@elseif(n.type === 'upload_success') - +
thumb
@@ -83,7 +83,7 @@
@elseif(n.type === 'upload_error') - + @if(n.item_id)
thumb @@ -104,7 +104,7 @@
@elseif(n.type === 'warning') -
+
{{ t('notifications.system') }}
@@ -116,7 +116,7 @@
@else - + @if(n.item_id)
thumb