gfdsgfds
This commit is contained in:
+249
-157
@@ -284,6 +284,8 @@
|
||||
};
|
||||
|
||||
|
||||
let favSeq = 0;
|
||||
|
||||
const toggleFavEvent = async (e) => {
|
||||
if (e && typeof e.preventDefault === 'function') e.preventDefault();
|
||||
if (window.f0ckSession?.is_anon && window.f0ckSession?.anon_permissions && window.f0ckSession.anon_permissions.favorite === false) {
|
||||
@@ -300,7 +302,12 @@
|
||||
const chanUrl = firstFavoBtn?.dataset?.chanUrl || chanRehostBtn?.dataset?.url;
|
||||
let localId = firstFavoBtn?.dataset?.localId;
|
||||
|
||||
const ctx = getContext();
|
||||
const postid = localId ? Number(localId) : ctx?.postid;
|
||||
if (!postid && !chanUrl) return;
|
||||
|
||||
const wasAlreadyFav = favoBtns[0].classList.contains('fa-solid') && !favoBtns[0].classList.contains('fa-spinner');
|
||||
const isNowFav = !wasAlreadyFav;
|
||||
|
||||
const setFavoUi = (isFav) => {
|
||||
favoBtns.forEach(btn => {
|
||||
@@ -308,178 +315,263 @@
|
||||
btn.classList.add('iconset', 'fa-heart');
|
||||
btn.classList.toggle('fa-solid', isFav);
|
||||
btn.classList.toggle('fa-regular', !isFav);
|
||||
btn.title = isFav ? (window.f0ckI18n?.fav_remove || 'Remove from favorites') : (window.f0ckI18n?.fav_add || 'Favorite');
|
||||
});
|
||||
};
|
||||
|
||||
const setFavoLoading = () => {
|
||||
favoBtns.forEach(btn => {
|
||||
btn.classList.remove('fa-heart');
|
||||
btn.classList.add('iconset', 'fa-solid', 'fa-spinner', 'fa-spin');
|
||||
});
|
||||
};
|
||||
// 1. Instantly apply client UI
|
||||
setFavoUi(isNowFav);
|
||||
|
||||
// If viewing un-rehosted 4chan post, auto-rehost first
|
||||
if (isChan && !localId && chanUrl) {
|
||||
setFavoLoading();
|
||||
try {
|
||||
const csrfToken = window.f0ckSession?.csrf_token || '';
|
||||
const rehostResp = await fetch('/api/v2/scroller/rehost', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {})
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
url: chanUrl,
|
||||
original_filename: firstFavoBtn?.dataset?.filename || chanRehostBtn?.dataset?.filename || '',
|
||||
width: firstFavoBtn?.dataset?.width || chanRehostBtn?.dataset?.width || '',
|
||||
height: firstFavoBtn?.dataset?.height || chanRehostBtn?.dataset?.height || ''
|
||||
})
|
||||
});
|
||||
const rehostData = await rehostResp.json();
|
||||
if (rehostData.success && rehostData.item_id) {
|
||||
localId = rehostData.item_id;
|
||||
favoBtns.forEach(btn => {
|
||||
btn.dataset.itemId = localId;
|
||||
btn.dataset.localId = localId;
|
||||
});
|
||||
const commentsEl = document.querySelector("#comments-container");
|
||||
if (commentsEl) commentsEl.dataset.itemId = localId;
|
||||
const infoEl = document.querySelector("#a_info");
|
||||
if (infoEl) infoEl.dataset.itemId = localId;
|
||||
// Micro-animation for tactile pop
|
||||
favoBtns.forEach(btn => {
|
||||
btn.classList.remove('fav-pop');
|
||||
void btn.offsetWidth;
|
||||
btn.classList.add('fav-pop');
|
||||
});
|
||||
|
||||
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'));
|
||||
// Instant flash message & vibration feedback
|
||||
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 (postid && window.invalidateItemCache) {
|
||||
window.invalidateItemCache(postid);
|
||||
}
|
||||
|
||||
// Optimistically update #favs badge list
|
||||
const favcontainer = document.querySelector('#favs');
|
||||
const prevFavsHtml = favcontainer ? favcontainer.innerHTML : '';
|
||||
const prevFavsHidden = favcontainer ? favcontainer.hidden : true;
|
||||
|
||||
if (favcontainer) {
|
||||
const currentUser = (window.f0ckSession?.user || '').toLowerCase();
|
||||
const isAnon = !!(window.f0ckSession?.is_anon || window.f0ckSession?.user === 'anonymous');
|
||||
const isAnonymized = !!(window.f0ckSession?.is_anonymized ?? (window.f0ckSession?.guest_anonymize && !window.f0ckSession?.logged_in) ?? (window.f0ckSession?.is_anon && window.f0ckSession?.anon_anonymize));
|
||||
|
||||
const findSelfBadge = () => {
|
||||
const selfBadges = favcontainer.querySelectorAll('[data-self="true"]');
|
||||
if (selfBadges.length) return selfBadges[0];
|
||||
if (currentUser && currentUser !== 'anonymous') {
|
||||
const links = favcontainer.querySelectorAll('a[href]');
|
||||
for (const a of links) {
|
||||
const path = a.getAttribute('href') || '';
|
||||
if (path.toLowerCase().endsWith('/user/' + currentUser)) return a;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
if (chanRehostBtn) {
|
||||
chanRehostBtn.classList.add('rehosted');
|
||||
chanRehostBtn.outerHTML = `
|
||||
<span class="chan-rehosted-wrap" style="display:inline-flex;gap:4px;align-items:center;">
|
||||
<a href="/${localId}" class="chan-rehost-action-btn rehosted" title="Already rehosted on f0ckm (Post #${localId})" 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> #${localId}</a>
|
||||
<button type="button" class="chan-rehost-edit-btn" data-item-id="${localId}" 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>
|
||||
`;
|
||||
if (!isNowFav) {
|
||||
const selfBadge = findSelfBadge();
|
||||
if (selfBadge) selfBadge.remove();
|
||||
if (favcontainer.children.length === 0) {
|
||||
favcontainer.hidden = true;
|
||||
}
|
||||
} else {
|
||||
let selfBadge = findSelfBadge();
|
||||
if (!selfBadge) {
|
||||
selfBadge = document.createElement('a');
|
||||
selfBadge.dataset.self = 'true';
|
||||
selfBadge.setAttribute('flow', 'up');
|
||||
if (isAnonymized || isAnon) {
|
||||
selfBadge.className = 'ghost-fav';
|
||||
selfBadge.setAttribute('tooltip', 'anonymous');
|
||||
selfBadge.style.cursor = 'default';
|
||||
const img = document.createElement('img');
|
||||
img.src = '/a/default.png';
|
||||
img.style.height = "32px";
|
||||
img.style.width = "32px";
|
||||
selfBadge.appendChild(img);
|
||||
} else {
|
||||
selfBadge.href = `/user/${currentUser}`;
|
||||
selfBadge.setAttribute('tooltip', window.f0ckSession?.display_name || window.f0ckSession?.user || 'anonymous');
|
||||
const img = document.createElement('img');
|
||||
const avatarFile = window.f0ckSession?.avatar_file;
|
||||
const avatar = window.f0ckSession?.avatar;
|
||||
img.src = avatarFile ? `/a/${avatarFile}` : (avatar ? `/t/${avatar}.webp` : '/a/default.png');
|
||||
img.style.height = "32px";
|
||||
img.style.width = "32px";
|
||||
if (window.f0ckSession?.username_color) img.style.borderColor = window.f0ckSession.username_color;
|
||||
selfBadge.appendChild(img);
|
||||
}
|
||||
favcontainer.appendChild(selfBadge);
|
||||
}
|
||||
favcontainer.hidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
const mySeq = ++favSeq;
|
||||
|
||||
// 2. Run server operation in background — can take as long as it needs
|
||||
(async () => {
|
||||
try {
|
||||
// If viewing un-rehosted 4chan post, auto-rehost first
|
||||
if (isChan && !localId && chanUrl) {
|
||||
if (mySeq !== favSeq) return;
|
||||
const csrfToken = window.f0ckSession?.csrf_token || '';
|
||||
const rehostResp = await fetch('/api/v2/scroller/rehost', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {})
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
url: chanUrl,
|
||||
original_filename: firstFavoBtn?.dataset?.filename || chanRehostBtn?.dataset?.filename || '',
|
||||
width: firstFavoBtn?.dataset?.width || chanRehostBtn?.dataset?.width || '',
|
||||
height: firstFavoBtn?.dataset?.height || chanRehostBtn?.dataset?.height || ''
|
||||
})
|
||||
});
|
||||
const rehostData = await rehostResp.json();
|
||||
if (rehostData.success && rehostData.item_id) {
|
||||
localId = rehostData.item_id;
|
||||
favoBtns.forEach(btn => {
|
||||
btn.dataset.itemId = localId;
|
||||
btn.dataset.localId = localId;
|
||||
});
|
||||
const commentsEl = document.querySelector("#comments-container");
|
||||
if (commentsEl) commentsEl.dataset.itemId = localId;
|
||||
const infoEl = document.querySelector("#a_info");
|
||||
if (infoEl) infoEl.dataset.itemId = localId;
|
||||
|
||||
const timeEl = document.querySelector('time.timeago');
|
||||
if (timeEl) {
|
||||
const nowIso = new Date().toISOString();
|
||||
timeEl.dataset.iso = nowIso;
|
||||
const fullDate = typeof window.f0ckFormatDateFull === 'function' ? window.f0ckFormatDateFull(nowIso) : new Date().toLocaleString();
|
||||
timeEl.setAttribute('tooltip', fullDate);
|
||||
timeEl.textContent = (window.f0ckTimeAgo ? window.f0ckTimeAgo(nowIso) : (window.f0ckI18n?.timeago_just_now || 'just now'));
|
||||
}
|
||||
|
||||
if (chanRehostBtn) {
|
||||
chanRehostBtn.classList.add('rehosted');
|
||||
chanRehostBtn.outerHTML = `
|
||||
<span class="chan-rehosted-wrap" style="display:inline-flex;gap:4px;align-items:center;">
|
||||
<a href="/${localId}" class="chan-rehost-action-btn rehosted" title="Already rehosted on f0ckm (Post #${localId})" 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> #${localId}</a>
|
||||
<button type="button" class="chan-rehost-edit-btn" data-item-id="${localId}" title="Edit rating" 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>
|
||||
`;
|
||||
}
|
||||
} else {
|
||||
throw new Error(rehostData.msg || 'Rehost failed');
|
||||
}
|
||||
}
|
||||
|
||||
const effectivePostId = localId ? Number(localId) : (postid || getContext()?.postid);
|
||||
if (!effectivePostId) {
|
||||
throw new Error('Missing post ID');
|
||||
}
|
||||
|
||||
if (mySeq !== favSeq) return;
|
||||
|
||||
const res = await post('/api/v2/togglefav', {
|
||||
postid: effectivePostId,
|
||||
chan_url: chanUrl,
|
||||
action: isNowFav ? 'add' : 'delete',
|
||||
favorited: isNowFav
|
||||
});
|
||||
|
||||
if (mySeq !== favSeq) return;
|
||||
|
||||
if (res && res.success) {
|
||||
if (window.invalidateItemCache) {
|
||||
window.invalidateItemCache(effectivePostId);
|
||||
}
|
||||
const finalIsFav = (res.favorited !== undefined) ? !!res.favorited : isNowFav;
|
||||
setFavoUi(finalIsFav);
|
||||
|
||||
const curFavContainer = document.querySelector('#favs');
|
||||
if (curFavContainer && Array.isArray(res.favs)) {
|
||||
curFavContainer.innerHTML = "";
|
||||
if (res.favs.length > 0) {
|
||||
const isAnonymized = !!(window.f0ckSession?.is_anonymized ?? (window.f0ckSession?.guest_anonymize && !window.f0ckSession?.logged_in) ?? (window.f0ckSession?.is_anon && window.f0ckSession?.anon_anonymize));
|
||||
const fragment = document.createDocumentFragment();
|
||||
res.favs.forEach(f => {
|
||||
const isSelf = window.f0ckSession && (
|
||||
(window.f0ckSession.id && f.user_id && Number(window.f0ckSession.id) === Number(f.user_id)) ||
|
||||
(window.f0ckSession.user && f.user && window.f0ckSession.user.toLowerCase() === f.user.toLowerCase()) ||
|
||||
(window.f0ckSession.login && f.login && window.f0ckSession.login.toLowerCase() === f.login.toLowerCase())
|
||||
);
|
||||
const isFavAnon = f.is_anon || f.user === 'anonymous' || (typeof f.user === 'string' && f.user.startsWith('anon_'));
|
||||
|
||||
if (f.hide_fav_badge && !isSelf) {
|
||||
const a = document.createElement('a');
|
||||
a.className = 'ghost-fav';
|
||||
a.setAttribute('tooltip', '?');
|
||||
a.setAttribute('flow', 'up');
|
||||
a.style.cursor = 'default';
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = '/s/img/ghost_fav.svg';
|
||||
img.style.height = "32px";
|
||||
img.style.width = "32px";
|
||||
|
||||
a.appendChild(img);
|
||||
fragment.appendChild(a);
|
||||
} else if (isAnonymized || isFavAnon) {
|
||||
const a = document.createElement('a');
|
||||
a.className = 'ghost-fav';
|
||||
if (isSelf) a.dataset.self = 'true';
|
||||
a.setAttribute('tooltip', 'anonymous');
|
||||
a.setAttribute('flow', 'up');
|
||||
a.style.cursor = 'default';
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = '/a/default.png';
|
||||
img.style.height = "32px";
|
||||
img.style.width = "32px";
|
||||
|
||||
a.appendChild(img);
|
||||
fragment.appendChild(a);
|
||||
} else {
|
||||
const a = document.createElement('a');
|
||||
if (isSelf) a.dataset.self = 'true';
|
||||
a.href = `/user/${(f.user || '').toLowerCase()}`;
|
||||
a.setAttribute('tooltip', f.display_name || f.user || 'anonymous');
|
||||
a.setAttribute('flow', 'up');
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = f.avatar_file ? `/a/${f.avatar_file}` : (f.avatar ? `/t/${f.avatar}.webp` : '/a/default.png');
|
||||
img.style.height = "32px";
|
||||
img.style.width = "32px";
|
||||
if (f.username_color) img.style.borderColor = f.username_color;
|
||||
|
||||
a.appendChild(img);
|
||||
fragment.appendChild(a);
|
||||
}
|
||||
});
|
||||
curFavContainer.appendChild(fragment);
|
||||
curFavContainer.hidden = false;
|
||||
} else {
|
||||
curFavContainer.hidden = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error(rehostData.msg || 'Rehost failed');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[CHAN-FAV-ADMIN] Auto-rehost error:', err);
|
||||
setFavoUi(wasAlreadyFav);
|
||||
if (typeof window.flashMessage === 'function') {
|
||||
window.flashMessage('Failed to favorite post', 3000, 'error');
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const ctx = getContext();
|
||||
if (!ctx) {
|
||||
setFavoUi(wasAlreadyFav);
|
||||
return;
|
||||
}
|
||||
const postid = localId ? Number(localId) : ctx.postid;
|
||||
|
||||
try {
|
||||
const res = await post('/api/v2/togglefav', {
|
||||
postid: postid,
|
||||
chan_url: chanUrl
|
||||
});
|
||||
if (res && res.success) {
|
||||
if (window.invalidateItemCache) {
|
||||
window.invalidateItemCache(postid);
|
||||
}
|
||||
// Authoritative server state takes priority; fallback to inverting previous UI state
|
||||
const isNowFav = (res.favorited !== undefined) ? !!res.favorited : !wasAlreadyFav;
|
||||
|
||||
setFavoUi(isNowFav);
|
||||
|
||||
const favcontainer = document.querySelector('#favs');
|
||||
if (favcontainer) {
|
||||
favcontainer.innerHTML = "";
|
||||
if (res.favs && res.favs.length > 0) {
|
||||
const isAnonymized = !!(window.f0ckSession?.is_anonymized ?? (window.f0ckSession?.guest_anonymize && !window.f0ckSession?.logged_in) ?? (window.f0ckSession?.is_anon && window.f0ckSession?.anon_anonymize));
|
||||
const fragment = document.createDocumentFragment();
|
||||
res.favs.forEach(f => {
|
||||
const isSelf = window.f0ckSession && (
|
||||
(window.f0ckSession.id && f.user_id && Number(window.f0ckSession.id) === Number(f.user_id)) ||
|
||||
(window.f0ckSession.user && f.user && window.f0ckSession.user.toLowerCase() === f.user.toLowerCase()) ||
|
||||
(window.f0ckSession.login && f.login && window.f0ckSession.login.toLowerCase() === f.login.toLowerCase())
|
||||
);
|
||||
const isFavAnon = f.is_anon || f.user === 'anonymous' || (typeof f.user === 'string' && f.user.startsWith('anon_'));
|
||||
|
||||
if (f.hide_fav_badge && !isSelf) {
|
||||
const a = document.createElement('a');
|
||||
a.className = 'ghost-fav';
|
||||
a.setAttribute('tooltip', '?');
|
||||
a.setAttribute('flow', 'up');
|
||||
a.style.cursor = 'default';
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = '/s/img/ghost_fav.svg';
|
||||
img.style.height = "32px";
|
||||
img.style.width = "32px";
|
||||
|
||||
a.appendChild(img);
|
||||
fragment.appendChild(a);
|
||||
} else if (isAnonymized || isFavAnon) {
|
||||
const a = document.createElement('a');
|
||||
a.className = 'ghost-fav';
|
||||
a.setAttribute('tooltip', 'anonymous');
|
||||
a.setAttribute('flow', 'up');
|
||||
a.style.cursor = 'default';
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = '/a/default.png';
|
||||
img.style.height = "32px";
|
||||
img.style.width = "32px";
|
||||
|
||||
a.appendChild(img);
|
||||
fragment.appendChild(a);
|
||||
} else {
|
||||
const a = document.createElement('a');
|
||||
a.href = `/user/${(f.user || '').toLowerCase()}`;
|
||||
a.setAttribute('tooltip', f.display_name || f.user || 'anonymous');
|
||||
a.setAttribute('flow', 'up');
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = f.avatar_file ? `/a/${f.avatar_file}` : (f.avatar ? `/t/${f.avatar}.webp` : '/a/default.png');
|
||||
img.style.height = "32px";
|
||||
img.style.width = "32px";
|
||||
if (f.username_color) img.style.borderColor = f.username_color;
|
||||
|
||||
a.appendChild(img);
|
||||
fragment.appendChild(a);
|
||||
}
|
||||
});
|
||||
favcontainer.appendChild(fragment);
|
||||
favcontainer.hidden = false;
|
||||
} else {
|
||||
favcontainer.hidden = true;
|
||||
if (mySeq !== favSeq) return;
|
||||
setFavoUi(wasAlreadyFav);
|
||||
const curFavContainer = document.querySelector('#favs');
|
||||
if (curFavContainer) {
|
||||
curFavContainer.innerHTML = prevFavsHtml;
|
||||
curFavContainer.hidden = prevFavsHidden;
|
||||
}
|
||||
const errMsg = (res && (res.msg || res.error)) || 'Anonymous favoriting is disabled.';
|
||||
if (typeof window.flashMessage === 'function') {
|
||||
window.flashMessage(errMsg, 3000, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
window.flashMessage((window.f0ckI18n && (isNowFav ? window.f0ckI18n.fav_added : window.f0ckI18n.fav_removed)) || (isNowFav ? 'ADDED TO FAVORITES' : 'REMOVED FROM FAVORITES'));
|
||||
if (navigator.vibrate) navigator.vibrate(50);
|
||||
} else {
|
||||
} catch (err) {
|
||||
console.error('[CHAN-FAV-ADMIN] Error during background favorite sync:', err);
|
||||
if (mySeq !== favSeq) return;
|
||||
setFavoUi(wasAlreadyFav);
|
||||
const errMsg = (res && (res.msg || res.error)) || 'Anonymous favoriting is disabled.';
|
||||
const curFavContainer = document.querySelector('#favs');
|
||||
if (curFavContainer) {
|
||||
curFavContainer.innerHTML = prevFavsHtml;
|
||||
curFavContainer.hidden = prevFavsHidden;
|
||||
}
|
||||
if (typeof window.flashMessage === 'function') {
|
||||
window.flashMessage(errMsg, 3000, 'error');
|
||||
window.flashMessage('Failed to update favorite.', 3000, 'error');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setFavoUi(wasAlreadyFav);
|
||||
if (typeof window.flashMessage === 'function') {
|
||||
window.flashMessage('Failed to update favorite.', 3000, 'error');
|
||||
}
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
const deleteSubItemEvent = async (subf0ck, postid) => {
|
||||
|
||||
Reference in New Issue
Block a user