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(); + } + + +} 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"); }); } );