1/** 2 * Easy Directory: Pro Map Rendering Module 3 * Supports Google Maps & OpenStreetMap Leaflet with Theme-Colored SVG Markers 4 * Full Two-Way Synchronization with Listing Cards (Hover, Click, Pan, Popup) 5 * Copyright (C) 2011 - 2026 JoomlaX. All rights reserved. 6 * License GNU General Public License version 3 or later 7 */ 8 9window.EasyDirectory = window.EasyDirectory || {}; 10 11(function() { 12 'use strict'; 13 14 var activeMapInstance = null; 15 var activeMapType = null; // 'google' or 'leaflet' 16 var activeMarkersMap = {}; 17 var activeMarkerDataMap = {}; 18 var activeBounds = null; 19 var activeMarkerList = []; 20 var activeInfoWindow = null; 21 var activeRadiusCircle = null; 22 var activeUserMarker = null; 23 24 function getThemeColor() { 25 var root = document.querySelector('.ed-component-wrapper') || document.documentElement; 26 var color = getComputedStyle(root).getPropertyValue('--ed-primary').trim(); 27 return color || '#2563eb'; 28 } 29 30 function getMarkerSvg(color, isFeatured) { 31 color = color || getThemeColor(); 32 var starIcon = isFeatured 33 ? '<path d="M12 6.5l1.6 3.3 3.6.5-2.6 2.5.6 3.6-3.2-1.7-3.2 1.7.6-3.6-2.6-2.5 3.6-.5z" fill="#ffffff"/>' 34 : '<circle cx="12" cy="11" r="4.5" fill="#ffffff"/><circle cx="12" cy="11" r="2.2" fill="' + color + '"/>'; 35 36 return '<svg xmlns="http://www.w3.org/2000/svg" width="36" height="46" viewBox="0 0 24 30" style="display: block; transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);">' + 37 '<path d="M12 1C6.48 1 2 5.48 2 11c0 7.5 10 18 10 18s10-10.5 10-18c0-5.52-4.48-10-10-10z" fill="' + color + '" stroke="#ffffff" stroke-width="1.5"/>' + 38 starIcon + 39 '</svg>'; 40 } 41 42 function buildPopupHtml(m, themeColor) { 43 var sym = m.currency || '$'; 44 var priceHtml = ''; 45 if (m.price > 0) { 46 var active = sym.repeat(m.price); 47 var inactive = sym.repeat(Math.max(0, 4 - m.price)); 48 priceHtml = '<span class="ed-submeta-price" title="Price: ' + active + '"><strong class="text-dark">' + active + '</strong><span class="text-muted opacity-50">' + inactive + '</span></span>'; 49 } 50 51 var badgeBg = m.badge_bg || themeColor; 52 var badgeIconHtml = m.badge_icon ? '<i class="' + m.badge_icon + ' me-1"></i>' : ''; 53 var badgeHtml = m.badge ? '<span class="ed-card-badge ed-popup-badge" style="background-color:' + badgeBg + ' !important; color:#ffffff !important;">' + badgeIconHtml + m.badge + '</span>' : ''; 54 55 return '<div class="ed-map-infowindow">' + 56 (m.image ? '<div class="ed-popup-media">' + 57 '<a href="' + m.url + '" class="ed-card-img-link" tabindex="-1">' + 58 '<img src="' + m.image + '" alt="' + (m.title || '') + '" loading="lazy" onerror="this.style.opacity=\'0\';">' + 59 '</a>' + 60 badgeHtml + 61 '</div>' : '') + 62 '<div class="ed-popup-body">' + 63 '<div class="ed-popup-main-info">' + 64 '<div class="ed-popup-title-row">' + 65 '<h4 class="ed-popup-title m-0"><a href="' + m.url + '">' + m.title + '</a></h4>' + 66 '</div>' + 67 (m.category ? '<div class="ed-popup-subtitle text-muted">' + m.category + '</div>' : '') + 68 '<div class="ed-card-submeta text-muted small">' + 69 (m.area ? '<span class="ed-submeta-area"><i class="fa-solid fa-location-dot me-1 text-primary"></i>' + m.area + '</span>' : '') + 70 (m.area && priceHtml ? '<span class="ed-meta-dot">â¢</span>' : '') + 71 priceHtml + 72 '</div>' + 73 '</div>' + 74 '<div class="ed-popup-action mt-2 pt-2 border-top">' + 75 '<a href="' + m.url + '" class="ed-btn ed-btn-primary ed-popup-btn w-100">' + 76 '<span>View Details</span> <i class="fa-solid fa-arrow-right ms-1"></i>' + 77 '</a>' + 78 '</div>' + 79 '</div>' + 80 '</div>'; 81 } 82 83 EasyDirectory.initSplitMap = function(elementId, retries) { 84 retries = retries || 0; 85 var el = document.getElementById(elementId); 86 if (!el) return; 87 88 var provider = (el.dataset.provider || 'google').toLowerCase(); 89 var rawData = el.dataset.markers; 90 var markers = []; 91 try { 92 markers = rawData ? JSON.parse(rawData) : []; 93 } catch (e) { 94 markers = []; 95 } 96 97 if (provider === 'google') { 98 if (typeof google !== 'undefined' && typeof google.maps !== 'undefined' && typeof google.maps.Map === 'function') { 99 initGoogleMap(el, markers); 100 } else if (retries < 25) { 101 // Wait for asynchronous Google Maps script to finish downloading 102 setTimeout(function() { 103 EasyDirectory.initSplitMap(elementId, retries + 1); 104 }, 120); 105 } else if (typeof L !== 'undefined' && typeof L.map === 'function') { 106 // Fallback to Leaflet if Google Maps failed to load 107 initLeafletMap(el, markers); 108 } 109 } else { 110 if (typeof L !== 'undefined' && typeof L.map === 'function') { 111 initLeafletMap(el, markers); 112 } else if (retries < 25) { 113 setTimeout(function() { 114 EasyDirectory.initSplitMap(elementId, retries + 1); 115 }, 100); 116 } 117 } 118 }; 119 120 EasyDirectory.initMiniMap = function(elementId, lat, lng, title, retries) { 121 retries = retries || 0; 122 var el = document.getElementById(elementId); 123 if (!el || isNaN(lat) || isNaN(lng)) return; 124 125 var provider = (el.dataset.provider || 'google').toLowerCase(); 126 var themeColor = getThemeColor(); 127 128 if (provider === 'google') { 129 if (typeof google !== 'undefined' && typeof google.maps !== 'undefined' && typeof google.maps.Map === 'function') { 130 var center = { lat: lat, lng: lng }; 131 var map = new google.maps.Map(el, { 132 zoom: 15, 133 center: center, 134 disableDefaultUI: true, 135 zoomControl: true, 136 styles: [ 137 { featureType: 'poi', elementType: 'labels', stylers: [{ visibility: 'off' }] } 138 ] 139 }); 140 141 var svgData = 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(getMarkerSvg(themeColor, false)); 142 new google.maps.Marker({ 143 position: center, 144 map: map, 145 title: title || '', 146 icon: { 147 url: svgData, 148 scaledSize: new google.maps.Size(36, 46), 149 anchor: new google.maps.Point(18, 46) 150 } 151 }); 152 } else if (retries < 25) { 153 setTimeout(function() { 154 EasyDirectory.initMiniMap(elementId, lat, lng, title, retries + 1); 155 }, 120); 156 } else if (typeof L !== 'undefined' && typeof L.map === 'function') { 157 // Fallback to Leaflet 158 var map = L.map(elementId, { 159 zoomControl: false, 160 attributionControl: false 161 }
161).setView([lat, lng], 15); 162 163 L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 164 maxZoom: 19, 165 attribution: '' 166 }).addTo(map); 167 168 L.control.zoom({ position: 'topleft' }).addTo(map); 169 170 var iconHtml = getMarkerSvg(themeColor, false); 171 var customIcon = L.divIcon({ 172 className: 'ed-leaflet-svg-pin', 173 html: iconHtml, 174 iconSize: [36, 46], 175 iconAnchor: [18, 46], 176 popupAnchor: [0, -42] 177 }); 178 179 L.marker([lat, lng], { icon: customIcon }) 180 .addTo(map) 181 .bindPopup('<div style="font-family:inherit; font-weight:700; font-size:14px; padding:2px;">' + (title || '') + '</div>'); 182 } 183 } else { 184 if (typeof L !== 'undefined' && typeof L.map === 'function') { 185 var map = L.map(elementId, { 186 zoomControl: false, 187 attributionControl: false 188 }).setView([lat, lng], 15); 189 190 L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 191 maxZoom: 19, 192 attribution: '' 193 }).addTo(map); 194 195 L.control.zoom({ position: 'topleft' }).addTo(map); 196 197 var iconHtml = getMarkerSvg(themeColor, false); 198 var customIcon = L.divIcon({ 199 className: 'ed-leaflet-svg-pin', 200 html: iconHtml, 201 iconSize: [36, 46], 202 iconAnchor: [18, 46], 203 popupAnchor: [0, -42] 204 }); 205 206 L.marker([lat, lng], { icon: customIcon }) 207 .addTo(map) 208 .bindPopup('<div style="font-family:inherit; font-weight:700; font-size:14px; padding:2px;">' + (title || '') + '</div>'); 209 } else if (retries < 25) { 210 setTimeout(function() { 211 EasyDirectory.initMiniMap(elementId, lat, lng, title, retries + 1); 212 }, 100); 213 } 214 } 215 }; 216 217 function initGoogleMap(el, markers) { 218 if (el.dataset.mapInitialized) return; 219 el.dataset.mapInitialized = 'true'; 220 221 var defaultCenter = { lat: 43.7592, lng: 15.7794 }; 222 var bounds = new google.maps.LatLngBounds(); 223 224 var map = new google.maps.Map(el, { 225 zoom: 13, 226 center: defaultCenter, 227 mapTypeControl: false, 228 streetViewControl: false, 229 fullscreenControl: false, 230 zoomControl: true, 231 styles: [ 232 { featureType: 'poi.business', stylers: [{ visibility: 'off' }] } 233 ] 234 }); 235 236 activeMapInstance = map; 237 activeMapType = 'google'; 238 activeInfoWindow = new google.maps.InfoWindow({ 239 maxWidth: 250, 240 minWidth: 240, 241 disableAutoPan: false 242 }); 243 activeMarkersMap = {}; 244 activeMarkerDataMap = {}; 245 activeMarkerList = []; 246 activeBounds = bounds; 247 248 EasyDirectory.addMapMarkers(markers); 249 observeMapSize(el); 250 } 251 252 function initLeafletMap(el, markers) { 253 if (el.dataset.mapInitialized) return; 254 el.dataset.mapInitialized = 'true'; 255 256 var defaultCenter = [43.7592, 15.7794]; 257 var map = L.map(el, { 258 zoomControl: true, 259 attributionControl: false 260 }).setView(defaultCenter, 13); 261 262 L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 263 maxZoom: 19, 264 attribution: '' 265 }).addTo(map); 266 267 activeMapInstance = map; 268 activeMapType = 'leaflet'; 269 activeMarkersMap = {}; 270 activeMarkerDataMap = {}; 271 activeMarkerList = []; 272 273 EasyDirectory.addMapMarkers(markers); 274 observeMapSize(el); 275 276 setTimeout(function() { 277 map.invalidateSize(); 278 }, 250); 279 } 280 281 function observeMapSize(el) { 282 if (!el || typeof ResizeObserver === 'undefined') { 283 return; 284 } 285 if (el.dataset.edResizeObserved === 'true') { 286 return; 287 } 288 el.dataset.edResizeObserved = 'true'; 289 290 var raf = 0; 291 var observer = new ResizeObserver(function() { 292 if (raf) { 293 cancelAnimationFrame(raf); 294 } 295 raf = requestAnimationFrame(function() { 296 raf = 0; 297 if (activeMapType === 'leaflet' && activeMapInstance) { 298 activeMapInstance.invalidateSize(); 299 } else if (activeMapType === 'google' && activeMapInstance && typeof google !== 'undefined') { 300 google.maps.event.trigger(activeMapInstance, 'resize'); 301 } 302 }); 303 });
304 305 observer.observe(el); 306 if (el.parentElement && el.parentElement.classList.contains('ed-split-map-col')) { 307 observer.observe(el.parentElement); 308 } 309 } 310 311 EasyDirectory.addMapMarkers = function(markers) { 312 if (!markers || !markers.length || !activeMapInstance) return; 313 314 var themeColor = getThemeColor(); 315 316 if (activeMapType === 'google') { 317 var map = activeMapInstance; 318 var infoWindow = activeInfoWindow; 319 var bounds = activeBounds || new google.maps.LatLngBounds(); 320 321 markers.forEach(function(m) { 322 if (!m.lat || !m.lng) return; 323 var pos = { lat: parseFloat(m.lat), lng: parseFloat(m.lng) }; 324 bounds.extend(pos); 325 326 var isSpecial = !!m.badge || (m.badge === 'TOP PICK'); 327 var svgData = 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(getMarkerSvg(themeColor, isSpecial)); 328 329 var marker = new google.maps.Marker({ 330 position: pos, 331 map: map, 332 title: m.title, 333 icon: { 334 url: svgData, 335 scaledSize: new google.maps.Size(36, 46), 336 anchor: new google.maps.Point(18, 46) 337 } 338 }); 339 340 activeMarkersMap[m.id] = marker; 341 activeMarkersMap[String(m.id)] = marker; 342 activeMarkerDataMap[m.id] = m; 343 activeMarkerDataMap[String(m.id)] = m; 344 activeMarkerList.push(marker); 345 346 marker.addListener('click', function() { 347 var html = buildPopupHtml(m, themeColor); 348 infoWindow.setContent(html); 349 infoWindow.open({ 350 map: map, 351 anchor: marker, 352 shouldFocus: false 353 }); 354 highlightListingCard(m.id); 355 ensureInfoWindowInView(map); 356 }); 357 }); 358 359 if (activeMarkerList.length > 1) { 360 map.fitBounds(bounds, { top: 40, right: 40, bottom: 40, left: 40 }); 361 } else if (activeMarkerList.length === 1) { 362 map.setCenter({ lat: parseFloat(markers[0].lat), lng: parseFloat(markers[0].lng) }); 363 map.setZoom(15); 364 } 365 } else if (activeMapType === 'leaflet') { 366 var map = activeMapInstance; 367 var group = []; 368 369 markers.forEach(function(m) { 370 if (!m.lat || !m.lng) return; 371 var latLng = [parseFloat(m.lat), parseFloat(m.lng)]; 372 group.push(latLng); 373 374 var isSpecial = !!m.badge || (m.badge === 'TOP PICK'); 375 var iconHtml = getMarkerSvg(themeColor, isSpecial); 376 377 var customIcon = L.divIcon({ 378 className: 'ed-leaflet-svg-pin', 379 html: iconHtml, 380 iconSize: [36, 46], 381 iconAnchor: [18, 46], 382 popupAnchor: [0, -42] 383 }); 384 385 var popupHtml = buildPopupHtml(m, themeColor); 386 var marker = L.marker(latLng, { icon: customIcon }).addTo(map).bindPopup(popupHtml, { 387 maxWidth: 240, 388 minWidth: 240, 389 className: 'ed-leaflet-popup', 390 autoPanPadding: [15, 15] 391 }); 392 activeMarkersMap[m.id] = marker; 393 activeMarkersMap[String(m.id)] = marker; 394 activeMarkerDataMap[m.id] = m; 395 activeMarkerDataMap[String(m.id)] = m; 396 activeMarkerList.push(marker); 397 398 marker.on('click', function() { 399 highlightListingCard(m.id); 400 }); 401 }); 402 403 if (group.length > 1) { 404 map.fitBounds(group, { padding: [40, 40] }); 405 } else if (group.length === 1 && activeMarkerList.length === 1) {
406 map.setView(group[0], 15); 407 } 408 } 409 }; 410 411 EasyDirectory.setMapMarkers = function(markers) { 412 if (!activeMapInstance) return; 413 414 // Clear existing markers 415 if (activeMapType === 'google') { 416 activeMarkerList.forEach(function(m) { 417 m.setMap(null); 418 }); 419 activeMarkerList = []; 420 activeMarkersMap = {}; 421 activeMarkerDataMap = {}; 422 activeBounds = new google.maps.LatLngBounds(); 423 } else if (activeMapType === 'leaflet') { 424 activeMarkerList.forEach(function(m) { 425 activeMapInstance.removeLayer(m); 426 }); 427 activeMarkerList = []; 428 activeMarkersMap = {}; 429 activeMarkerDataMap = {}; 430 } 431 432 EasyDirectory.addMapMarkers(markers); 433 }; 434 435 EasyDirectory.setUserLocationAndRadius = function(lat, lng, radius, unit) { 436 if (!activeMapInstance) return; 437 438 var themeColor = getThemeColor(); 439 440 // Clean up existing circle and user marker 441 if (activeMapType === 'leaflet') { 442 if (activeRadiusCircle) { 443 activeMapInstance.removeLayer(activeRadiusCircle); 444 activeRadiusCircle = null; 445 } 446 if (activeUserMarker) { 447 activeMapInstance.removeLayer(activeUserMarker); 448 activeUserMarker = null; 449 } 450 451 if (lat && lng && radius > 0) { 452 var numLat = parseFloat(lat); 453 var numLng = parseFloat(lng); 454 var numRadius = parseFloat(radius); 455 var meters = (unit === 'mi') ? (numRadius * 1609.34) : (numRadius * 1000); 456 457 // Draw translucent radius circle 458 activeRadiusCircle = L.circle([numLat, numLng], { 459 radius: meters, 460 color: themeColor, 461 fillColor: themeColor, 462 fillOpacity: 0.12, 463 weight: 2, 464 dashArray: '5, 6' 465 }).addTo(activeMapInstance); 466 467 // User pulse dot marker 468 var userIcon = L.divIcon({ 469 className: 'ed-user-location-icon', 470 html: '<div class="ed-user-pulse-dot" style="background:' + themeColor + ';"></div>', 471 iconSize: [20, 20], 472 iconAnchor: [10, 10] 473 }); 474 activeUserMarker = L.marker([numLat, numLng], { icon: userIcon, zIndexOffset: 2000 }).addTo(activeMapInstance); 475 476 activeMapInstance.fitBounds(activeRadiusCircle.getBounds(), { padding: [30, 30] }); 477 } 478 } else if (activeMapType === 'google') { 479 if (activeRadiusCircle) { 480 activeRadiusCircle.setMap(null); 481 activeRadiusCircle = null; 482 } 483 if (activeUserMarker) { 484 activeUserMarker.setMap(null); 485 activeUserMarker = null; 486 } 487 488 if (lat && lng && radius > 0) { 489 var numLat = parseFloat(lat); 490 var numLng = parseFloat(lng); 491 var numRadius = parseFloat(radius); 492 var meters = (unit === 'mi') ? (numRadius * 1609.34) : (numRadius * 1000); 493 494 var centerLatLng = new google.maps.LatLng(numLat, numLng); 495 496 activeRadiusCircle = new google.maps.Circle({ 497 center: centerLatLng, 498 radius: meters, 499 strokeColor: themeColor, 500 strokeOpacity: 0.8, 501 strokeWeight: 2, 502 fillColor: themeColor, 503 fillOpacity: 0.12, 504 map: activeMapInstance 505 }); 506 507 activeUserMarker = new google.maps.Marker({ 508 position: centerLatLng, 509 map: activeMapInstance, 510 icon: { 511 path: google.maps.SymbolPath.CIRCLE, 512 scale: 7, 513 fillColor: themeColor, 514 fillOpacity: 1, 515 strokeColor: '#ffffff', 516 strokeWeight: 2.5 517 }, 518 zIndex: 99999 519 }); 520 521 activeMapInstance.fitBounds(activeRadiusCircle.getBounds()); 522 } 523 } 524 }; 525 526 EasyDirectory.resizeMap = function() { 527 function doResize() { 528 if (activeMapType === 'leaflet' && activeMapInstance) { 529 activeMapInstance.invalidateSize(); 530 } else if (activeMapType === 'google' && activeMapInstance) { 531 google.maps.event.trigger(activeMapInstance, 'resize'); 532 } 533 } 534 doResize(); 535 setTimeout(doResize, 60); 536 setTimeout(doResize, 200);
537 setTimeout(doResize, 380); 538 }; 539 540 EasyDirectory.highlightMarker = function(listingId, isHover) { 541 if (!listingId || !activeMapInstance) return; 542 var m = activeMarkersMap[listingId] || activeMarkersMap[String(listingId)]; 543 if (!m) return; 544 545 if (activeMapType === 'leaflet') { 546 var el = (typeof m.getElement === 'function' ? m.getElement() : null) || m._icon; 547 if (el) { 548 var svg = el.querySelector('svg') || el; 549 if (isHover) { 550 svg.style.transition = 'transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), filter 0.25s ease'; 551 svg.style.transform = 'scale(1.4) translateY(-10px)'; 552 svg.style.filter = 'drop-shadow(0 10px 20px rgba(37,99,235,0.7))'; 553 if (typeof m.setZIndexOffset === 'function') { 554 m.setZIndexOffset(1000); 555 } 556 } else { 557 svg.style.transform = ''; 558 svg.style.filter = ''; 559 if (typeof m.setZIndexOffset === 'function') { 560 m.setZIndexOffset(0); 561 } 562 } 563 } 564 } else if (activeMapType === 'google') { 565 if (isHover) { 566 if (typeof m.setAnimation === 'function') { 567 m.setAnimation(google.maps.Animation.BOUNCE); 568 } 569 if (typeof m.setZIndex === 'function') { 570 m.setZIndex(99999); 571 } 572 } else { 573 if (typeof m.setAnimation === 'function') { 574 m.setAnimation(null); 575 } 576 if (typeof m.setZIndex === 'function') { 577 m.setZIndex(1); 578 } 579 } 580 } 581 }; 582 583 function ensureInfoWindowInView(mapInstance) { 584 if (!mapInstance) return; 585 setTimeout(function() { 586 var iw = document.querySelector('.ed-component-wrapper .gm-style-iw-c') || document.querySelector('.gm-style-iw-c'); 587 var mapDiv = typeof mapInstance.getDiv === 'function' ? mapInstance.getDiv() : document.getElementById('ed-split-map-canvas'); 588 if (!iw || !mapDiv) return; 589 590 var iwRect = iw.getBoundingClientRect(); 591 var mapRect = mapDiv.getBoundingClientRect(); 592 var pad = 15; 593 var dx = 0; 594 var dy = 0; 595 596 if (iwRect.top < mapRect.top + pad) { 597 dy = iwRect.top - mapRect.top - pad; 598 } else if (iwRect.bottom > mapRect.bottom - pad) { 599 dy = iwRect.bottom - mapRect.bottom + pad; 600 } 601 602 if (iwRect.left < mapRect.left + pad) { 603 dx = iwRect.left - mapRect.left - pad; 604 } else if (iwRect.right > mapRect.right - pad) { 605 dx = iwRect.right - mapRect.right + pad; 606 } 607 608 if ((dx !== 0 || dy !== 0) && typeof mapInstance.panBy === 'function') { 609 mapInstance.panBy(dx, dy); 610 } 611 }, 50); 612 } 613 614 EasyDirectory.focusMarker = function(listingId) { 615 if (!listingId || !activeMapInstance) return; 616 var m = activeMarkersMap[listingId] || activeMarkersMap[String(listingId)]; 617 if (!m) return; 618 619 if (activeMapType === 'leaflet') { 620 if (typeof m.getLatLng === 'function') { 621 var curZoom = typeof activeMapInstance.getZoom === 'function' ? activeMapInstance.getZoom() : 13; 622 var targetZoom = curZoom < 12 ? 14 : curZoom; 623 activeMapInstance.setView(m.getLatLng(), targetZoom, { animate: true, duration: 0.5 }); 624 } 625 if (typeof m.openPopup === 'function') { 626 m.openPopup(); 627 } 628 } else if (activeMapType === 'google') { 629 var themeColor = getThemeColor(); 630 var data = activeMarkerDataMap[listingId] || activeMarkerDataMap[String(listingId)]; 631 if (data && activeInfoWindow) { 632 if (typeof activeMapInstance.getZoom === 'function' && activeMapInstance.getZoom() < 12) { 633 activeMapInstance.setZoom(14); 634 } 635 activeInfoWindow.setContent(buildPopupHtml(data, themeColor)); 636 activeInfoWindow.open({ 637 anchor: m, 638 map: activeMapInstance, 639 shouldFocus: false 640 }); 641 ensureInfoWindowInView(activeMapInstance); 642 } 643 } 644 }; 645 646 function highlightListingCard(listingId) { 647 var card = document.querySelector('[data-listing-id="' + listingId + '"]'); 648 if (!card) return; 649 650 var container = document.querySelector('.ed-split-content'); 651 if (container) { 652 var topOffset = card.offsetTop - container.offsetTop - 16; 653 container.scrollTo({ top: topOffset, behavior: 'smooth' });
654 } 655 656 document.querySelectorAll('.ed-card-horizontal, .ed-card').forEach(function(c) { 657 c.classList.remove('ed-card-highlighted'); 658 }); 659 660 card.classList.add('ed-card-highlighted'); 661 setTimeout(function() { 662 card.classList.remove('ed-card-highlighted'); 663 }, 2000); 664 } 665 666 // Global Delegated Listeners for Two-Way Card -> Map Interaction 667 document.addEventListener('mouseover', function(e) { 668 var card = e.target.closest('[data-listing-id]'); 669 if (!card) return; 670 var id = card.getAttribute('data-listing-id'); 671 if (id) { 672 EasyDirectory.highlightMarker(id, true); 673 } 674 }); 675 676 document.addEventListener('mouseout', function(e) { 677 var card = e.target.closest('[data-listing-id]'); 678 if (!card) return; 679 var id = card.getAttribute('data-listing-id'); 680 if (id) { 681 EasyDirectory.highlightMarker(id, false); 682 } 683 }); 684 685 document.addEventListener('click', function(e) { 686 var card = e.target.closest('[data-listing-id]'); 687 if (!card) return; 688 // Don't intercept clicks on links, buttons, or favorite icons 689 if (e.target.closest('a, button, input, select, textarea, label')) return; 690 var id = card.getAttribute('data-listing-id'); 691 if (id) { 692 EasyDirectory.focusMarker(id); 693 } 694 }); 695})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.