69
This commit is contained in:
@@ -965,7 +965,7 @@ window.cancelAnimFrame = (function () {
|
||||
'login-modal', 'register-modal', 'forgot-modal', 'reset-modal',
|
||||
'report-modal', 'halls-modal', 'metadata-modal', 'warning-modal',
|
||||
'shortcuts-modal', 'upload-drag-modal', 'excluded-tags-overlay',
|
||||
'content-warning-modal', 'gchat-img-modal', 'image-modal', 'info-modal'
|
||||
'content-warning-modal', 'gchat-img-modal', 'image-modal', 'info-modal', 'visibility-modal'
|
||||
];
|
||||
modalIds.forEach(id => {
|
||||
// Don't close the filter modal during a background mime-filter reload
|
||||
@@ -2442,8 +2442,8 @@ window.cancelAnimFrame = (function () {
|
||||
const isUpload = pathname.match(/\/upload\/?(?:$|\?)/);
|
||||
const parts = pathname.split('/').filter(Boolean);
|
||||
const isItem = !pathname.match(/\/p\//) && (
|
||||
pathname.match(/^\/\d+/) ||
|
||||
(parts.length >= 3 && (parts[0] === 'tag' || parts[0] === 'user' || parts[0] === 'h') && /^\d+$/.test(parts[parts.length - 1]))
|
||||
pathname.match(/^\/\d+/) || pathname.match(/^\/[a-zA-Z0-9_-]{11}(?:[?#]|$)/) ||
|
||||
(parts.length >= 3 && (parts[0] === 'tag' || parts[0] === 'user' || parts[0] === 'h') && (/^\d+$/.test(parts[parts.length - 1]) || /^[a-zA-Z0-9_-]{11}$/.test(parts[parts.length - 1])))
|
||||
);
|
||||
const isMessages = !!pathname.match(/^\/messages(\/|$)/);
|
||||
const isAbyss = !!pathname.match(/^\/abyss(\/|$|\?|#)/) || pathname === '/abyss';
|
||||
@@ -3356,19 +3356,19 @@ window.cancelAnimFrame = (function () {
|
||||
// Extract item ID from URL. Use the last numeric segment to avoid matching context IDs (like tag/1/...)
|
||||
// Split path, filter numeric, pop last.
|
||||
const pathSegments = new URL(url, window.location.origin).pathname.split('/');
|
||||
const numericSegments = pathSegments.filter(s => /^\d+$/.test(s));
|
||||
const keySegments = pathSegments.filter(s => /^\d+$/.test(s) || /^[a-zA-Z0-9_-]{11}$/.test(s));
|
||||
|
||||
// Clear and Hide navbar pagination for Item View (Never show grid pagination on item view)
|
||||
// document.querySelectorAll('.pagination-wrapper').forEach(el => el.innerHTML = ''); // Don't destroy content (cached grid needs it)
|
||||
document.querySelectorAll('.pagination-container-fluid').forEach(el => el.style.display = 'none');
|
||||
|
||||
if (numericSegments.length === 0) {
|
||||
console.warn("loadItemAjax: No ID match found in URL", url);
|
||||
if (keySegments.length === 0) {
|
||||
console.warn("loadItemAjax: No ID or slug match found in URL", url);
|
||||
// fallback for weird/external links
|
||||
window.location.href = url;
|
||||
return;
|
||||
}
|
||||
const itemid = numericSegments.pop();
|
||||
const itemid = keySegments.pop();
|
||||
|
||||
// Extract context from Target URL first
|
||||
let tag = null, user = null, isFavs = false, mime = null, hall = null, userHall = null, userHallOwner = null, tagger = null;
|
||||
@@ -3551,18 +3551,19 @@ window.cancelAnimFrame = (function () {
|
||||
}
|
||||
|
||||
const _hash = new URL(url, window.location.origin).hash;
|
||||
let _pushUrl = `/${itemid}`;
|
||||
if (userHall && userHallOwner) _pushUrl = `/user/${encodeURIComponent(userHallOwner)}/hall/${encodeURIComponent(userHall)}/${itemid}`;
|
||||
else if (user) { _pushUrl = `/user/${encodeURIComponent(user)}/${itemid}`; if (isFavs) _pushUrl = `/user/${encodeURIComponent(user)}/favs/${itemid}`; }
|
||||
else if (tag) _pushUrl = `/tag/${encodeURIComponent(tag).replace(/%2C/g,',').replace(/%20/g,' ')}/${itemid}`;
|
||||
else if (hall) _pushUrl = `/h/${encodeURIComponent(hall).replace(/%20/g,' ')}/${itemid}`;
|
||||
const itemKey = _cachedItem.slug || itemid;
|
||||
let _pushUrl = `/${itemKey}`;
|
||||
if (userHall && userHallOwner) _pushUrl = `/user/${encodeURIComponent(userHallOwner)}/hall/${encodeURIComponent(userHall)}/${itemKey}`;
|
||||
else if (user) { _pushUrl = `/user/${encodeURIComponent(user)}/${itemKey}`; if (isFavs) _pushUrl = `/user/${encodeURIComponent(user)}/favs/${itemKey}`; }
|
||||
else if (tag) _pushUrl = `/tag/${encodeURIComponent(tag).replace(/%2C/g,',').replace(/%20/g,' ')}/${itemKey}`;
|
||||
else if (hall) _pushUrl = `/h/${encodeURIComponent(hall).replace(/%20/g,' ')}/${itemKey}`;
|
||||
if (tagger && tag) _pushUrl += `?tagger=${encodeURIComponent(tagger)}`;
|
||||
if (mime) _pushUrl = _pushUrl.replace(new RegExp(`/${itemid}$`), `/${mime}/${itemid}`);
|
||||
if (mime) _pushUrl = _pushUrl.replace(new RegExp(`/${itemKey}$`), `/${mime}/${itemKey}`);
|
||||
if (_hash) _pushUrl += _hash;
|
||||
|
||||
if (!options.keepMedia && !options.skipPush) history.pushState({}, '', _pushUrl);
|
||||
|
||||
document.title = `${window.f0ckDomain} - ${itemid}`;
|
||||
document.title = `${window.f0ckDomain} - ${itemKey}`;
|
||||
if (navbar) navbar.classList.remove('pbwork');
|
||||
|
||||
if (!options.keepMedia) {
|
||||
@@ -3587,14 +3588,14 @@ window.cancelAnimFrame = (function () {
|
||||
.then(r => r.ok ? r.text() : null)
|
||||
.then(freshText => {
|
||||
if (!freshText) return;
|
||||
try { JSON.parse(freshText); } catch(_) {}
|
||||
// Re-parse to get html string
|
||||
let freshHtml = freshText;
|
||||
let freshSlug = null;
|
||||
try {
|
||||
const d = JSON.parse(freshText);
|
||||
if (d && typeof d.html === 'string') freshHtml = d.html;
|
||||
if (d && (d.slug || d.item?.slug)) freshSlug = d.slug || d.item?.slug;
|
||||
} catch(_) {}
|
||||
itemCacheMap.set(_itemCacheKey, { html: freshHtml, ts: Date.now() });
|
||||
itemCacheMap.set(_itemCacheKey, { html: freshHtml, slug: freshSlug, ts: Date.now() });
|
||||
window.f0ckDebug('[itemCache] Background revalidation complete for', _itemCacheKey);
|
||||
})
|
||||
.catch(() => {});
|
||||
@@ -3619,7 +3620,7 @@ window.cancelAnimFrame = (function () {
|
||||
- Total Network: ${(tBody - tStart).toFixed(2)}ms
|
||||
- Content Size: ${(rawText.length / 1024).toFixed(2)} KB`);
|
||||
|
||||
let html, paginationHtml;
|
||||
let html, paginationHtml, responseSlug = null;
|
||||
|
||||
try {
|
||||
// Optimistically try to parse as JSON first
|
||||
@@ -3632,6 +3633,7 @@ window.cancelAnimFrame = (function () {
|
||||
if (data && typeof data.html === 'string') {
|
||||
html = data.html;
|
||||
paginationHtml = data.pagination;
|
||||
responseSlug = data.slug || data.item?.slug || null;
|
||||
} else {
|
||||
html = rawText;
|
||||
}
|
||||
@@ -3642,7 +3644,7 @@ window.cancelAnimFrame = (function () {
|
||||
|
||||
// ── Store in item cache (stale-while-revalidate) ───────────────────────
|
||||
if (html && !options.skipCache) {
|
||||
itemCacheMap.set(_itemCacheKey, { html, ts: Date.now() });
|
||||
itemCacheMap.set(_itemCacheKey, { html, slug: responseSlug, ts: Date.now() });
|
||||
if (itemCacheMap.size > ITEM_CACHE_MAX) {
|
||||
// Evict oldest entry
|
||||
itemCacheMap.delete(itemCacheMap.keys().next().value);
|
||||
@@ -3749,26 +3751,25 @@ window.cancelAnimFrame = (function () {
|
||||
// Construct proper History URL (Context Aware)
|
||||
// If we inherited context, we should reflect it in the URL
|
||||
const hash = new URL(url, window.location.origin).hash;
|
||||
let pushUrl = `/${itemid}`;
|
||||
const itemKey = responseSlug || itemid;
|
||||
let pushUrl = `/${itemKey}`;
|
||||
// Logic from ajax.mjs context reconstruction:
|
||||
if (userHall && userHallOwner) {
|
||||
pushUrl = `/user/${encodeURIComponent(userHallOwner)}/hall/${encodeURIComponent(userHall)}/${itemid}`;
|
||||
pushUrl = `/user/${encodeURIComponent(userHallOwner)}/hall/${encodeURIComponent(userHall)}/${itemKey}`;
|
||||
} else if (user) {
|
||||
pushUrl = `/user/${encodeURIComponent(user)}/${itemid}`;
|
||||
if (isFavs) pushUrl = `/user/${encodeURIComponent(user)}/favs/${itemid}`;
|
||||
pushUrl = `/user/${encodeURIComponent(user)}/${itemKey}`;
|
||||
if (isFavs) pushUrl = `/user/${encodeURIComponent(user)}/favs/${itemKey}`;
|
||||
}
|
||||
else if (tag) pushUrl = `/tag/${encodeURIComponent(tag).replace(/%2C/g, ',').replace(/%20/g, ' ')}/${itemid}`;
|
||||
else if (hall) pushUrl = `/h/${encodeURIComponent(hall).replace(/%20/g, ' ')}/${itemid}`;
|
||||
else if (tag) pushUrl = `/tag/${encodeURIComponent(tag).replace(/%2C/g, ',').replace(/%20/g, ' ')}/${itemKey}`;
|
||||
else if (hall) pushUrl = `/h/${encodeURIComponent(hall).replace(/%20/g, ' ')}/${itemKey}`;
|
||||
// Append tagger filter so item nav stays in tagger context
|
||||
if (tagger && tag) pushUrl += `?tagger=${encodeURIComponent(tagger)}`;
|
||||
|
||||
if (mime) {
|
||||
// If it already has itemid at the end, insert mime before it
|
||||
pushUrl = pushUrl.replace(new RegExp(`/${itemid}$`), `/${mime}/${itemid}`);
|
||||
// If it already has itemKey at the end, insert mime before it
|
||||
pushUrl = pushUrl.replace(new RegExp(`/${itemKey}$`), `/${mime}/${itemKey}`);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Re-append hash if present
|
||||
if (hash) pushUrl += hash;
|
||||
|
||||
@@ -3788,8 +3789,7 @@ window.cancelAnimFrame = (function () {
|
||||
if (window.initVisualizer) window.initVisualizer();
|
||||
}
|
||||
// Try to extract ID from response if possible or just use itemid
|
||||
document.title = `${window.f0ckDomain} - ${itemid}`;
|
||||
if (navbar) navbar.classList.remove("pbwork");
|
||||
document.title = `${window.f0ckDomain} - ${itemKey}`;
|
||||
window.f0ckDebug("AJAX load complete");
|
||||
|
||||
// Notify extensions — also triggers CommentSystem init which renders comments
|
||||
@@ -3821,6 +3821,7 @@ window.cancelAnimFrame = (function () {
|
||||
console.error("AJAX load failed:", err);
|
||||
} finally {
|
||||
isNavigating = false;
|
||||
if (navbar) navbar.classList.remove("pbwork");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3999,15 +4000,16 @@ window.cancelAnimFrame = (function () {
|
||||
fetch(randomUrl)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success && data.items && data.items.id) {
|
||||
if (data.success && data.items && (data.items.slug || data.items.id)) {
|
||||
const targetKey = data.items.slug || data.items.id;
|
||||
// Navigate in the same context (user hall, favs, tag, etc.)
|
||||
if (wUserHall && wUserHallOwner) {
|
||||
loadItemAjax(`/user/${encodeURIComponent(wUserHallOwner)}/hall/${encodeURIComponent(wUserHall)}/${data.items.id}`, true);
|
||||
loadItemAjax(`/user/${encodeURIComponent(wUserHallOwner)}/hall/${encodeURIComponent(wUserHall)}/${targetKey}`, true);
|
||||
} else if (wFavsUser) {
|
||||
// Preserve /user/:name/favs/:id context so next/prev arrows stay within favs
|
||||
loadItemAjax(`/user/${encodeURIComponent(wFavsUser)}/favs/${data.items.id}`, true);
|
||||
loadItemAjax(`/user/${encodeURIComponent(wFavsUser)}/favs/${targetKey}`, true);
|
||||
} else {
|
||||
loadItemAjax(`/${data.items.id}`, true);
|
||||
loadItemAjax(`/${targetKey}`, true);
|
||||
}
|
||||
} else {
|
||||
window.location.href = link.href;
|
||||
@@ -4079,7 +4081,7 @@ window.cancelAnimFrame = (function () {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isSpecialLink && (pathname === '/' || pathname.startsWith('/halls') || pathname.startsWith('/h/') || pathname.startsWith('/notifications') || pathname.startsWith('/tag') || pathname.startsWith('/user/') || pathname.match(/^\/(image|video|audio)/) || pathname.match(/\/p\/\d+/) || pathname.match(/^\/\d+/) || pathname.match(/^\/(about|rules|terms|upload|subscriptions|stats|docs|settings|admin|mod|ranking|messages|meme|memes)/) || pathname.startsWith('/abyss'))) {
|
||||
if (!isSpecialLink && (pathname === '/' || pathname.startsWith('/halls') || pathname.startsWith('/h/') || pathname.startsWith('/notifications') || pathname.startsWith('/tag') || pathname.startsWith('/user/') || pathname.match(/^\/(image|video|audio)/) || pathname.match(/\/p\/\d+/) || pathname.match(/^\/\d+/) || pathname.match(/^\/[a-zA-Z0-9_-]{11}(?:[?#]|$)/) || pathname.match(/^\/(about|rules|terms|upload|subscriptions|stats|docs|settings|admin|mod|ranking|messages|meme|memes)/) || pathname.startsWith('/abyss'))) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
@@ -4092,9 +4094,10 @@ window.cancelAnimFrame = (function () {
|
||||
const parts = pathname.split('/').filter(Boolean);
|
||||
const isItemLink = !pathname.match(/\/p\//) && (
|
||||
pathname.match(/^\/\d+/) ||
|
||||
(parts.length >= 3 && parts[0] === 'tag' && /^\d+$/.test(parts[parts.length - 1])) ||
|
||||
(parts.length >= 3 && parts[0] === 'user' && /^\d+$/.test(parts[parts.length - 1])) ||
|
||||
(parts.length >= 3 && parts[0] === 'h' && /^\d+$/.test(parts[parts.length - 1]))
|
||||
pathname.match(/^\/[a-zA-Z0-9_-]{11}(?:[?#]|$)/) ||
|
||||
(parts.length >= 3 && parts[0] === 'tag' && (/^\d+$/.test(parts[parts.length - 1]) || /^[a-zA-Z0-9_-]{11}$/.test(parts[parts.length - 1]))) ||
|
||||
(parts.length >= 3 && parts[0] === 'user' && (/^\d+$/.test(parts[parts.length - 1]) || /^[a-zA-Z0-9_-]{11}$/.test(parts[parts.length - 1]))) ||
|
||||
(parts.length >= 3 && parts[0] === 'h' && (/^\d+$/.test(parts[parts.length - 1]) || /^[a-zA-Z0-9_-]{11}$/.test(parts[parts.length - 1])))
|
||||
);
|
||||
if (isItemLink) {
|
||||
// Links inside comment bodies or MOTD should not inherit tag/hall/user context
|
||||
@@ -4162,6 +4165,25 @@ window.cancelAnimFrame = (function () {
|
||||
})
|
||||
.catch(console.error);
|
||||
|
||||
} else if (target.closest('#a_visibility') || target.closest('#info-visibility-edit-btn')) {
|
||||
e.preventDefault();
|
||||
const visTrigger = target.closest('#a_visibility') || target.closest('#info-visibility-edit-btn');
|
||||
const id = visTrigger.dataset.itemId || document.getElementById('visibility-item-id')?.value;
|
||||
const visBtnMain = document.getElementById('a_visibility');
|
||||
const rawVis = visTrigger.dataset.visibility ?? visBtnMain?.dataset.visibility ?? '0';
|
||||
const currentVis = parseInt(rawVis, 10);
|
||||
const modal = document.getElementById('visibility-modal');
|
||||
if (modal) {
|
||||
const inputId = document.getElementById('visibility-item-id');
|
||||
if (inputId && id) inputId.value = id;
|
||||
const radios = modal.querySelectorAll('input[name="visibility"]');
|
||||
radios.forEach(r => {
|
||||
r.checked = (parseInt(r.value, 10) === currentVis);
|
||||
});
|
||||
modal.style.display = 'flex';
|
||||
document.body.classList.add('modal-open');
|
||||
}
|
||||
|
||||
} else if (target.closest('#a_rethumb')) {
|
||||
e.preventDefault();
|
||||
const reBtn = target.closest('#a_rethumb');
|
||||
@@ -4530,8 +4552,10 @@ window.cancelAnimFrame = (function () {
|
||||
const parts = p.split('/').filter(Boolean);
|
||||
const isItem = !p.match(/\/p\//) && (
|
||||
p.match(/^\/\d+/) ||
|
||||
(parts.length >= 3 && parts[0] === 'tag' && /^\d+$/.test(parts[parts.length - 1])) ||
|
||||
(parts.length >= 3 && parts[0] === 'user' && /^\d+$/.test(parts[parts.length - 1]))
|
||||
p.match(/^\/[a-zA-Z0-9_-]{11}(?:[?#]|$)/) ||
|
||||
(parts.length >= 3 && parts[0] === 'tag' && (/^\d+$/.test(parts[parts.length - 1]) || /^[a-zA-Z0-9_-]{11}$/.test(parts[parts.length - 1]))) ||
|
||||
(parts.length >= 3 && parts[0] === 'user' && (/^\d+$/.test(parts[parts.length - 1]) || /^[a-zA-Z0-9_-]{11}$/.test(parts[parts.length - 1]))) ||
|
||||
(parts.length >= 3 && parts[0] === 'h' && (/^\d+$/.test(parts[parts.length - 1]) || /^[a-zA-Z0-9_-]{11}$/.test(parts[parts.length - 1])))
|
||||
);
|
||||
const isSpecial = p.startsWith('/notifications') || p.startsWith('/tags') || p.startsWith('/user/') || p.startsWith('/subscriptions') || p.startsWith('/ranking');
|
||||
const isGridLike = url.match(/\/p\/\d+/) || url.match(/[?&]page=\d+/) || p === '/';
|
||||
@@ -8103,7 +8127,7 @@ class NotificationSystem {
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof window.loadItemAjax === 'function' && href.match(/^\/\d+/)) {
|
||||
if (typeof window.loadItemAjax === 'function' && (href.match(/^\/\d+/) || href.match(/^\/[a-zA-Z0-9_-]{11}(?:[?#]|$)/) || href.match(/\/(?:user|tag|h)\/.*?\/(?:\d+|[a-zA-Z0-9_-]{11})/))) {
|
||||
window.loadItemAjax(href, false);
|
||||
} else if (typeof window.loadPageAjax === 'function') {
|
||||
window.loadPageAjax(href, true);
|
||||
@@ -8389,7 +8413,8 @@ class NotificationSystem {
|
||||
}
|
||||
|
||||
renderHistoryItem(n) {
|
||||
let link = `/${n.item_id}`;
|
||||
const itemKey = n.item_slug || n.slug || n.item_id;
|
||||
let link = `/${itemKey}`;
|
||||
let msg = '';
|
||||
let user = n.from_display_name || n.from_user || 'System';
|
||||
|
||||
@@ -8398,14 +8423,14 @@ class NotificationSystem {
|
||||
const isDeleted = n.type === 'item_deleted';
|
||||
const label = isDeleted ? (i18n.notif_upload_deleted || 'A moderator deleted your upload') : (i18n.notif_upload_denied || 'Your Upload was denied');
|
||||
const userLabel = isDeleted ? (i18n.notif_moderation || 'Moderation') : (i18n.notif_system || 'System');
|
||||
const itemLink = `/${n.item_id}`;
|
||||
const itemLink = `/${itemKey}`;
|
||||
return `
|
||||
<a href="${itemLink}" class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}">
|
||||
<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-user"><strong>${userLabel}</strong></div>
|
||||
<div class="notif-msg">
|
||||
<strong>${label} #${n.item_id}</strong>
|
||||
<strong>${label} #${itemKey}</strong>
|
||||
<div class="notif-reason">${i18n.notif_reason_label || 'Reason:'} ${n.reason || (i18n.notif_no_reason || 'No reason provided')}</div>
|
||||
</div>
|
||||
<div class="notif-time">${new Date(n.created_at).toLocaleString()}</div>
|
||||
@@ -8425,7 +8450,7 @@ class NotificationSystem {
|
||||
if (n.data?.msg) msg += ` <br><span style="color: #ff6060; font-size: 0.9em;">${n.data.msg}</span>`;
|
||||
if (n.data?.url) msg += ` <br><small style="opacity: 0.6; word-break: break-all;">${n.data.url}</small>`;
|
||||
user = (window.f0ckI18n && window.f0ckI18n.notif_system) || 'System';
|
||||
link = n.item_id ? `/${n.item_id}` : '#';
|
||||
link = n.item_id ? `/${itemKey}` : '#';
|
||||
} else if (n.type === 'admin_pending') {
|
||||
link = '/mod/approve';
|
||||
user = (window.f0ckI18n && window.f0ckI18n.notif_admin) || 'Admin';
|
||||
@@ -8441,7 +8466,7 @@ class NotificationSystem {
|
||||
if (n.reason) msg += `<br><div class="notif-reason" style="font-size: 0.85em; color: #ffb8b8; margin-top: 3px;">${n.reason}</div>`;
|
||||
} else {
|
||||
// Comment notification
|
||||
link = `/${n.item_id}#c${n.comment_id || n.reference_id}`;
|
||||
link = `/${itemKey}#c${n.comment_id || n.reference_id}`;
|
||||
if (n.type === 'comment_reply') msg = (window.f0ckI18n && window.f0ckI18n.notif_replied) || 'replied to you';
|
||||
else if (n.type === 'subscription') msg = (window.f0ckI18n && window.f0ckI18n.notif_subscribed) || 'commented in a thread you follow';
|
||||
else if (n.type === 'mention') msg = (window.f0ckI18n && window.f0ckI18n.notif_mentioned) || 'highlighted you';
|
||||
@@ -8482,8 +8507,9 @@ class NotificationSystem {
|
||||
}
|
||||
|
||||
renderItem(n) {
|
||||
const itemKey = n.item_slug || n.slug || n.item_id;
|
||||
if (n.type === 'approve') {
|
||||
const link = `/${n.item_id}`;
|
||||
const link = `/${itemKey}`;
|
||||
return `
|
||||
<a href="${link}" class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.id}">
|
||||
<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>
|
||||
@@ -8498,7 +8524,7 @@ class NotificationSystem {
|
||||
}
|
||||
|
||||
if (n.type === 'upload_success') {
|
||||
const link = `/${n.item_id}`;
|
||||
const link = `/${itemKey}`;
|
||||
return `
|
||||
<a href="${link}" class="notif-item ${n.is_read ? '' : 'unread'} notif-with-thumb" data-id="${n.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>
|
||||
@@ -8517,7 +8543,7 @@ class NotificationSystem {
|
||||
const errMsg = n.data?.msg || '';
|
||||
const errDisplay = errMsg ? `<div style="color: #ffbaba; font-size: 0.85em; margin-top: 2px;">${errMsg}</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 ? `/${n.item_id}` : '#';
|
||||
const link = n.item_id ? `/${itemKey}` : '#';
|
||||
return `
|
||||
<a href="${link}" class="notif-item ${n.is_read ? '' : 'unread'}" data-id="${n.id}">
|
||||
<div class="notif-content">
|
||||
@@ -8541,7 +8567,7 @@ class NotificationSystem {
|
||||
<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>
|
||||
<strong>${label} #${n.item_id}</strong>
|
||||
<strong>${label} #${itemKey}</strong>
|
||||
<div style="font-size: 0.85em; color: #ffb8b8; margin-top: 3px;">${(window.f0ckI18n && window.f0ckI18n.notif_click_reason) || 'Click to see reason'}</div>
|
||||
</div>
|
||||
<small class="notif-time">${new Date(n.created_at).toLocaleString()}</small>
|
||||
@@ -8600,10 +8626,10 @@ class NotificationSystem {
|
||||
if (n.type === 'comment_reply') typeText = (window.f0ckI18n && window.f0ckI18n.notif_replied) || 'replied to you';
|
||||
else if (n.type === 'subscription') typeText = (window.f0ckI18n && window.f0ckI18n.notif_subscribed) || 'commented in a thread you follow';
|
||||
else if (n.type === 'mention') typeText = (window.f0ckI18n && window.f0ckI18n.notif_mentioned) || 'highlighted you';
|
||||
else if (n.type === 'upload_comment') typeText = `${(window.f0ckI18n && window.f0ckI18n.notif_commented) || 'commented on your upload'} #${n.item_id}`;
|
||||
else if (n.type === 'upload_comment') typeText = `${(window.f0ckI18n && window.f0ckI18n.notif_commented) || 'commented on your upload'} #${itemKey}`;
|
||||
|
||||
const cid = n.comment_id || n.reference_id;
|
||||
const link = `/${n.item_id}#c${cid}`;
|
||||
const link = `/${itemKey}#c${cid}`;
|
||||
|
||||
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 `
|
||||
@@ -8768,10 +8794,11 @@ class NotificationSystem {
|
||||
// Build DOM nodes imperatively (avoids Sanitizer stripping inline border-color)
|
||||
const fragment = document.createDocumentFragment();
|
||||
data.favs.forEach(fav => {
|
||||
if (fav.hide_fav_badge) {
|
||||
const isSelf = window.f0ckSession && window.f0ckSession.user && (window.f0ckSession.user === fav.user);
|
||||
if (fav.hide_fav_badge && !isSelf) {
|
||||
const a = document.createElement('a');
|
||||
a.className = 'ghost-fav';
|
||||
a.setAttribute('tooltip', 'Ghost Fav');
|
||||
a.setAttribute('tooltip', '?');
|
||||
a.setAttribute('flow', 'up');
|
||||
a.style.cursor = 'default';
|
||||
|
||||
@@ -8884,12 +8911,14 @@ class NotificationSystem {
|
||||
|
||||
if (!isOwnUpload) return; // Silently drop as before for other users' uploads
|
||||
|
||||
const itemKey = data.slug || data.id;
|
||||
|
||||
// Don't add duplicates
|
||||
if (grid.querySelector(`a[href$="/${data.id}"]`)) return;
|
||||
if (grid.querySelector(`a[href$="/${itemKey}"]`) || grid.querySelector(`a[href$="/${data.id}"]`)) return;
|
||||
|
||||
// Determine the link prefix from existing items
|
||||
const firstThumb = grid.querySelector('a.thumb, a.lazy-thumb');
|
||||
const linkBase = firstThumb ? firstThumb.getAttribute('href').replace(/\d+$/, '') : '/';
|
||||
const linkBase = firstThumb ? firstThumb.getAttribute('href').replace(/(\d+|[a-zA-Z0-9_-]{11})$/, '') : '/';
|
||||
|
||||
// Build filter-reason label
|
||||
const i18n = window.f0ckI18n || {};
|
||||
@@ -8904,7 +8933,7 @@ class NotificationSystem {
|
||||
const mode = data.tag_id ? (data.tag_id === 1 ? 'sfw' : (data.tag_id === 2 ? 'nsfw' : (data.tag_id == nsflId ? 'nsfl' : 'null'))) : 'null';
|
||||
|
||||
const ghost = document.createElement('a');
|
||||
ghost.href = `${linkBase}${data.id}`;
|
||||
ghost.href = `${linkBase}${itemKey}`;
|
||||
ghost.className = 'thumb lazy-thumb filtered-upload-ghost loaded';
|
||||
ghost.dataset.file = data.dest;
|
||||
ghost.dataset.mime = data.mime;
|
||||
@@ -8935,19 +8964,21 @@ class NotificationSystem {
|
||||
}
|
||||
}
|
||||
|
||||
const itemKey = data.slug || data.id;
|
||||
|
||||
// Don't add duplicates
|
||||
if (grid.querySelector(`a[href$="/${data.id}"]`)) return;
|
||||
if (grid.querySelector(`a[href$="/${itemKey}"]`) || grid.querySelector(`a[href$="/${data.id}"]`)) return;
|
||||
|
||||
// Determine the link prefix from existing items
|
||||
const firstThumb = grid.querySelector('a.thumb, a.lazy-thumb');
|
||||
const linkBase = firstThumb ? firstThumb.getAttribute('href').replace(/\d+$/, '') : '/';
|
||||
const linkBase = firstThumb ? firstThumb.getAttribute('href').replace(/(\d+|[a-zA-Z0-9_-]{11})$/, '') : '/';
|
||||
|
||||
// Respect mode filter
|
||||
const nsflId = window.f0ckSession?.nsfl_tag_id;
|
||||
const mode = data.tag_id ? (data.tag_id === 1 ? 'sfw' : (data.tag_id === 2 ? 'nsfw' : (data.tag_id == nsflId ? 'nsfl' : 'null'))) : 'null';
|
||||
|
||||
const thumb = document.createElement('a');
|
||||
thumb.href = `${linkBase}${data.id}`;
|
||||
thumb.href = `${linkBase}${itemKey}`;
|
||||
thumb.className = 'thumb lazy-thumb';
|
||||
thumb.dataset.file = data.dest;
|
||||
thumb.dataset.mime = data.mime;
|
||||
@@ -9129,10 +9160,11 @@ class NotificationSystem {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
const itemKey = c.item_slug || c.slug || c.item_id;
|
||||
itemPreview = `
|
||||
<div class="item-preview" style="margin-top: 10px; display: flex; align-items: center; gap: 10px; background: rgba(0,0,0,0.2); padding: 5px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.05);">
|
||||
<a href="/${c.item_id}">${mediaHtml}</a>
|
||||
<a href="/${c.item_id}#c${c.id}" style="font-size: 0.8em; color: var(--accent); text-decoration: none;">View Context »</a>
|
||||
<a href="/${itemKey}">${mediaHtml}</a>
|
||||
<a href="/${itemKey}#c${c.id}" style="font-size: 0.8em; color: var(--accent); text-decoration: none;">View Context »</a>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -11302,6 +11334,83 @@ document.addEventListener('click', (e) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const visCancelBtn = e.target.closest('#visibility-modal-cancel');
|
||||
if (visCancelBtn || e.target.id === 'visibility-modal') {
|
||||
const modal = document.getElementById('visibility-modal');
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
document.body.classList.remove('modal-open');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const visSaveBtn = e.target.closest('#visibility-modal-save');
|
||||
if (visSaveBtn) {
|
||||
e.preventDefault();
|
||||
const modal = document.getElementById('visibility-modal');
|
||||
const inputId = document.getElementById('visibility-item-id');
|
||||
const selectedRadio = modal ? modal.querySelector('input[name="visibility"]:checked') : null;
|
||||
if (!inputId || !selectedRadio) return;
|
||||
|
||||
const id = inputId.value;
|
||||
const nextVis = parseInt(selectedRadio.value, 10);
|
||||
|
||||
visSaveBtn.disabled = true;
|
||||
const origText = visSaveBtn.textContent;
|
||||
visSaveBtn.textContent = 'Saving...';
|
||||
|
||||
fetch('/api/v2/item/visibility', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'X-CSRF-Token': window.f0ckSession?.csrf_token
|
||||
},
|
||||
body: new URLSearchParams({ postid: id, id: id, visibility: nextVis })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
visSaveBtn.disabled = false;
|
||||
visSaveBtn.textContent = origText;
|
||||
if (data.success) {
|
||||
const visVal = parseInt(data.visibility, 10);
|
||||
const titles = ['Public', 'Unlisted', 'Private'];
|
||||
const icons = ['fa-globe', 'fa-link', 'fa-lock'];
|
||||
const colors = ['var(--color-success, #00C851)', 'var(--color-warning, #ffbb33)', 'var(--color-danger, #ff4444)'];
|
||||
|
||||
const visBtn = document.getElementById('a_visibility');
|
||||
if (visBtn) {
|
||||
visBtn.dataset.visibility = visVal;
|
||||
visBtn.setAttribute('title', `Visibility: ${titles[visVal]} (Click to change)`);
|
||||
}
|
||||
|
||||
const infoVisBtn = document.getElementById('info-visibility-edit-btn');
|
||||
if (infoVisBtn) {
|
||||
infoVisBtn.dataset.visibility = visVal;
|
||||
}
|
||||
|
||||
const infoVisLabel = document.getElementById('info-visibility-label');
|
||||
if (infoVisLabel) {
|
||||
infoVisLabel.innerHTML = `<i class="fa-solid ${icons[visVal]}" style="color: ${colors[visVal]};"></i> ${titles[visVal]}`;
|
||||
}
|
||||
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
document.body.classList.remove('modal-open');
|
||||
}
|
||||
if (window.flashMessage) window.flashMessage(`VISIBILITY SET TO ${titles[visVal].toUpperCase()}`);
|
||||
} else {
|
||||
if (window.flashError) window.flashError(data.msg || 'Failed to update visibility');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
visSaveBtn.disabled = false;
|
||||
visSaveBtn.textContent = origText;
|
||||
console.error('Error changing visibility:', err);
|
||||
if (window.flashError) window.flashError('Network error');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Title save button
|
||||
const saveBtn = e.target.closest('#info-title-save');
|
||||
if (saveBtn) {
|
||||
|
||||
Reference in New Issue
Block a user