feat(halls): immediately render hall badge in DOM upon adding/removing without requiring page refresh
This commit is contained in:
+112
-19
@@ -3667,7 +3667,7 @@ window.cancelAnimFrame = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Store in item cache (stale-while-revalidate) ───────────────────────
|
// ── Store in item cache (stale-while-revalidate) ───────────────────────
|
||||||
if (html && !options.skipCache) {
|
if (html && !options.noCacheStore) {
|
||||||
itemCacheMap.set(_itemCacheKey, { html, slug: responseSlug, ts: Date.now() });
|
itemCacheMap.set(_itemCacheKey, { html, slug: responseSlug, ts: Date.now() });
|
||||||
if (itemCacheMap.size > ITEM_CACHE_MAX) {
|
if (itemCacheMap.size > ITEM_CACHE_MAX) {
|
||||||
// Evict oldest entry
|
// Evict oldest entry
|
||||||
@@ -10544,6 +10544,91 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// </image-modal>
|
// </image-modal>
|
||||||
|
|
||||||
// Halls Management (Admin Only / My Halls for all logged-in users)
|
// Halls Management (Admin Only / My Halls for all logged-in users)
|
||||||
|
const escapeHtml = (str) => String(str ?? '')
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
|
||||||
|
const updateHallBadgeInDom = (hallSlug, hallName, action = 'add', isUserHall = false) => {
|
||||||
|
if (!hallSlug) return;
|
||||||
|
const liveHallBtn = document.getElementById('a_hall');
|
||||||
|
const isModern = document.body.classList.contains('layout-modern') || !document.body.classList.contains('layout-legacy');
|
||||||
|
|
||||||
|
// 1. Update dataset on live #a_hall button
|
||||||
|
if (liveHallBtn) {
|
||||||
|
const datasetKey = isUserHall ? 'userHalls' : 'halls';
|
||||||
|
const existing = (liveHallBtn.dataset[datasetKey] || '').split(',').filter(Boolean);
|
||||||
|
if (action === 'add') {
|
||||||
|
if (!existing.includes(hallSlug)) existing.push(hallSlug);
|
||||||
|
if (isUserHall) liveHallBtn.dataset.currentUserHall = hallSlug;
|
||||||
|
else liveHallBtn.dataset.currentHall = hallSlug;
|
||||||
|
} else if (action === 'remove') {
|
||||||
|
const idx = existing.indexOf(hallSlug);
|
||||||
|
if (idx !== -1) existing.splice(idx, 1);
|
||||||
|
if (isUserHall && liveHallBtn.dataset.currentUserHall === hallSlug) {
|
||||||
|
liveHallBtn.dataset.currentUserHall = existing[0] || '';
|
||||||
|
} else if (!isUserHall && liveHallBtn.dataset.currentHall === hallSlug) {
|
||||||
|
liveHallBtn.dataset.currentHall = existing[0] || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
liveHallBtn.dataset[datasetKey] = existing.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Main item header badge reflects site halls
|
||||||
|
if (isUserHall) return;
|
||||||
|
|
||||||
|
let container = document.getElementById('hall-badge-container');
|
||||||
|
if (!container) {
|
||||||
|
// Check for existing .hall-badge-wrap without wrapper
|
||||||
|
const existingWrap = document.querySelector('.hall-badge-wrap');
|
||||||
|
if (existingWrap) {
|
||||||
|
if (action === 'remove') {
|
||||||
|
existingWrap.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
existingWrap.outerHTML = `<span class="badge hall-badge-wrap"><a href="/h/${escapeHtml(hallSlug)}" class="hall-badge-primary"><i class="fa-solid fa-layer-group"></i> ${escapeHtml(hallName || hallSlug)}</a></span>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Dynamically create container and insert at the appropriate place
|
||||||
|
container = document.createElement('span');
|
||||||
|
container.id = 'hall-badge-container';
|
||||||
|
if (isModern) {
|
||||||
|
const tsLink = document.querySelector('.blahlol .timestamp-link');
|
||||||
|
const tsBadge = tsLink ? tsLink.closest('.badge') : null;
|
||||||
|
if (tsBadge && tsBadge.parentNode) {
|
||||||
|
tsBadge.parentNode.insertBefore(container, tsBadge);
|
||||||
|
} else {
|
||||||
|
const blahlol = document.querySelector('.blahlol');
|
||||||
|
if (blahlol) blahlol.appendChild(container);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const gapRight = document.querySelector('.blahlol .gapRight');
|
||||||
|
if (gapRight && gapRight.parentNode) {
|
||||||
|
gapRight.parentNode.insertBefore(container, gapRight);
|
||||||
|
} else {
|
||||||
|
const blahlol = document.querySelector('.blahlol');
|
||||||
|
if (blahlol) blahlol.appendChild(container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'add') {
|
||||||
|
const badgeHtml = `<span class="badge hall-badge-wrap"><a href="/h/${escapeHtml(hallSlug)}" class="hall-badge-primary"><i class="fa-solid fa-layer-group"></i> ${escapeHtml(hallName || hallSlug)}</a></span>`;
|
||||||
|
container.innerHTML = isModern ? ` — ${badgeHtml} — ` : ` — ${badgeHtml}`;
|
||||||
|
} else if (action === 'remove') {
|
||||||
|
const remaining = liveHallBtn ? (liveHallBtn.dataset.halls || '').split(',').filter(Boolean) : [];
|
||||||
|
if (remaining.length > 0) {
|
||||||
|
const nextSlug = remaining[0];
|
||||||
|
const badgeHtml = `<span class="badge hall-badge-wrap"><a href="/h/${escapeHtml(nextSlug)}" class="hall-badge-primary"><i class="fa-solid fa-layer-group"></i> ${escapeHtml(nextSlug)}</a></span>`;
|
||||||
|
container.innerHTML = isModern ? ` — ${badgeHtml} — ` : ` — ${badgeHtml}`;
|
||||||
|
} else {
|
||||||
|
container.innerHTML = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
document.addEventListener('click', async (e) => {
|
document.addEventListener('click', async (e) => {
|
||||||
// Add to Hall
|
// Add to Hall
|
||||||
const hallBtn = e.target.closest('#a_hall');
|
const hallBtn = e.target.closest('#a_hall');
|
||||||
@@ -10709,8 +10794,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
|
const hallName = (select.selectedOptions && select.selectedOptions[0]) ? select.selectedOptions[0].textContent : hallSlug;
|
||||||
|
updateHallBadgeInDom(hallSlug, hallName, 'add', false);
|
||||||
|
if (window.invalidateItemCache) window.invalidateItemCache(itemid);
|
||||||
if (window.flashMessage) window.flashMessage((window.f0ckI18n && window.f0ckI18n.hall_added) || 'Added to hall!');
|
if (window.flashMessage) window.flashMessage((window.f0ckI18n && window.f0ckI18n.hall_added) || 'Added to hall!');
|
||||||
if (window.loadItemAjax) window.loadItemAjax(window.location.href, true, { keepMedia: true });
|
if (window.loadItemAjax) window.loadItemAjax(window.location.href, true, { keepMedia: true, skipCache: true });
|
||||||
close();
|
close();
|
||||||
} else {
|
} else {
|
||||||
throw new Error(data.msg || 'Failed to add to hall');
|
throw new Error(data.msg || 'Failed to add to hall');
|
||||||
@@ -10771,17 +10859,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (data.success) {
|
if (data.success) {
|
||||||
myHallsConfirmBtn.disabled = false;
|
myHallsConfirmBtn.disabled = false;
|
||||||
myHallsConfirmBtn.textContent = window.f0ckI18n?.hall_add_btn || 'Add';
|
myHallsConfirmBtn.textContent = window.f0ckI18n?.hall_add_btn || 'Add';
|
||||||
// Patch the live #a_hall button NOW so next modal open pre-selects correctly
|
const hallName = (myHallsSelect && myHallsSelect.selectedOptions && myHallsSelect.selectedOptions[0])
|
||||||
// (before loadItemAjax finishes re-rendering the DOM)
|
? myHallsSelect.selectedOptions[0].textContent.replace(/\s*🔒\s*$/, '')
|
||||||
const liveHallBtn = document.getElementById('a_hall');
|
: hallSlug;
|
||||||
if (liveHallBtn) {
|
updateHallBadgeInDom(hallSlug, hallName, 'add', true);
|
||||||
liveHallBtn.dataset.currentUserHall = hallSlug;
|
if (window.invalidateItemCache) window.invalidateItemCache(itemid);
|
||||||
const existing = (liveHallBtn.dataset.userHalls || '').split(',').filter(Boolean);
|
|
||||||
if (!existing.includes(hallSlug)) existing.push(hallSlug);
|
|
||||||
liveHallBtn.dataset.userHalls = existing.join(',');
|
|
||||||
}
|
|
||||||
if (window.flashMessage) window.flashMessage((window.f0ckI18n && window.f0ckI18n.hall_added) || 'Added to hall!');
|
if (window.flashMessage) window.flashMessage((window.f0ckI18n && window.f0ckI18n.hall_added) || 'Added to hall!');
|
||||||
if (window.loadItemAjax) window.loadItemAjax(window.location.href, true, { keepMedia: true });
|
if (window.loadItemAjax) window.loadItemAjax(window.location.href, true, { keepMedia: true, skipCache: true });
|
||||||
close();
|
close();
|
||||||
} else {
|
} else {
|
||||||
throw new Error(data.message || data.msg || 'Failed to add');
|
throw new Error(data.message || data.msg || 'Failed to add');
|
||||||
@@ -10814,8 +10898,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
|
updateHallBadgeInDom(hallSlug, '', 'remove', false);
|
||||||
|
if (window.invalidateItemCache) window.invalidateItemCache(itemid);
|
||||||
if (window.flashMessage) window.flashMessage((window.f0ckI18n && window.f0ckI18n.hall_removed) || 'Removed from hall!');
|
if (window.flashMessage) window.flashMessage((window.f0ckI18n && window.f0ckI18n.hall_removed) || 'Removed from hall!');
|
||||||
if (window.loadItemAjax) window.loadItemAjax(window.location.href, true, { keepMedia: true });
|
if (window.loadItemAjax) window.loadItemAjax(window.location.href, true, { keepMedia: true, skipCache: true });
|
||||||
close();
|
close();
|
||||||
} else {
|
} else {
|
||||||
throw new Error(data.msg || 'Failed to remove from hall');
|
throw new Error(data.msg || 'Failed to remove from hall');
|
||||||
@@ -10845,8 +10931,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (data.success) {
|
if (data.success) {
|
||||||
myHallsRemoveBtn.disabled = false;
|
myHallsRemoveBtn.disabled = false;
|
||||||
myHallsRemoveBtn.textContent = 'Remove';
|
myHallsRemoveBtn.textContent = 'Remove';
|
||||||
|
updateHallBadgeInDom(slug, '', 'remove', true);
|
||||||
|
if (window.invalidateItemCache) window.invalidateItemCache(itemid);
|
||||||
window.flashMessage((window.f0ckI18n && window.f0ckI18n.hall_removed) || 'Removed from hall!');
|
window.flashMessage((window.f0ckI18n && window.f0ckI18n.hall_removed) || 'Removed from hall!');
|
||||||
if (window.loadItemAjax) window.loadItemAjax(window.location.href, true, { keepMedia: true });
|
if (window.loadItemAjax) window.loadItemAjax(window.location.href, true, { keepMedia: true, skipCache: true });
|
||||||
close();
|
close();
|
||||||
} else {
|
} else {
|
||||||
throw new Error(data.message || data.msg || 'Failed to remove');
|
throw new Error(data.message || data.msg || 'Failed to remove');
|
||||||
@@ -10869,20 +10957,25 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (!confirm('Remove from this hall?')) return;
|
if (!confirm('Remove from this hall?')) return;
|
||||||
|
|
||||||
const container = document.getElementById('comments-container');
|
const container = document.getElementById('comments-container');
|
||||||
const itemid = container ? container.dataset.itemId : null;
|
const itemid = container ? container.dataset.itemId : (document.querySelector('.id-link')?.dataset.itemId || null);
|
||||||
const hallSlug = removeBtn.dataset.hall;
|
const hallSlug = removeBtn.dataset.hall;
|
||||||
|
|
||||||
if (!itemid || !hallSlug) return window.flashMessage('Error: Missing itemid or hallSlug', 3000, 'error');
|
if (!itemid || !hallSlug) return window.flashMessage('Error: Missing itemid or hallSlug', 3000, 'error');
|
||||||
|
|
||||||
|
const token = (window.f0ckSession && window.f0ckSession.csrf_token) || '';
|
||||||
const resp = await fetch('/mod/halls/remove', {
|
const resp = await fetch('/mod/halls/remove', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
headers: {
|
||||||
body: `id=${itemid}&hall=${encodeURIComponent(hallSlug)}`
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
'X-CSRF-Token': token
|
||||||
|
},
|
||||||
|
body: `id=${itemid}&hall=${encodeURIComponent(hallSlug)}&csrf_token=${encodeURIComponent(token)}`
|
||||||
});
|
});
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
// Refresh item view to remove hall badge
|
updateHallBadgeInDom(hallSlug, '', 'remove', false);
|
||||||
if (window.loadItemAjax) window.loadItemAjax(window.location.href, true, { keepMedia: true });
|
if (window.invalidateItemCache) window.invalidateItemCache(itemid);
|
||||||
|
if (window.loadItemAjax) window.loadItemAjax(window.location.href, true, { keepMedia: true, skipCache: true });
|
||||||
} else {
|
} else {
|
||||||
window.flashMessage(data.msg || 'Failed to remove from hall', 3000, 'error');
|
window.flashMessage(data.msg || 'Failed to remove from hall', 3000, 'error');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,14 +131,11 @@
|
|||||||
<span id="oc-badge-container">@if(item.is_oc) — <span class="oc-badge" tooltip="Original Content">OC</span>@endif</span>
|
<span id="oc-badge-container">@if(item.is_oc) — <span class="oc-badge" tooltip="Original Content">OC</span>@endif</span>
|
||||||
@endif
|
@endif
|
||||||
</span>
|
</span>
|
||||||
@if(!user_alternative_infobox) — <span class="badge badge-dark"><a href="/{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}" class="timestamp-link"><time class="timeago" tooltip="{{ item.timestamp.timefull }}">{{item.timestamp.timeago }}</time></a></span>@if(item.expires_at) — <span class="badge badge-warning" style="background: rgba(255, 170, 0, 0.2); color: #ffaa00; border: 1px solid rgba(255, 170, 0, 0.4);" tooltip="Self-destructs {{ item.expires_in }}" flow="up"><i class="fa-solid fa-clock"></i> Expiring ({{ item.expires_in }})</span>@endif@if(halls_enabled && item.primaryHall) — @endif @endif
|
@if(!user_alternative_infobox) — <span class="badge badge-dark"><a href="/{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}" class="timestamp-link"><time class="timeago" tooltip="{{ item.timestamp.timefull }}">{{item.timestamp.timeago }}</time></a></span>@if(item.expires_at) — <span class="badge badge-warning" style="background: rgba(255, 170, 0, 0.2); color: #ffaa00; border: 1px solid rgba(255, 170, 0, 0.4);" tooltip="Self-destructs {{ item.expires_in }}" flow="up"><i class="fa-solid fa-clock"></i> Expiring ({{ item.expires_in }})</span>@endif @endif
|
||||||
|
|
||||||
|
<span id="hall-badge-container">@if(halls_enabled && item.primaryHall) — <span class="badge hall-badge-wrap">
|
||||||
@if(halls_enabled && item.primaryHall)
|
|
||||||
<span class="badge hall-badge-wrap">
|
|
||||||
<a href="/h/{{ item.primaryHall.slug }}" class="hall-badge-primary"><i class="fa-solid fa-layer-group"></i> {{ item.primaryHall.name }}</a>@if(item.otherHalls && item.otherHalls.length)<span class="hall-overflow-pill">+{{ item.otherHalls.length }}<span class="hall-overflow-tooltip">@each(item.otherHalls as oh)<a href="/h/{{ oh.slug }}">{{ oh.name }}</a>@endeach</span></span>@endif
|
<a href="/h/{{ item.primaryHall.slug }}" class="hall-badge-primary"><i class="fa-solid fa-layer-group"></i> {{ item.primaryHall.name }}</a>@if(item.otherHalls && item.otherHalls.length)<span class="hall-overflow-pill">+{{ item.otherHalls.length }}<span class="hall-overflow-tooltip">@each(item.otherHalls as oh)<a href="/h/{{ oh.slug }}">{{ oh.name }}</a>@endeach</span></span>@endif
|
||||||
</span>
|
</span>@endif</span>
|
||||||
@endif
|
|
||||||
|
|
||||||
|
|
||||||
<div class="gapRight">
|
<div class="gapRight">
|
||||||
|
|||||||
@@ -120,12 +120,10 @@
|
|||||||
<div class="blahlol">
|
<div class="blahlol">
|
||||||
<span class="badge badge-dark">
|
<span class="badge badge-dark">
|
||||||
<a href="/{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}" class="id-link" data-item-id="{{ item.id }}">{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}</a> — [<a id="a_username" data-username="{{ item.username || '' }}" @if(session) data-author-id="{{ item.author_id || '' }}" @endif href="/user/{{ item_username_lower }}" @if(session && item.author_id) tooltip="ID: {{ item.author_id }}" @endif @if(item.author_color) style="color: {{ item.author_color }}" @endif>{!! item.author_display_name || item.username || 'unknown' !!}</a>] <span id="oc-badge-container">@if(item.is_oc) — <span class="oc-badge" tooltip="Original Content">OC</span>@endif</span>
|
<a href="/{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}" class="id-link" data-item-id="{{ item.id }}">{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}</a> — [<a id="a_username" data-username="{{ item.username || '' }}" @if(session) data-author-id="{{ item.author_id || '' }}" @endif href="/user/{{ item_username_lower }}" @if(session && item.author_id) tooltip="ID: {{ item.author_id }}" @endif @if(item.author_color) style="color: {{ item.author_color }}" @endif>{!! item.author_display_name || item.username || 'unknown' !!}</a>] <span id="oc-badge-container">@if(item.is_oc) — <span class="oc-badge" tooltip="Original Content">OC</span>@endif</span>
|
||||||
</span>@if(halls_enabled && item.primaryHall) — @endif
|
</span>
|
||||||
@if(halls_enabled && item.primaryHall)
|
<span id="hall-badge-container">@if(halls_enabled && item.primaryHall) — <span class="badge hall-badge-wrap">
|
||||||
<span class="badge hall-badge-wrap">
|
|
||||||
<a href="/h/{{ item.primaryHall.slug }}" class="hall-badge-primary"><i class="fa-solid fa-layer-group"></i> {{ item.primaryHall.name }}</a>@if(item.otherHalls && item.otherHalls.length)<span class="hall-overflow-pill">+{{ item.otherHalls.length }}<span class="hall-overflow-tooltip">@each(item.otherHalls as oh)<a href="/h/{{ oh.slug }}">{{ oh.name }}</a>@endeach</span></span>@endif
|
<a href="/h/{{ item.primaryHall.slug }}" class="hall-badge-primary"><i class="fa-solid fa-layer-group"></i> {{ item.primaryHall.name }}</a>@if(item.otherHalls && item.otherHalls.length)<span class="hall-overflow-pill">+{{ item.otherHalls.length }}<span class="hall-overflow-tooltip">@each(item.otherHalls as oh)<a href="/h/{{ oh.slug }}">{{ oh.name }}</a>@endeach</span></span>@endif
|
||||||
</span> —
|
</span> — @endif</span>
|
||||||
@endif
|
|
||||||
<span class="badge badge-dark"><a href="/{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}" class="timestamp-link"><time class="timeago" tooltip="{{ item.timestamp.timefull }}">{{item.timestamp.timeago }}</time></a></span>
|
<span class="badge badge-dark"><a href="/{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}" class="timestamp-link"><time class="timeago" tooltip="{{ item.timestamp.timefull }}">{{item.timestamp.timeago }}</time></a></span>
|
||||||
@if(item.expires_at)
|
@if(item.expires_at)
|
||||||
— <span class="badge badge-warning" style="background: rgba(255, 170, 0, 0.2); color: #ffaa00; border: 1px solid rgba(255, 170, 0, 0.4);" tooltip="Self-destructs {{ item.expires_in }}" flow="up"><i class="fa-solid fa-clock"></i> Expiring ({{ item.expires_in }})</span>
|
— <span class="badge badge-warning" style="background: rgba(255, 170, 0, 0.2); color: #ffaa00; border: 1px solid rgba(255, 170, 0, 0.4);" tooltip="Self-destructs {{ item.expires_in }}" flow="up"><i class="fa-solid fa-clock"></i> Expiring ({{ item.expires_in }})</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user