From: Tom Hughes Date: Sun, 23 Mar 2025 19:42:27 +0000 (+0000) Subject: Merge remote-tracking branch 'upstream/pull/5826' X-Git-Tag: live~119 X-Git-Url: https://git.openstreetmap.org/rails.git/commitdiff_plain/c947bfa431c7c23b26f271fcb4f0afb4f423be9c?hp=0782778ba665167822640e94c677249091198a79 Merge remote-tracking branch 'upstream/pull/5826' --- diff --git a/app/assets/javascripts/index/history.js b/app/assets/javascripts/index/history.js index 229fdc2b6..9f883367a 100644 --- a/app/assets/javascripts/index/history.js +++ b/app/assets/javascripts/index/history.js @@ -4,7 +4,7 @@ OSM.History = function (map) { const page = {}; $("#sidebar_content") - .on("click", ".changeset_more a", loadMore) + .on("click", ".changeset_more a", loadMoreChangesets) .on("mouseover", "[data-changeset]", function () { highlightChangeset($(this).data("changeset").id); }) @@ -27,6 +27,65 @@ OSM.History = function (map) { return layer.id; }; + let changesetIntersectionObserver; + + function disableChangesetIntersectionObserver() { + if (changesetIntersectionObserver) { + changesetIntersectionObserver.disconnect(); + changesetIntersectionObserver = null; + } + } + + function enableChangesetIntersectionObserver() { + disableChangesetIntersectionObserver(); + if (!window.IntersectionObserver) return; + + let ignoreIntersectionEvents = true; + + changesetIntersectionObserver = new IntersectionObserver((entries) => { + if (ignoreIntersectionEvents) { + ignoreIntersectionEvents = false; + return; + } + + let closestTargetToTop, + closestDistanceToTop = Infinity, + closestTargetToBottom, + closestDistanceToBottom = Infinity; + + for (const entry of entries) { + if (entry.isIntersecting) continue; + + const distanceToTop = entry.rootBounds.top - entry.boundingClientRect.bottom; + const distanceToBottom = entry.boundingClientRect.top - entry.rootBounds.bottom; + if (distanceToTop >= 0 && distanceToTop < closestDistanceToTop) { + closestDistanceToTop = distanceToTop; + closestTargetToTop = entry.target; + } + if (distanceToBottom >= 0 && distanceToBottom <= closestDistanceToBottom) { + closestDistanceToBottom = distanceToBottom; + closestTargetToBottom = entry.target; + } + } + + if (closestTargetToTop && closestDistanceToTop < closestDistanceToBottom) { + const id = $(closestTargetToTop).data("changeset")?.id; + if (id) { + OSM.router.replace(location.pathname + "?" + new URLSearchParams({ before: id }) + location.hash); + } + } else if (closestTargetToBottom) { + const id = $(closestTargetToBottom).data("changeset")?.id; + if (id) { + OSM.router.replace(location.pathname + "?" + new URLSearchParams({ after: id }) + location.hash); + } + } + }, { root: $("#sidebar")[0] }); + + $("#sidebar_content .changesets ol").children().each(function () { + changesetIntersectionObserver.observe(this); + }); + } + function highlightChangeset(id) { const layer = group.getLayer(id); if (layer) layer.setStyle({ fillOpacity: 0.3, color: "#FF6600", weight: 3 }); @@ -73,10 +132,12 @@ OSM.History = function (map) { } } - function update() { + function loadFirstChangesets() { const data = new URLSearchParams(); const params = new URLSearchParams(location.search); + disableChangesetIntersectionObserver(); + if (location.pathname === "/history") { data.set("bbox", map.getBounds().wrap().toBBoxString()); const feedLink = $("link[type=\"application/atom+xml\"]"), @@ -97,11 +158,22 @@ OSM.History = function (map) { .then(response => response.text()) .then(function (html) { displayFirstChangesets(html); + enableChangesetIntersectionObserver(); + + if (params.has("before")) { + const [firstItem] = $("#sidebar_content .changesets ol").children().first(); + firstItem?.scrollIntoView(); + } + if (params.has("after")) { + const [lastItem] = $("#sidebar_content .changesets ol").children().last(); + lastItem?.scrollIntoView(false); + } + updateMap(); }); } - function loadMore(e) { + function loadMoreChangesets(e) { e.preventDefault(); e.stopPropagation(); @@ -112,10 +184,16 @@ OSM.History = function (map) { $.get($(this).attr("href"), function (html) { displayMoreChangesets(div, html); + enableChangesetIntersectionObserver(); updateMap(); }); } + function reloadChangesetsBecauseOfMapMovement() { + OSM.router.replace("/history" + window.location.hash); + loadFirstChangesets(); + } + let changesets = []; function updateBounds() { @@ -177,18 +255,19 @@ OSM.History = function (map) { map.addLayer(group); if (location.pathname === "/history") { - map.on("moveend", update); + map.on("moveend", reloadChangesetsBecauseOfMapMovement); } map.on("zoomend", updateBounds); - update(); + loadFirstChangesets(); }; page.unload = function () { map.removeLayer(group); - map.off("moveend", update); + map.off("moveend", reloadChangesetsBecauseOfMapMovement); map.off("zoomend", updateBounds); + disableChangesetIntersectionObserver(); }; return page; diff --git a/test/system/history_test.rb b/test/system/history_test.rb index 7d730355a..be7700c3b 100644 --- a/test/system/history_test.rb +++ b/test/system/history_test.rb @@ -109,6 +109,30 @@ class HistoryTest < ApplicationSystemTestCase end end + test "update sidebar when before param is included and map is moved" do + changeset1 = create(:changeset, :num_changes => 1, :min_lat => 50000000, :max_lat => 50000001, :min_lon => 50000000, :max_lon => 50000001) + create(:changeset_tag, :changeset => changeset1, :k => "comment", :v => "changeset-at-fives") + changeset2 = create(:changeset, :num_changes => 1, :min_lat => 50100000, :max_lat => 50100001, :min_lon => 50100000, :max_lon => 50100001) + create(:changeset_tag, :changeset => changeset2, :k => "comment", :v => "changeset-close-to-fives") + changeset3 = create(:changeset) + + visit "/history?before=#{changeset3.id}#map=17/5/5" + + within_sidebar do + assert_link "changeset-at-fives" + assert_no_link "changeset-close-to-fives" + end + + find("#map [aria-label='Zoom Out']").click(:shift) + + within_sidebar do + assert_link "changeset-at-fives" + assert_link "changeset-close-to-fives" + end + + assert_current_path history_path + end + private def create_visible_changeset(user, comment)