diff --git a/content/search.html b/content/search.html index 2c5ed9b4bca..5c5127c380f 100644 --- a/content/search.html +++ b/content/search.html @@ -321,7 +321,7 @@ return distance; // in km } -function renderResults(lunrRes) { +function renderResults(lunrRes, gotHereByDragging=false) { const ul = document.getElementById("results"); ul.innerHTML = ""; @@ -332,43 +332,27 @@ return; } - const mapCenter = leafletMap ? leafletMap.getCenter() : null; + let mapCenter = leafletMap ? leafletMap.getCenter() : null; - const processedResults = lunrRes.map(r => { + const processedResultsWithoutDistances = lunrRes.map(r => { const locData = locsByPermalink[r.ref]; if (!locData) return null; - let distance = null; - if (mapCenter && locData.lat !== undefined && locData.lng !== undefined) { - const lat = parseFloat(locData.lat); - const lng = parseFloat(locData.lng); - if (!isNaN(lat) && !isNaN(lng)) { - distance = haversineDistance(mapCenter.lat, mapCenter.lng, lat, lng); - } - } const permalinkParts = r.ref.split("/"); const countryCode = permalinkParts[permalinkParts.length - 2]; - return { - ...r, - name: locData.name, - permalink: r.ref, - country_code: countryCode, - distance: distance, - lat: parseFloat(locData.lat), - lng: parseFloat(locData.lng) -}; + return { + ...r, + name: locData.name, + permalink: r.ref, + country_code: countryCode, + lat: parseFloat(locData.lat), + lng: parseFloat(locData.lng), + isVisibleOnMap: true + }; }).filter(r => r !== null); - if (mapCenter) { - processedResults.sort((a, b) => { - const distA = (a.distance === null || isNaN(a.distance)) ? Infinity : a.distance; - const distB = (b.distance === null || isNaN(b.distance)) ? Infinity : b.distance; - return distA - distB; - }); - } - - if (processedResults.length === 0 && lunrRes.length > 0) { + if (processedResultsWithoutDistances.length === 0 && lunrRes.length > 0) { ul.innerHTML = "
  • No results with valid location data.
  • "; markersLayer.clearLayers(); } @@ -378,6 +362,58 @@ // Add markers for search results const bounds = L.latLngBounds(); + // calculate the bounds + for (const r of processedResultsWithoutDistances) { + // Add marker to map + if (!isNaN(r.lat) && !isNaN(r.lng)) { + // Extend bounds to include this marker + bounds.extend([r.lat, r.lng]); + } + } + // Only fit bounds on initial search, not when the map is manually moved + // this is mostly done by trapping manual moves via dragend, not moveend + // because us moving the map with fitbounds triggers moveend, not dragend + if ((!mapCenter || processedResultsWithoutDistances.length >= 1) && !gotHereByDragging) { + leafletMap.fitBounds(bounds, { + padding: [50, 50], + maxZoom: 13 + }); + mapCenter = leafletMap ? leafletMap.getCenter() : null; + } else if (gotHereByDragging) { + // if they dragged the map, we want to indicate in the search list which + // markers are still on the map! + const mapBounds = leafletMap.getBounds() + processedResultsWithoutDistances.forEach(r => { + r.isVisibleOnMap = mapBounds.contains([r.lat, r.lng]); + }) + } + + // now that we know where the map bounds have been set to, + // calculate distances from it to the centre of the map and + // update the distances from the centre of the map + const processedResults = []; + for (const r of processedResultsWithoutDistances) { + let distance = null; + if (mapCenter && r.lat !== undefined && r.lng !== undefined) { + if (!isNaN(r.lat) && !isNaN(r.lng)) { + distance = haversineDistance(mapCenter.lat, mapCenter.lng, r.lat, r.lng); + } + } + processedResults.push(Object.assign({distance}, r)); + } + + if (mapCenter) { + processedResults.sort((a, b) => { + if (a.isVisibleOnMap && !b.isVisibleOnMap) { return -1; } + if (!a.isVisibleOnMap && b.isVisibleOnMap) { return 1; } + const distA = (a.distance === null || isNaN(a.distance)) ? Infinity : a.distance; + const distB = (b.distance === null || isNaN(b.distance)) ? Infinity : b.distance; + return distA - distB; + }); + } + + // Finally, actually render the results list, and add the markets to the map + let previousMarkerWasOnMap = true; for (const r of processedResults) { // Add to list const s = snippet.cloneNode(true); @@ -389,6 +425,18 @@ tempDiv.innerHTML = r.name; a.textContent = tempDiv.textContent; // This will contain decoded entities + if (r.lat && r.lng) { + const marker = L.marker([r.lat, r.lng]) + .bindPopup(`${tempDiv.textContent}`) + .addTo(markersLayer); + a.onmouseenter = () => { + marker._icon.classList.add("highlight"); + } + a.onmouseleave = () => { + marker._icon.classList.remove("highlight"); + } + } + const i = s.querySelector("i"); i.textContent = COUNTRIES[r.country_code] || r.country_code; @@ -398,29 +446,15 @@ } else if (distSpan) { distSpan.textContent = ""; } - ul.append(s); - - // Add marker to map - if (!isNaN(r.lat) && !isNaN(r.lng)) { - const marker = L.marker([r.lat, r.lng]) - .bindPopup(`${tempDiv.textContent}`) - .addTo(markersLayer); - - // Extend bounds to include this marker - bounds.extend([r.lat, r.lng]); + if (previousMarkerWasOnMap && !r.isVisibleOnMap) { + const li = document.createElement("li"); + li.append(" ↓ not on the map ↓ "); + li.className = "offMapDivider"; + ul.append(li); } - } - - // If we have results with locations, fit the map to show all markers - if (markersLayer.getLayers().length > 0) { - // Only fit bounds on initial search, not when the map is manually moved - if (!mapCenter || processedResults.length === 1) { - leafletMap.fitBounds(bounds, { - padding: [50, 50], - maxZoom: 13 - }); - } - } + previousMarkerWasOnMap = r.isVisibleOnMap; + ul.append(s); + } } function searchFor(q) { @@ -485,9 +519,9 @@ alert('Could not get your location: ' + e.message); }); - leafletMap.on('moveend', () => { + leafletMap.on('dragend', (e) => { if (lastQueryResults && lastQueryResults.length > 0) { - renderResults(lastQueryResults); // Re-sort and re-display with new map center + renderResults(lastQueryResults, true); // Re-sort and re-display with new map center } }); diff --git a/themes/ndt2/assets/css/main.css b/themes/ndt2/assets/css/main.css index e03bdd96bb7..a200d618aeb 100644 --- a/themes/ndt2/assets/css/main.css +++ b/themes/ndt2/assets/css/main.css @@ -738,3 +738,17 @@ main#content:has(> article) { contain: layout style; pointer-events: none; /* Make sure clicks go through to the map underneath */ } + +#results .offMapDivider { + list-style: none; + display: flex; + align-items: center; +} +#results .offMapDivider::before,#results .offMapDivider::after { + content: ""; + flex: 1 1 auto; + height: 50%; + border-bottom: 1px solid rgba(255, 255, 255, 0.3); + display: inline-block; +} +img.highlight { filter: hue-rotate(120deg); } \ No newline at end of file