// markerMedia.js - Media, image, video, gallery handling import { escapeHtml } from "./markerUtils"; export function normalizeImageUrl(raw) { const value = (raw || "").toString().trim(); if (!value) return ""; if (value.startsWith("//")) return `https:${value}`; if (value.startsWith("http://")) return `https://${value.slice(7)}`; return value; } export function normalizeMediaUrl(raw) { const value = (raw || "").toString().trim(); if (!value) return ""; if (value.startsWith("//")) return `https:${value}`; if (value.startsWith("http://")) return `https://${value.slice(7)}`; return value; } export function resolvePostImage(post) { return normalizeImageUrl( post?.image_small || post?.image_medium || post?.image_large || post?.image || post?.media_url || "" ); } export function heroImageForPost(post) { const raw = post?.image_medium || post?.image_large || post?.image_small || post?.image || post?.media_url || ""; const v = normalizeImageUrl(raw); if (v) return v; const cat = (post?.category || "").toString().trim(); return `/og/category?cat=${encodeURIComponent(cat || "NEWS")}`; } export function fullImageForPost(post) { const raw = post?.image_large || post?.image_medium || post?.image_small || post?.image || post?.media_url || ""; return normalizeImageUrl(raw); } export function collectGalleryImages(post) { const result = []; const seen = new Set(); const add = (value) => { const normalized = normalizeImageUrl(value); if (!normalized) return; if (seen.has(normalized)) return; seen.add(normalized); result.push(normalized); }; if (Array.isArray(post?.media_urls)) { post.media_urls.forEach(add); } ["image_large", "image_medium", "image_small", "image", "media_url"].forEach((key) => { add(post?.[key]); }); return result; } // Extract camera ID from Quebec 511 URL export function extractCameraId(url) { if (!url) return null; const match = url.match(/[?&]id=(\d+)/); return match ? match[1] : null; } // Build stream proxy URL for camera export function getCameraStreamUrl(postData) { const embedUrl = normalizeMediaUrl(postData?.embed_url || postData?.video_url || ''); const cameraId = extractCameraId(embedUrl); if (cameraId) { return `/live/api/stream/${cameraId}`; } return embedUrl; } export function renderTagPills(tags, topicImpacts = {}) { if (!Array.isArray(tags) || tags.length === 0) return ""; const uniq = []; const seen = new Set(); for (const raw of tags) { const t = (raw || "").toString().trim().toLowerCase(); if (!t || seen.has(t)) continue; seen.add(t); uniq.push(t); if (uniq.length >= 6) break; } if (uniq.length === 0) return ""; // Get impact color class: negative=red, neutral=blue, positive=green const getImpactClass = (tag) => { const impact = topicImpacts?.[tag] ?? topicImpacts?.[tag.toLowerCase()] ?? 0; if (impact < 0) return "sw-tag-negative"; if (impact > 0) return "sw-tag-positive"; return "sw-tag-neutral"; }; return `
`; } export function setModalTags(modalWrap, post) { if (!modalWrap) return; const tagsHtml = renderTagPills(post?.event_tags || post?.tags, post?.topic_impacts); const existing = modalWrap.querySelector(".sw-modal-tags"); if (tagsHtml) { if (existing) { existing.outerHTML = tagsHtml; } else { const titleEl = modalWrap.querySelector(".sw-modal-title") || modalWrap.querySelector(".sw-cluster-title"); if (titleEl) titleEl.insertAdjacentHTML("afterend", tagsHtml); } } else if (existing) { existing.remove(); } } export function bindHeroLightbox(modalWrap) { if (!modalWrap) return; const img = modalWrap.querySelector(".sw-modal-hero img, .sw-cluster-hero-media img"); if (!img || img.dataset.zoomBound === "1") return; img.dataset.zoomBound = "1"; img.style.cursor = "zoom-in"; img.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); const src = (img.getAttribute("data-fullsrc") || img.getAttribute("src") || "").trim(); if (src) { openImageLightbox(src); } }); } export function initModalGallery(modalWrap, gallery) { if (!modalWrap || !Array.isArray(gallery) || gallery.length === 0) return; const heroImg = modalWrap.querySelector(".sw-modal-hero img, .sw-cluster-hero-media img"); if (!heroImg) return; const arrows = modalWrap.querySelectorAll(".sw-modal-hero-arrow"); let activeIndex = 0; const indicator = modalWrap.querySelector(".sw-modal-gallery-indicator"); const currentIndicator = indicator?.querySelector(".sw-modal-gallery-current"); const totalIndicator = indicator?.querySelector(".sw-modal-gallery-total"); if (totalIndicator) { totalIndicator.textContent = String(gallery.length); } const setActive = (idx) => { if (!Number.isFinite(idx)) return; const normalized = (idx + gallery.length) % gallery.length; activeIndex = normalized; heroImg.src = gallery[normalized]; heroImg.dataset.fullsrc = gallery[normalized]; heroImg.dataset.galleryIndex = String(normalized); if (currentIndicator) { currentIndicator.textContent = String(normalized + 1); } }; arrows.forEach((arrow) => { const dir = arrow.classList.contains("sw-modal-hero-arrow-prev") ? -1 : 1; arrow.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); setActive(activeIndex + dir); }); }); // Touch swipe for gallery let touchStartX = 0; const touchThreshold = 50; heroImg.addEventListener("touchstart", (event) => { touchStartX = event.touches[0].clientX; }); heroImg.addEventListener("touchend", (event) => { const dx = event.changedTouches[0].clientX - touchStartX; if (Math.abs(dx) < touchThreshold) return; const direction = dx > 0 ? -1 : 1; const nextIndex = (activeIndex + direction + gallery.length) % gallery.length; setActive(nextIndex); }); } export function openImageLightbox(src) { if (!src) return; const overlay = document.createElement("div"); overlay.className = "sw-image-lightbox"; overlay.style.position = "fixed"; overlay.style.inset = "0"; overlay.style.background = "rgba(8,10,16,0.86)"; overlay.style.zIndex = "6000"; /* Image lightbox - above post modal (5000), below auth (10000) */ overlay.style.display = "flex"; overlay.style.alignItems = "center"; overlay.style.justifyContent = "center"; overlay.style.padding = "16px"; const img = document.createElement("img"); img.src = src; img.alt = ""; img.style.maxWidth = "96vw"; img.style.maxHeight = "96vh"; img.style.objectFit = "contain"; img.style.borderRadius = "14px"; img.style.boxShadow = "0 20px 50px rgba(0,0,0,0.45)"; overlay.appendChild(img); const close = (opts = {}) => { try { document.removeEventListener("keydown", onKey); } catch {} try { window.removeEventListener("popstate", onPop); } catch {} if (overlay.parentNode) overlay.parentNode.removeChild(overlay); if (!opts.skipHistory && overlay.__swPushed) { overlay.__swPushed = false; try { history.back(); } catch {} } }; const onKey = (e) => { if (e.key === "Escape") close(); }; const onPop = () => close({ skipHistory: true }); overlay.addEventListener("click", close); document.addEventListener("keydown", onKey); window.addEventListener("popstate", onPop); try { history.pushState({ sw: "image" }, ""); overlay.__swPushed = true; } catch {} document.body.appendChild(overlay); } export function buildClusterItemsHtml(items) { if (!Array.isArray(items) || items.length === 0) return ""; const rows = items.slice(0, 10).map((item) => { const title = escapeHtml(item?.title || ""); const source = escapeHtml(item?.source || ""); const url = item?.url || ""; const img = normalizeImageUrl(item?.image_small || item?.image_medium || item?.image_large || ""); return ` ${img ? `