Fix image gallery deduplication for absolute vs relative URLs
- Extract filename only (not full path) for deduplication - Same image as https://domain/path/file.jpg and /path/file.jpg were not being deduplicated because full paths differ - Updated both PostDetailModal.jsx and markerManager.js - Added fetchPostById to get full post data with image_urls Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3a29812319
commit
91e024e156
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue