|
-
+
@@ -958,7 +967,7 @@ else
-
+
diff --git a/GanjooRazor/Areas/Admin/Pages/ReviewEdits.cshtml.cs b/GanjooRazor/Areas/Admin/Pages/ReviewEdits.cshtml.cs
index 1a8a4f21..22d23976 100644
--- a/GanjooRazor/Areas/Admin/Pages/ReviewEdits.cshtml.cs
+++ b/GanjooRazor/Areas/Admin/Pages/ReviewEdits.cshtml.cs
@@ -68,6 +68,17 @@ namespace GanjooRazor.Areas.Admin.Pages
///
public List Locations { get; set; }
+ ///
+ /// camelCase JSON of Locations (id/name/latitude/longitude only), used client-side for the
+ /// search-as-you-type location picker on each geo tag (see setupLocationAutocomplete in bk.js) -
+ /// same shape/purpose as Editor.cshtml.cs's AllLocationsJson
+ ///
+ public string AllLocationsJson =>
+ JsonConvert.SerializeObject(
+ (Locations ?? new List()).Select(l => new { l.Id, l.Name, l.Latitude, l.Longitude }),
+ new JsonSerializerSettings { ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver() }
+ );
+
///
/// groups verses into couplets - same logic as SuggestQuoted.cshtml.cs's/Editor.cshtml.cs's GetCouplets,
/// duplicated here rather than shared, so this page doesn't take on a cross-file dependency on those
diff --git a/GanjooRazor/Areas/User/Pages/Editor.cshtml b/GanjooRazor/Areas/User/Pages/Editor.cshtml
index 7a3228e6..db506606 100644
--- a/GanjooRazor/Areas/User/Pages/Editor.cshtml
+++ b/GanjooRazor/Areas/User/Pages/Editor.cshtml
@@ -126,6 +126,42 @@
var allGeoLocations = @Html.Raw(Model.AllLocationsJson);
$(document).ready(function () {
renderPendingGeoDateTags();
+ setupLocationAutocomplete(
+ document.getElementById('geotag-location-search'),
+ document.getElementById('geotag-location-select'),
+ document.getElementById('geotag-location-results'),
+ allGeoLocations,
+ function (loc) {
+ // picking an existing location (or clearing the field) means whatever was
+ // typed into the "new location" panel no longer applies to this suggestion
+ var widget = document.getElementById('new-location-widget');
+ if (loc != null && widget.style.display !== 'none') {
+ toggleNewLocationWidget();
+ }
+ },
+ function (typedText) {
+ var widget = document.getElementById('new-location-widget');
+ if (widget.style.display === 'none' || widget.style.display === '') {
+ toggleNewLocationWidget();
+ }
+ document.getElementById('geotag-new-location-name').value = typedText;
+ }
+ );
+ var directAddLocationSearch = document.getElementById('location-search');
+ if (directAddLocationSearch != null) {
+ // admin-only direct-add row (bypasses the suggestion/moderation flow entirely) -
+ // no "new location" concept here, so an unmatched search just has nothing to pick
+ setupLocationAutocomplete(
+ directAddLocationSearch,
+ document.getElementById('location'),
+ document.getElementById('location-results'),
+ allGeoLocations,
+ null,
+ function (typedText) {
+ upToast('این مکان در فهرست نیست. برای افزودن مکان جدید از «مکانها» در پایین این جدول استفاده کنید.', 'error');
+ }
+ );
+ }
});
var geoDateTagMap = null;
var geoDateTagMarker = null;
@@ -268,7 +304,7 @@
var s = geoDateTagSuggestions[i];
var status = s.markForDelete ? '🗑️ درخواست حذف' : '➕ پیشنهاد جدید';
var coupletText = (s.coupletIndex != null && geoDateTagCoupletTexts[s.coupletIndex]) ? geoDateTagCoupletTexts[s.coupletIndex] : '— کل شعر —';
- var locationText = s.markForDelete ? '' : (s.locationId ? $('#geotag-location-select option[value="' + s.locationId + '"]').text() : (s.suggestedLocationName || ''));
+ var locationText = s.markForDelete ? '' : (s.locationId ? ((allGeoLocations.filter(function (l) { return l.id === s.locationId; })[0] || {}).name || ('مکان #' + s.locationId)) : (s.suggestedLocationName || ''));
html += '| ' + status + ' | ' + coupletText + ' | ' + locationText + ' | ' + (s.lunarYear || '') + ' | ' + (s.lunarMonth || '') + ' | ' + (s.lunarDay || '') + ' | ' +
'✕ حذف از فهرست | ';
}
@@ -354,6 +390,8 @@
function resetGeoDateTagForm() {
$('#geotag-couplet-select').val('');
$('#geotag-location-select').val('');
+ $('#geotag-location-search').val('');
+ $('#geotag-location-results').hide().empty();
$('#geotag-new-location-name').val('');
$('#geotag-new-location-lat').val('');
$('#geotag-new-location-lng').val('');
@@ -2001,14 +2039,13 @@ else
-
-
+
+
+ شروع به تایپ کنید تا فهرست مکانهای موجود بر اساس نام فیلتر شود؛ اگر مکان مورد نظر پیدا نشد، گزینهٔ تعریف آن بهعنوان مکان جدید نمایش داده میشود.
📍 مکان جدید پیشنهاد بده
@@ -2203,14 +2240,11 @@ else
}
|
-
+
|
diff --git a/GanjooRazor/Areas/User/Pages/Editor.cshtml.cs b/GanjooRazor/Areas/User/Pages/Editor.cshtml.cs
index d97b8b79..bf71031c 100644
--- a/GanjooRazor/Areas/User/Pages/Editor.cshtml.cs
+++ b/GanjooRazor/Areas/User/Pages/Editor.cshtml.cs
@@ -218,6 +218,11 @@ namespace GanjooRazor.Areas.User.Pages
FatalError = "";
CanEdit = Request.Cookies["CanEdit"] == "True";
+ // set before any of the early "API call failed" returns below, so the view - which
+ // reads Model.Couplets unconditionally near the top of its script block, before the
+ // FatalError check further down - never sees a null and throws ArgumentNullException
+ // out of Couplets.ToDictionary(...) on a failed request
+ Couplets = Array.Empty>();
ShowAdminOps = CanEdit && Request.Query["admin"] == "1";
using (HttpClient secureClient = new HttpClient(new GanjoorReloginHandler(Request, Response)))
diff --git a/GanjooRazor/wwwroot/css/user-panel.css b/GanjooRazor/wwwroot/css/user-panel.css
index f820d00f..93cdbcc1 100644
--- a/GanjooRazor/wwwroot/css/user-panel.css
+++ b/GanjooRazor/wwwroot/css/user-panel.css
@@ -612,6 +612,51 @@ body.up-modal-open {
box-shadow: 0 0 0 3px rgba(51, 0, 0, 0.12);
}
+/* ---------- location search-as-you-type (see setupLocationAutocomplete in bk.js) ---------- */
+
+.up-autocomplete-wrap {
+ position: relative;
+}
+
+.up-autocomplete-results {
+ position: absolute;
+ z-index: 50;
+ top: calc(100% + 2px);
+ right: 0;
+ left: 0;
+ max-height: 260px;
+ overflow-y: auto;
+ background: var(--up-surface);
+ border: 1px solid var(--up-border);
+ border-radius: var(--up-radius-sm);
+ box-shadow: var(--up-shadow-sm);
+}
+
+.up-autocomplete-item {
+ padding: 8px 12px;
+ font-size: 0.9rem;
+ cursor: pointer;
+ border-bottom: 1px solid var(--up-border);
+}
+
+.up-autocomplete-item:last-child {
+ border-bottom: none;
+}
+
+.up-autocomplete-item:hover {
+ background: var(--up-bg);
+}
+
+.up-autocomplete-item--empty,
+.up-autocomplete-item--more {
+ color: var(--up-text-muted);
+ cursor: default;
+}
+
+.up-autocomplete-item--empty {
+ cursor: pointer;
+}
+
/* ---------- editable surfaces (contenteditable divs standing in for inputs) ----------
Needs a strong, unmistakable affordance since it isn't a native form control -
nothing else on the page should look like this. */
diff --git a/GanjooRazor/wwwroot/js/bk.js b/GanjooRazor/wwwroot/js/bk.js
index 09605c72..c254aa48 100644
--- a/GanjooRazor/wwwroot/js/bk.js
+++ b/GanjooRazor/wwwroot/js/bk.js
@@ -2498,4 +2498,104 @@ function load7DaysVisits(url) {
}
},
});
-}
\ No newline at end of file
+}
+// Turns a plain text into a "search as you type" picker over a small
+// in-memory location catalog ({id, name, latitude, longitude}[]), instead of a
+// long | |