diff --git a/src/components/Map/markerManager.js b/src/components/Map/markerManager.js index e96e61d..5b6a972 100644 --- a/src/components/Map/markerManager.js +++ b/src/components/Map/markerManager.js @@ -789,11 +789,14 @@ function collectGalleryImages(post) { const seen = new Set(); const seenBase = new Set(); // Get base filename without size suffix to detect same image in different sizes + // Extract just the filename (not full path) to handle absolute vs relative URL dedup // Handles both long (_large, _medium, _small) and short (_l, _m, _s) variants const getBase = (url) => { if (!url) return ''; - return url.replace(/[-_](large|medium|small|thumb|thumbnail|l|m|s)(\.[^.]+)?$/i, '$2') - .replace(/\?.*$/, ''); // Remove query params + // Extract just the filename, remove path and domain + const filename = url.split('/').pop() || url; + return filename.replace(/[-_](large|medium|small|thumb|thumbnail|l|m|s)(\.[^.]+)?$/i, '$2') + .replace(/\?.*$/, ''); // Remove query params }; const add = (value) => { const normalized = normalizeImageUrl(value); diff --git a/src/components/Posts/PostDetailModal.jsx b/src/components/Posts/PostDetailModal.jsx index 079463c..818a5a0 100644 --- a/src/components/Posts/PostDetailModal.jsx +++ b/src/components/Posts/PostDetailModal.jsx @@ -17,6 +17,7 @@ import { deletePost, updatePostVisibility, fetchProfile, + fetchPostById, } from "../../api/client"; // ============================================================================ @@ -567,6 +568,7 @@ export default function PostDetailModal({ post, onClose, onPostUpdated, onPostDe const [showComments, setShowComments] = useState(true); const [showEdit, setShowEdit] = useState(false); const [showDelete, setShowDelete] = useState(false); + const [fullPost, setFullPost] = useState(null); // Full post data fetched from API const [editTitle, setEditTitle] = useState(post?.title || ""); const [editSnippet, setEditSnippet] = useState(post?.snippet || ""); const [editVisibility, setEditVisibility] = useState(post?.visibility || "public"); @@ -585,6 +587,9 @@ export default function PostDetailModal({ post, onClose, onPostUpdated, onPostDe const config = getCategoryConfig(post?.category); const author = post?.author || post?.username || post?.source_name || t("common.anon"); + // Merged post data: prefer fullPost (fetched from API) over initial post prop + const postData = useMemo(() => fullPost ?? post, [fullPost, post]); + // Media gallery - dedupe by base filename (ignore size variants like _large, _medium, _small, _l, _m, _s) // Note: media_urls is for videos only, image_urls is for images const gallery = useMemo(() => { @@ -592,10 +597,12 @@ export default function PostDetailModal({ post, onClose, onPostUpdated, onPostDe const seenBase = new Set(); const images = []; const getBase = (url) => { + // Extract just the filename, remove path and domain + const filename = url.split('/').pop() || url; // Remove size suffixes to detect same image in different sizes // Handles both long (_large, _medium, _small) and short (_l, _m, _s) variants - return url.replace(/[-_](large|medium|small|thumb|thumbnail|l|m|s)(\.[^.]+)?$/i, '$2') - .replace(/\?.*$/, ''); // Remove query params + return filename.replace(/[-_](large|medium|small|thumb|thumbnail|l|m|s)(\.[^.]+)?$/i, '$2') + .replace(/\?.*$/, ''); // Remove query params }; const add = (url) => { const n = normalizeMediaUrl(url); @@ -608,11 +615,11 @@ export default function PostDetailModal({ post, onClose, onPostUpdated, onPostDe images.push(n); }; // Add from image_urls array first (user-uploaded galleries) - NOT media_urls (that's for videos) - post?.image_urls?.forEach(add); + postData?.image_urls?.forEach(add); // Then single images (prefer large > medium > small) - [post?.image_large, post?.image_medium, post?.image_small, post?.image_url, post?.image].forEach(add); + [postData?.image_large, postData?.image_medium, postData?.image_small, postData?.image_url, postData?.image].forEach(add); return images; - }, [post]); + }, [postData]); // Initial data fetch useEffect(() => { @@ -624,6 +631,14 @@ export default function PostDetailModal({ post, onClose, onPostUpdated, onPostDe fetchPostTruth(postId).then(res => { if (res) setTruth({ score: res.truth_score ?? res.user_score ?? 50, count: res.vote_count ?? 0 }); }); }, [postId]); + // Fetch full post data from API to get image_urls (map markers don't have it) + useEffect(() => { + if (!postId) return; + fetchPostById(postId).then(fresh => { + if (fresh) setFullPost(fresh); + }); + }, [postId]); + // Fetch author avatar useEffect(() => { const authorName = post?.author || post?.username;