diff --git a/assets/javascripts/discourse/components/custom-wizard-field-location.gjs b/assets/javascripts/discourse/components/custom-wizard-field-location.gjs
index 1d7344d..34e4ae5 100644
--- a/assets/javascripts/discourse/components/custom-wizard-field-location.gjs
+++ b/assets/javascripts/discourse/components/custom-wizard-field-location.gjs
@@ -1,5 +1,6 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
+import { array } from "@ember/helper";
import { action, computed } from "@ember/object";
import { service } from "@ember/service";
import I18n from "I18n";
@@ -19,7 +20,6 @@ export default class CustomWizardFieldLocationComponent extends Component {
includeGeoLocation = true;
inputFieldsEnabled = true;
layoutName = "javascripts/wizard/templates/components/wizard-field-location";
- showType = true;
constructor() {
super(...arguments);
@@ -141,6 +141,8 @@ export default class CustomWizardFieldLocationComponent extends Component {
@searchOnInit={{this.searchOnInit}}
@setGeoLocation={{this.setGeoLocation}}
@searchError={{this.searchError}}
+ @geoAttrs={{array}}
+ @showType={{true}}
/>
}
diff --git a/assets/javascripts/discourse/components/location-form.gjs b/assets/javascripts/discourse/components/location-form.gjs
index f9c2245..5219800 100644
--- a/assets/javascripts/discourse/components/location-form.gjs
+++ b/assets/javascripts/discourse/components/location-form.gjs
@@ -357,6 +357,8 @@ export default class LocationForm extends Component {
class="input-xxlarge location-selector"
@searchError={{@searchError}}
@context={{this.context}}
+ @geoAttrs={{@geoAttrs}}
+ @showType={{@showType}}
/>
{{else}}
{
+ if (!term || term.length === 0) {
+ return [];
+ }
+
+ let request = { query: term };
+
+ const context = this.args.context;
+ if (context) {
+ request["context"] = context;
+ }
+
+ this.loading = true;
+
+ try {
+ const result = await geoLocationSearch(
+ request,
+ this.siteSettings.location_geocoding_debounce
+ );
+
+ if (result.error) {
+ throw new Error(result.error);
+ }
+
+ const defaultProvider = this.siteSettings.location_geocoding_provider;
+ const geoAttrs = this.args.geoAttrs;
+ const showType = this.args.showType;
+ let locations = [];
+
+ // Store current provider for display
+ this.currentProvider =
+ providerDetails[result.provider || defaultProvider];
+
+ if (!result.locations || result.locations.length === 0) {
+ locations = [];
+ } else {
+ locations = result.locations.map((l) => {
+ if (geoAttrs) {
+ l["geoAttrs"] = geoAttrs;
+ }
+ if (showType !== undefined) {
+ l["showType"] = showType;
+ }
+ // Ensure each location has an ID for comparison
+ l.id = l.address || JSON.stringify(l);
+ return l;
+ });
+ }
+
+ // Add provider info as non-selectable display item
+ if (this.currentProvider) {
+ locations.push({
+ provider: this.currentProvider,
+ address: i18n("location.geo.desc", {
+ provider: this.currentProvider,
+ }),
+ });
+ }
+
+ return locations;
+ } catch (e) {
+ if (this.searchError) {
+ this.searchError(e);
+ }
+ return [];
+ } finally {
+ this.loading = false;
+ }
+ };
+ }
+
+ @action
+ handleSelectionChange(selectedLocations) {
+ // Only handle single selection
+ const location = selectedLocations?.[selectedLocations.length - 1];
+
+ if (!location) {
+ this.selectedLocation = null;
+ // Don't call onChangeCallback with null - original implementation
+ // only called callback when selecting valid location objects
+ return;
+ }
+
+ // Don't select special items (a location with provider prop is there for display only)
+ if (location.provider) {
+ return;
+ }
+
+ this.selectedLocation = location;
+
+ if (this.args.onChangeCallback) {
+ this.args.onChangeCallback(location);
+ }
+ }
+
+ @action
+ compareLocations(a, b) {
+ if (!a || !b) {
+ return false;
+ }
+ return a.id === b.id || a.address === b.address;
+ }
+
+ @action
+ getDisplayText(location) {
+ if (!location) {
+ return "";
+ }
+
+ const geoAttrs = this.args.geoAttrs;
+ if (typeof location === "object" && location.address) {
+ return geoLocationFormat(location, this.site.country_codes, { geoAttrs });
+ }
+
+ return location.address || location.toString();
+ }
+
+
+
+ {{#if this.loading}}
+
+
+
+ {{/if}}
+
+
+ <:selection as |location|>
+ {{this.getDisplayText location}}
+
+
+ <:result as |location|>
+ {{#if location.provider}}
+
+
+
+ {{else}}
+
+ {{/if}}
+
+
+
+
+
+}
diff --git a/assets/javascripts/discourse/components/location-selector.js b/assets/javascripts/discourse/components/location-selector.js
deleted file mode 100644
index e43e6e7..0000000
--- a/assets/javascripts/discourse/components/location-selector.js
+++ /dev/null
@@ -1,164 +0,0 @@
-import $ from "jquery";
-import TextField from "discourse/components/text-field";
-import { observes } from "discourse/lib/decorators";
-import { escapeExpression } from "discourse/lib/utilities";
-import { i18n } from "discourse-i18n";
-import {
- geoLocationFormat,
- geoLocationSearch,
- providerDetails,
-} from "../lib/location-utilities";
-
-export default TextField.extend({
- autocorrect: false,
- autocapitalize: false,
- classNames: "location-selector",
- context: null,
- size: 400,
-
- didInsertElement() {
- this._super();
- let self = this;
- const location = this.get("location.address");
-
- let val = "";
- if (location) {
- val = location;
- }
-
- $(self.element)
- .val(val)
- .autocomplete({
- template: locationAutocompleteTemplate,
- single: true,
- updateData: false,
-
- dataSource: function (term) {
- let request = { query: term };
-
- const context = self.get("context");
- if (context) {
- request["context"] = context;
- }
-
- self.set("loading", true);
-
- return geoLocationSearch(
- request,
- self.siteSettings.location_geocoding_debounce
- )
- .then((result) => {
- if (result.error) {
- throw new Error(result.error);
- }
-
- const defaultProvider =
- self.siteSettings.location_geocoding_provider;
- const geoAttrs = self.get("geoAttrs");
- const showType = self.get("showType");
- let locations = [];
-
- if (!result.locations || result.locations.length === 0) {
- locations = [
- {
- no_results: true,
- },
- ];
- } else {
- locations = result.locations.map((l) => {
- if (geoAttrs) {
- l["geoAttrs"] = geoAttrs;
- }
- if (showType !== undefined) {
- l["showType"] = showType;
- }
- return l;
- });
- }
-
- locations.push({
- provider: providerDetails[result.provider || defaultProvider],
- });
-
- self.set("loading", false);
-
- return locations;
- })
- .catch((e) => {
- self.set("loading", false);
- this.searchError(e);
- });
- },
-
- transformComplete: function (l) {
- if (typeof l === "object") {
- self.onChangeCallback(l);
- const geoAttrs = self.get("geoAttrs");
- return geoLocationFormat(l, self.site.country_codes, { geoAttrs });
- } else {
- // hack to get around the split autocomplete performs on strings
- document
- .querySelectorAll(".location-form .ac-wrap .item")
- .forEach((element) => {
- element.remove();
- });
- document
- .querySelectorAll(".user-location-selector .ac-wrap .item")
- .forEach((element) => {
- element.remove();
- });
- return self.element.value;
- }
- },
-
- onChangeItems: function (items) {
- if (items[0] == null) {
- self.set("location", "{}");
- }
- },
- });
- },
-
- @observes("loading")
- showLoadingSpinner() {
- const loading = this.get("loading");
- const wrap = this.element.parentNode;
- const spinner = document.createElement("span");
- spinner.className = "ac-loading";
- spinner.innerHTML = "
";
- if (loading) {
- wrap.insertBefore(spinner, wrap.firstChild);
- } else {
- const existingSpinner = wrap.querySelectorAll(".ac-loading");
- existingSpinner.forEach((el) => el.remove());
- }
- },
-
- willDestroyElement() {
- this._super();
- $(this.element).autocomplete("destroy");
- },
-});
-
-function locationAutocompleteTemplate(context) {
- const optionHtml = context.options.map((o) => {
- if (o.no_results) {
- return `${i18n("location.geo.no_results")}
`;
- } else if (o.provider) {
- return ``;
- } else {
- const typeHtml = o.showType
- ? `${escapeExpression(o.type)}
`
- : "";
-
- return `
-
-
- ${typeHtml}
- `;
- }
- });
- return ``;
-}
diff --git a/assets/javascripts/discourse/components/modal/add-location.gjs b/assets/javascripts/discourse/components/modal/add-location.gjs
index 96762be..639163f 100644
--- a/assets/javascripts/discourse/components/modal/add-location.gjs
+++ b/assets/javascripts/discourse/components/modal/add-location.gjs
@@ -1,6 +1,7 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { Input } from "@ember/component";
+import { array } from "@ember/helper";
import { action, computed } from "@ember/object";
import { service } from "@ember/service";
import DButton from "discourse/components/d-button";
@@ -159,6 +160,8 @@ export default class AddLocationComponent extends Component {
@searchOnInit={{this.searchOnInit}}
@setGeoLocation={{this.setGeoLocation}}
@searchError={{this.searchError}}
+ @geoAttrs={{array}}
+ @showType={{false}}
/>
diff --git a/assets/javascripts/discourse/connectors/user-custom-preferences/map-location.gjs b/assets/javascripts/discourse/connectors/user-custom-preferences/map-location.gjs
index 41b53d4..48e77bf 100644
--- a/assets/javascripts/discourse/connectors/user-custom-preferences/map-location.gjs
+++ b/assets/javascripts/discourse/connectors/user-custom-preferences/map-location.gjs
@@ -1,5 +1,6 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
+import { array } from "@ember/helper";
import { action } from "@ember/object";
import { service } from "@ember/service";
import icon from "discourse-common/helpers/d-icon";
@@ -35,6 +36,8 @@ export default class UserCustomPrefsMapLocation extends Component {
class="input-xxlarge location-selector"
@searchError={{this.searchError}}
@context={{this.context}}
+ @geoAttrs={{array}}
+ @showType={{false}}
/>
diff --git a/assets/stylesheets/common/locations-form.scss b/assets/stylesheets/common/locations-form.scss
index da57dfb..4a568e6 100644
--- a/assets/stylesheets/common/locations-form.scss
+++ b/assets/stylesheets/common/locations-form.scss
@@ -120,27 +120,34 @@ div.input-location {
}
}
+.location-selector-wrapper {
+ .d-multi-select-trigger {
+ width: 100%;
+ }
+}
+
.location-form-result {
cursor: pointer;
- background-color: var(--secondary);
display: flex;
+ align-items: center;
- &:hover,
- &.selected {
- background-color: var(--tertiary);
- color: white;
-
- label {
- color: white;
- }
- }
-
- label {
+ label,
+ span {
flex: 1 1 auto;
margin-bottom: 0;
cursor: pointer;
}
+ .location-type {
+ flex: 0 0 auto;
+ margin-left: 0.5em;
+ padding: 0.125em 0.375em;
+ font-size: 0.875em;
+ color: var(--primary-medium);
+ border-radius: 0.2em;
+ white-space: nowrap;
+ }
+
i {
margin-right: 5px;
}
diff --git a/plugin.rb b/plugin.rb
index f5b7860..496f92c 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# name: discourse-locations
# about: Tools for handling locations in Discourse
-# version: 6.8.14
+# version: 6.8.15
# authors: Robert Barrow, Angus McLeod
# contact_emails: merefield@gmail.com
# url: https://github.com/merefield/discourse-locations
diff --git a/test/javascripts/acceptance/topic-location-input-fields-disabled-short-name-test.js b/test/javascripts/acceptance/topic-location-input-fields-disabled-short-name-test.js
index 97de88b..2a0f685 100644
--- a/test/javascripts/acceptance/topic-location-input-fields-disabled-short-name-test.js
+++ b/test/javascripts/acceptance/topic-location-input-fields-disabled-short-name-test.js
@@ -1,11 +1,6 @@
import { click, fillIn, visit } from "@ember/test-helpers";
import { test } from "qunit";
-import {
- acceptance,
- query,
- simulateKeys,
- visible,
-} from "discourse/tests/helpers/qunit-helpers";
+import { acceptance, visible } from "discourse/tests/helpers/qunit-helpers";
import { cloneJSON } from "discourse-common/lib/object";
import locationFixtures from "../fixtures/location-fixtures";
import siteFixtures from "../fixtures/site-fixtures";
@@ -37,27 +32,27 @@ acceptance(
await click("a.fancy-title");
await click("button.add-location-btn");
assert.ok(visible(".add-location-modal"), "add location modal is shown");
- await simulateKeys(query(".location-selector"), "liver building");
- await click("li.location-form-result:first-child label");
+ await click(".location-selector .d-multi-select-trigger");
+ await fillIn(".d-multi-select__search-input", "liver building");
+ await click(".location-form-result:first-child label");
- assert.equal(
- query(".location-selector-container .item span").innerText,
- "Royal Liver Building, Water Street, Ropewalks, Liverpool, Liverpool City Region, England, L3 1EG, United Kingdom"
- );
+ assert
+ .dom(".location-selector .d-multi-select-trigger__selection-label")
+ .hasText(
+ "Royal Liver Building, Water Street, Ropewalks, Liverpool, Liverpool City Region, England, L3 1EG, United Kingdom"
+ );
await fillIn(".location-name", "Home Sweet Home");
await click("#save-location");
- assert.equal(
- query("button.add-location-btn span.d-button-label").innerText,
- "Home Sweet Home, L3 1EG, Liverpool, United Kingdom"
- );
+ assert
+ .dom("button.add-location-btn span.d-button-label")
+ .hasText("Home Sweet Home, L3 1EG, Liverpool, United Kingdom");
await click("button.submit-edit");
- assert.equal(
- query(".location-label-container .location-text").innerText,
- "Home Sweet Home"
- );
+ assert
+ .dom(".location-label-container .location-text")
+ .hasText("Home Sweet Home");
});
}
);
diff --git a/test/javascripts/acceptance/topic-location-input-fields-disabled-test.js b/test/javascripts/acceptance/topic-location-input-fields-disabled-test.js
index cec7765..d3aa077 100644
--- a/test/javascripts/acceptance/topic-location-input-fields-disabled-test.js
+++ b/test/javascripts/acceptance/topic-location-input-fields-disabled-test.js
@@ -1,11 +1,6 @@
import { click, fillIn, visit } from "@ember/test-helpers";
import { test } from "qunit";
-import {
- acceptance,
- query,
- simulateKeys,
- visible,
-} from "discourse/tests/helpers/qunit-helpers";
+import { acceptance, visible } from "discourse/tests/helpers/qunit-helpers";
import { cloneJSON } from "discourse-common/lib/object";
import locationFixtures from "../fixtures/location-fixtures";
import siteFixtures from "../fixtures/site-fixtures";
@@ -37,21 +32,22 @@ acceptance(
await click("a.fancy-title");
await click("button.add-location-btn");
assert.ok(visible(".add-location-modal"), "add location modal is shown");
- await simulateKeys(query(".location-selector"), "liver building");
- await click("li.location-form-result:first-child label");
+ await click(".location-selector .d-multi-select-trigger");
+ await fillIn(".d-multi-select__search-input", "liver building");
+ await click(".location-form-result:first-child label");
- assert.equal(
- query(".location-selector-container .item span").innerText,
- "Royal Liver Building, Water Street, Ropewalks, Liverpool, Liverpool City Region, England, L3 1EG, United Kingdom"
- );
+ assert
+ .dom(".location-selector .d-multi-select-trigger__selection-label")
+ .hasText(
+ "Royal Liver Building, Water Street, Ropewalks, Liverpool, Liverpool City Region, England, L3 1EG, United Kingdom"
+ );
await fillIn(".location-name", "Home Sweet Home");
await click("#save-location");
- assert.equal(
- query("button.add-location-btn span.d-button-label").innerText,
- "Home Sweet Home, L3 1EG, Liverpool, United Kingdom"
- );
+ assert
+ .dom("button.add-location-btn span.d-button-label")
+ .hasText("Home Sweet Home, L3 1EG, Liverpool, United Kingdom");
});
}
);