hip
This commit is contained in:
@@ -7331,6 +7331,53 @@ body.layout-modern.sidebar-right-hidden .pagination-container-fluid {
|
|||||||
text-decoration: underline;
|
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-title,
|
.item-title,
|
||||||
.item-itle {
|
.item-itle {
|
||||||
|
|||||||
+37
-13
@@ -293,17 +293,34 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const favoBtns = document.querySelectorAll("#a_favo");
|
const favoBtns = document.querySelectorAll("#a_favo");
|
||||||
|
if (!favoBtns.length) return;
|
||||||
const firstFavoBtn = favoBtns[0];
|
const firstFavoBtn = favoBtns[0];
|
||||||
const chanRehostBtn = document.querySelector('#chan-item-rehost-btn');
|
const chanRehostBtn = document.querySelector('#chan-item-rehost-btn');
|
||||||
const isChan = firstFavoBtn?.dataset?.isChan === 'true' || !!chanRehostBtn;
|
const isChan = firstFavoBtn?.dataset?.isChan === 'true' || !!chanRehostBtn;
|
||||||
const chanUrl = firstFavoBtn?.dataset?.chanUrl || chanRehostBtn?.dataset?.url;
|
const chanUrl = firstFavoBtn?.dataset?.chanUrl || chanRehostBtn?.dataset?.url;
|
||||||
let localId = firstFavoBtn?.dataset?.localId;
|
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 viewing un-rehosted 4chan post, auto-rehost first
|
||||||
if (isChan && !localId && chanUrl) {
|
if (isChan && !localId && chanUrl) {
|
||||||
favoBtns.forEach(btn => {
|
setFavoLoading();
|
||||||
btn.className = 'iconset fa-solid fa-spinner fa-spin';
|
|
||||||
});
|
|
||||||
try {
|
try {
|
||||||
const csrfToken = window.f0ckSession?.csrf_token || '';
|
const csrfToken = window.f0ckSession?.csrf_token || '';
|
||||||
const rehostResp = await fetch('/api/v2/scroller/rehost', {
|
const rehostResp = await fetch('/api/v2/scroller/rehost', {
|
||||||
@@ -331,6 +348,15 @@
|
|||||||
const infoEl = document.querySelector("#a_info");
|
const infoEl = document.querySelector("#a_info");
|
||||||
if (infoEl) infoEl.dataset.itemId = localId;
|
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) {
|
if (chanRehostBtn) {
|
||||||
chanRehostBtn.classList.add('rehosted');
|
chanRehostBtn.classList.add('rehosted');
|
||||||
chanRehostBtn.outerHTML = `
|
chanRehostBtn.outerHTML = `
|
||||||
@@ -345,9 +371,7 @@
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[CHAN-FAV-ADMIN] Auto-rehost error:', err);
|
console.error('[CHAN-FAV-ADMIN] Auto-rehost error:', err);
|
||||||
favoBtns.forEach(btn => {
|
setFavoUi(wasAlreadyFav);
|
||||||
btn.className = 'iconset fa-regular fa-heart';
|
|
||||||
});
|
|
||||||
if (typeof window.flashMessage === 'function') {
|
if (typeof window.flashMessage === 'function') {
|
||||||
window.flashMessage('Failed to favorite post', 3000, 'error');
|
window.flashMessage('Failed to favorite post', 3000, 'error');
|
||||||
}
|
}
|
||||||
@@ -356,11 +380,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ctx = getContext();
|
const ctx = getContext();
|
||||||
if (!ctx) return;
|
if (!ctx) {
|
||||||
|
setFavoUi(wasAlreadyFav);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const postid = localId ? Number(localId) : ctx.postid;
|
const postid = localId ? Number(localId) : ctx.postid;
|
||||||
|
|
||||||
const wasAlreadyFav = favoBtns.length > 0 && favoBtns[0].classList.contains('fa-solid');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await post('/api/v2/togglefav', {
|
const res = await post('/api/v2/togglefav', {
|
||||||
postid: postid,
|
postid: postid,
|
||||||
@@ -373,10 +398,7 @@
|
|||||||
// Authoritative server state takes priority; fallback to inverting previous UI state
|
// Authoritative server state takes priority; fallback to inverting previous UI state
|
||||||
const isNowFav = (res.favorited !== undefined) ? !!res.favorited : !wasAlreadyFav;
|
const isNowFav = (res.favorited !== undefined) ? !!res.favorited : !wasAlreadyFav;
|
||||||
|
|
||||||
favoBtns.forEach(favoBtn => {
|
setFavoUi(isNowFav);
|
||||||
favoBtn.classList.toggle('fa-solid', isNowFav);
|
|
||||||
favoBtn.classList.toggle('fa-regular', !isNowFav);
|
|
||||||
});
|
|
||||||
|
|
||||||
const favcontainer = document.querySelector('#favs');
|
const favcontainer = document.querySelector('#favs');
|
||||||
if (favcontainer) {
|
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'));
|
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);
|
if (navigator.vibrate) navigator.vibrate(50);
|
||||||
} else {
|
} else {
|
||||||
|
setFavoUi(wasAlreadyFav);
|
||||||
const errMsg = (res && (res.msg || res.error)) || 'Anonymous favoriting is disabled.';
|
const errMsg = (res && (res.msg || res.error)) || 'Anonymous favoriting is disabled.';
|
||||||
if (typeof window.flashMessage === 'function') {
|
if (typeof window.flashMessage === 'function') {
|
||||||
window.flashMessage(errMsg, 3000, 'error');
|
window.flashMessage(errMsg, 3000, 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
setFavoUi(wasAlreadyFav);
|
||||||
if (typeof window.flashMessage === 'function') {
|
if (typeof window.flashMessage === 'function') {
|
||||||
window.flashMessage('Failed to update favorite.', 3000, 'error');
|
window.flashMessage('Failed to update favorite.', 3000, 'error');
|
||||||
}
|
}
|
||||||
|
|||||||
+423
-62
@@ -3476,7 +3476,15 @@ window.cancelAnimFrame = (function () {
|
|||||||
}
|
}
|
||||||
const locEl = document.querySelector('.location');
|
const locEl = document.querySelector('.location');
|
||||||
if (locEl && !locEl.textContent.includes('#')) {
|
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 =>
|
const foundIdx = albumData.findIndex(item =>
|
||||||
String(item.slug || '') === initialHash ||
|
String(item.slug || '') === initialHash ||
|
||||||
@@ -3600,7 +3608,15 @@ window.cancelAnimFrame = (function () {
|
|||||||
}
|
}
|
||||||
const locEl = document.querySelector('.location');
|
const locEl = document.querySelector('.location');
|
||||||
if (locEl && subKey) {
|
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
|
// Pause any playing audio or video
|
||||||
@@ -11023,6 +11039,50 @@ window.cancelAnimFrame = (function () {
|
|||||||
if (_navEl && window.ResizeObserver) {
|
if (_navEl && window.ResizeObserver) {
|
||||||
new ResizeObserver(window.syncNavbarHeight).observe(_navEl);
|
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
|
// Sidebar state init — apply persisted hidden/visible state on load
|
||||||
window.initSidebarRightToggle = () => {
|
window.initSidebarRightToggle = () => {
|
||||||
@@ -11152,7 +11212,10 @@ window.cancelAnimFrame = (function () {
|
|||||||
const itemIdMatch = url.match(/\/([a-zA-Z0-9_-]{11}|\d+)(?:\?.*|#.*)?$/);
|
const itemIdMatch = url.match(/\/([a-zA-Z0-9_-]{11}|\d+)(?:\?.*|#.*)?$/);
|
||||||
if (itemIdMatch) {
|
if (itemIdMatch) {
|
||||||
const itemId = itemIdMatch[1];
|
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');
|
el.classList.remove('has-notif');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -11265,11 +11328,12 @@ window.cancelAnimFrame = (function () {
|
|||||||
|
|
||||||
// Restore scroll position
|
// Restore scroll position
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
document.documentElement.scrollTop = 0;
|
||||||
|
document.body.scrollTop = 0;
|
||||||
const ic = document.querySelector('.index-container');
|
const ic = document.querySelector('.index-container');
|
||||||
if (ic && cache.scroll !== undefined) {
|
if (ic && cache.scroll !== undefined) {
|
||||||
ic.scrollTo(0, cache.scroll);
|
ic.scrollTo(0, cache.scroll);
|
||||||
} else {
|
|
||||||
window.scrollTo(0, cache.scroll || 0);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -11283,8 +11347,9 @@ window.cancelAnimFrame = (function () {
|
|||||||
window.updateVisitIndicators();
|
window.updateVisitIndicators();
|
||||||
window.initLazyLoading();
|
window.initLazyLoading();
|
||||||
if (window.initSidebarRightToggle) window.initSidebarRightToggle();
|
if (window.initSidebarRightToggle) window.initSidebarRightToggle();
|
||||||
if (window.syncNavbarHeight) window.syncNavbarHeight();
|
if (window.NotificationSystemInstance) {
|
||||||
window.NotificationSystemInstance?.pollDebounced?.();
|
window.NotificationSystemInstance.poll();
|
||||||
|
}
|
||||||
window._onaraCurrentGridUrl = urlObj.pathname + urlObj.search;
|
window._onaraCurrentGridUrl = urlObj.pathname + urlObj.search;
|
||||||
isNavigating = false;
|
isNavigating = false;
|
||||||
return; // SKIP FETCH
|
return; // SKIP FETCH
|
||||||
@@ -11342,6 +11407,11 @@ window.cancelAnimFrame = (function () {
|
|||||||
indexWrapper.className = 'index-layout-wrapper';
|
indexWrapper.className = 'index-layout-wrapper';
|
||||||
indexWrapper.innerHTML = '<div class="index-container"><div class="posts grid-transition"></div></div>';
|
indexWrapper.innerHTML = '<div class="index-container"><div class="posts grid-transition"></div></div>';
|
||||||
|
|
||||||
|
// 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.innerHTML = '';
|
||||||
main.appendChild(indexWrapper);
|
main.appendChild(indexWrapper);
|
||||||
|
|
||||||
@@ -11764,7 +11834,10 @@ window.cancelAnimFrame = (function () {
|
|||||||
// SCROLL TO TOP ON EVERY NEW NAVIGATION
|
// SCROLL TO TOP ON EVERY NEW NAVIGATION
|
||||||
if (!options.skipScroll) {
|
if (!options.skipScroll) {
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
|
document.documentElement.scrollTop = 0;
|
||||||
|
document.body.scrollTop = 0;
|
||||||
}
|
}
|
||||||
|
if (window.syncNavbarHeight) window.syncNavbarHeight();
|
||||||
|
|
||||||
// Re-init activity feed if applicable
|
// Re-init activity feed if applicable
|
||||||
if (document.getElementById('activity-container')) {
|
if (document.getElementById('activity-container')) {
|
||||||
@@ -11891,15 +11964,20 @@ window.cancelAnimFrame = (function () {
|
|||||||
if (options.skipPush && history.state && history.state.scroll !== undefined) {
|
if (options.skipPush && history.state && history.state.scroll !== undefined) {
|
||||||
// Back/Forward Navigation: Restore saved scroll position
|
// Back/Forward Navigation: Restore saved scroll position
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
document.documentElement.scrollTop = 0;
|
||||||
|
document.body.scrollTop = 0;
|
||||||
const ic = document.querySelector('.index-container');
|
const ic = document.querySelector('.index-container');
|
||||||
if (ic) ic.scrollTo(0, history.state.scroll);
|
if (ic) ic.scrollTo(0, history.state.scroll);
|
||||||
else window.scrollTo(0, history.state.scroll);
|
else window.scrollTo(0, history.state.scroll);
|
||||||
});
|
});
|
||||||
} else if (replace) {
|
} else if (replace) {
|
||||||
// New Navigation: Reset to top
|
// New Navigation: Reset to top
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
document.documentElement.scrollTop = 0;
|
||||||
|
document.body.scrollTop = 0;
|
||||||
const ic = document.querySelector('.index-container');
|
const ic = document.querySelector('.index-container');
|
||||||
if (ic) ic.scrollTo(0, 0);
|
if (ic) ic.scrollTo(0, 0);
|
||||||
else window.scrollTo(0, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger fade-in
|
// Trigger fade-in
|
||||||
@@ -11981,6 +12059,7 @@ window.cancelAnimFrame = (function () {
|
|||||||
// Notify extensions and re-init systems (e.g. CommentSystem)
|
// Notify extensions and re-init systems (e.g. CommentSystem)
|
||||||
document.dispatchEvent(new Event('f0ck:contentLoaded'));
|
document.dispatchEvent(new Event('f0ck:contentLoaded'));
|
||||||
if (window.initSidebarRightToggle) window.initSidebarRightToggle();
|
if (window.initSidebarRightToggle) window.initSidebarRightToggle();
|
||||||
|
if (window.syncNavbarHeight) window.syncNavbarHeight();
|
||||||
// Sync has-notif highlights after grid loads — handles PWA where visibilitychange doesn't fire
|
// Sync has-notif highlights after grid loads — handles PWA where visibilitychange doesn't fire
|
||||||
window.NotificationSystemInstance?.pollDebounced?.();
|
window.NotificationSystemInstance?.pollDebounced?.();
|
||||||
window._onaraCurrentGridUrl = urlObj.pathname + urlObj.search;
|
window._onaraCurrentGridUrl = urlObj.pathname + urlObj.search;
|
||||||
@@ -12237,6 +12316,14 @@ window.cancelAnimFrame = (function () {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const itemid = keySegments.pop();
|
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
|
// Extract context from Target URL first
|
||||||
let tag = null, user = null, isFavs = false, mime = null, hall = null, userHall = null, userHallOwner = null, tagger = null;
|
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 _ic = main.querySelector('.index-container');
|
||||||
const _ntc = _iw || _ic;
|
const _ntc = _iw || _ic;
|
||||||
if (_ntc && _isIndexPage) {
|
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 _ck = window.location.pathname + window.location.search;
|
||||||
const _pw = document.querySelector('.pagination-wrapper');
|
const _pw = document.querySelector('.pagination-wrapper');
|
||||||
gridCacheMap.set(_ck, { node: _ntc, scroll: currentScroll, pagination: _pw ? _pw.innerHTML : '' });
|
gridCacheMap.set(_ck, { node: _ntc, scroll: currentScroll, pagination: _pw ? _pw.innerHTML : '' });
|
||||||
@@ -12707,6 +12799,11 @@ window.cancelAnimFrame = (function () {
|
|||||||
const nodeToCache = indexWrapper || indexContainer;
|
const nodeToCache = indexWrapper || indexContainer;
|
||||||
|
|
||||||
if (nodeToCache && isIndexPage) {
|
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 cacheKey = window.location.pathname + window.location.search;
|
||||||
const paginationWrapper = document.querySelector('.pagination-wrapper');
|
const paginationWrapper = document.querySelector('.pagination-wrapper');
|
||||||
gridCacheMap.set(cacheKey, {
|
gridCacheMap.set(cacheKey, {
|
||||||
@@ -13021,6 +13118,13 @@ window.cancelAnimFrame = (function () {
|
|||||||
const thumbnail = target.closest('.posts > a:not(.notif-item)');
|
const thumbnail = target.closest('.posts > a:not(.notif-item)');
|
||||||
if (thumbnail && !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey) {
|
if (thumbnail && !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey) {
|
||||||
e.preventDefault();
|
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()) {
|
if (isOnaraActive()) {
|
||||||
const wasOpen = document.body.classList.contains('onara-modal-open');
|
const wasOpen = document.body.classList.contains('onara-modal-open');
|
||||||
document.querySelectorAll('.onara-active').forEach(el => el.classList.remove('onara-active'));
|
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');
|
const ratingGroup = modal.querySelector('#chan-rehost-rating-group');
|
||||||
let selectedRating = 'untagged';
|
let selectedRating = 'untagged';
|
||||||
|
let userSelectedRating = false;
|
||||||
|
let userEditedTags = false;
|
||||||
|
|
||||||
ratingGroup.addEventListener('click', (e) => {
|
ratingGroup.addEventListener('click', (e) => {
|
||||||
const pill = e.target.closest('.chan-rehost-pill');
|
const pill = e.target.closest('.chan-rehost-pill');
|
||||||
if (!pill) return;
|
if (!pill) return;
|
||||||
|
userSelectedRating = true;
|
||||||
ratingGroup.querySelectorAll('.chan-rehost-pill').forEach(b => b.classList.remove('active'));
|
ratingGroup.querySelectorAll('.chan-rehost-pill').forEach(b => b.classList.remove('active'));
|
||||||
pill.classList.add('active');
|
pill.classList.add('active');
|
||||||
selectedRating = pill.dataset.rating;
|
selectedRating = pill.dataset.rating;
|
||||||
@@ -13471,19 +13578,23 @@ window.cancelAnimFrame = (function () {
|
|||||||
const tagsInput = modal.querySelector('#chan-rehost-tags-input');
|
const tagsInput = modal.querySelector('#chan-rehost-tags-input');
|
||||||
const commentInput = modal.querySelector('#chan-rehost-comment-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}`)
|
fetch(`/api/v2/scroller/rehost/details/${itemId}`)
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data && data.success) {
|
if (data && data.success) {
|
||||||
if (data.rating) {
|
if (data.rating && data.rating !== 'untagged' && !userSelectedRating) {
|
||||||
selectedRating = data.rating;
|
selectedRating = data.rating;
|
||||||
ratingGroup.querySelectorAll('.chan-rehost-pill').forEach(b => {
|
ratingGroup.querySelectorAll('.chan-rehost-pill').forEach(b => {
|
||||||
b.classList.toggle('active', b.dataset.rating === data.rating);
|
b.classList.toggle('active', b.dataset.rating === data.rating);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (data.tags && data.tags.length) {
|
if (data.tags && data.tags.length && !userEditedTags && !tagsInput.value) {
|
||||||
tagsInput.value = data.tags.join(', ');
|
tagsInput.value = data.tags.map(t => (typeof t === 'string' ? t : t.tag)).join(', ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
@@ -13507,6 +13618,122 @@ window.cancelAnimFrame = (function () {
|
|||||||
if (e.target === modal) closeModal();
|
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 = `<span class="rating-label">${rMeta.label}</span>`;
|
||||||
|
|
||||||
|
// 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 = `<i class="fa-solid fa-check"></i> #${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 = `
|
||||||
|
<span class="chan-rehost-btn-group" style="display:inline-flex;gap:4px;align-items:center;">
|
||||||
|
<a href="/${itemId}" class="chan-rehost-action-btn rehosted" title="View Post #${itemId}" style="display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:4px 12px;border-radius:6px;font-size:0.8rem;background:rgba(74,222,128,0.2);color:#4ade80;border:1px solid rgba(74,222,128,0.4);text-decoration:none;font-weight:600;white-space:nowrap;flex-shrink:0;">
|
||||||
|
<i class="fa-solid fa-check"></i> #${itemId}
|
||||||
|
</a>
|
||||||
|
<button type="button" class="chan-rehost-edit-btn" data-item-id="${itemId}" title="Edit rating, tags & comment" style="display:inline-flex;align-items:center;padding:3px 7px;border-radius:6px;font-size:0.8rem;background:rgba(255,255,255,0.1);color:#fff;border:1px solid rgba(255,255,255,0.2);cursor:pointer;">
|
||||||
|
<i class="fa-solid fa-pen"></i>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 saveDetails = async (andView = false) => {
|
||||||
const saveBtn = modal.querySelector('.chan-rehost-modal-save');
|
const saveBtn = modal.querySelector('.chan-rehost-modal-save');
|
||||||
const saveViewBtn = modal.querySelector('.chan-rehost-modal-save-view');
|
const saveViewBtn = modal.querySelector('.chan-rehost-modal-save-view');
|
||||||
@@ -13535,12 +13762,13 @@ window.cancelAnimFrame = (function () {
|
|||||||
window.showToast('Details saved for #' + itemId + '!');
|
window.showToast('Details saved for #' + itemId + '!');
|
||||||
}
|
}
|
||||||
closeModal();
|
closeModal();
|
||||||
|
updateDomAfterSave(resData);
|
||||||
if (typeof onSave === 'function') {
|
if (typeof onSave === 'function') {
|
||||||
onSave(resData);
|
onSave(resData);
|
||||||
}
|
}
|
||||||
if (andView) {
|
if (andView) {
|
||||||
if (typeof window.loadItemAjax === 'function') {
|
if (typeof window.loadItemAjax === 'function') {
|
||||||
window.loadItemAjax('/' + itemId);
|
window.loadItemAjax('/' + itemId, true, { skipCache: true });
|
||||||
} else {
|
} else {
|
||||||
window.location.href = '/' + itemId;
|
window.location.href = '/' + itemId;
|
||||||
}
|
}
|
||||||
@@ -13602,9 +13830,19 @@ window.cancelAnimFrame = (function () {
|
|||||||
if (data.success && data.item_id) {
|
if (data.success && data.item_id) {
|
||||||
btn.classList.remove('loading');
|
btn.classList.remove('loading');
|
||||||
btn.classList.add('rehosted');
|
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 = `
|
btn.outerHTML = `
|
||||||
<span class="chan-rehost-btn-group" style="display:inline-flex;gap:4px;align-items:center;">
|
<span class="chan-rehost-btn-group" style="display:inline-flex;gap:4px;align-items:center;">
|
||||||
<a href="/${data.item_id}" class="chan-rehost-action-btn rehosted" title="View Post #${data.item_id}" style="display:inline-flex;align-items:center;gap:5px;padding:3px 9px;border-radius:6px;font-size:0.8rem;background:rgba(74,222,128,0.2);color:#4ade80;border:1px solid rgba(74,222,128,0.4);text-decoration:none;font-weight:600;">
|
<a href="/${data.item_id}" class="chan-rehost-action-btn rehosted" title="View Post #${data.item_id}" style="display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:4px 12px;border-radius:6px;font-size:0.8rem;background:rgba(74,222,128,0.2);color:#4ade80;border:1px solid rgba(74,222,128,0.4);text-decoration:none;font-weight:600;white-space:nowrap;flex-shrink:0;">
|
||||||
<i class="fa-solid fa-check"></i> #${data.item_id}
|
<i class="fa-solid fa-check"></i> #${data.item_id}
|
||||||
</a>
|
</a>
|
||||||
<button type="button" class="chan-rehost-edit-btn" data-item-id="${data.item_id}" title="Edit rating, tags & comment" style="display:inline-flex;align-items:center;padding:3px 7px;border-radius:6px;font-size:0.8rem;background:rgba(255,255,255,0.1);color:#fff;border:1px solid rgba(255,255,255,0.2);cursor:pointer;">
|
<button type="button" class="chan-rehost-edit-btn" data-item-id="${data.item_id}" title="Edit rating, tags & comment" style="display:inline-flex;align-items:center;padding:3px 7px;border-radius:6px;font-size:0.8rem;background:rgba(255,255,255,0.1);color:#fff;border:1px solid rgba(255,255,255,0.2);cursor:pointer;">
|
||||||
@@ -14146,6 +14384,10 @@ window.cancelAnimFrame = (function () {
|
|||||||
loadPageAjax(url, true, { skipPush: true });
|
loadPageAjax(url, true, { skipPush: true });
|
||||||
} else {
|
} else {
|
||||||
document.title = window._onaraReturnTitle || window.f0ckDomain || 'f0ck';
|
document.title = window._onaraReturnTitle || window.f0ckDomain || 'f0ck';
|
||||||
|
if (window.updateVisitIndicators) window.updateVisitIndicators();
|
||||||
|
if (window.NotificationSystemInstance) {
|
||||||
|
window.NotificationSystemInstance.poll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -17723,31 +17965,9 @@ class NotificationSystem {
|
|||||||
document.dispatchEvent(new CustomEvent('f0ck:global_chat_presence', { detail: data.data }));
|
document.dispatchEvent(new CustomEvent('f0ck:global_chat_presence', { detail: data.data }));
|
||||||
} else if (data.type === 'brand_image') {
|
} else if (data.type === 'brand_image') {
|
||||||
window.f0ckDebug(`[SSE] Brand image update received:`, data.data?.url);
|
window.f0ckDebug(`[SSE] Brand image update received:`, data.data?.url);
|
||||||
if (data.data?.url) {
|
if (window.updateBrandImage) {
|
||||||
const src = data.data.url;
|
window.updateBrandImage(data.data?.url || null);
|
||||||
// Update the randomizeLogo pool so clicking the brand always uses the current image
|
|
||||||
window.f0ckBrandImages = [src];
|
|
||||||
const logos = document.querySelectorAll('#navbar-logo');
|
|
||||||
if (logos.length > 0) {
|
|
||||||
logos.forEach(el => { el.src = src; el.style.display = ''; });
|
|
||||||
} else {
|
|
||||||
// Logo element doesn't exist yet (no image was set before) — create it
|
|
||||||
document.querySelectorAll('a.navbar-brand').forEach(brandLink => {
|
|
||||||
const textNode = Array.from(brandLink.childNodes).find(n => n.nodeType === Node.TEXT_NODE);
|
|
||||||
const img = document.createElement('img');
|
|
||||||
img.id = 'navbar-logo';
|
|
||||||
img.src = src;
|
|
||||||
img.alt = document.title;
|
|
||||||
img.style.cssText = 'max-height:40px;vertical-align:middle;max-width:180px;width:auto;';
|
|
||||||
if (textNode) brandLink.insertBefore(img, textNode);
|
|
||||||
else brandLink.prepend(img);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
window.f0ckBrandImages = [];
|
|
||||||
document.querySelectorAll('#navbar-logo').forEach(el => el.remove());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('SSE data parse error', err);
|
console.error('SSE data parse error', err);
|
||||||
@@ -18053,7 +18273,12 @@ class NotificationSystem {
|
|||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener('f0ck:contentLoaded', reapplyOwnBanner);
|
document.addEventListener('f0ck:contentLoaded', reapplyOwnBanner);
|
||||||
window.addEventListener('pageshow', (e) => { if (e.persisted) reapplyOwnBanner(); });
|
window.addEventListener('pageshow', (e) => {
|
||||||
|
if (e.persisted) {
|
||||||
|
reapplyOwnBanner();
|
||||||
|
this.poll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Poll fresh display_name from DB — used on tab focus to catch missed SSE events
|
// Poll fresh display_name from DB — used on tab focus to catch missed SSE events
|
||||||
this.syncDisplayName = async () => {
|
this.syncDisplayName = async () => {
|
||||||
@@ -18218,6 +18443,12 @@ class NotificationSystem {
|
|||||||
|
|
||||||
|
|
||||||
// Handle "Mark as Read"
|
// Handle "Mark as Read"
|
||||||
|
const notifTargetItemId = link.dataset.itemId || link.getAttribute('data-item-id') || link.getAttribute('href')?.match(/\/([a-zA-Z0-9_-]{11}|\d+)(?:[?#]|$)/)?.[1];
|
||||||
|
const notifTargetItemSlug = link.dataset.itemSlug || link.getAttribute('data-item-slug');
|
||||||
|
if (notifTargetItemId || notifTargetItemSlug) {
|
||||||
|
this.markItemRead(notifTargetItemId, notifTargetItemSlug);
|
||||||
|
}
|
||||||
|
|
||||||
if (link.dataset.id && link.classList.contains('unread')) {
|
if (link.dataset.id && link.classList.contains('unread')) {
|
||||||
window.f0ckDebug(`[NotificationSystem] Marking ${link.dataset.id} as read...`);
|
window.f0ckDebug(`[NotificationSystem] Marking ${link.dataset.id} as read...`);
|
||||||
// Fire and forget (keepalive ensures it survives navigation)
|
// Fire and forget (keepalive ensures it survives navigation)
|
||||||
@@ -18226,8 +18457,8 @@ class NotificationSystem {
|
|||||||
keepalive: true
|
keepalive: true
|
||||||
}).catch(err => console.error("Failed to mark read", err));
|
}).catch(err => console.error("Failed to mark read", err));
|
||||||
|
|
||||||
// Decrement badge count
|
// Decrement badge count if not already decremented by markItemRead
|
||||||
if (this.countBadge) {
|
if (!notifTargetItemId && !notifTargetItemSlug && this.countBadge) {
|
||||||
let count = parseInt(this.countBadge.textContent) || 0;
|
let count = parseInt(this.countBadge.textContent) || 0;
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
count--;
|
count--;
|
||||||
@@ -18521,21 +18752,32 @@ class NotificationSystem {
|
|||||||
const unreadItemIds = new Set();
|
const unreadItemIds = new Set();
|
||||||
notifications.forEach(n => {
|
notifications.forEach(n => {
|
||||||
if (!n.is_read && n.item_id) {
|
if (!n.is_read && n.item_id) {
|
||||||
unreadItemIds.add(String(n.item_id));
|
const idStr = String(n.item_id);
|
||||||
if (currentPath === `/${n.item_id}` || currentPath === `/${n.item_id}/`) return;
|
unreadItemIds.add(idStr);
|
||||||
document.querySelectorAll(`a.thumb[href$="/${n.item_id}"], a.lazy-thumb[href$="/${n.item_id}"]`).forEach(el => {
|
if (n.item_slug) unreadItemIds.add(String(n.item_slug));
|
||||||
|
if (n.slug) unreadItemIds.add(String(n.slug));
|
||||||
|
if (currentPath === `/${n.item_id}` || currentPath === `/${n.item_id}/` || (n.item_slug && currentPath === `/${n.item_slug}`)) return;
|
||||||
|
document.querySelectorAll(`a.thumb[data-item-id="${idStr}"], a.lazy-thumb[data-item-id="${idStr}"], a.thumb[href$="/${idStr}"], a.lazy-thumb[href$="/${idStr}"]`).forEach(el => {
|
||||||
el.classList.add('has-notif');
|
el.classList.add('has-notif');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove .has-notif from any thumb whose item is no longer in the unread set.
|
// Remove .has-notif from any thumb whose item is no longer in the unread set.
|
||||||
document.querySelectorAll('a.thumb.has-notif, a.lazy-thumb.has-notif').forEach(el => {
|
const removeStaleHasNotif = (root = document) => {
|
||||||
const match = el.getAttribute('href')?.match(/\/(\d+)$/);
|
root.querySelectorAll('a.thumb.has-notif, a.lazy-thumb.has-notif').forEach(el => {
|
||||||
if (match && !unreadItemIds.has(match[1])) {
|
const id = el.dataset.itemId || el.getAttribute('data-item-id') || el.getAttribute('href')?.match(/\/([a-zA-Z0-9_-]{11}|\d+)(?:[?#]|$)/)?.[1];
|
||||||
el.classList.remove('has-notif');
|
if (id && !unreadItemIds.has(String(id))) {
|
||||||
}
|
el.classList.remove('has-notif');
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
removeStaleHasNotif(document);
|
||||||
|
if (typeof gridCacheMap !== 'undefined' && gridCacheMap) {
|
||||||
|
gridCacheMap.forEach(cacheEntry => {
|
||||||
|
if (cacheEntry?.node) removeStaleHasNotif(cacheEntry.node);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Render the active tab
|
// Render the active tab
|
||||||
this._renderActiveTab();
|
this._renderActiveTab();
|
||||||
@@ -18592,7 +18834,7 @@ class NotificationSystem {
|
|||||||
const userLabel = isDeleted ? (i18n.notif_moderation || 'Moderation') : (i18n.notif_system || 'System');
|
const userLabel = isDeleted ? (i18n.notif_moderation || 'Moderation') : (i18n.notif_system || 'System');
|
||||||
const itemLink = `/${itemKey}`;
|
const itemLink = `/${itemKey}`;
|
||||||
return `
|
return `
|
||||||
<a href="${itemLink}" class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}">
|
<a href="${itemLink}" class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}" data-item-id="${n.item_id || ''}" data-item-slug="${n.item_slug || n.slug || ''}">
|
||||||
<div class="notif-thumb"${n.item_mode ? ` data-mode="${n.item_mode}"` : ''}><img src="/mod/deleted/t/${n.item_id}.webp" alt="thumb" onerror="this.onerror=null;this.src='/t/${n.item_id}.webp';this.onerror=function(){this.style.display='none';}"></div>
|
<div class="notif-thumb"${n.item_mode ? ` data-mode="${n.item_mode}"` : ''}><img src="/mod/deleted/t/${n.item_id}.webp" alt="thumb" onerror="this.onerror=null;this.src='/t/${n.item_id}.webp';this.onerror=function(){this.style.display='none';}"></div>
|
||||||
<div class="notif-content">
|
<div class="notif-content">
|
||||||
<div class="notif-user"><strong>${userLabel}</strong></div>
|
<div class="notif-user"><strong>${userLabel}</strong></div>
|
||||||
@@ -18644,7 +18886,7 @@ class NotificationSystem {
|
|||||||
let thumbSrc, thumbOnerror;
|
let thumbSrc, thumbOnerror;
|
||||||
if (n.type === 'warning') {
|
if (n.type === 'warning') {
|
||||||
return `
|
return `
|
||||||
<div class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}">
|
<div class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}" data-item-id="${n.item_id || ''}" data-item-slug="${n.item_slug || n.slug || ''}">
|
||||||
<div class="notif-thumb" style="display:flex;align-items:center;justify-content:center;background:var(--bg-lighter);color:var(--danger);font-size:1.5em;"><i class="fa-solid fa-triangle-exclamation"></i></div>
|
<div class="notif-thumb" style="display:flex;align-items:center;justify-content:center;background:var(--bg-lighter);color:var(--danger);font-size:1.5em;"><i class="fa-solid fa-triangle-exclamation"></i></div>
|
||||||
<div class="notif-content">
|
<div class="notif-content">
|
||||||
<div class="notif-user"><strong ${n.username_color ? `style="color: ${n.username_color}"` : ''}>${user}</strong></div>
|
<div class="notif-user"><strong ${n.username_color ? `style="color: ${n.username_color}"` : ''}>${user}</strong></div>
|
||||||
@@ -18662,7 +18904,7 @@ class NotificationSystem {
|
|||||||
}
|
}
|
||||||
const thumb = n.item_id ? `<div class="notif-thumb"${n.item_mode ? ` data-mode="${n.item_mode}"` : ''}><img src="${thumbSrc}" alt="thumb" onerror="${thumbOnerror}"></div>` : '';
|
const thumb = n.item_id ? `<div class="notif-thumb"${n.item_mode ? ` data-mode="${n.item_mode}"` : ''}><img src="${thumbSrc}" alt="thumb" onerror="${thumbOnerror}"></div>` : '';
|
||||||
return `
|
return `
|
||||||
<a href="${link}" class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}">
|
<a href="${link}" class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}" data-item-id="${n.item_id || ''}" data-item-slug="${n.item_slug || n.slug || ''}">
|
||||||
${thumb}
|
${thumb}
|
||||||
<div class="notif-content">
|
<div class="notif-content">
|
||||||
<div class="notif-user"><strong ${n.username_color ? `style="color: ${n.username_color}"` : ''}>${user}</strong></div>
|
<div class="notif-user"><strong ${n.username_color ? `style="color: ${n.username_color}"` : ''}>${user}</strong></div>
|
||||||
@@ -18678,7 +18920,7 @@ class NotificationSystem {
|
|||||||
if (n.type === 'approve') {
|
if (n.type === 'approve') {
|
||||||
const link = `/${itemKey}`;
|
const link = `/${itemKey}`;
|
||||||
return `
|
return `
|
||||||
<a href="${link}" class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}">
|
<a href="${link}" class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}" data-item-id="${n.item_id || ''}" data-item-slug="${n.item_slug || n.slug || ''}">
|
||||||
<div class="notif-thumb"${n.item_mode ? ` data-mode="${n.item_mode}"` : ''}><img src="/t/${n.item_id}.webp" alt="thumb" onerror="this.onerror=null;this.src='/mod/pending/t/${n.item_id}.webp';this.onerror=function(){this.onerror=null;this.src='/mod/deleted/t/${n.item_id}.webp';this.onerror=function(){this.style.display='none';};}"></div>
|
<div class="notif-thumb"${n.item_mode ? ` data-mode="${n.item_mode}"` : ''}><img src="/t/${n.item_id}.webp" alt="thumb" onerror="this.onerror=null;this.src='/mod/pending/t/${n.item_id}.webp';this.onerror=function(){this.onerror=null;this.src='/mod/deleted/t/${n.item_id}.webp';this.onerror=function(){this.style.display='none';};}"></div>
|
||||||
<div class="notif-content">
|
<div class="notif-content">
|
||||||
<div>
|
<div>
|
||||||
@@ -18693,7 +18935,7 @@ class NotificationSystem {
|
|||||||
if (n.type === 'upload_success') {
|
if (n.type === 'upload_success') {
|
||||||
const link = `/${itemKey}`;
|
const link = `/${itemKey}`;
|
||||||
return `
|
return `
|
||||||
<a href="${link}" class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}">
|
<a href="${link}" class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}" data-item-id="${n.item_id || ''}" data-item-slug="${n.item_slug || n.slug || ''}">
|
||||||
<div class="notif-thumb"${n.item_mode ? ` data-mode="${n.item_mode}"` : ''}><img src="/t/${n.item_id}.webp" alt="thumb" onerror="this.style.display='none';"></div>
|
<div class="notif-thumb"${n.item_mode ? ` data-mode="${n.item_mode}"` : ''}><img src="/t/${n.item_id}.webp" alt="thumb" onerror="this.style.display='none';"></div>
|
||||||
<div class="notif-content">
|
<div class="notif-content">
|
||||||
<div>
|
<div>
|
||||||
@@ -18712,7 +18954,7 @@ class NotificationSystem {
|
|||||||
const urlDisplay = url ? `<div style="font-size: 0.8em; opacity: 0.7; margin-top: 4px; word-break: break-all; max-height: 3.2em; overflow: hidden;">${url}</div>` : '';
|
const urlDisplay = url ? `<div style="font-size: 0.8em; opacity: 0.7; margin-top: 4px; word-break: break-all; max-height: 3.2em; overflow: hidden;">${url}</div>` : '';
|
||||||
const link = n.item_id ? `/${itemKey}` : '#';
|
const link = n.item_id ? `/${itemKey}` : '#';
|
||||||
return `
|
return `
|
||||||
<a href="${link}" class="notif-item ${n.is_read ? '' : 'unread'}" data-id="${n.id}">
|
<a href="${link}" class="notif-item ${n.is_read ? '' : 'unread'}" data-id="${n.id}" data-item-id="${n.item_id || ''}" data-item-slug="${n.item_slug || n.slug || ''}">
|
||||||
<div class="notif-content">
|
<div class="notif-content">
|
||||||
<div>
|
<div>
|
||||||
<strong>${(window.f0ckI18n && window.f0ckI18n.notif_upload_error) || 'Your background upload failed.'}</strong>
|
<strong>${(window.f0ckI18n && window.f0ckI18n.notif_upload_error) || 'Your background upload failed.'}</strong>
|
||||||
@@ -18730,7 +18972,7 @@ class NotificationSystem {
|
|||||||
const label = isDeleted ? ((window.f0ckI18n && window.f0ckI18n.notif_upload_deleted) || 'A moderator deleted your upload') : ((window.f0ckI18n && window.f0ckI18n.notif_upload_denied) || 'Your upload was denied');
|
const label = isDeleted ? ((window.f0ckI18n && window.f0ckI18n.notif_upload_deleted) || 'A moderator deleted your upload') : ((window.f0ckI18n && window.f0ckI18n.notif_upload_denied) || 'Your upload was denied');
|
||||||
const link = `/notifications#notif-${n.id}`;
|
const link = `/notifications#notif-${n.id}`;
|
||||||
return `
|
return `
|
||||||
<a href="${link}" class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}" data-notif-nav="true">
|
<a href="${link}" class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}" data-item-id="${n.item_id || ''}" data-item-slug="${n.item_slug || n.slug || ''}" data-notif-nav="true">
|
||||||
<div class="notif-thumb"${n.item_mode ? ` data-mode="${n.item_mode}"` : ''}><img src="/mod/deleted/t/${n.item_id}.webp" alt="thumb" onerror="this.onerror=null;this.src='/t/${n.item_id}.webp';this.onerror=function(){this.style.display='none';}"></div>
|
<div class="notif-thumb"${n.item_mode ? ` data-mode="${n.item_mode}"` : ''}><img src="/mod/deleted/t/${n.item_id}.webp" alt="thumb" onerror="this.onerror=null;this.src='/t/${n.item_id}.webp';this.onerror=function(){this.style.display='none';}"></div>
|
||||||
<div class="notif-content">
|
<div class="notif-content">
|
||||||
<div>
|
<div>
|
||||||
@@ -18746,7 +18988,7 @@ class NotificationSystem {
|
|||||||
if (n.type === 'admin_pending') {
|
if (n.type === 'admin_pending') {
|
||||||
const link = '/mod/approve';
|
const link = '/mod/approve';
|
||||||
return `
|
return `
|
||||||
<a href="${link}" class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}">
|
<a href="${link}" class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}" data-item-id="${n.item_id || ''}" data-item-slug="${n.item_slug || n.slug || ''}">
|
||||||
<div class="notif-thumb"${n.item_mode ? ` data-mode="${n.item_mode}"` : ''}><img src="/mod/pending/t/${n.item_id}.webp" alt="thumb" onerror="this.onerror=null;this.src='/t/${n.item_id}.webp';this.onerror=function(){this.style.display='none';}"></div>
|
<div class="notif-thumb"${n.item_mode ? ` data-mode="${n.item_mode}"` : ''}><img src="/mod/pending/t/${n.item_id}.webp" alt="thumb" onerror="this.onerror=null;this.src='/t/${n.item_id}.webp';this.onerror=function(){this.style.display='none';}"></div>
|
||||||
<div class="notif-content">
|
<div class="notif-content">
|
||||||
<div>
|
<div>
|
||||||
@@ -18761,7 +19003,7 @@ class NotificationSystem {
|
|||||||
if (n.type === 'report') {
|
if (n.type === 'report') {
|
||||||
const link = '/mod/reports';
|
const link = '/mod/reports';
|
||||||
return `
|
return `
|
||||||
<a href="${link}" class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}">
|
<a href="${link}" class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}" data-item-id="${n.item_id || ''}" data-item-slug="${n.item_slug || n.slug || ''}">
|
||||||
<div class="notif-thumb"${n.item_mode ? ` data-mode="${n.item_mode}"` : ''}><img src="/t/${n.item_id}.webp" alt="thumb" onerror="this.onerror=null;this.src='/mod/pending/t/${n.item_id}.webp';this.onerror=function(){this.onerror=null;this.src='/mod/deleted/t/${n.item_id}.webp';this.onerror=function(){this.style.display='none';};}"></div>
|
<div class="notif-thumb"${n.item_mode ? ` data-mode="${n.item_mode}"` : ''}><img src="/t/${n.item_id}.webp" alt="thumb" onerror="this.onerror=null;this.src='/mod/pending/t/${n.item_id}.webp';this.onerror=function(){this.onerror=null;this.src='/mod/deleted/t/${n.item_id}.webp';this.onerror=function(){this.style.display='none';};}"></div>
|
||||||
<div class="notif-content">
|
<div class="notif-content">
|
||||||
<div>
|
<div>
|
||||||
@@ -18776,7 +19018,7 @@ class NotificationSystem {
|
|||||||
if (n.type === 'warning') {
|
if (n.type === 'warning') {
|
||||||
const link = `/notifications?tab=system#notif-${n.id}`;
|
const link = `/notifications?tab=system#notif-${n.id}`;
|
||||||
return `
|
return `
|
||||||
<a href="${link}" class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}" data-notif-nav="true">
|
<a href="${link}" class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}" data-item-id="${n.item_id || ''}" data-item-slug="${n.item_slug || n.slug || ''}" data-notif-nav="true">
|
||||||
<div class="notif-thumb" style="display:flex;align-items:center;justify-content:center;background:var(--bg-lighter);color:var(--danger);font-size:1.5em;"><i class="fa-solid fa-triangle-exclamation"></i></div>
|
<div class="notif-thumb" style="display:flex;align-items:center;justify-content:center;background:var(--bg-lighter);color:var(--danger);font-size:1.5em;"><i class="fa-solid fa-triangle-exclamation"></i></div>
|
||||||
<div class="notif-content">
|
<div class="notif-content">
|
||||||
<div>
|
<div>
|
||||||
@@ -18800,7 +19042,7 @@ class NotificationSystem {
|
|||||||
|
|
||||||
const thumb = n.item_id ? `<div class="notif-thumb"${n.item_mode ? ` data-mode="${n.item_mode}"` : ''}><img src="/t/${n.item_id}.webp" alt="thumb" onerror="this.style.display='none'"></div>` : '';
|
const thumb = n.item_id ? `<div class="notif-thumb"${n.item_mode ? ` data-mode="${n.item_mode}"` : ''}><img src="/t/${n.item_id}.webp" alt="thumb" onerror="this.style.display='none'"></div>` : '';
|
||||||
return `
|
return `
|
||||||
<a href="${link}" class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}">
|
<a href="${link}" class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}" data-item-id="${n.item_id || ''}" data-item-slug="${n.item_slug || n.slug || ''}">
|
||||||
${thumb}
|
${thumb}
|
||||||
<div class="notif-content">
|
<div class="notif-content">
|
||||||
<div>
|
<div>
|
||||||
@@ -18812,6 +19054,113 @@ class NotificationSystem {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
markItemRead(itemId, slug = null) {
|
||||||
|
if (!itemId && !slug) return;
|
||||||
|
const strId = itemId ? String(itemId) : null;
|
||||||
|
const strSlug = slug ? String(slug) : null;
|
||||||
|
|
||||||
|
const matchesItem = (n) => {
|
||||||
|
if (!n) return false;
|
||||||
|
if (strId && String(n.item_id) === strId) return true;
|
||||||
|
if (strSlug && (String(n.item_slug) === strSlug || String(n.slug) === strSlug)) return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
[this._cachedUser, this._cachedSystem].forEach(list => {
|
||||||
|
if (!Array.isArray(list)) return;
|
||||||
|
list.forEach(n => {
|
||||||
|
if (matchesItem(n) && !n.is_read) {
|
||||||
|
n.is_read = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove matching .notif-item elements from dropdown
|
||||||
|
if (this.list) {
|
||||||
|
const items = this.list.querySelectorAll('.notif-item');
|
||||||
|
items.forEach(el => {
|
||||||
|
const notifItemId = el.dataset.itemId;
|
||||||
|
const notifItemSlug = el.dataset.itemSlug;
|
||||||
|
const notifHref = el.getAttribute('href') || '';
|
||||||
|
const isMatch = (strId && notifItemId === strId) ||
|
||||||
|
(strSlug && notifItemSlug === strSlug) ||
|
||||||
|
(strId && (notifHref === `/${strId}` || notifHref.startsWith(`/${strId}#`) || notifHref.startsWith(`/${strId}?`))) ||
|
||||||
|
(strSlug && (notifHref === `/${strSlug}` || notifHref.startsWith(`/${strSlug}#`) || notifHref.startsWith(`/${strSlug}?`)));
|
||||||
|
if (isMatch) {
|
||||||
|
el.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (this.list.children.length === 0) {
|
||||||
|
this.list.innerHTML = `<div class="notif-empty">${window.f0ckI18n?.no_notifications || 'No new notifications'}</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark as read in full history page if open
|
||||||
|
document.querySelectorAll('.notifications-list-full .notif-item.unread').forEach(el => {
|
||||||
|
const notifItemId = el.dataset.itemId;
|
||||||
|
const notifItemSlug = el.dataset.itemSlug;
|
||||||
|
const notifHref = el.getAttribute('href') || '';
|
||||||
|
const isMatch = (strId && notifItemId === strId) ||
|
||||||
|
(strSlug && notifItemSlug === strSlug) ||
|
||||||
|
(strId && (notifHref === `/${strId}` || notifHref.startsWith(`/${strId}#`) || notifHref.startsWith(`/${strId}?`))) ||
|
||||||
|
(strSlug && (notifHref === `/${strSlug}` || notifHref.startsWith(`/${strSlug}#`) || notifHref.startsWith(`/${strSlug}?`)));
|
||||||
|
if (isMatch) {
|
||||||
|
el.classList.remove('unread');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Recalculate unread counts
|
||||||
|
const userUnread = (this._cachedUser || []).filter(n => !n.is_read).length;
|
||||||
|
const systemUnread = (this._cachedSystem || []).filter(n => !n.is_read).length;
|
||||||
|
const totalUnread = userUnread + systemUnread;
|
||||||
|
|
||||||
|
if (this.countBadge) {
|
||||||
|
this.countBadge.textContent = totalUnread;
|
||||||
|
this.countBadge.style.display = totalUnread > 0 ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
const userBadge = document.getElementById('notif-tab-badge-user');
|
||||||
|
const systemBadge = document.getElementById('notif-tab-badge-system');
|
||||||
|
if (userBadge) {
|
||||||
|
userBadge.textContent = userUnread;
|
||||||
|
userBadge.style.display = userUnread > 0 ? '' : 'none';
|
||||||
|
}
|
||||||
|
if (systemBadge) {
|
||||||
|
systemBadge.textContent = systemUnread;
|
||||||
|
systemBadge.style.display = systemUnread > 0 ? '' : 'none';
|
||||||
|
}
|
||||||
|
if (typeof window._scrollerNotifHook === 'function') {
|
||||||
|
window._scrollerNotifHook(totalUnread);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove .has-notif from matching thumbnails in DOM
|
||||||
|
const removeHasNotif = (root = document) => {
|
||||||
|
root.querySelectorAll('a.thumb.has-notif, a.lazy-thumb.has-notif').forEach(el => {
|
||||||
|
const id = el.dataset.itemId || el.getAttribute('data-item-id') || el.getAttribute('href')?.match(/\/([a-zA-Z0-9_-]{11}|\d+)(?:[?#]|$)/)?.[1];
|
||||||
|
if (id && (id === strId || id === strSlug)) {
|
||||||
|
el.classList.remove('has-notif');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
removeHasNotif(document);
|
||||||
|
|
||||||
|
// Also strip from any detached cached grids in gridCacheMap
|
||||||
|
if (typeof gridCacheMap !== 'undefined' && gridCacheMap) {
|
||||||
|
gridCacheMap.forEach(cacheEntry => {
|
||||||
|
if (cacheEntry && cacheEntry.node) {
|
||||||
|
removeHasNotif(cacheEntry.node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tell server in background (fire-and-forget with keepalive)
|
||||||
|
const serverId = strId || strSlug;
|
||||||
|
if (serverId) {
|
||||||
|
fetch(`/api/notifications/item/${encodeURIComponent(serverId)}/read`, {
|
||||||
|
method: 'POST',
|
||||||
|
keepalive: true
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async markAllRead() {
|
async markAllRead() {
|
||||||
try {
|
try {
|
||||||
@@ -18829,7 +19178,7 @@ class NotificationSystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
markAllReadUI() {
|
markAllReadUI() {
|
||||||
this.countBadge.style.display = 'none';
|
if (this.countBadge) this.countBadge.style.display = 'none';
|
||||||
// Clear per-tab badges
|
// Clear per-tab badges
|
||||||
const userBadge = document.getElementById('notif-tab-badge-user');
|
const userBadge = document.getElementById('notif-tab-badge-user');
|
||||||
const systemBadge = document.getElementById('notif-tab-badge-system');
|
const systemBadge = document.getElementById('notif-tab-badge-system');
|
||||||
@@ -18843,6 +19192,16 @@ class NotificationSystem {
|
|||||||
}
|
}
|
||||||
// Also update History page items if present
|
// Also update History page items if present
|
||||||
document.querySelectorAll('.notifications-list-full .notif-item.unread').forEach(el => el.classList.remove('unread'));
|
document.querySelectorAll('.notifications-list-full .notif-item.unread').forEach(el => el.classList.remove('unread'));
|
||||||
|
// Remove .has-notif highlights
|
||||||
|
document.querySelectorAll('a.thumb.has-notif, a.lazy-thumb.has-notif').forEach(el => el.classList.remove('has-notif'));
|
||||||
|
if (typeof gridCacheMap !== 'undefined' && gridCacheMap) {
|
||||||
|
gridCacheMap.forEach(cacheEntry => {
|
||||||
|
if (cacheEntry?.node) cacheEntry.node.querySelectorAll('a.thumb.has-notif, a.lazy-thumb.has-notif').forEach(el => el.classList.remove('has-notif'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (typeof window._scrollerNotifHook === 'function') {
|
||||||
|
window._scrollerNotifHook(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTagsUpdate(data) {
|
handleTagsUpdate(data) {
|
||||||
@@ -19053,6 +19412,8 @@ class NotificationSystem {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
favoBtns.forEach(favoBtn => {
|
favoBtns.forEach(favoBtn => {
|
||||||
|
favoBtn.classList.remove('fa-spinner', 'fa-spin');
|
||||||
|
favoBtn.classList.add('iconset', 'fa-heart');
|
||||||
favoBtn.classList.toggle('fa-solid', isNowFav);
|
favoBtn.classList.toggle('fa-solid', isNowFav);
|
||||||
favoBtn.classList.toggle('fa-regular', !isNowFav);
|
favoBtn.classList.toggle('fa-regular', !isNowFav);
|
||||||
});
|
});
|
||||||
|
|||||||
+37
-13
@@ -257,17 +257,34 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const favoBtns = document.querySelectorAll("#a_favo");
|
const favoBtns = document.querySelectorAll("#a_favo");
|
||||||
|
if (!favoBtns.length) return;
|
||||||
const firstFavoBtn = favoBtns[0];
|
const firstFavoBtn = favoBtns[0];
|
||||||
const chanRehostBtn = document.querySelector('#chan-item-rehost-btn');
|
const chanRehostBtn = document.querySelector('#chan-item-rehost-btn');
|
||||||
const isChan = firstFavoBtn?.dataset?.isChan === 'true' || !!chanRehostBtn;
|
const isChan = firstFavoBtn?.dataset?.isChan === 'true' || !!chanRehostBtn;
|
||||||
const chanUrl = firstFavoBtn?.dataset?.chanUrl || chanRehostBtn?.dataset?.url;
|
const chanUrl = firstFavoBtn?.dataset?.chanUrl || chanRehostBtn?.dataset?.url;
|
||||||
let localId = firstFavoBtn?.dataset?.localId;
|
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 viewing un-rehosted 4chan post, auto-rehost first
|
||||||
if (isChan && !localId && chanUrl) {
|
if (isChan && !localId && chanUrl) {
|
||||||
favoBtns.forEach(btn => {
|
setFavoLoading();
|
||||||
btn.className = 'iconset fa-solid fa-spinner fa-spin';
|
|
||||||
});
|
|
||||||
try {
|
try {
|
||||||
const csrfToken = window.f0ckSession?.csrf_token || '';
|
const csrfToken = window.f0ckSession?.csrf_token || '';
|
||||||
const rehostResp = await fetch('/api/v2/scroller/rehost', {
|
const rehostResp = await fetch('/api/v2/scroller/rehost', {
|
||||||
@@ -295,6 +312,15 @@
|
|||||||
const infoEl = document.querySelector("#a_info");
|
const infoEl = document.querySelector("#a_info");
|
||||||
if (infoEl) infoEl.dataset.itemId = localId;
|
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) {
|
if (chanRehostBtn) {
|
||||||
chanRehostBtn.classList.add('rehosted');
|
chanRehostBtn.classList.add('rehosted');
|
||||||
chanRehostBtn.outerHTML = `
|
chanRehostBtn.outerHTML = `
|
||||||
@@ -309,9 +335,7 @@
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[CHAN-FAV] Auto-rehost error:', err);
|
console.error('[CHAN-FAV] Auto-rehost error:', err);
|
||||||
favoBtns.forEach(btn => {
|
setFavoUi(wasAlreadyFav);
|
||||||
btn.className = 'iconset fa-regular fa-heart';
|
|
||||||
});
|
|
||||||
if (typeof window.flashMessage === 'function') {
|
if (typeof window.flashMessage === 'function') {
|
||||||
window.flashMessage('Failed to favorite post', 3000, 'error');
|
window.flashMessage('Failed to favorite post', 3000, 'error');
|
||||||
}
|
}
|
||||||
@@ -320,11 +344,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ctx = getContext();
|
const ctx = getContext();
|
||||||
if (!ctx) return;
|
if (!ctx) {
|
||||||
|
setFavoUi(wasAlreadyFav);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const postid = localId ? Number(localId) : ctx.postid;
|
const postid = localId ? Number(localId) : ctx.postid;
|
||||||
|
|
||||||
const wasAlreadyFav = favoBtns.length > 0 && favoBtns[0].classList.contains('fa-solid');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await post('/api/v2/togglefav', {
|
const res = await post('/api/v2/togglefav', {
|
||||||
postid: postid,
|
postid: postid,
|
||||||
@@ -337,10 +362,7 @@
|
|||||||
// Authoritative server state takes priority; fallback to inverting previous UI state
|
// Authoritative server state takes priority; fallback to inverting previous UI state
|
||||||
const isNowFav = (res.favorited !== undefined) ? !!res.favorited : !wasAlreadyFav;
|
const isNowFav = (res.favorited !== undefined) ? !!res.favorited : !wasAlreadyFav;
|
||||||
|
|
||||||
favoBtns.forEach(favoBtn => {
|
setFavoUi(isNowFav);
|
||||||
favoBtn.classList.toggle('fa-solid', isNowFav);
|
|
||||||
favoBtn.classList.toggle('fa-regular', !isNowFav);
|
|
||||||
});
|
|
||||||
|
|
||||||
// span#favs
|
// span#favs
|
||||||
const favcontainer = document.querySelector('#favs');
|
const favcontainer = document.querySelector('#favs');
|
||||||
@@ -412,12 +434,14 @@
|
|||||||
if (navigator.vibrate) navigator.vibrate(50);
|
if (navigator.vibrate) navigator.vibrate(50);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
setFavoUi(wasAlreadyFav);
|
||||||
const errMsg = (res && (res.msg || res.error)) || 'Anonymous favoriting is disabled.';
|
const errMsg = (res && (res.msg || res.error)) || 'Anonymous favoriting is disabled.';
|
||||||
if (typeof window.flashMessage === 'function') {
|
if (typeof window.flashMessage === 'function') {
|
||||||
window.flashMessage(errMsg, 3000, 'error');
|
window.flashMessage(errMsg, 3000, 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
setFavoUi(wasAlreadyFav);
|
||||||
if (typeof window.flashMessage === 'function') {
|
if (typeof window.flashMessage === 'function') {
|
||||||
window.flashMessage('Failed to update favorite.', 3000, 'error');
|
window.flashMessage('Failed to update favorite.', 3000, 'error');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1476,7 +1476,7 @@ const f0cklib = {
|
|||||||
]);
|
]);
|
||||||
console.log(`[GETF0CK_OPT] All queries finished in ${Date.now() - runTimings}ms`);
|
console.log(`[GETF0CK_OPT] All queries finished in ${Date.now() - runTimings}ms`);
|
||||||
|
|
||||||
const link = lib.genLink({ user, tag, hall: (hall && typeof hall === 'object') ? hall.slug : hall, mime, type: fav ? 'favs' : 'uploads', path: '', strict: false });
|
const link = lib.genLink({ user, tag, hall: (hall && typeof hall === 'object') ? hall.slug : hall, mime, type: fav ? 'favs' : 'uploads', path: '', strict: strict });
|
||||||
// Override link for title searches — pagination must use the /tag/title:... prefix
|
// Override link for title searches — pagination must use the /tag/title:... prefix
|
||||||
if (isTitleSearch && titleQuery) {
|
if (isTitleSearch && titleQuery) {
|
||||||
link.main = `/tag/title:${encodeURIComponent(titleQuery)}/`;
|
link.main = `/tag/title:${encodeURIComponent(titleQuery)}/`;
|
||||||
|
|||||||
+26
-11
@@ -170,13 +170,19 @@ export default (router, tpl) => {
|
|||||||
cdnUrls.push(`http://i.4cdn.org/${board}/${p.tim}${ext}`);
|
cdnUrls.push(`http://i.4cdn.org/${board}/${p.tim}${ext}`);
|
||||||
cdnUrls.push(`/api/v2/scroller/external/4chan/${board}/media/${p.tim}${ext}`);
|
cdnUrls.push(`/api/v2/scroller/external/4chan/${board}/media/${p.tim}${ext}`);
|
||||||
});
|
});
|
||||||
|
const rehostMap = {};
|
||||||
if (cdnUrls.length > 0) {
|
if (cdnUrls.length > 0) {
|
||||||
try {
|
try {
|
||||||
const rows = await db`SELECT id, src FROM items WHERE src = ANY(${cdnUrls})`;
|
const rows = await db`SELECT id, src, stamp FROM items WHERE src = ANY(${cdnUrls})`;
|
||||||
rows.forEach(r => {
|
rows.forEach(r => {
|
||||||
|
const info = { id: r.id, stamp: r.stamp };
|
||||||
rehosts[r.src] = r.id;
|
rehosts[r.src] = r.id;
|
||||||
|
rehostMap[r.src] = info;
|
||||||
const m = r.src.match(/(\d{13,20})/);
|
const m = r.src.match(/(\d{13,20})/);
|
||||||
if (m) rehosts[m[1]] = r.id;
|
if (m) {
|
||||||
|
rehosts[m[1]] = r.id;
|
||||||
|
rehostMap[m[1]] = info;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[CHAN] Rehost check error:', e.message);
|
console.error('[CHAN] Rehost check error:', e.message);
|
||||||
@@ -199,7 +205,10 @@ export default (router, tpl) => {
|
|||||||
const isVideo = ['.webm', '.mp4'].includes(ext);
|
const isVideo = ['.webm', '.mp4'].includes(ext);
|
||||||
const isImage = ['.jpg', '.jpeg', '.png', '.gif', '.webp'].includes(ext);
|
const isImage = ['.jpg', '.jpeg', '.png', '.gif', '.webp'].includes(ext);
|
||||||
const externalMediaUrl = p.tim ? `https://i.4cdn.org/${board}/${p.tim}${ext}` : null;
|
const externalMediaUrl = p.tim ? `https://i.4cdn.org/${board}/${p.tim}${ext}` : null;
|
||||||
const localId = (p.tim && rehosts[p.tim]) || (externalMediaUrl && rehosts[externalMediaUrl]) || null;
|
const rehostInfo = (p.tim && rehostMap[p.tim]) || (externalMediaUrl && rehostMap[externalMediaUrl]) || null;
|
||||||
|
const localId = rehostInfo ? rehostInfo.id : null;
|
||||||
|
const effectiveTime = (localId && rehostInfo?.stamp) ? Number(rehostInfo.stamp) : p.time;
|
||||||
|
const isoStr = new Date(effectiveTime * 1000).toISOString();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
no: p.no,
|
no: p.no,
|
||||||
@@ -208,9 +217,10 @@ export default (router, tpl) => {
|
|||||||
com_formatted: formatComment(p.com || ''),
|
com_formatted: formatComment(p.com || ''),
|
||||||
name: p.name || 'Anonymous',
|
name: p.name || 'Anonymous',
|
||||||
trip: p.trip || '',
|
trip: p.trip || '',
|
||||||
time: p.time,
|
time: effectiveTime,
|
||||||
timeago: lib.timeAgo(p.time * 1000, req.lang || 'en'),
|
timeago: lib.timeAgo(effectiveTime * 1000, req.lang || 'en'),
|
||||||
date_str: p.now || new Date(p.time * 1000).toLocaleString(),
|
date_str: (localId && rehostInfo?.stamp) ? new Date(effectiveTime * 1000).toLocaleString() : (p.now || new Date(effectiveTime * 1000).toLocaleString()),
|
||||||
|
iso_str: isoStr,
|
||||||
has_media: !!(p.tim && p.ext),
|
has_media: !!(p.tim && p.ext),
|
||||||
tim: p.tim,
|
tim: p.tim,
|
||||||
ext: ext,
|
ext: ext,
|
||||||
@@ -240,6 +250,7 @@ export default (router, tpl) => {
|
|||||||
first_media_no: firstMediaPost ? firstMediaPost.no : null,
|
first_media_no: firstMediaPost ? firstMediaPost.no : null,
|
||||||
rehosts_count: Object.keys(rehosts).length,
|
rehosts_count: Object.keys(rehosts).length,
|
||||||
session: req.session ? { ...req.session } : false,
|
session: req.session ? { ...req.session } : false,
|
||||||
|
csrf_token: req.session?.csrf_token || '',
|
||||||
domain: cfg.main.url.domain,
|
domain: cfg.main.url.domain,
|
||||||
tmp: null,
|
tmp: null,
|
||||||
page_meta: {
|
page_meta: {
|
||||||
@@ -306,7 +317,7 @@ export default (router, tpl) => {
|
|||||||
const rehosts = {};
|
const rehosts = {};
|
||||||
if (cdnUrls.length > 0) {
|
if (cdnUrls.length > 0) {
|
||||||
try {
|
try {
|
||||||
const rows = await db`SELECT id, src FROM items WHERE src = ANY(${cdnUrls})`;
|
const rows = await db`SELECT id, src, stamp FROM items WHERE src = ANY(${cdnUrls})`;
|
||||||
rows.forEach(r => {
|
rows.forEach(r => {
|
||||||
rehosts[r.src] = r.id;
|
rehosts[r.src] = r.id;
|
||||||
const m = r.src.match(/(\d{13,20})/);
|
const m = r.src.match(/(\d{13,20})/);
|
||||||
@@ -333,13 +344,15 @@ export default (router, tpl) => {
|
|||||||
let localDest = null;
|
let localDest = null;
|
||||||
let localThumb = null;
|
let localThumb = null;
|
||||||
let localTitle = null;
|
let localTitle = null;
|
||||||
|
let localStamp = null;
|
||||||
let itemFavorites = [];
|
let itemFavorites = [];
|
||||||
let userHasFavorited = false;
|
let userHasFavorited = false;
|
||||||
if (localId) {
|
if (localId) {
|
||||||
try {
|
try {
|
||||||
const localItem = await db`SELECT id, username, dest, title, mime, width, height FROM items WHERE id = ${localId} LIMIT 1`;
|
const localItem = await db`SELECT id, username, dest, title, mime, width, height, stamp FROM items WHERE id = ${localId} LIMIT 1`;
|
||||||
if (localItem.length > 0) {
|
if (localItem.length > 0) {
|
||||||
const li = localItem[0];
|
const li = localItem[0];
|
||||||
|
localStamp = li.stamp;
|
||||||
if (li.title) localTitle = li.title;
|
if (li.title) localTitle = li.title;
|
||||||
if (li.dest) {
|
if (li.dest) {
|
||||||
localDest = `/b/${li.dest}`;
|
localDest = `/b/${li.dest}`;
|
||||||
@@ -368,6 +381,7 @@ export default (router, tpl) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Construct item matching f0ckm's standard item view expectations
|
// Construct item matching f0ckm's standard item view expectations
|
||||||
|
const effectiveStamp = (localId && localStamp) ? Number(localStamp) : targetPost.time;
|
||||||
const item = {
|
const item = {
|
||||||
id: targetPost.no,
|
id: targetPost.no,
|
||||||
slug: null,
|
slug: null,
|
||||||
@@ -379,10 +393,10 @@ export default (router, tpl) => {
|
|||||||
height: targetPost.h || null,
|
height: targetPost.h || null,
|
||||||
size: targetPost.fsize ? lib.formatSize(targetPost.fsize) : '0 B',
|
size: targetPost.fsize ? lib.formatSize(targetPost.fsize) : '0 B',
|
||||||
username: targetPost.name || 'Anonymous',
|
username: targetPost.name || 'Anonymous',
|
||||||
stamp: targetPost.time,
|
stamp: effectiveStamp,
|
||||||
timestamp: {
|
timestamp: {
|
||||||
timeago: lib.timeAgo(targetPost.time * 1000, req.lang || 'en'),
|
timeago: lib.timeAgo(effectiveStamp * 1000, req.lang || 'en'),
|
||||||
timefull: new Date(targetPost.time * 1000).toISOString()
|
timefull: new Date(effectiveStamp * 1000).toISOString()
|
||||||
},
|
},
|
||||||
comment: targetPost.com ? targetPost.com.replace(/<br\s*\/?>/gi, '\n').replace(/<[^>]+>/g, '') : '',
|
comment: targetPost.com ? targetPost.com.replace(/<br\s*\/?>/gi, '\n').replace(/<[^>]+>/g, '') : '',
|
||||||
is_chan: true,
|
is_chan: true,
|
||||||
@@ -466,6 +480,7 @@ export default (router, tpl) => {
|
|||||||
link,
|
link,
|
||||||
chan_thread: chanThreadMeta,
|
chan_thread: chanThreadMeta,
|
||||||
session: req.session ? { ...req.session } : false,
|
session: req.session ? { ...req.session } : false,
|
||||||
|
csrf_token: req.session?.csrf_token || '',
|
||||||
domain: cfg.main.url.domain,
|
domain: cfg.main.url.domain,
|
||||||
tmp: null,
|
tmp: null,
|
||||||
hidePagination: true,
|
hidePagination: true,
|
||||||
|
|||||||
+36
-19
@@ -618,7 +618,7 @@ export default (router) => {
|
|||||||
return res.reply({ code: 401, body: JSON.stringify({ success: false, msg: 'Unauthorized' }) });
|
return res.reply({ code: 401, body: JSON.stringify({ success: false, msg: 'Unauthorized' }) });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { item_id, rating, tags, comment } = req.post || {};
|
const { item_id, rating, tags, comment } = req.post || req.body || {};
|
||||||
const itemId = parseInt(item_id, 10);
|
const itemId = parseInt(item_id, 10);
|
||||||
if (!itemId || isNaN(itemId)) {
|
if (!itemId || isNaN(itemId)) {
|
||||||
return res.reply({ code: 400, body: JSON.stringify({ success: false, msg: 'Valid item_id is required' }) });
|
return res.reply({ code: 400, body: JSON.stringify({ success: false, msg: 'Valid item_id is required' }) });
|
||||||
@@ -636,11 +636,12 @@ export default (router) => {
|
|||||||
return res.reply({ code: 403, body: JSON.stringify({ success: false, msg: 'Unauthorized' }) });
|
return res.reply({ code: 403, body: JSON.stringify({ success: false, msg: 'Unauthorized' }) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
// 1. Update Rating if provided
|
// 1. Update Rating if provided
|
||||||
if (rating !== undefined && rating !== null && rating !== '') {
|
if (rating !== undefined && rating !== null && rating !== '') {
|
||||||
const r = String(rating).toLowerCase().trim();
|
const r = String(rating).toLowerCase().trim();
|
||||||
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);
|
|
||||||
|
|
||||||
await db`
|
await db`
|
||||||
DELETE FROM tags_assign
|
DELETE FROM tags_assign
|
||||||
@@ -658,22 +659,30 @@ export default (router) => {
|
|||||||
// If r === 'untagged', no rating tag is inserted
|
// If r === 'untagged', no rating tag is inserted
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Add Tags if provided
|
// 2. Sync Tags if provided
|
||||||
const rawList = Array.isArray(tags) ? tags : (typeof tags === 'string' ? tags.split(',') : []);
|
if (tags !== undefined && tags !== null) {
|
||||||
const cleanTags = rawList.map(t => t.trim()).filter(t => t.length > 0 && !['sfw', 'nsfw', 'nsfl'].includes(t.toLowerCase()));
|
const rawList = Array.isArray(tags) ? tags : (typeof tags === 'string' ? tags.split(',') : []);
|
||||||
for (const tagName of cleanTags) {
|
const cleanTags = rawList.map(t => t.trim()).filter(t => t.length > 0 && !['sfw', 'nsfw', 'nsfl'].includes(t.toLowerCase()));
|
||||||
let tagRow = await db`SELECT id FROM tags WHERE normalized = slugify(${tagName}) LIMIT 1`;
|
|
||||||
if (tagRow.length === 0) {
|
// Delete existing non-rating tags for this item by this user (or all non-rating tags if owner)
|
||||||
await db`INSERT INTO tags ${db({ tag: tagName }, 'tag')} ON CONFLICT DO NOTHING`;
|
await db`
|
||||||
tagRow = await db`SELECT id FROM tags WHERE normalized = slugify(${tagName}) LIMIT 1`;
|
DELETE FROM tags_assign
|
||||||
|
WHERE item_id = ${itemId}
|
||||||
|
AND tag_id NOT IN (1, 2, ${nsflId})
|
||||||
|
AND tag_id NOT IN (SELECT id FROM tags WHERE normalized IN ('sfw', 'nsfw', 'nsfl'))
|
||||||
|
${isOwner ? db`` : db`AND user_id = ${session.id}`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
for (const tagName of cleanTags) {
|
||||||
|
let tagRow = await db`SELECT id FROM tags WHERE normalized = slugify(${tagName}) LIMIT 1`;
|
||||||
|
if (tagRow.length === 0) {
|
||||||
|
await db`INSERT INTO tags ${db({ tag: tagName }, 'tag')} ON CONFLICT DO NOTHING`;
|
||||||
|
tagRow = await db`SELECT id FROM tags WHERE normalized = slugify(${tagName}) LIMIT 1`;
|
||||||
|
}
|
||||||
|
if (tagRow.length) {
|
||||||
|
await db`INSERT INTO tags_assign ${db({ item_id: itemId, tag_id: tagRow[0].id, user_id: session.id })} ON CONFLICT DO NOTHING`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (tagRow.length) {
|
|
||||||
await db`INSERT INTO tags_assign ${db({ item_id: itemId, tag_id: tagRow[0].id, user_id: session.id })} ON CONFLICT DO NOTHING`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cleanTags.length > 0) {
|
|
||||||
const freshTags = await lib.getTags(itemId, session);
|
|
||||||
await db.notify('tags', JSON.stringify({ item_id: itemId, fresh: true, tags: freshTags }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Add Comment if provided
|
// 3. Add Comment if provided
|
||||||
@@ -689,7 +698,15 @@ export default (router) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const freshTags = await lib.getTags(itemId, session);
|
const freshTags = await lib.getTags(itemId, session);
|
||||||
|
await db.notify('tags', JSON.stringify({ item_id: itemId, fresh: true, tags: freshTags })).catch(() => {});
|
||||||
|
|
||||||
const ratingTag = freshTags.find(t => ['sfw', 'nsfw', 'nsfl'].includes(t.normalized));
|
const ratingTag = freshTags.find(t => ['sfw', 'nsfw', 'nsfl'].includes(t.normalized));
|
||||||
|
const cleanTagObjects = freshTags.filter(t => !['sfw', 'nsfw', 'nsfl'].includes(t.normalized)).map(t => ({
|
||||||
|
id: t.id,
|
||||||
|
tag: t.tag,
|
||||||
|
normalized: t.normalized,
|
||||||
|
badge: t.badge
|
||||||
|
}));
|
||||||
|
|
||||||
return res.reply({
|
return res.reply({
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
@@ -697,7 +714,7 @@ export default (router) => {
|
|||||||
success: true,
|
success: true,
|
||||||
item_id: itemId,
|
item_id: itemId,
|
||||||
rating: ratingTag ? ratingTag.normalized : 'untagged',
|
rating: ratingTag ? ratingTag.normalized : 'untagged',
|
||||||
tags: freshTags.filter(t => !['sfw', 'nsfw', 'nsfl'].includes(t.normalized)).map(t => t.tag)
|
tags: cleanTagObjects
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -665,17 +665,19 @@ export default (router, tpl) => {
|
|||||||
// Used when the user receives a live notification while already viewing that item.
|
// Used when the user receives a live notification while already viewing that item.
|
||||||
// System-type notifications (item_deleted, deny, report, admin_pending) are excluded —
|
// System-type notifications (item_deleted, deny, report, admin_pending) are excluded —
|
||||||
// they require explicit user acknowledgment.
|
// they require explicit user acknowledgment.
|
||||||
router.post(/\/api\/notifications\/item\/(?<itemId>\d+)\/read/, async (req, res) => {
|
router.post(/\/api\/notifications\/item\/(?<itemId>[a-zA-Z0-9_-]{11}|\d+)\/read/, async (req, res) => {
|
||||||
if (!req.session) return res.reply({ code: 401, body: JSON.stringify({ success: false }) });
|
if (!req.session?.id) return res.reply({ code: 401, body: JSON.stringify({ success: false }) });
|
||||||
const itemId = req.params.itemId;
|
const rawItemId = req.params.itemId;
|
||||||
|
const numericId = await f0cklib.resolveNumericItemId(rawItemId);
|
||||||
|
if (!numericId) return res.reply({ code: 404, body: JSON.stringify({ success: false }) });
|
||||||
const SYSTEM_TYPES = ['item_deleted', 'deny', 'admin_pending', 'report', 'warning'];
|
const SYSTEM_TYPES = ['item_deleted', 'deny', 'admin_pending', 'report', 'warning'];
|
||||||
console.log(`[NotificationRoute] Marking comment notifications for item ${itemId} as read for user ${req.session.id}`);
|
console.log(`[NotificationRoute] Marking notifications for item ${numericId} (${rawItemId}) as read for user ${req.session.id}`);
|
||||||
try {
|
try {
|
||||||
await db`
|
await db`
|
||||||
UPDATE notifications
|
UPDATE notifications
|
||||||
SET is_read = true
|
SET is_read = true
|
||||||
WHERE user_id = ${req.session.id}
|
WHERE user_id = ${req.session.id}
|
||||||
AND item_id = ${+itemId}
|
AND item_id = ${numericId}
|
||||||
AND NOT (type = ANY(${SYSTEM_TYPES}))
|
AND NOT (type = ANY(${SYSTEM_TYPES}))
|
||||||
`;
|
`;
|
||||||
return res.reply({
|
return res.reply({
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ export default (router, tpl) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Main tags page
|
// Main tags page
|
||||||
router.get(/^\/tags$/, async (req, res) => {
|
router.get(/^\/tags?\/?$/, async (req, res) => {
|
||||||
const phrase = cfg.websrv.phrases[~~(Math.random() * cfg.websrv.phrases.length)];
|
const phrase = cfg.websrv.phrases[~~(Math.random() * cfg.websrv.phrases.length)];
|
||||||
const mode = req.mode ?? 0;
|
const mode = req.mode ?? 0;
|
||||||
const query = req.url.qs || {};
|
const query = req.url.qs || {};
|
||||||
|
|||||||
@@ -268,6 +268,9 @@
|
|||||||
}
|
}
|
||||||
preview.src = data.url;
|
preview.src = data.url;
|
||||||
|
|
||||||
|
// Immediately update navbar brand logo
|
||||||
|
if (window.updateBrandImage) window.updateBrandImage(data.url);
|
||||||
|
|
||||||
// Enable remove button
|
// Enable remove button
|
||||||
removeBtn.disabled = false;
|
removeBtn.disabled = false;
|
||||||
removeBtn.style.cssText = 'background:rgba(220,53,69,0.15);border:1px solid rgba(220,53,69,0.4);color:#dc3545;padding:7px 16px;border-radius:4px;cursor:pointer;font-size:0.82em;font-weight:bold;transition:background 0.2s;';
|
removeBtn.style.cssText = 'background:rgba(220,53,69,0.15);border:1px solid rgba(220,53,69,0.4);color:#dc3545;padding:7px 16px;border-radius:4px;cursor:pointer;font-size:0.82em;font-weight:bold;transition:background 0.2s;';
|
||||||
@@ -310,6 +313,9 @@
|
|||||||
const wrap = document.getElementById('brand-preview-wrap');
|
const wrap = document.getElementById('brand-preview-wrap');
|
||||||
wrap.innerHTML = '<span id="brand-preview" style="font-size:0.75em;color:#666;font-style:italic;">No image set</span>';
|
wrap.innerHTML = '<span id="brand-preview" style="font-size:0.75em;color:#666;font-style:italic;">No image set</span>';
|
||||||
|
|
||||||
|
// Immediately update navbar brand logo (revert to text)
|
||||||
|
if (window.updateBrandImage) window.updateBrandImage(null);
|
||||||
|
|
||||||
// Disable remove button
|
// Disable remove button
|
||||||
removeBtn.disabled = true;
|
removeBtn.disabled = true;
|
||||||
removeBtn.style.cssText = 'background:rgba(220,53,69,0.05);border:1px solid rgba(220,53,69,0.15);color:#884040;padding:7px 16px;border-radius:4px;cursor:not-allowed;font-size:0.82em;font-weight:bold;';
|
removeBtn.style.cssText = 'background:rgba(220,53,69,0.05);border:1px solid rgba(220,53,69,0.15);color:#884040;padding:7px 16px;border-radius:4px;cursor:not-allowed;font-size:0.82em;font-weight:bold;';
|
||||||
|
|||||||
+35
-9
@@ -64,7 +64,7 @@
|
|||||||
@if(p.trip)
|
@if(p.trip)
|
||||||
<span class="chan-post-trip">{{ p.trip }}</span>
|
<span class="chan-post-trip">{{ p.trip }}</span>
|
||||||
@endif
|
@endif
|
||||||
<time class="chan-post-time" title="{{ p.date_str }}">{{ p.timeago }}</time>
|
<time class="chan-post-time timeago" data-iso="{{ p.iso_str }}" tooltip="{{ p.iso_str }}" title="{{ p.date_str }}">{{ p.timeago }}</time>
|
||||||
<a href="#p{{ p.no }}" class="chan-post-no">No. {{ p.no }}</a>
|
<a href="#p{{ p.no }}" class="chan-post-no">No. {{ p.no }}</a>
|
||||||
@if(p.local_id)
|
@if(p.local_id)
|
||||||
<a href="/{{ p.local_id }}" target="_blank" class="chan-post-rehosted-pill" title="Already rehosted on f0ckm as Post #{{ p.local_id }}">
|
<a href="/{{ p.local_id }}" target="_blank" class="chan-post-rehosted-pill" title="Already rehosted on f0ckm as Post #{{ p.local_id }}">
|
||||||
@@ -674,6 +674,20 @@
|
|||||||
'<i class="fa-solid fa-check"></i> Rehosted #' + data.item_id + '</a>' +
|
'<i class="fa-solid fa-check"></i> Rehosted #' + data.item_id + '</a>' +
|
||||||
'<button type="button" class="chan-rehost-edit-btn" data-item-id="' + data.item_id + '" title="Edit rating, tags & comment" style="padding:4px 7px;border-radius:4px;background:rgba(255,255,255,0.1);color:#fff;border:1px solid rgba(255,255,255,0.2);cursor:pointer;font-size:0.75rem;">' +
|
'<button type="button" class="chan-rehost-edit-btn" data-item-id="' + data.item_id + '" title="Edit rating, tags & comment" style="padding:4px 7px;border-radius:4px;background:rgba(255,255,255,0.1);color:#fff;border:1px solid rgba(255,255,255,0.2);cursor:pointer;font-size:0.75rem;">' +
|
||||||
'<i class="fa-solid fa-pen"></i></button></span>';
|
'<i class="fa-solid fa-pen"></i></button></span>';
|
||||||
|
|
||||||
|
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') {
|
if (typeof window.showToast === 'function') {
|
||||||
window.showToast(data.repost ? 'Already on site — linked to existing post' : 'Rehosted successfully!');
|
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');
|
const editBtn = e.target.closest('.chan-rehost-edit-btn');
|
||||||
if (editBtn) {
|
if (editBtn) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
const itemId = editBtn.dataset.itemId;
|
const itemId = editBtn.dataset.itemId;
|
||||||
if (itemId && typeof window.openChanRehostModal === 'function') {
|
if (itemId && typeof window.openChanRehostModal === 'function') {
|
||||||
window.openChanRehostModal({ itemId: Number(itemId), isPlayerView: false });
|
window.openChanRehostModal({ itemId: Number(itemId), isPlayerView: false });
|
||||||
@@ -762,14 +777,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add thumb badge if missing
|
// Add thumb badge if missing
|
||||||
const postCard = favBtn.closest('.chan-post');
|
const postCard = document.getElementById('p' + postNo) || favBtn.closest('.chan-post-card');
|
||||||
const thumbLink = postCard?.querySelector('.chan-thumb-link');
|
if (postCard) {
|
||||||
if (thumbLink && !thumbLink.querySelector('.chan-thumb-rehost-badge')) {
|
const thumbLink = postCard.querySelector('.chan-thumb-link');
|
||||||
const badge = document.createElement('span');
|
if (thumbLink && !thumbLink.querySelector('.chan-thumb-rehost-badge')) {
|
||||||
badge.className = 'chan-thumb-rehost-badge';
|
const badge = document.createElement('span');
|
||||||
badge.title = 'Rehosted on f0ckm as Post #' + localId;
|
badge.className = 'chan-thumb-rehost-badge';
|
||||||
badge.innerHTML = '<i class="fa-solid fa-check"></i> #' + localId;
|
badge.title = 'Rehosted on f0ckm as Post #' + localId;
|
||||||
thumbLink.appendChild(badge);
|
badge.innerHTML = '<i class="fa-solid fa-check"></i> #' + 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 {
|
} else {
|
||||||
throw new Error(rehostData.msg || 'Rehost failed');
|
throw new Error(rehostData.msg || 'Rehost failed');
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{{-- LEGACY LAYOUT — 2-column (main + fixed right sidebar) --}}
|
{{-- LEGACY LAYOUT — 2-column (main + fixed right sidebar) --}}
|
||||||
{{-- Used by: members with use_new_layout = false --}}
|
{{-- Used by: members with use_new_layout = false --}}
|
||||||
<div class="item-layout-container" data-item-id="{{ item.id }}" data-requested-subf0ck="{{ item.requested_subf0ck_slug || '' }}">
|
<div class="item-layout-container" data-item-id="{{ item.local_id || item.id }}" data-requested-subf0ck="{{ item.requested_subf0ck_slug || '' }}">
|
||||||
|
|
||||||
{{-- MAIN CONTENT: media + navigation + metadata + comments --}}
|
{{-- MAIN CONTENT: media + navigation + metadata + comments --}}
|
||||||
<div class="item-main-content">
|
<div class="item-main-content">
|
||||||
|
|
||||||
<div class="_204863">
|
<div class="_204863">
|
||||||
<div class="location">@if(item.is_chan)<a href="/4/{{ item.external_board }}/catalogue" class="location-link" title="Board /{{ item.external_board }}/ catalogue">/4</a><a href="/4/{{ item.external_board }}/catalogue" class="location-link" title="Board /{{ item.external_board }}/ catalogue">/{{ item.external_board }}</a><a href="/4/{{ item.external_board }}/{{ item.external_tid }}" class="location-link" title="Thread #{{ item.external_tid }} overview">/{{ item.external_tid }}</a><a href="/4/{{ item.external_board }}/{{ item.external_tid }}/{{ item.external_id }}" class="location-link" data-inherit-context="false" title="Post #{{ item.external_id }}">/{{ item.external_id }}</a>@if(item.local_id)<span class="location-sep" style="opacity: 0.6; margin: 0 4px;">→</span><a href="/{{ item.local_id }}" class="location-link is-rehosted" title="View rehosted post #{{ item.local_id }} on f0ckm" style="color: #4ade80; font-weight: 700;"><i class="fa-solid fa-check"></i> #{{ item.local_id }}</a>@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</div>
|
<div class="location">@if(item.is_chan)<a href="/4/{{ item.external_board }}/catalogue" class="location-link" title="Board /{{ item.external_board }}/ catalogue">/4</a><a href="/4/{{ item.external_board }}/catalogue" class="location-link" title="Board /{{ item.external_board }}/ catalogue">/{{ item.external_board }}</a><a href="/4/{{ item.external_board }}/{{ item.external_tid }}" class="location-link" title="Thread #{{ item.external_tid }} overview">/{{ item.external_tid }}</a><a href="/4/{{ item.external_board }}/{{ item.external_tid }}/{{ item.external_id }}" class="location-link" data-inherit-context="false" title="Post #{{ item.external_id }}">/{{ item.external_id }}</a>@if(item.local_id)<span class="location-sep" style="opacity: 0.6; margin: 0 4px;">→</span><a href="/{{ item.local_id }}" class="location-link is-rehosted" title="View rehosted post #{{ item.local_id }} on f0ckm" style="color: #4ade80; font-weight: 700;"><i class="fa-solid fa-check"></i> #{{ item.local_id }}</a>@endif@elseif(tmp && tmp.tag)<a href="/tags" class="location-link" title="All tags">/tag</a><a href="/tag/{{ encodeURIComponent(tmp.tag) }}{{ link.suffix || '' }}" class="location-link" title="Tag: {{ tmp.tag }}">/{{ tmp.tag }}</a><a href="/tag/{{ encodeURIComponent(tmp.tag) }}/{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}{{ link.suffix || '' }}{{ item.requested_subf0ck_slug ? ('#' + item.requested_subf0ck_slug) : '' }}" class="location-link" title="Post #{{ item.id }}">/{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}{{ item.requested_subf0ck_slug ? ('#' + item.requested_subf0ck_slug) : '' }}</a>@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</div>
|
||||||
<div class="gapLeft"></div>
|
<div class="gapLeft"></div>
|
||||||
</div>
|
</div>
|
||||||
@if(enable_item_title)
|
@if(enable_item_title)
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<span class="item_title_text@if(!item.title) no-title@endif"@if(can_manage_item) title="Click to edit"@endif>@if(item.title){!! item.title !!}@elseif(can_manage_item)<em>Add a title</em>@endif</span>
|
<span class="item_title_text@if(!item.title) no-title@endif"@if(can_manage_item) title="Click to edit"@endif>@if(item.title){!! item.title !!}@elseif(can_manage_item)<em>Add a title</em>@endif</span>
|
||||||
@if(can_manage_item)
|
@if(can_manage_item)
|
||||||
<div class="info-title-edit-wrap" style="display:none;">
|
<div class="info-title-edit-wrap" style="display:none;">
|
||||||
<input type="text" id="info-title-input" class="info-title-input" value="{!! item.title || '' !!}" placeholder="Add a title…" maxlength="500" data-item-id="{{ item.id }}" />
|
<input type="text" id="info-title-input" class="info-title-input" value="{!! item.title || '' !!}" placeholder="Add a title…" maxlength="500" data-item-id="{{ item.local_id || item.id }}" />
|
||||||
<button type="button" id="info-title-save" class="info-title-save-btn" title="Save Title"><i class="fa-solid fa-check"></i></button>
|
<button type="button" id="info-title-save" class="info-title-save-btn" title="Save Title"><i class="fa-solid fa-check"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<span id="info-title-status" class="info-title-status" style="display:none"></span>
|
<span id="info-title-status" class="info-title-status" style="display:none"></span>
|
||||||
@@ -129,11 +129,11 @@
|
|||||||
@if(item.is_chan)
|
@if(item.is_chan)
|
||||||
@if(item.local_id)
|
@if(item.local_id)
|
||||||
<span class="chan-rehosted-wrap" style="display:inline-flex;gap:4px;align-items:center;">
|
<span class="chan-rehosted-wrap" style="display:inline-flex;gap:4px;align-items:center;">
|
||||||
<a href="/{{ item.local_id }}" class="chan-rehost-action-btn rehosted" title="Already rehosted on f0ckm (Post #{{ item.local_id }})" style="display:inline-flex;align-items:center;gap:5px;padding:3px 9px;border-radius:6px;font-size:0.8rem;background:rgba(74,222,128,0.2);color:#4ade80;border:1px solid rgba(74,222,128,0.4);text-decoration:none;font-weight:600;"><i class="fa-solid fa-check"></i> #{{ item.local_id }}</a>
|
<a href="/{{ item.local_id }}" class="chan-rehost-action-btn rehosted" title="Already rehosted on f0ckm (Post #{{ item.local_id }})" style="display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:4px 12px;border-radius:6px;font-size:0.8rem;background:rgba(74,222,128,0.2);color:#4ade80;border:1px solid rgba(74,222,128,0.4);text-decoration:none;font-weight:600;white-space:nowrap;flex-shrink:0;"><i class="fa-solid fa-check"></i> #{{ item.local_id }}</a>
|
||||||
<button type="button" class="chan-rehost-edit-btn" data-item-id="{{ item.local_id }}" title="Edit rating, tags & comment" style="display:inline-flex;align-items:center;padding:3px 7px;border-radius:6px;font-size:0.8rem;background:rgba(255,255,255,0.1);color:#fff;border:1px solid rgba(255,255,255,0.2);cursor:pointer;"><i class="fa-solid fa-pen"></i></button>
|
<button type="button" class="chan-rehost-edit-btn" data-item-id="{{ item.local_id }}" title="Edit rating, tags & comment" style="display:inline-flex;align-items:center;padding:3px 7px;border-radius:6px;font-size:0.8rem;background:rgba(255,255,255,0.1);color:#fff;border:1px solid rgba(255,255,255,0.2);cursor:pointer;"><i class="fa-solid fa-pen"></i></button>
|
||||||
</span>
|
</span>
|
||||||
@else
|
@else
|
||||||
<button type="button" class="chan-rehost-action-btn" id="chan-item-rehost-btn" data-url="{{ item.external_media_url }}" data-board="{{ item.external_board }}" data-tid="{{ item.external_tid }}" data-post="{{ item.external_id }}" data-filename="{{ item.original_filename || '' }}" data-width="{{ item.width || '' }}" data-height="{{ item.height || '' }}" title="Rehost to f0ckm" style="display:inline-flex;align-items:center;gap:5px;padding:3px 9px;border-radius:6px;font-size:0.8rem;background:rgba(74,222,128,0.15);color:#4ade80;border:1px solid rgba(74,222,128,0.3);cursor:pointer;font-weight:600;"><i class="fa-solid fa-cloud-arrow-up"></i> Rehost</button>
|
<button type="button" class="chan-rehost-action-btn" id="chan-item-rehost-btn" data-url="{{ item.external_media_url }}" data-board="{{ item.external_board }}" data-tid="{{ item.external_tid }}" data-post="{{ item.external_id }}" data-filename="{{ item.original_filename || '' }}" data-width="{{ item.width || '' }}" data-height="{{ item.height || '' }}" title="Rehost to f0ckm" style="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;background:rgba(74,222,128,0.15);color:#4ade80;border:1px solid rgba(74,222,128,0.3);cursor:pointer;font-weight:600;"><i class="fa-solid fa-cloud-arrow-up"></i> Rehost</button>
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
@if(session)
|
@if(session)
|
||||||
@@ -190,9 +190,9 @@
|
|||||||
@if(!user_alternative_infobox && item.is_chan) — <span class="badge badge-dark chan-post-badge" style="background: rgba(74, 222, 128, 0.15); border: 1px solid rgba(74, 222, 128, 0.3); display: inline-flex; align-items: center; gap: 6px;">
|
@if(!user_alternative_infobox && item.is_chan) — <span class="badge badge-dark chan-post-badge" style="background: rgba(74, 222, 128, 0.15); border: 1px solid rgba(74, 222, 128, 0.3); display: inline-flex; align-items: center; gap: 6px;">
|
||||||
<a href="/4/{{ item.external_board }}/{{ item.external_tid }}" style="color: #4ade80; text-decoration: none; font-weight: 600;" title="View thread overview"><i class="fa-solid fa-clover"></i> /{{ item.external_board }}/{{ item.external_tid }}</a>
|
<a href="/4/{{ item.external_board }}/{{ item.external_tid }}" style="color: #4ade80; text-decoration: none; font-weight: 600;" title="View thread overview"><i class="fa-solid fa-clover"></i> /{{ item.external_board }}/{{ item.external_tid }}</a>
|
||||||
@if(item.local_id)
|
@if(item.local_id)
|
||||||
<a href="/{{ item.local_id }}" class="chan-rehost-action-btn rehosted" title="Already rehosted on f0ckm (Post #{{ item.local_id }})" style="display:inline-flex;align-items:center;gap:4px;padding:2px 6px;border-radius:4px;font-size:0.75rem;background:rgba(74,222,128,0.2);color:#4ade80;border:1px solid rgba(74,222,128,0.4);text-decoration:none;font-weight:600;"><i class="fa-solid fa-check"></i> #{{ item.local_id }}</a>
|
<a href="/{{ item.local_id }}" class="chan-rehost-action-btn rehosted" title="Already rehosted on f0ckm (Post #{{ item.local_id }})" style="display:inline-flex;align-items:center;justify-content:center;gap:5px;padding:3px 10px;border-radius:4px;font-size:0.75rem;background:rgba(74,222,128,0.2);color:#4ade80;border:1px solid rgba(74,222,128,0.4);text-decoration:none;font-weight:600;white-space:nowrap;flex-shrink:0;"><i class="fa-solid fa-check"></i> #{{ item.local_id }}</a>
|
||||||
@else
|
@else
|
||||||
<button type="button" class="chan-rehost-action-btn" id="chan-item-rehost-btn" data-url="{{ item.external_media_url }}" data-board="{{ item.external_board }}" data-tid="{{ item.external_tid }}" data-post="{{ item.external_id }}" data-filename="{{ item.original_filename || '' }}" data-width="{{ item.width || '' }}" data-height="{{ item.height || '' }}" title="Rehost to f0ckm" style="display:inline-flex;align-items:center;gap:4px;padding:2px 6px;border-radius:4px;font-size:0.75rem;background:rgba(74,222,128,0.15);color:#4ade80;border:1px solid rgba(74,222,128,0.3);cursor:pointer;font-weight:600;"><i class="fa-solid fa-cloud-arrow-up"></i> Rehost</button>
|
<button type="button" class="chan-rehost-action-btn" id="chan-item-rehost-btn" data-url="{{ item.external_media_url }}" data-board="{{ item.external_board }}" data-tid="{{ item.external_tid }}" data-post="{{ item.external_id }}" data-filename="{{ item.original_filename || '' }}" data-width="{{ item.width || '' }}" data-height="{{ item.height || '' }}" title="Rehost to f0ckm" style="display:inline-flex;align-items:center;justify-content:center;gap:5px;padding:3px 12px;min-width:88px;box-sizing:border-box;white-space:nowrap;flex-shrink:0;border-radius:4px;font-size:0.75rem;background:rgba(74,222,128,0.15);color:#4ade80;border:1px solid rgba(74,222,128,0.3);cursor:pointer;font-weight:600;"><i class="fa-solid fa-cloud-arrow-up"></i> Rehost</button>
|
||||||
@endif
|
@endif
|
||||||
</span>@endif
|
</span>@endif
|
||||||
|
|
||||||
@@ -201,17 +201,17 @@
|
|||||||
</span>@endif</span>
|
</span>@endif</span>
|
||||||
|
|
||||||
|
|
||||||
<span class="badge badge-dark" id="tags" data-item-id="{{ item.id }}" data-can-manage="{{ can_manage_item ? 'true' : 'false' }}">
|
<span class="badge badge-dark" id="tags" data-item-id="{{ item.local_id || item.id }}" data-can-manage="{{ can_manage_item ? 'true' : 'false' }}">
|
||||||
<span class="tags-inner">
|
<span class="tags-inner">
|
||||||
@if(!item.is_sfw && !item.is_nsfw && !item.is_nsfl)
|
@if(!item.is_sfw && !item.is_nsfw && !item.is_nsfl)
|
||||||
<span class="badge badge-untagged rating-tag is-untagged @if(can_manage_item) can-cycle @endif" data-item-id="{{ item.id }}" data-rating="untagged">
|
<span class="badge badge-untagged rating-tag is-untagged @if(can_manage_item) can-cycle @endif" data-item-id="{{ item.local_id || item.id }}" data-rating="untagged">
|
||||||
<span class="rating-label">?</span>
|
<span class="rating-label">?</span>
|
||||||
</span>
|
</span>
|
||||||
@endif
|
@endif
|
||||||
@if(typeof item.tags !== "undefined")
|
@if(typeof item.tags !== "undefined")
|
||||||
@each(item.tags as tag)
|
@each(item.tags as tag)
|
||||||
@if(tag.normalized === 'sfw' || tag.normalized === 'nsfw' || tag.normalized === 'nsfl')
|
@if(tag.normalized === 'sfw' || tag.normalized === 'nsfw' || tag.normalized === 'nsfl')
|
||||||
<span class="badge {{ tag.badge }} rating-tag is-{{ tag.normalized }} @if(can_manage_item) can-cycle @endif" data-item-id="{{ item.id }}" data-rating="{{ tag.normalized }}">
|
<span class="badge {{ tag.badge }} rating-tag is-{{ tag.normalized }} @if(can_manage_item) can-cycle @endif" data-item-id="{{ item.local_id || item.id }}" data-rating="{{ tag.normalized }}">
|
||||||
<span class="rating-label">{!! tag.tag !!}</span>
|
<span class="rating-label">{!! tag.tag !!}</span>
|
||||||
</span>
|
</span>
|
||||||
@else
|
@else
|
||||||
@@ -256,7 +256,7 @@
|
|||||||
|
|
||||||
@if(enable_comments && (session || !hide_comments_from_public))
|
@if(enable_comments && (session || !hide_comments_from_public))
|
||||||
<div id="comments-container"
|
<div id="comments-container"
|
||||||
data-item-id="{{ item.id }}"
|
data-item-id="{{ item.local_id || item.id }}"
|
||||||
@if(session) data-user="{{ session.user }}" @endif
|
@if(session) data-user="{{ session.user }}" @endif
|
||||||
@if(session && (session.admin || session.is_moderator)) data-is-admin="true" @endif
|
@if(session && (session.admin || session.is_moderator)) data-is-admin="true" @endif
|
||||||
@if(item.is_comments_locked) data-is-locked="true" @endif>
|
@if(item.is_comments_locked) data-is-locked="true" @endif>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{{-- MODERN LAYOUT — 3-column grid --}}
|
{{-- MODERN LAYOUT — 3-column grid --}}
|
||||||
{{-- Used by: guests (no session) + members with use_new_layout = true --}}
|
{{-- Used by: guests (no session) + members with use_new_layout = true --}}
|
||||||
<div class="item-layout-container" data-item-id="{{ item.id }}" data-requested-subf0ck="{{ item.requested_subf0ck_slug || '' }}">
|
<div class="item-layout-container" data-item-id="{{ item.local_id || item.id }}" data-requested-subf0ck="{{ item.requested_subf0ck_slug || '' }}">
|
||||||
|
|
||||||
{{-- LEFT SIDEBAR: comments + tags --}}
|
{{-- LEFT SIDEBAR: comments + tags --}}
|
||||||
<div class="item-sidebar-left">
|
<div class="item-sidebar-left">
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
@if(enable_comments && (session || !hide_comments_from_public))
|
@if(enable_comments && (session || !hide_comments_from_public))
|
||||||
<div id="comments-container"
|
<div id="comments-container"
|
||||||
data-item-id="{{ item.id }}"
|
data-item-id="{{ item.local_id || item.id }}"
|
||||||
@if(session) data-user="{{ session.user }}" @endif
|
@if(session) data-user="{{ session.user }}" @endif
|
||||||
@if(session && is_mod_or_admin) data-is-admin="true" @endif
|
@if(session && is_mod_or_admin) data-is-admin="true" @endif
|
||||||
@if(item.is_comments_locked) data-is-locked="true" @endif>
|
@if(item.is_comments_locked) data-is-locked="true" @endif>
|
||||||
@@ -40,16 +40,16 @@
|
|||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div class="sidebar-tags-container">
|
<div class="sidebar-tags-container">
|
||||||
<span class="badge badge-dark" id="tags" data-item-id="{{ item.id }}" data-can-manage="{{ can_manage_item ? 'true' : 'false' }}" style="display: flex; flex-wrap: wrap; gap: 5px; background: transparent; padding: 0; text-align: left; white-space: normal;">
|
<span class="badge badge-dark" id="tags" data-item-id="{{ item.local_id || item.id }}" data-can-manage="{{ can_manage_item ? 'true' : 'false' }}" style="display: flex; flex-wrap: wrap; gap: 5px; background: transparent; padding: 0; text-align: left; white-space: normal;">
|
||||||
@if(!item.is_sfw && !item.is_nsfw && !item.is_nsfl)
|
@if(!item.is_sfw && !item.is_nsfw && !item.is_nsfl)
|
||||||
<span class="badge badge-untagged rating-tag is-untagged @if(can_manage_item) can-cycle @endif" data-item-id="{{ item.id }}" data-rating="untagged">
|
<span class="badge badge-untagged rating-tag is-untagged @if(can_manage_item) can-cycle @endif" data-item-id="{{ item.local_id || item.id }}" data-rating="untagged">
|
||||||
<span class="rating-label">?</span>
|
<span class="rating-label">?</span>
|
||||||
</span>
|
</span>
|
||||||
@endif
|
@endif
|
||||||
@if(typeof item.tags !== "undefined")
|
@if(typeof item.tags !== "undefined")
|
||||||
@each(item.tags as tag)
|
@each(item.tags as tag)
|
||||||
@if(tag.normalized === 'sfw' || tag.normalized === 'nsfw' || tag.normalized === 'nsfl')
|
@if(tag.normalized === 'sfw' || tag.normalized === 'nsfw' || tag.normalized === 'nsfl')
|
||||||
<span class="badge {{ tag.badge }} rating-tag is-{{ tag.normalized }} @if(can_manage_item) can-cycle @endif" data-item-id="{{ item.id }}" data-rating="{{ tag.normalized }}">
|
<span class="badge {{ tag.badge }} rating-tag is-{{ tag.normalized }} @if(can_manage_item) can-cycle @endif" data-item-id="{{ item.local_id || item.id }}" data-rating="{{ tag.normalized }}">
|
||||||
<span class="rating-label">{!! tag.tag !!}</span>
|
<span class="rating-label">{!! tag.tag !!}</span>
|
||||||
</span>
|
</span>
|
||||||
@else
|
@else
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
<div class="item-main-content">
|
<div class="item-main-content">
|
||||||
|
|
||||||
<div class="_204863">
|
<div class="_204863">
|
||||||
<div class="location">@if(item.is_chan)<a href="/4/{{ item.external_board }}/catalogue" class="location-link" title="Board /{{ item.external_board }}/ catalogue">/4</a><a href="/4/{{ item.external_board }}/catalogue" class="location-link" title="Board /{{ item.external_board }}/ catalogue">/{{ item.external_board }}</a><a href="/4/{{ item.external_board }}/{{ item.external_tid }}" class="location-link" title="Thread #{{ item.external_tid }} overview">/{{ item.external_tid }}</a><a href="/4/{{ item.external_board }}/{{ item.external_tid }}/{{ item.external_id }}" class="location-link" data-inherit-context="false" title="Post #{{ item.external_id }}">/{{ item.external_id }}</a>@if(item.local_id)<span class="location-sep" style="opacity: 0.6; margin: 0 4px;">→</span><a href="/{{ item.local_id }}" class="location-link is-rehosted" title="View rehosted post #{{ item.local_id }} on f0ckm" style="color: #4ade80; font-weight: 700;"><i class="fa-solid fa-check"></i> #{{ item.local_id }}</a>@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</div>
|
<div class="location">@if(item.is_chan)<a href="/4/{{ item.external_board }}/catalogue" class="location-link" title="Board /{{ item.external_board }}/ catalogue">/4</a><a href="/4/{{ item.external_board }}/catalogue" class="location-link" title="Board /{{ item.external_board }}/ catalogue">/{{ item.external_board }}</a><a href="/4/{{ item.external_board }}/{{ item.external_tid }}" class="location-link" title="Thread #{{ item.external_tid }} overview">/{{ item.external_tid }}</a><a href="/4/{{ item.external_board }}/{{ item.external_tid }}/{{ item.external_id }}" class="location-link" data-inherit-context="false" title="Post #{{ item.external_id }}">/{{ item.external_id }}</a>@if(item.local_id)<span class="location-sep" style="opacity: 0.6; margin: 0 4px;">→</span><a href="/{{ item.local_id }}" class="location-link is-rehosted" title="View rehosted post #{{ item.local_id }} on f0ckm" style="color: #4ade80; font-weight: 700;"><i class="fa-solid fa-check"></i> #{{ item.local_id }}</a>@endif@elseif(tmp && tmp.tag)<a href="/tags" class="location-link" title="All tags">/tag</a><a href="/tag/{{ encodeURIComponent(tmp.tag) }}{{ link.suffix || '' }}" class="location-link" title="Tag: {{ tmp.tag }}">/{{ tmp.tag }}</a><a href="/tag/{{ encodeURIComponent(tmp.tag) }}/{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}{{ link.suffix || '' }}{{ item.requested_subf0ck_slug ? ('#' + item.requested_subf0ck_slug) : '' }}" class="location-link" title="Post #{{ item.id }}">/{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}{{ item.requested_subf0ck_slug ? ('#' + item.requested_subf0ck_slug) : '' }}</a>@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</div>
|
||||||
<div class="gapLeft"></div>
|
<div class="gapLeft"></div>
|
||||||
</div>
|
</div>
|
||||||
@if(enable_item_title)
|
@if(enable_item_title)
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
<span class="item_title_text@if(!item.title) no-title@endif"@if(can_manage_item) title="Click to edit"@endif>@if(item.title){!! item.title !!}@elseif(can_manage_item)<em>Add a title</em>@endif</span>
|
<span class="item_title_text@if(!item.title) no-title@endif"@if(can_manage_item) title="Click to edit"@endif>@if(item.title){!! item.title !!}@elseif(can_manage_item)<em>Add a title</em>@endif</span>
|
||||||
@if(can_manage_item)
|
@if(can_manage_item)
|
||||||
<div class="info-title-edit-wrap" style="display:none;">
|
<div class="info-title-edit-wrap" style="display:none;">
|
||||||
<input type="text" id="info-title-input" class="info-title-input" value="{!! item.title || '' !!}" placeholder="Add a title…" maxlength="500" data-item-id="{{ item.id }}" />
|
<input type="text" id="info-title-input" class="info-title-input" value="{!! item.title || '' !!}" placeholder="Add a title…" maxlength="500" data-item-id="{{ item.local_id || item.id }}" />
|
||||||
<button type="button" id="info-title-save" class="info-title-save-btn" title="Save Title"><i class="fa-solid fa-check"></i></button>
|
<button type="button" id="info-title-save" class="info-title-save-btn" title="Save Title"><i class="fa-solid fa-check"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<span id="info-title-status" class="info-title-status" style="display:none"></span>
|
<span id="info-title-status" class="info-title-status" style="display:none"></span>
|
||||||
@@ -161,11 +161,11 @@
|
|||||||
@if(item.is_chan)
|
@if(item.is_chan)
|
||||||
@if(item.local_id)
|
@if(item.local_id)
|
||||||
<span class="chan-rehosted-wrap" style="display:inline-flex;gap:4px;align-items:center;">
|
<span class="chan-rehosted-wrap" style="display:inline-flex;gap:4px;align-items:center;">
|
||||||
<a href="/{{ item.local_id }}" class="chan-rehost-action-btn rehosted" title="Already rehosted on f0ckm (Post #{{ item.local_id }})" style="display:inline-flex;align-items:center;gap:5px;padding:3px 9px;border-radius:6px;font-size:0.8rem;background:rgba(74,222,128,0.2);color:#4ade80;border:1px solid rgba(74,222,128,0.4);text-decoration:none;font-weight:600;"><i class="fa-solid fa-check"></i> #{{ item.local_id }}</a>
|
<a href="/{{ item.local_id }}" class="chan-rehost-action-btn rehosted" title="Already rehosted on f0ckm (Post #{{ item.local_id }})" style="display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:4px 12px;border-radius:6px;font-size:0.8rem;background:rgba(74,222,128,0.2);color:#4ade80;border:1px solid rgba(74,222,128,0.4);text-decoration:none;font-weight:600;white-space:nowrap;flex-shrink:0;"><i class="fa-solid fa-check"></i> #{{ item.local_id }}</a>
|
||||||
<button type="button" class="chan-rehost-edit-btn" data-item-id="{{ item.local_id }}" title="Edit rating, tags & comment" style="display:inline-flex;align-items:center;padding:3px 7px;border-radius:6px;font-size:0.8rem;background:rgba(255,255,255,0.1);color:#fff;border:1px solid rgba(255,255,255,0.2);cursor:pointer;"><i class="fa-solid fa-pen"></i></button>
|
<button type="button" class="chan-rehost-edit-btn" data-item-id="{{ item.local_id }}" title="Edit rating, tags & comment" style="display:inline-flex;align-items:center;padding:3px 7px;border-radius:6px;font-size:0.8rem;background:rgba(255,255,255,0.1);color:#fff;border:1px solid rgba(255,255,255,0.2);cursor:pointer;"><i class="fa-solid fa-pen"></i></button>
|
||||||
</span>
|
</span>
|
||||||
@else
|
@else
|
||||||
<button type="button" class="chan-rehost-action-btn" id="chan-item-rehost-btn" data-url="{{ item.external_media_url }}" data-board="{{ item.external_board }}" data-tid="{{ item.external_tid }}" data-post="{{ item.external_id }}" data-filename="{{ item.original_filename || '' }}" data-width="{{ item.width || '' }}" data-height="{{ item.height || '' }}" title="Rehost to f0ckm" style="display:inline-flex;align-items:center;gap:5px;padding:3px 9px;border-radius:6px;font-size:0.8rem;background:rgba(74,222,128,0.15);color:#4ade80;border:1px solid rgba(74,222,128,0.3);cursor:pointer;font-weight:600;"><i class="fa-solid fa-cloud-arrow-up"></i> Rehost</button>
|
<button type="button" class="chan-rehost-action-btn" id="chan-item-rehost-btn" data-url="{{ item.external_media_url }}" data-board="{{ item.external_board }}" data-tid="{{ item.external_tid }}" data-post="{{ item.external_id }}" data-filename="{{ item.original_filename || '' }}" data-width="{{ item.width || '' }}" data-height="{{ item.height || '' }}" title="Rehost to f0ckm" style="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;background:rgba(74,222,128,0.15);color:#4ade80;border:1px solid rgba(74,222,128,0.3);cursor:pointer;font-weight:600;"><i class="fa-solid fa-cloud-arrow-up"></i> Rehost</button>
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
@if(session)
|
@if(session)
|
||||||
|
|||||||
@@ -506,7 +506,7 @@
|
|||||||
anon_anonymize: @if(anon_anonymize) true @else false @endif,
|
anon_anonymize: @if(anon_anonymize) true @else false @endif,
|
||||||
is_anonymized: @if(is_anonymized) true @else false @endif,
|
is_anonymized: @if(is_anonymized) true @else false @endif,
|
||||||
use_new_layout: @if(session)@if(session.use_new_layout) true @else false @endif@else @if(default_layout === 'legacy')false @else true @endif@endif,
|
use_new_layout: @if(session)@if(session.use_new_layout) true @else false @endif@else @if(default_layout === 'legacy')false @else true @endif@endif,
|
||||||
csrf_token: "{{ csrf_token }}",
|
csrf_token: "{{ csrf_token || (session && session.csrf_token) || '' }}",
|
||||||
default_theme: "{{ default_theme }}",
|
default_theme: "{{ default_theme }}",
|
||||||
private_society: @if(private_society) true @else false @endif,
|
private_society: @if(private_society) true @else false @endif,
|
||||||
show_content_warning: @if(show_content_warning) true @else false @endif,
|
show_content_warning: @if(show_content_warning) true @else false @endif,
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
@endif
|
@endif
|
||||||
<link rel="stylesheet" href="/s/css/upload.css?v={{ ts }}">
|
<link rel="stylesheet" href="/s/css/upload.css?v={{ ts }}">
|
||||||
@endif
|
@endif
|
||||||
<script>window.f0ckThemes = {{ themes_json }}; window.f0ckDefaultTheme = "{{ default_theme }}"; window.f0ckDefaultLayout = "{{ default_layout }}"; window.f0ckDomain = "{{ domain }}"; window.f0ckGitHash = "{{ git_hash }}"; window.f0ckAllowedImages = {{ allowed_comment_images_json }}; window.f0ckEmbedYoutubeInComments = {{ embed_youtube_in_comments ? 'true' : 'false' }}; window.f0ckEnableYoutubeUpload = {{ enable_youtube_upload ? 'true' : 'false' }}; window.f0ckBrandImages = {{ custom_brand_images_json }}; window.f0ckMediaBase = "{{ paths_images }}"; window.f0ckShitpostMode = {{ shitpost_mode ? 'true' : 'false' }}; window.f0ckShitpostRequireRating = {{ shitpost_require_rating ? 'true' : 'false' }}; window.f0ckShitpostMinTags = {{ shitpost_min_tags || 0 }}; window.f0ckEnableItemTitle = {{ enable_item_title ? 'true' : 'false' }}; window.f0ckCommentBannerEnabled = @if(comment_banner_enabled) true @else false @endif; window.f0ckServerAudioTuner = {{ audio_tuner_json || 'null' }}; window.f0ckEnableComments = {{ enable_comments ? 'true' : 'false' }};</script>
|
<script>window.f0ckThemes = {{ themes_json }}; window.f0ckDefaultTheme = "{{ default_theme }}"; window.f0ckDefaultLayout = "{{ default_layout }}"; window.f0ckDomain = "{{ domain }}"; window.f0ckGitHash = "{{ git_hash }}"; window.f0ckAllowedImages = {{ allowed_comment_images_json }}; window.f0ckEmbedYoutubeInComments = {{ embed_youtube_in_comments ? 'true' : 'false' }}; window.f0ckEnableYoutubeUpload = {{ enable_youtube_upload ? 'true' : 'false' }}; window.f0ckBrandImages = {{ custom_brand_images_json }}; window.f0ckCustomBrandText = "{{ custom_navbar_brand_text || '' }}"; window.f0ckMediaBase = "{{ paths_images }}"; window.f0ckShitpostMode = {{ shitpost_mode ? 'true' : 'false' }}; window.f0ckShitpostRequireRating = {{ shitpost_require_rating ? 'true' : 'false' }}; window.f0ckShitpostMinTags = {{ shitpost_min_tags || 0 }}; window.f0ckEnableItemTitle = {{ enable_item_title ? 'true' : 'false' }}; window.f0ckCommentBannerEnabled = @if(comment_banner_enabled) true @else false @endif; window.f0ckServerAudioTuner = {{ audio_tuner_json || 'null' }}; window.f0ckEnableComments = {{ enable_comments ? 'true' : 'false' }};</script>
|
||||||
@if(!private_society || session)
|
@if(!private_society || session)
|
||||||
@if(enable_comments)
|
@if(enable_comments)
|
||||||
<script src="/s/js/marked.min.js" defer></script>
|
<script src="/s/js/marked.min.js" defer></script>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<!-- Brand: always visible, flush left -->
|
<!-- Brand: always visible, flush left -->
|
||||||
<a class="navbar-brand" href="/">
|
<a class="navbar-brand" href="/">
|
||||||
@if(custom_brand_image)
|
@if(custom_brand_image)
|
||||||
<img id="navbar-logo" src="{{ custom_brand_image }}" alt="{{ domain }}" style="max-height: 40px; vertical-align: middle; max-width: 180px; width: auto;">
|
<img id="navbar-logo" src="{{ custom_brand_image }}" alt="{{ domain }}" onload="if(window.syncNavbarHeight)window.syncNavbarHeight();" style="max-height: 40px; vertical-align: middle; max-width: 180px; width: auto;">
|
||||||
@else
|
@else
|
||||||
<span class="f0ck">{{ custom_navbar_brand_text || domain }}</span>
|
<span class="f0ck">{{ custom_navbar_brand_text || domain }}</span>
|
||||||
@endif
|
@endif
|
||||||
@@ -249,7 +249,7 @@
|
|||||||
|
|
||||||
<a class="navbar-brand" href="/">
|
<a class="navbar-brand" href="/">
|
||||||
@if(custom_brand_image)
|
@if(custom_brand_image)
|
||||||
<img id="navbar-logo" src="{{ custom_brand_image }}" alt="{{ domain }}" style="max-height: 40px; vertical-align: middle; max-width: 180px; width: auto;">
|
<img id="navbar-logo" src="{{ custom_brand_image }}" alt="{{ domain }}" onload="if(window.syncNavbarHeight)window.syncNavbarHeight();" style="max-height: 40px; vertical-align: middle; max-width: 180px; width: auto;">
|
||||||
@else
|
@else
|
||||||
<span class="f0ck">{{ custom_navbar_brand_text || domain }}</span>
|
<span class="f0ck">{{ custom_navbar_brand_text || domain }}</span>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@each(notifications as n)
|
@each(notifications as n)
|
||||||
@if(n.type === 'approve')
|
@if(n.type === 'approve')
|
||||||
<a href="/{{ n.item_slug || n.item_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}">
|
<a href="/{{ n.item_slug || n.item_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
|
||||||
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
|
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
|
||||||
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.onerror=null;this.src='/mod/pending/t/{{ n.item_id }}.webp';this.onerror=function(){this.onerror=null;this.src='/mod/deleted/t/{{ n.item_id }}.webp';this.onerror=function(){this.style.display='none';};};"/>
|
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.onerror=null;this.src='/mod/pending/t/{{ n.item_id }}.webp';this.onerror=function(){this.onerror=null;this.src='/mod/deleted/t/{{ n.item_id }}.webp';this.onerror=function(){this.style.display='none';};};"/>
|
||||||
</div>
|
</div>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@elseif(n.type === 'admin_pending')
|
@elseif(n.type === 'admin_pending')
|
||||||
<a href="/mod/approve" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}">
|
<a href="/mod/approve" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
|
||||||
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
|
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
|
||||||
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.onerror=null;this.src='/t/{{ n.item_id }}.webp';this.onerror=function(){this.style.display='none';};"/>
|
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.onerror=null;this.src='/t/{{ n.item_id }}.webp';this.onerror=function(){this.style.display='none';};"/>
|
||||||
</div>
|
</div>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@elseif(n.type === 'report')
|
@elseif(n.type === 'report')
|
||||||
<a href="/mod/reports" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}">
|
<a href="/mod/reports" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
|
||||||
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
|
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
|
||||||
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.onerror=null;this.src='/mod/pending/t/{{ n.item_id }}.webp';this.onerror=function(){this.onerror=null;this.src='/mod/deleted/t/{{ n.item_id }}.webp';this.onerror=function(){this.style.display='none';};};"/>
|
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.onerror=null;this.src='/mod/pending/t/{{ n.item_id }}.webp';this.onerror=function(){this.onerror=null;this.src='/mod/deleted/t/{{ n.item_id }}.webp';this.onerror=function(){this.style.display='none';};};"/>
|
||||||
</div>
|
</div>
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@elseif(n.type === 'deny')
|
@elseif(n.type === 'deny')
|
||||||
<a href="/{{ n.item_slug || n.item_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}">
|
<a href="/{{ n.item_slug || n.item_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
|
||||||
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
|
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
|
||||||
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.onerror=null;this.src='/t/{{ n.item_id }}.webp';this.onerror=function(){this.style.display='none'};" />
|
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.onerror=null;this.src='/t/{{ n.item_id }}.webp';this.onerror=function(){this.style.display='none'};" />
|
||||||
</div>
|
</div>
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@elseif(n.type === 'item_deleted')
|
@elseif(n.type === 'item_deleted')
|
||||||
<a href="/{{ n.item_slug || n.item_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}">
|
<a href="/{{ n.item_slug || n.item_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
|
||||||
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
|
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
|
||||||
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.onerror=null;this.src='/t/{{ n.item_id }}.webp';this.onerror=function(){this.style.display='none'};" />
|
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.onerror=null;this.src='/t/{{ n.item_id }}.webp';this.onerror=function(){this.style.display='none'};" />
|
||||||
</div>
|
</div>
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@elseif(n.type === 'upload_comment')
|
@elseif(n.type === 'upload_comment')
|
||||||
<a href="/{{ n.item_slug || n.item_id }}#c{{ n.reference_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}">
|
<a href="/{{ n.item_slug || n.item_id }}#c{{ n.reference_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
|
||||||
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
|
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
|
||||||
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumbnail" onerror="this.style.display='none'">
|
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumbnail" onerror="this.style.display='none'">
|
||||||
</div>
|
</div>
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@elseif(n.type === 'upload_success')
|
@elseif(n.type === 'upload_success')
|
||||||
<a href="/{{ n.item_slug || n.item_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}">
|
<a href="/{{ n.item_slug || n.item_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
|
||||||
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
|
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
|
||||||
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.style.display='none'" />
|
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.style.display='none'" />
|
||||||
</div>
|
</div>
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@elseif(n.type === 'upload_error')
|
@elseif(n.type === 'upload_error')
|
||||||
<a href="{{ n.item_id ? '/' + (n.item_slug || n.item_id) : '#' }}" class="notif-item {{ n.is_read ? '' : 'unread' }} {{ n.item_id ? 'notif-with-thumb' : '' }}" data-id="{{ n.id }}">
|
<a href="{{ n.item_id ? '/' + (n.item_slug || n.item_id) : '#' }}" class="notif-item {{ n.is_read ? '' : 'unread' }} {{ n.item_id ? 'notif-with-thumb' : '' }}" data-id="{{ n.id }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
|
||||||
@if(n.item_id)
|
@if(n.item_id)
|
||||||
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
|
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
|
||||||
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.onerror=null;this.src='/mod/pending/t/{{ n.item_id }}.webp';this.onerror=function(){this.style.display='none'};" />
|
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.onerror=null;this.src='/mod/pending/t/{{ n.item_id }}.webp';this.onerror=function(){this.style.display='none'};" />
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@elseif(n.type === 'warning')
|
@elseif(n.type === 'warning')
|
||||||
<div class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}">
|
<div class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
|
||||||
<div class="notif-thumb" style="display:flex;align-items:center;justify-content:center;background:var(--bg-lighter);color:var(--danger);font-size:1.5em;"><i class="fa-solid fa-triangle-exclamation"></i></div>
|
<div class="notif-thumb" style="display:flex;align-items:center;justify-content:center;background:var(--bg-lighter);color:var(--danger);font-size:1.5em;"><i class="fa-solid fa-triangle-exclamation"></i></div>
|
||||||
<div class="notif-content">
|
<div class="notif-content">
|
||||||
<div class="notif-user"><strong>{{ t('notifications.system') }}</strong></div>
|
<div class="notif-user"><strong>{{ t('notifications.system') }}</strong></div>
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<a href="/{{ n.item_slug || n.item_id }}#c{{ n.comment_id || n.reference_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}">
|
<a href="/{{ n.item_slug || n.item_id }}#c{{ n.comment_id || n.reference_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
|
||||||
@if(n.item_id)
|
@if(n.item_id)
|
||||||
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
|
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
|
||||||
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.style.display='none'" />
|
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.style.display='none'" />
|
||||||
|
|||||||
Reference in New Issue
Block a user