Fix fakalyzer UI and realtime updates
This commit is contained in:
parent
b8ac7d2010
commit
55ecfbd34e
|
|
@ -246,6 +246,40 @@ export async function fetchPostById(postId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchPostTruth(postId) {
|
||||||
|
if (!postId) return null;
|
||||||
|
const qs = new URLSearchParams();
|
||||||
|
qs.set("post_id", String(postId));
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_BASE}/post/truth?${qs.toString()}`, {
|
||||||
|
credentials: "include",
|
||||||
|
headers: { ...authHeaders() },
|
||||||
|
});
|
||||||
|
if (!res.ok) return null;
|
||||||
|
const data = await res.json().catch(() => null);
|
||||||
|
return data?.truth || null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function votePostTruth(postId, vote) {
|
||||||
|
if (!postId) throw new Error("post_id required");
|
||||||
|
const body = JSON.stringify({ post_id: postId, vote });
|
||||||
|
const res = await fetch(`${API_BASE}/post/truth-vote`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json", ...authHeaders() },
|
||||||
|
credentials: "include",
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const text = await res.text().catch(() => "");
|
||||||
|
throw new Error(text || "truth vote failed");
|
||||||
|
}
|
||||||
|
const data = await res.json().catch(() => ({}));
|
||||||
|
return data?.truth || null;
|
||||||
|
}
|
||||||
|
|
||||||
export async function updatePostVisibility(postId, payload = {}, token) {
|
export async function updatePostVisibility(postId, payload = {}, token) {
|
||||||
if (!postId) throw new Error("post_id required");
|
if (!postId) throw new Error("post_id required");
|
||||||
const headers = { "Content-Type": "application/json" };
|
const headers = { "Content-Type": "application/json" };
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,8 @@ export default function MapView({
|
||||||
const overlayTimerRef = useRef(null);
|
const overlayTimerRef = useRef(null);
|
||||||
const pendingOpenPostIdRef = useRef(null);
|
const pendingOpenPostIdRef = useRef(null);
|
||||||
const pendingOpenFullRef = useRef(false);
|
const pendingOpenFullRef = useRef(false);
|
||||||
|
const searchFitQueryRef = useRef("");
|
||||||
|
const searchUserMovedRef = useRef(false);
|
||||||
|
|
||||||
const openOverlayWhenReady = useCallback(
|
const openOverlayWhenReady = useCallback(
|
||||||
(post, opts = {}) => {
|
(post, opts = {}) => {
|
||||||
|
|
@ -95,7 +97,7 @@ export default function MapView({
|
||||||
[mapRef, theme]
|
[mapRef, theme]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { status, visiblePosts, loadingPosts, loadError, handleIncomingPost } =
|
const { status, visiblePosts, loadingPosts, loadError, handleIncomingPost, handlePostUpdate } =
|
||||||
usePostsEngine({
|
usePostsEngine({
|
||||||
theme,
|
theme,
|
||||||
mapRef,
|
mapRef,
|
||||||
|
|
@ -312,12 +314,18 @@ export default function MapView({
|
||||||
ws.onmessage = (ev) => {
|
ws.onmessage = (ev) => {
|
||||||
try {
|
try {
|
||||||
const msg = JSON.parse(ev.data);
|
const msg = JSON.parse(ev.data);
|
||||||
if (msg.type === "new_post" && msg.post) handleIncomingPost(msg.post);
|
if (msg.type === "new_post" && msg.post) {
|
||||||
|
handleIncomingPost(msg.post);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (msg.type === "post_update") {
|
||||||
|
handlePostUpdate(msg);
|
||||||
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
};
|
};
|
||||||
|
|
||||||
return () => ws && ws.close();
|
return () => ws && ws.close();
|
||||||
}, [handleIncomingPost]);
|
}, [handleIncomingPost, handlePostUpdate]);
|
||||||
|
|
||||||
const handleFlyToMe = () => {
|
const handleFlyToMe = () => {
|
||||||
const map = mapRef.current;
|
const map = mapRef.current;
|
||||||
|
|
@ -422,13 +430,15 @@ export default function MapView({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Zoom intelligent: quand on cherche, zoom pour voir tous les résultats
|
// Zoom intelligent: quand on cherche, zoom pour voir tous les résultats (une seule fois)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!searchQuery || !visiblePosts.length) return;
|
if (!searchQuery || !visiblePosts.length) return;
|
||||||
if (skipAutoZoomRef.current) {
|
if (skipAutoZoomRef.current) {
|
||||||
skipAutoZoomRef.current = false;
|
skipAutoZoomRef.current = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (searchUserMovedRef.current) return;
|
||||||
|
if (searchFitQueryRef.current === searchQuery) return;
|
||||||
|
|
||||||
const map = mapRef.current;
|
const map = mapRef.current;
|
||||||
if (!map) return;
|
if (!map) return;
|
||||||
|
|
@ -472,12 +482,29 @@ export default function MapView({
|
||||||
duration: 1000
|
duration: 1000
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
searchFitQueryRef.current = searchQuery;
|
||||||
}
|
}
|
||||||
}, 300); // Délai pour laisser les markers se créer
|
}, 300); // Délai pour laisser les markers se créer
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [searchQuery, visiblePosts, mapRef]);
|
}, [searchQuery, visiblePosts, mapRef]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
searchFitQueryRef.current = "";
|
||||||
|
searchUserMovedRef.current = false;
|
||||||
|
}, [searchQuery]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const map = mapRef.current;
|
||||||
|
if (!map) return;
|
||||||
|
if (!searchQuery) return;
|
||||||
|
const handleMove = () => {
|
||||||
|
searchUserMovedRef.current = true;
|
||||||
|
};
|
||||||
|
map.on("movestart", handleMove);
|
||||||
|
return () => map.off("movestart", handleMove);
|
||||||
|
}, [mapRef, searchQuery]);
|
||||||
|
|
||||||
const handleProfileVisitIsland = (island) => {
|
const handleProfileVisitIsland = (island) => {
|
||||||
console.log('[MapView] Visiting island from profile:', island.username);
|
console.log('[MapView] Visiting island from profile:', island.username);
|
||||||
setSelectedProfile(null); // Close profile modal
|
setSelectedProfile(null); // Close profile modal
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ import {
|
||||||
createPostComment,
|
createPostComment,
|
||||||
fetchPostLikeState,
|
fetchPostLikeState,
|
||||||
fetchProfile,
|
fetchProfile,
|
||||||
|
fetchPostTruth,
|
||||||
|
votePostTruth,
|
||||||
} from "../../api/client";
|
} from "../../api/client";
|
||||||
import { trackEvent } from "../../utils/analytics";
|
import { trackEvent } from "../../utils/analytics";
|
||||||
|
|
||||||
|
|
@ -126,6 +128,38 @@ function updateTruth(modalWrap, post) {
|
||||||
if (label) label.textContent = `${score}`;
|
if (label) label.textContent = `${score}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateTruthVoteUI(modalWrap, stats) {
|
||||||
|
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}`;
|
||||||
|
|
||||||
|
const vote = (stats.user_vote || stats.UserVote || "").toString();
|
||||||
|
const btns = modalWrap.querySelectorAll(".sw-truth-vote-btn");
|
||||||
|
btns.forEach((btn) => {
|
||||||
|
const v = btn.getAttribute("data-truth-vote");
|
||||||
|
btn.classList.toggle("is-active", vote && v === vote);
|
||||||
|
});
|
||||||
|
const railUp = modalWrap.querySelector(".sw-truth-rail-btn-up");
|
||||||
|
const railDown = modalWrap.querySelector(".sw-truth-rail-btn-down");
|
||||||
|
if (railUp) railUp.classList.toggle("is-active", vote === "true");
|
||||||
|
if (railDown) railDown.classList.toggle("is-active", vote === "false");
|
||||||
|
}
|
||||||
|
|
||||||
function isAuthed() {
|
function isAuthed() {
|
||||||
try {
|
try {
|
||||||
const auth = loadAuth();
|
const auth = loadAuth();
|
||||||
|
|
@ -272,6 +306,34 @@ function mountCard(container, spec, data, theme, post) {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const rawScore = Number(
|
||||||
|
post?.truth_score ??
|
||||||
|
post?.truthScore ??
|
||||||
|
post?.truth_ai ??
|
||||||
|
post?.truthAi ??
|
||||||
|
post?.ai_score ??
|
||||||
|
post?.AIScore
|
||||||
|
);
|
||||||
|
const hasAI = Number.isFinite(
|
||||||
|
Number(post?.truth_ai ?? post?.truthAi ?? post?.ai_score ?? post?.AIScore)
|
||||||
|
);
|
||||||
|
const hasScore = Number.isFinite(rawScore) && !(rawScore === 50 && !hasAI);
|
||||||
|
if (!hasScore && post?.id) {
|
||||||
|
fetchPostTruth(post.id).then((stats) => {
|
||||||
|
if (!stats) return;
|
||||||
|
const nextScore = Number(stats.truth_score ?? stats.TruthScore);
|
||||||
|
if (!Number.isFinite(nextScore)) return;
|
||||||
|
post.truth_score = nextScore;
|
||||||
|
const markerEl = container.querySelector(".sw-truth-mini-marker");
|
||||||
|
const scoreEl = container.querySelector(".sw-truth-mini-score");
|
||||||
|
if (markerEl) markerEl.style.top = `${100 - nextScore}%`;
|
||||||
|
if (scoreEl) {
|
||||||
|
scoreEl.style.background = truthColor(nextScore);
|
||||||
|
scoreEl.textContent = `${Math.round(nextScore)}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
try { root.unmount(); } catch {}
|
try { root.unmount(); } catch {}
|
||||||
};
|
};
|
||||||
|
|
@ -353,16 +415,18 @@ function getPostTime(post) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function truthScore(post) {
|
function truthScore(post) {
|
||||||
const raw = Number(post?.truth_score || post?.truthScore);
|
const raw = Number(
|
||||||
|
post?.truth_score ??
|
||||||
|
post?.truthScore ??
|
||||||
|
post?.truth_ai ??
|
||||||
|
post?.truthAi ??
|
||||||
|
post?.ai_score ??
|
||||||
|
post?.AIScore
|
||||||
|
);
|
||||||
if (Number.isFinite(raw)) {
|
if (Number.isFinite(raw)) {
|
||||||
return clamp(Math.round(raw), 0, 100);
|
return clamp(Math.round(raw), 0, 100);
|
||||||
}
|
}
|
||||||
const seed = `${post?.id || ""}|${post?.url || ""}|${post?.title || ""}`;
|
return 50;
|
||||||
let h = 0;
|
|
||||||
for (let i = 0; i < seed.length; i++) {
|
|
||||||
h = (h * 31 + seed.charCodeAt(i)) >>> 0;
|
|
||||||
}
|
|
||||||
return 35 + (h % 61);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function truthColor(score) {
|
function truthColor(score) {
|
||||||
|
|
@ -414,6 +478,16 @@ function closeCenteredOverlay(map, opts = {}) {
|
||||||
|
|
||||||
map.__swCenteredOverlay = null;
|
map.__swCenteredOverlay = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const st = ov.interactionState || {};
|
||||||
|
if (map) {
|
||||||
|
if (st.dragPan) map.dragPan?.enable?.();
|
||||||
|
if (st.scrollZoom) map.scrollZoom?.enable?.();
|
||||||
|
if (st.doubleClickZoom) map.doubleClickZoom?.enable?.();
|
||||||
|
if (st.touchZoomRotate) map.touchZoomRotate?.enable?.();
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
if (!skipHistory && ov.historyPushed && !ov.historyClosed) {
|
if (!skipHistory && ov.historyPushed && !ov.historyClosed) {
|
||||||
ov.historyClosed = true;
|
ov.historyClosed = true;
|
||||||
try { window.history.back(); } catch {}
|
try { window.history.back(); } catch {}
|
||||||
|
|
@ -426,6 +500,17 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
||||||
|
|
||||||
closeCenteredOverlay(map, { force: true, reason: "switch", skipHistory: true });
|
closeCenteredOverlay(map, { force: true, reason: "switch", skipHistory: true });
|
||||||
|
|
||||||
|
const interactionState = {
|
||||||
|
dragPan: map.dragPan && map.dragPan.isEnabled ? map.dragPan.isEnabled() : null,
|
||||||
|
scrollZoom: map.scrollZoom && map.scrollZoom.isEnabled ? map.scrollZoom.isEnabled() : null,
|
||||||
|
doubleClickZoom: map.doubleClickZoom && map.doubleClickZoom.isEnabled ? map.doubleClickZoom.isEnabled() : null,
|
||||||
|
touchZoomRotate: map.touchZoomRotate && map.touchZoomRotate.isEnabled ? map.touchZoomRotate.isEnabled() : null,
|
||||||
|
};
|
||||||
|
try { if (map.dragPan) map.dragPan.disable(); } catch {}
|
||||||
|
try { if (map.scrollZoom) map.scrollZoom.disable(); } catch {}
|
||||||
|
try { if (map.doubleClickZoom) map.doubleClickZoom.disable(); } catch {}
|
||||||
|
try { if (map.touchZoomRotate) map.touchZoomRotate.disable(); } catch {}
|
||||||
|
|
||||||
let postData = { ...(post || {}) };
|
let postData = { ...(post || {}) };
|
||||||
const data = adaptPostToTemplateData(postData);
|
const data = adaptPostToTemplateData(postData);
|
||||||
const cat = normCatLabel(postData);
|
const cat = normCatLabel(postData);
|
||||||
|
|
@ -494,12 +579,12 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
||||||
|
|
||||||
<div class="sw-modal-hero-row">
|
<div class="sw-modal-hero-row">
|
||||||
<div class="sw-truth-rail" data-score="${truth}">
|
<div class="sw-truth-rail" data-score="${truth}">
|
||||||
<div class="sw-truth-rail-top">TRUE</div>
|
<button class="sw-truth-rail-btn sw-truth-rail-btn-up" type="button">TRUE</button>
|
||||||
<div class="sw-truth-rail-bar">
|
<div class="sw-truth-rail-bar">
|
||||||
<div class="sw-truth-rail-marker" style="top:${100 - truth}%"></div>
|
<div class="sw-truth-rail-marker" style="top:${100 - truth}%"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sw-truth-rail-score">${truth}</div>
|
<div class="sw-truth-rail-score">${truth}</div>
|
||||||
<div class="sw-truth-rail-bottom">FALSE</div>
|
<button class="sw-truth-rail-btn sw-truth-rail-btn-down" type="button">FALSE</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="sw-modal-hero">
|
<div class="sw-modal-hero">
|
||||||
${
|
${
|
||||||
|
|
@ -509,7 +594,6 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sw-modal-title">${escapeHtml(data?.headline || postData?.title || "Untitled")}</div>
|
<div class="sw-modal-title">${escapeHtml(data?.headline || postData?.title || "Untitled")}</div>
|
||||||
|
|
||||||
<div class="sw-modal-summary">
|
<div class="sw-modal-summary">
|
||||||
|
|
@ -612,7 +696,11 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
||||||
closeCenteredOverlay(map, { force: true });
|
closeCenteredOverlay(map, { force: true });
|
||||||
});
|
});
|
||||||
// inside click doesn't close
|
// inside click doesn't close
|
||||||
modalWrap.addEventListener("click", (e) => e.stopPropagation());
|
const stopPropagation = (e) => e.stopPropagation();
|
||||||
|
modalWrap.addEventListener("click", stopPropagation);
|
||||||
|
modalWrap.addEventListener("pointerdown", stopPropagation);
|
||||||
|
modalWrap.addEventListener("mousedown", stopPropagation);
|
||||||
|
modalWrap.addEventListener("touchstart", stopPropagation, { passive: true });
|
||||||
|
|
||||||
// close button
|
// close button
|
||||||
const xbtn = modalWrap.querySelector(".sw-modal-x");
|
const xbtn = modalWrap.querySelector(".sw-modal-x");
|
||||||
|
|
@ -668,6 +756,12 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (postID > 0) {
|
||||||
|
fetchPostTruth(postID).then((stats) => {
|
||||||
|
if (stats) updateTruthVoteUI(modalWrap, stats);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let liked = false;
|
let liked = false;
|
||||||
const likeBtn = modalWrap.querySelector('[data-action="like"]');
|
const likeBtn = modalWrap.querySelector('[data-action="like"]');
|
||||||
const commentInput = modalWrap.querySelector(".sw-livechat-input");
|
const commentInput = modalWrap.querySelector(".sw-livechat-input");
|
||||||
|
|
@ -784,6 +878,26 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rail = modalWrap.querySelector(".sw-truth-rail");
|
||||||
|
if (rail && postID > 0) {
|
||||||
|
const doVote = async (vote) => {
|
||||||
|
if (!isAuthed()) {
|
||||||
|
requestAuth("Please sign in or create a free account to vote on truth.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
};
|
||||||
|
const voteUp = rail.querySelector(".sw-truth-rail-btn-up");
|
||||||
|
const voteDown = rail.querySelector(".sw-truth-rail-btn-down");
|
||||||
|
if (voteUp) voteUp.addEventListener("click", () => doVote("true"));
|
||||||
|
if (voteDown) voteDown.addEventListener("click", () => doVote("false"));
|
||||||
|
}
|
||||||
|
|
||||||
applyAuthUI();
|
applyAuthUI();
|
||||||
const onAuth = () => {
|
const onAuth = () => {
|
||||||
if (postID > 0 && isAuthed()) {
|
if (postID > 0 && isAuthed()) {
|
||||||
|
|
@ -808,6 +922,7 @@ export function openCenteredOverlay({ map, post, theme, fullScreen = false }) {
|
||||||
postId: postData?.id,
|
postId: postData?.id,
|
||||||
modalWrapRef: modalWrap,
|
modalWrapRef: modalWrap,
|
||||||
backdrop,
|
backdrop,
|
||||||
|
interactionState,
|
||||||
onKey,
|
onKey,
|
||||||
onPop,
|
onPop,
|
||||||
onAuth,
|
onAuth,
|
||||||
|
|
|
||||||
|
|
@ -533,7 +533,7 @@ export function usePostsEngine({
|
||||||
);
|
);
|
||||||
|
|
||||||
const refreshVisibleAndMarkers = useCallback(
|
const refreshVisibleAndMarkers = useCallback(
|
||||||
(tf) => {
|
(tf, force = false) => {
|
||||||
const vAll = computeVisible(tf);
|
const vAll = computeVisible(tf);
|
||||||
const map = mapRef.current;
|
const map = mapRef.current;
|
||||||
let v = (searchQuery || "").trim() ? vAll : filterByBounds(vAll, map);
|
let v = (searchQuery || "").trim() ? vAll : filterByBounds(vAll, map);
|
||||||
|
|
@ -543,7 +543,10 @@ export function usePostsEngine({
|
||||||
}
|
}
|
||||||
|
|
||||||
// reduce wall blink: don't update if same ids
|
// reduce wall blink: don't update if same ids
|
||||||
setVisiblePosts((prev) => (sameIdList(prev, v) ? prev : v));
|
setVisiblePosts((prev) => {
|
||||||
|
if (force) return v;
|
||||||
|
return sameIdList(prev, v) ? prev : v;
|
||||||
|
});
|
||||||
|
|
||||||
// keep status silent (no annoying bubbles)
|
// keep status silent (no annoying bubbles)
|
||||||
setStatus("");
|
setStatus("");
|
||||||
|
|
@ -843,5 +846,85 @@ export function usePostsEngine({
|
||||||
[mainFilter, subFilter, timeFilter, searchQuery, mapRef, markersRef, expandedElRef, theme, refreshVisibleAndMarkers]
|
[mainFilter, subFilter, timeFilter, searchQuery, mapRef, markersRef, expandedElRef, theme, refreshVisibleAndMarkers]
|
||||||
);
|
);
|
||||||
|
|
||||||
return { status, visiblePosts, loadingPosts, loadError, handleIncomingPost };
|
const handlePostUpdate = useCallback(
|
||||||
|
(payload) => {
|
||||||
|
const postId = payload?.post_id || payload?.postId || payload?.id;
|
||||||
|
if (!postId) return;
|
||||||
|
|
||||||
|
const counts = payload?.counts || null;
|
||||||
|
const truth = payload?.truth || null;
|
||||||
|
const truthScore = Number(
|
||||||
|
truth?.truth_score ??
|
||||||
|
truth?.TruthScore ??
|
||||||
|
payload?.truth_score ??
|
||||||
|
payload?.truthScore
|
||||||
|
);
|
||||||
|
|
||||||
|
const updated = [];
|
||||||
|
let changed = false;
|
||||||
|
for (const p of allPostsRef.current || []) {
|
||||||
|
if ((p?.id || p?.ID) !== postId) {
|
||||||
|
updated.push(p);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const next = { ...p };
|
||||||
|
if (counts) {
|
||||||
|
if (Number.isFinite(counts.view_count)) next.view_count = counts.view_count;
|
||||||
|
if (Number.isFinite(counts.like_count)) next.like_count = counts.like_count;
|
||||||
|
if (Number.isFinite(counts.share_count)) next.share_count = counts.share_count;
|
||||||
|
if (Number.isFinite(counts.comment_count)) next.comment_count = counts.comment_count;
|
||||||
|
}
|
||||||
|
if (Number.isFinite(truthScore)) next.truth_score = truthScore;
|
||||||
|
updated.push(next);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
allPostsRef.current = updated;
|
||||||
|
refreshVisibleAndMarkers(timeFilter, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (Number.isFinite(truthScore)) {
|
||||||
|
const markers = markersRef.current || [];
|
||||||
|
for (const item of markers) {
|
||||||
|
if (!item || item.id !== postId) continue;
|
||||||
|
const el = item.el;
|
||||||
|
if (!el) continue;
|
||||||
|
const marker = el.querySelector(".sw-truth-mini-marker");
|
||||||
|
const scoreEl = el.querySelector(".sw-truth-mini-score");
|
||||||
|
if (marker) marker.style.top = `${100 - truthScore}%`;
|
||||||
|
if (scoreEl) {
|
||||||
|
scoreEl.style.background = `hsl(${Math.round((truthScore / 100) * 120)} 80% 42%)`;
|
||||||
|
scoreEl.textContent = `${Math.round(truthScore)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const map = mapRef.current;
|
||||||
|
const ov = map?.__swCenteredOverlay;
|
||||||
|
if (ov && ov.postId === postId && ov.modalWrapRef) {
|
||||||
|
const wrap = ov.modalWrapRef;
|
||||||
|
if (counts) {
|
||||||
|
const mapStat = (key, value) => {
|
||||||
|
if (!Number.isFinite(value)) return;
|
||||||
|
const el = wrap.querySelector(`[data-stat="${key}"] .sw-stat-num`);
|
||||||
|
if (el) el.textContent = String(value);
|
||||||
|
};
|
||||||
|
mapStat("views", counts.view_count);
|
||||||
|
mapStat("likes", counts.like_count);
|
||||||
|
mapStat("shares", counts.share_count);
|
||||||
|
mapStat("comments", counts.comment_count);
|
||||||
|
}
|
||||||
|
if (Number.isFinite(truthScore)) {
|
||||||
|
const marker = wrap.querySelector(".sw-truth-rail-marker");
|
||||||
|
const label = wrap.querySelector(".sw-truth-rail-score");
|
||||||
|
if (marker) marker.style.top = `${100 - truthScore}%`;
|
||||||
|
if (label) label.textContent = `${Math.round(truthScore)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
},
|
||||||
|
[mapRef, refreshVisibleAndMarkers, timeFilter]
|
||||||
|
);
|
||||||
|
|
||||||
|
return { status, visiblePosts, loadingPosts, loadError, handleIncomingPost, handlePostUpdate };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -598,18 +598,22 @@ body[data-theme="light"] .sw-chat-bubble::before{
|
||||||
|
|
||||||
.sw-modal-hero-row{
|
.sw-modal-hero-row{
|
||||||
display:flex;
|
display:flex;
|
||||||
gap:10px;
|
gap:16px;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sw-truth-rail{
|
.sw-truth-rail{
|
||||||
width: 16px;
|
width: 22px;
|
||||||
min-width: 16px;
|
min-width: 22px;
|
||||||
height: 180px;
|
height: 180px;
|
||||||
min-height: 180px;
|
min-height: 180px;
|
||||||
margin-left: 3px;
|
margin-left: -2px;
|
||||||
|
margin-right: 10px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
display:flex;
|
display:flex;
|
||||||
flex-direction:column;
|
flex-direction:column;
|
||||||
align-items:center;
|
align-items:center;
|
||||||
|
|
@ -619,6 +623,8 @@ body[data-theme="light"] .sw-chat-bubble::before{
|
||||||
letter-spacing: .08em;
|
letter-spacing: .08em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color:#e2e8f0;
|
color:#e2e8f0;
|
||||||
|
pointer-events: auto;
|
||||||
|
touch-action: manipulation;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sw-truth-rail-bar{
|
.sw-truth-rail-bar{
|
||||||
|
|
@ -629,6 +635,7 @@ body[data-theme="light"] .sw-chat-bubble::before{
|
||||||
border: 2px solid rgba(0,0,0,0.45);
|
border: 2px solid rgba(0,0,0,0.45);
|
||||||
position: relative;
|
position: relative;
|
||||||
box-shadow: inset 0 0 10px rgba(0,0,0,0.35);
|
box-shadow: inset 0 0 10px rgba(0,0,0,0.35);
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sw-truth-rail-marker{
|
.sw-truth-rail-marker{
|
||||||
|
|
@ -651,12 +658,122 @@ body[data-theme="light"] .sw-chat-bubble::before{
|
||||||
padding: 4px 6px;
|
padding: 4px 6px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
box-shadow: 0 6px 12px rgba(0,0,0,0.35);
|
box-shadow: 0 6px 12px rgba(0,0,0,0.35);
|
||||||
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
|
left: -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-truth-rail-btn{
|
||||||
|
width: 100%;
|
||||||
|
border: 0;
|
||||||
|
background: rgba(56,189,248,0.16);
|
||||||
|
color:#e8f5ff;
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: .06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 6px 2px;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
pointer-events: auto;
|
||||||
|
touch-action: manipulation;
|
||||||
|
position: relative;
|
||||||
|
left: -8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-truth-rail-btn-down{
|
||||||
|
background: rgba(239,68,68,0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-truth-rail-btn.is-active{
|
||||||
|
background: rgba(34,197,94,0.35);
|
||||||
|
color: #eafff3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-truth-rail-btn-down.is-active{
|
||||||
|
background: rgba(239,68,68,0.35);
|
||||||
|
color: #ffecec;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-truth-vote-disabled{
|
||||||
|
opacity: 0.7;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sw-modal-hero-row{
|
.sw-modal-hero-row{
|
||||||
display:flex;
|
display:flex;
|
||||||
gap:12px;
|
gap:12px;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-truth-vote-row{
|
||||||
|
display:flex;
|
||||||
|
gap:12px;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:space-between;
|
||||||
|
background: rgba(3, 14, 32, 0.55);
|
||||||
|
border: 1px solid rgba(90, 190, 255, 0.22);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
margin: 6px 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-truth-vote-meta{
|
||||||
|
display:flex;
|
||||||
|
flex-direction:column;
|
||||||
|
gap:4px;
|
||||||
|
min-width:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-truth-vote-title{
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: .08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #c7e3ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-truth-vote-scores{
|
||||||
|
display:flex;
|
||||||
|
gap:10px;
|
||||||
|
font-size: 12px;
|
||||||
|
color:#e8f5ff;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-truth-vote-count{
|
||||||
|
font-size: 11px;
|
||||||
|
color: rgba(215,233,255,0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-truth-vote-actions{
|
||||||
|
display:flex;
|
||||||
|
gap:8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-truth-vote-btn{
|
||||||
|
background: rgba(56,189,248,0.14);
|
||||||
|
border: 1px solid rgba(56,189,248,0.35);
|
||||||
|
color: #e8f5ff;
|
||||||
|
font-weight: 900;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor:pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-truth-vote-btn.is-active{
|
||||||
|
background: rgba(34,197,94,0.25);
|
||||||
|
border-color: rgba(34,197,94,0.6);
|
||||||
|
color:#eafff3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sw-truth-vote-btn[data-truth-vote="false"].is-active{
|
||||||
|
background: rgba(239,68,68,0.25);
|
||||||
|
border-color: rgba(239,68,68,0.6);
|
||||||
|
color:#ffecec;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sw-card-wrap{
|
.sw-card-wrap{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue