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) {
|
export async function sendFriendRequest(username) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${apiBase()}/friends/request`, {
|
const res = await fetch(`${apiBase()}/friends/request`, {
|
||||||
|
|
@ -911,6 +918,7 @@ export async function sendFriendRequest(username) {
|
||||||
body: JSON.stringify({ username }),
|
body: JSON.stringify({ username }),
|
||||||
});
|
});
|
||||||
const data = await res.json().catch(() => ({}));
|
const data = await res.json().catch(() => ({}));
|
||||||
|
if (res.ok) emitFriendsChanged();
|
||||||
return { ok: res.ok, ...data };
|
return { ok: res.ok, ...data };
|
||||||
} catch {
|
} catch {
|
||||||
return { ok: false };
|
return { ok: false };
|
||||||
|
|
@ -926,6 +934,7 @@ export async function acceptFriendRequest(username) {
|
||||||
body: JSON.stringify({ username }),
|
body: JSON.stringify({ username }),
|
||||||
});
|
});
|
||||||
const data = await res.json().catch(() => ({}));
|
const data = await res.json().catch(() => ({}));
|
||||||
|
if (res.ok) emitFriendsChanged();
|
||||||
return { ok: res.ok, ...data };
|
return { ok: res.ok, ...data };
|
||||||
} catch {
|
} catch {
|
||||||
return { ok: false };
|
return { ok: false };
|
||||||
|
|
@ -941,6 +950,7 @@ export async function rejectFriendRequest(username) {
|
||||||
body: JSON.stringify({ username }),
|
body: JSON.stringify({ username }),
|
||||||
});
|
});
|
||||||
const data = await res.json().catch(() => ({}));
|
const data = await res.json().catch(() => ({}));
|
||||||
|
if (res.ok) emitFriendsChanged();
|
||||||
return { ok: res.ok, ...data };
|
return { ok: res.ok, ...data };
|
||||||
} catch {
|
} catch {
|
||||||
return { ok: false };
|
return { ok: false };
|
||||||
|
|
@ -956,6 +966,7 @@ export async function cancelFriendRequest(username) {
|
||||||
body: JSON.stringify({ username }),
|
body: JSON.stringify({ username }),
|
||||||
});
|
});
|
||||||
const data = await res.json().catch(() => ({}));
|
const data = await res.json().catch(() => ({}));
|
||||||
|
if (res.ok) emitFriendsChanged();
|
||||||
return { ok: res.ok, ...data };
|
return { ok: res.ok, ...data };
|
||||||
} catch {
|
} catch {
|
||||||
return { ok: false };
|
return { ok: false };
|
||||||
|
|
@ -971,6 +982,7 @@ export async function removeFriend(username) {
|
||||||
body: JSON.stringify({ username }),
|
body: JSON.stringify({ username }),
|
||||||
});
|
});
|
||||||
const data = await res.json().catch(() => ({}));
|
const data = await res.json().catch(() => ({}));
|
||||||
|
if (res.ok) emitFriendsChanged();
|
||||||
return { ok: res.ok, ...data };
|
return { ok: res.ok, ...data };
|
||||||
} catch {
|
} catch {
|
||||||
return { ok: false };
|
return { ok: false };
|
||||||
|
|
|
||||||
|
|
@ -432,8 +432,8 @@ body[data-theme="light"] .contacts-empty {
|
||||||
|
|
||||||
.contacts-dropdown {
|
.contacts-dropdown {
|
||||||
right: 8px;
|
right: 8px;
|
||||||
left: 8px;
|
left: auto;
|
||||||
width: auto;
|
width: 260px;
|
||||||
bottom: 70px;
|
bottom: 70px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -199,6 +199,15 @@ export default function ChatContainer() {
|
||||||
};
|
};
|
||||||
|
|
||||||
loadFriends();
|
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]);
|
}, [authenticated]);
|
||||||
|
|
||||||
// Load conversations to get initial unread counts
|
// Load conversations to get initial unread counts
|
||||||
|
|
|
||||||
|
|
@ -194,25 +194,29 @@ function updateTruth(modalWrap, post) {
|
||||||
if (label) label.textContent = `${score}`;
|
if (label) label.textContent = `${score}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTruthVoteUI(modalWrap, stats) {
|
function updateTruthVoteUI(modalWrap, stats, updateScore = false) {
|
||||||
if (!modalWrap || !stats) return;
|
if (!modalWrap || !stats) return;
|
||||||
const aiEl = modalWrap.querySelector("[data-truth-ai]");
|
const aiEl = modalWrap.querySelector("[data-truth-ai]");
|
||||||
const userEl = modalWrap.querySelector("[data-truth-user]");
|
const userEl = modalWrap.querySelector("[data-truth-user]");
|
||||||
const countEl = modalWrap.querySelector("[data-truth-count]");
|
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 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 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;
|
const totalVotes = Number.isFinite(stats.total_votes) ? stats.total_votes : stats.TotalVotes;
|
||||||
|
|
||||||
if (aiEl) aiEl.textContent = Number.isFinite(aiScore) ? String(aiScore) : "—";
|
if (aiEl) aiEl.textContent = Number.isFinite(aiScore) ? String(aiScore) : "—";
|
||||||
if (userEl) userEl.textContent = Number.isFinite(userScore) ? String(userScore) : "—";
|
if (userEl) userEl.textContent = Number.isFinite(userScore) ? String(userScore) : "—";
|
||||||
if (countEl) countEl.textContent = `${totalVotes || 0} votes`;
|
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 vote = (stats.user_vote || stats.UserVote || "").toString();
|
||||||
const btns = modalWrap.querySelectorAll(".sw-truth-vote-btn");
|
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) => {
|
fetchPostById(postID).then((fresh) => {
|
||||||
if (!fresh) return;
|
if (!fresh) return;
|
||||||
postData = { ...postData, ...fresh };
|
postData = { ...postData, ...fresh };
|
||||||
|
|
@ -1936,18 +1942,29 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const rail = modalWrap.querySelector(".sw-truth-rail");
|
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) => {
|
const doVote = async (vote) => {
|
||||||
if (!isAuthed()) {
|
if (!isAuthed()) {
|
||||||
requestAuth("Please sign in or create a free account to vote on truth.");
|
requestAuth("Please sign in or create a free account to vote on truth.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (rail.classList.contains("sw-truth-vote-disabled")) {
|
||||||
|
return; // Already voting
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
rail.classList.add("sw-truth-vote-disabled");
|
rail.classList.add("sw-truth-vote-disabled");
|
||||||
const stats = await votePostTruth(postID, vote);
|
const stats = await votePostTruth(postID, vote);
|
||||||
if (stats) updateTruthVoteUI(modalWrap, stats);
|
if (stats) {
|
||||||
} catch {}
|
updateTruthVoteUI(modalWrap, stats, true);
|
||||||
rail.classList.remove("sw-truth-vote-disabled");
|
} 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 voteUp = rail.querySelector(".sw-truth-rail-btn-up");
|
||||||
const voteDown = rail.querySelector(".sw-truth-rail-btn-down");
|
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;
|
background:#0b1220;
|
||||||
border:2px solid #fff;
|
border:2px solid #fff;
|
||||||
box-shadow: 0 4px 10px rgba(0,0,0,0.35);
|
box-shadow: 0 4px 10px rgba(0,0,0,0.35);
|
||||||
|
top: 35%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sw-truth-rail-score{
|
.sw-truth-rail-score{
|
||||||
|
|
@ -750,6 +751,7 @@ body[data-theme="blue"] .sw-centered-modal .post-card.sw-expanded-shell{
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
left: -6px;
|
left: -6px;
|
||||||
|
transition: opacity 0.2s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sw-truth-rail-btn{
|
.sw-truth-rail-btn{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue