--- /dev/null
+OSM.HomeLocationNameGeocoder = function Endpoint(latInput, lonInput, locationNameInput) {
+ const endpoint = {
+ autofill: true,
+ countryName: locationNameInput.val().trim()
+ };
+
+ let requestController = null;
+
+ endpoint.updateHomeLocationName = function (
+ updateInput = true,
+ lat = latInput.val().trim(),
+ lon = lonInput.val().trim(),
+ successFn
+ ) {
+ if (!lat || !lon || !endpoint.autofill) {
+ return;
+ }
+
+ const geocodeUrl = "/search/nominatim_reverse_query",
+ csrf_param = $("meta[name=csrf-param]").attr("content"),
+ csrf_token = $("meta[name=csrf-token]").attr("content"),
+ params = new URLSearchParams({
+ lat,
+ lon,
+ zoom: 3
+ });
+ params.set(csrf_param, csrf_token);
+
+ if (requestController) {
+ requestController.abort();
+ }
+ const currentRequestController = new AbortController();
+ requestController = currentRequestController;
+
+ fetch(geocodeUrl, {
+ method: "POST",
+ body: params,
+ signal: requestController.signal,
+ headers: { accept: "application/json" }
+ })
+ .then((response) => response.json())
+ .then((data) => {
+ const country = data.length ? data[0].name : "";
+
+ if (updateInput) {
+ $("#home_location_name").val(country);
+ } else if (endpoint.countryName !== country) {
+ endpoint.autofill = false;
+ }
+ endpoint.countryName = country;
+ requestController = null;
+
+ if (successFn) {
+ successFn();
+ }
+ })
+ .catch(() => {
+ if (currentRequestController === requestController) {
+ requestController = null;
+ }
+ });
+ };
+
+ return endpoint;
+};
//= require leaflet.locate
+//= require ./home_location_name-endpoint
(function () {
$(document).on("change", "#user_all", function () {
$(function () {
const defaultHomeZoom = 12;
- let map, marker, deleted_lat, deleted_lon;
+ let map, marker, deleted_lat, deleted_lon, deleted_home_name, homeLocationNameGeocoder, savedLat, savedLon;
if ($("#map").length) {
map = L.map("map", {
zoomControl: false
}).addLayer(new L.OSM.Mapnik());
+ savedLat = $("#home_lat").val();
+ savedLon = $("#home_lon").val();
+ homeLocationNameGeocoder = OSM.HomeLocationNameGeocoder($("#home_lat"), $("#home_lon"), $("#home_location_name"));
+
const position = $("html").attr("dir") === "rtl" ? "topleft" : "topright";
L.OSM.zoom({ position }).addTo(map);
$("#home_lat").val(lat);
$("#home_lon").val(lon);
- deleted_lat = null;
- deleted_lon = null;
- respondToHomeUpdate();
+ clearDeletedText();
+ respondToHomeLatLonUpdate();
}).on("moveend", function () {
const lat = $("#home_lat").val().trim(),
lon = $("#home_lon").val().trim();
});
$("#home_lat, #home_lon").on("input", function () {
- deleted_lat = null;
- deleted_lon = null;
- respondToHomeUpdate();
+ clearDeletedText();
+ respondToHomeLatLonUpdate();
+ });
+
+ $("#home_location_name").on("input", function () {
+ homeLocationNameGeocoder.autofill = false;
+ clearDeletedText();
+
+ respondToHomeLatLonUpdate(false);
});
$("#home_show").click(function () {
$("#home_delete").click(function () {
const lat = $("#home_lat").val(),
- lon = $("#home_lon").val();
+ lon = $("#home_lon").val(),
+ locationName = $("#home_location_name").val();
- $("#home_lat, #home_lon").val("");
+ $("#home_lat, #home_lon, #home_location_name").val("");
deleted_lat = lat;
deleted_lon = lon;
- respondToHomeUpdate();
+ deleted_home_name = locationName;
+
+ respondToHomeLatLonUpdate(false);
$("#home_undelete").trigger("focus");
});
$("#home_undelete").click(function () {
$("#home_lat").val(deleted_lat);
$("#home_lon").val(deleted_lon);
- deleted_lat = null;
- deleted_lon = null;
- respondToHomeUpdate();
+ $("#home_location_name").val(deleted_home_name);
+ clearDeletedText();
+
+ respondToHomeLatLonUpdate(false);
$("#home_delete").trigger("focus");
});
} else {
}
}
- function respondToHomeUpdate() {
+ function respondToHomeLatLonUpdate(updateLocationName = true) {
const lat = $("#home_lat").val().trim(),
- lon = $("#home_lon").val().trim();
+ lon = $("#home_lon").val().trim(),
+ locationName = $("#home_location_name").val().trim();
let location;
try {
if (lat && lon) {
location = L.latLng(lat, lon);
+ if (updateLocationName) {
+ if (savedLat && savedLon && $("#home_location_name").val().trim()) {
+ homeLocationNameGeocoder.updateHomeLocationName(false, savedLat, savedLon, () => {
+ savedLat = savedLon = null;
+ homeLocationNameGeocoder.updateHomeLocationName();
+ });
+ } else {
+ savedLat = savedLon = null;
+ homeLocationNameGeocoder.updateHomeLocationName();
+ }
+ }
}
$("#home_lat, #home_lon").removeClass("is-invalid");
} catch (error) {
$("#home_message").toggleClass("invisible", Boolean(location));
$("#home_show").prop("hidden", !location);
- $("#home_delete").prop("hidden", !location);
- $("#home_undelete").prop("hidden", !(!location && deleted_lat && deleted_lon));
+ $("#home_delete").prop("hidden", !location && !locationName);
+ $("#home_undelete").prop("hidden", !(
+ (!location || !locationName) &&
+ ((deleted_lat && deleted_lon) || deleted_home_name)
+ ));
if (location) {
marker.setLatLng([lat, lon]);
marker.addTo(map);
}
}
+ function clearDeletedText() {
+ deleted_lat = null;
+ deleted_lon = null;
+ deleted_home_name = null;
+ }
+
updateAuthUID();
$("select#user_auth_provider").on("change", updateAuthUID);
current_user.home_lat = params[:user][:home_lat]
current_user.home_lon = params[:user][:home_lon]
+ current_user.home_location_name = params[:user][:home_location_name]
if current_user.save
flash[:notice] = t ".success"
:zoom => zoom,
:name => description,
:type => object_type, :id => object_id)
+
+ respond_to do |format|
+ format.html
+ format.json { render :json => @results }
+ end
end
rescue StandardError => e
host = URI(Settings.nominatim_url).host
# home_lat :float
# home_lon :float
# home_zoom :integer default(3)
+# home_location_name :string
# pass_salt :string
# email_valid :boolean default(FALSE), not null
# new_email :string
<button type="button" id="home_undelete" class="btn btn-outline-primary" hidden><%= t ".undelete" %></button>
</div>
</div>
+ <%= f.text_field :home_location_name, :wrapper_class => "my-2 col-sm-4 pe-3", :class => "mt-auto", :id => "home_location_name" %>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="updatehome" value="1" <% unless current_user.home_location? %> checked <% end %> id="updatehome" />
<label class="form-check-label" for="updatehome"><%= t ".update home location on click" %></label>
<div class='text-body-secondary'>
<small>
<dl class="list-inline">
+ <% if @user.home_location_name&.strip.present? %>
+ <dt class="list-inline-item m-0 align-text-bottom">
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-geo-alt-fill" viewBox="0 0 16 16">
+ <path d="M8 16s6-5.686 6-10A6 6 0 0 0 2 6c0 4.314 6 10 6 10m0-7a3 3 0 1 1 0-6 3 3 0 0 1 0 6">
+ <title><%= t ".home location" %></title>
+ </path>
+ </svg>
+ </dt>
+ <dd class="list-inline-item"><%= @user.home_location_name %></dd>
+ <% end %>
<dt class="list-inline-item m-0"><%= t ".mapper since" %></dt>
<dd class="list-inline-item"><%= l @user.created_at.to_date, :format => :long %></dd>
<dt class="list-inline-item m-0"><%= t ".last map edit" %></dt>
follow: Follow
mapper since: "Mapper since:"
last map edit: "Last map edit:"
+ home location: "Home location"
no activity yet: "No activity yet"
uid: "User id:"
ct status: "Contributor terms:"
--- /dev/null
+class AddUserLocationName < ActiveRecord::Migration[7.2]
+ def change
+ add_column :users, :home_location_name, :string
+ end
+end
tou_agreed timestamp without time zone,
diary_comments_count integer DEFAULT 0,
note_comments_count integer DEFAULT 0,
- creation_address inet
+ creation_address inet,
+ home_location_name character varying
);
('20250121191749'),
('20250105154621'),
('20250104140952'),
+('20241030090336'),
('20241023004427'),
('20241022141247'),
('20240913171951'),
search_check "foo bar baz", %w[nominatim]
end
+ ##
+ # Test the nominatim reverse JSON search
+ def test_search_osm_nominatim_reverse_json
+ with_http_stubs "nominatim" do
+ post search_nominatim_reverse_query_path(:lat => 51.7632, :lon => -0.0076, :zoom => 15, :format => "json"), :xhr => true
+ result_name_check_json("Broxbourne, Hertfordshire, East of England, England, United Kingdom")
+
+ post search_nominatim_reverse_query_path(:lat => 51.7632, :lon => -0.0076, :zoom => 17, :format => "json"), :xhr => true
+ result_name_check_json("Dinant Link Road, Broxbourne, Hertfordshire, East of England, England, EN11 8HX, United Kingdom")
+
+ post search_nominatim_reverse_query_path(:lat => 13.7709, :lon => 100.50507, :zoom => 19, :format => "json"), :xhr => true
+ result_name_check_json("MM Steak&Grill, ถนนศรีอยุธยา, บางขุนพรหม, กรุงเทพมหานคร, เขตดุสิต, กรุงเทพมหานคร, 10300, ประเทศไทย")
+ end
+ end
+
private
def latlon_check(query, lat, lon)
assert_template :layout => "xhr"
assert_equal sources, assigns(:sources).pluck(:name)
end
+
+ def result_name_check_json(name)
+ assert_response :success
+ js = ActiveSupport::JSON.decode(@response.body)
+ assert_not_nil js
+ assert_equal name, js[0]["name"]
+ end
end
--- /dev/null
+require "application_system_test_case"
+
+class UserLocationChangeTest < ApplicationSystemTestCase
+ def setup
+ stub_request(:get, /.*gravatar.com.*d=404/).to_return(:status => 404)
+ end
+
+ test "User can change their location" do
+ user = create(:user)
+ sign_in_as(user)
+
+ visit user_path(user)
+ assert_no_selector ".bi.bi-geo-alt-fill"
+
+ visit edit_profile_path
+ fill_in "Home location name", :with => "Test Location"
+ click_on "Update Profile"
+
+ visit user_path(user)
+ assert_text "Test Location"
+ end
+end