Fix truth voting, chat updates, and mobile contact dropdown
- Fix truth voting: add error logging, prevent duplicate event listeners, guard against concurrent votes - Add real-time friends list updates in chat via custom event - Make mobile contact dropdown narrower (260px) and stay on right side - Add data-vote-bound attribute to prevent duplicate vote handlers Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
35da962800
commit
fa8134fd41
|
|
@ -902,6 +902,13 @@ export async function fetchFriendRequests() {
|
|||
}
|
||||
}
|
||||
|
||||
// Emit event when friends list changes
|
||||
function emitFriendsChanged() {
|
||||
try {
|
||||
window.dispatchEvent(new CustomEvent("sociowire:friends-changed"));
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export async function sendFriendRequest(username) {
|
||||
try {
|
||||
const res = await fetch(`${apiBase()}/friends/request`, {
|
||||
|
|
@ -911,6 +918,7 @@ export async function sendFriendRequest(username) {
|
|||
body: JSON.stringify({ username }),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (res.ok) emitFriendsChanged();
|
||||
return { ok: res.ok, ...data };
|
||||
} catch {
|
||||
return { ok: false };
|
||||
|
|
@ -926,6 +934,7 @@ export async function acceptFriendRequest(username) {
|
|||
body: JSON.stringify({ username }),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (res.ok) emitFriendsChanged();
|
||||
return { ok: res.ok, ...data };
|
||||
} catch {
|
||||
return { ok: false };
|
||||
|
|
@ -941,6 +950,7 @@ export async function rejectFriendRequest(username) {
|
|||
body: JSON.stringify({ username }),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (res.ok) emitFriendsChanged();
|
||||
return { ok: res.ok, ...data };
|
||||
} catch {
|
||||
return { ok: false };
|
||||
|
|
@ -956,6 +966,7 @@ export async function cancelFriendRequest(username) {
|
|||
body: JSON.stringify({ username }),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (res.ok) emitFriendsChanged();
|
||||
return { ok: res.ok, ...data };
|
||||
} catch {
|
||||
return { ok: false };
|
||||
|
|
@ -971,6 +982,7 @@ export async function removeFriend(username) {
|
|||
body: JSON.stringify({ username }),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (res.ok) emitFriendsChanged();
|
||||
return { ok: res.ok, ...data };
|
||||
} catch {
|
||||
return { ok: false };
|
||||
|
|
|
|||
|
|
@ -432,8 +432,8 @@ body[data-theme="light"] .contacts-empty {
|
|||
|
||||
.contacts-dropdown {
|
||||
right: 8px;
|
||||
left: 8px;
|
||||
width: auto;
|
||||
left: auto;
|
||||
width: 260px;
|
||||
bottom: 70px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -199,6 +199,15 @@ export default function ChatContainer() {
|
|||
};
|
||||
|
||||
loadFriends();
|
||||
|
||||
// Listen for friend changes to refresh the list
|
||||
const handleFriendsChanged = () => {
|
||||
loadFriends();
|
||||
};
|
||||
window.addEventListener("sociowire:friends-changed", handleFriendsChanged);
|
||||
return () => {
|
||||
window.removeEventListener("sociowire:friends-changed", handleFriendsChanged);
|
||||
};
|
||||
}, [authenticated]);
|
||||
|
||||
// Load conversations to get initial unread counts
|
||||
|
|
|
|||
|
|
@ -194,25 +194,29 @@ function updateTruth(modalWrap, post) {
|
|||
if (label) label.textContent = `${score}`;
|
||||
}
|
||||
|
||||
function updateTruthVoteUI(modalWrap, stats) {
|
||||
function updateTruthVoteUI(modalWrap, stats, updateScore = false) {
|
||||
if (!modalWrap || !stats) return;
|
||||
const aiEl = modalWrap.querySelector("[data-truth-ai]");
|
||||
const userEl = modalWrap.querySelector("[data-truth-user]");
|
||||
const countEl = modalWrap.querySelector("[data-truth-count]");
|
||||
const rail = modalWrap.querySelector(".sw-truth-rail");
|
||||
const marker = rail ? rail.querySelector(".sw-truth-rail-marker") : null;
|
||||
const label = rail ? rail.querySelector(".sw-truth-rail-score") : null;
|
||||
|
||||
const aiScore = Number.isFinite(stats.ai_score) ? Math.round(stats.ai_score) : stats.AIScore;
|
||||
const userScore = Number.isFinite(stats.user_score) ? Math.round(stats.user_score) : stats.UserScore;
|
||||
const truthScoreValue = Number.isFinite(stats.truth_score) ? Math.round(stats.truth_score) : stats.TruthScore;
|
||||
const totalVotes = Number.isFinite(stats.total_votes) ? stats.total_votes : stats.TotalVotes;
|
||||
|
||||
if (aiEl) aiEl.textContent = Number.isFinite(aiScore) ? String(aiScore) : "—";
|
||||
if (userEl) userEl.textContent = Number.isFinite(userScore) ? String(userScore) : "—";
|
||||
if (countEl) countEl.textContent = `${totalVotes || 0} votes`;
|
||||
if (marker && Number.isFinite(truthScoreValue)) marker.style.top = `${100 - truthScoreValue}%`;
|
||||
if (label && Number.isFinite(truthScoreValue)) label.textContent = `${truthScoreValue}`;
|
||||
|
||||
// Only update the score display if explicitly requested (e.g., after user votes)
|
||||
if (updateScore) {
|
||||
const rail = modalWrap.querySelector(".sw-truth-rail");
|
||||
const marker = rail ? rail.querySelector(".sw-truth-rail-marker") : null;
|
||||
const label = rail ? rail.querySelector(".sw-truth-rail-score") : null;
|
||||
const truthScoreValue = Number.isFinite(stats.truth_score) ? Math.round(stats.truth_score) : stats.TruthScore;
|
||||
if (marker && Number.isFinite(truthScoreValue)) marker.style.top = `${100 - truthScoreValue}%`;
|
||||
if (label && Number.isFinite(truthScoreValue)) label.textContent = `${truthScoreValue}`;
|
||||
}
|
||||
|
||||
const vote = (stats.user_vote || stats.UserVote || "").toString();
|
||||
const btns = modalWrap.querySelectorAll(".sw-truth-vote-btn");
|
||||
|
|
@ -1881,7 +1885,9 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
|||
});
|
||||
}
|
||||
|
||||
if (postID > 0) {
|
||||
// Only refetch if initial data is missing critical fields
|
||||
const hasCompleteData = postData?.title && (postData?.published_at || postData?.created_at);
|
||||
if (postID > 0 && !hasCompleteData) {
|
||||
fetchPostById(postID).then((fresh) => {
|
||||
if (!fresh) return;
|
||||
postData = { ...postData, ...fresh };
|
||||
|
|
@ -1936,18 +1942,29 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
|||
}
|
||||
|
||||
const rail = modalWrap.querySelector(".sw-truth-rail");
|
||||
if (rail && postID > 0) {
|
||||
if (rail && postID > 0 && !rail.hasAttribute("data-vote-bound")) {
|
||||
rail.setAttribute("data-vote-bound", "1");
|
||||
const doVote = async (vote) => {
|
||||
if (!isAuthed()) {
|
||||
requestAuth("Please sign in or create a free account to vote on truth.");
|
||||
return;
|
||||
}
|
||||
if (rail.classList.contains("sw-truth-vote-disabled")) {
|
||||
return; // Already voting
|
||||
}
|
||||
try {
|
||||
rail.classList.add("sw-truth-vote-disabled");
|
||||
const stats = await votePostTruth(postID, vote);
|
||||
if (stats) updateTruthVoteUI(modalWrap, stats);
|
||||
} catch {}
|
||||
rail.classList.remove("sw-truth-vote-disabled");
|
||||
if (stats) {
|
||||
updateTruthVoteUI(modalWrap, stats, true);
|
||||
} else {
|
||||
console.warn("votePostTruth returned no stats for post", postID);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Truth vote error:", err);
|
||||
} finally {
|
||||
rail.classList.remove("sw-truth-vote-disabled");
|
||||
}
|
||||
};
|
||||
const voteUp = rail.querySelector(".sw-truth-rail-btn-up");
|
||||
const voteDown = rail.querySelector(".sw-truth-rail-btn-down");
|
||||
|
|
|
|||
|
|
@ -737,6 +737,7 @@ body[data-theme="blue"] .sw-centered-modal .post-card.sw-expanded-shell{
|
|||
background:#0b1220;
|
||||
border:2px solid #fff;
|
||||
box-shadow: 0 4px 10px rgba(0,0,0,0.35);
|
||||
top: 35%;
|
||||
}
|
||||
|
||||
.sw-truth-rail-score{
|
||||
|
|
@ -750,6 +751,7 @@ body[data-theme="blue"] .sw-centered-modal .post-card.sw-expanded-shell{
|
|||
margin: 0 auto;
|
||||
position: relative;
|
||||
left: -6px;
|
||||
transition: opacity 0.2s ease-out;
|
||||
}
|
||||
|
||||
.sw-truth-rail-btn{
|
||||
|
|
|
|||
Loading…
Reference in New Issue