diff --git a/assets/css/_autocomplete-theme.scss b/assets/css/_autocomplete-theme.scss index 34e4d72205..c624d72584 100644 --- a/assets/css/_autocomplete-theme.scss +++ b/assets/css/_autocomplete-theme.scss @@ -91,7 +91,8 @@ .aa-Item { border-bottom: $border; border-radius: 0; - font-weight: normal; + display: flex; + padding: calc(#{$base-spacing} / 2) $base-spacing; &:hover { background-color: $brand-primary-lightest; @@ -102,25 +103,17 @@ font-weight: bold; } - > a, - > button { - color: currentColor; - display: flex; - font-weight: inherit; - gap: .25rem; - min-width: 0; - padding: calc(#{$base-spacing} / 2) $base-spacing; - } - a:hover { text-decoration: none; } [class*='c-svg__icon'] { - width: 1em; + height: auto; + top: .15em; } } + .aa-ItemLink, .aa-ItemContent, .aa-ItemContentBody { display: unset; @@ -141,6 +134,26 @@ } // stylelint-enable declaration-no-important } + + // more specific layout + [data-autocomplete-source-id='geolocation'] { + .aa-ItemContentTitle { + color: $brand-primary; + } + } + [data-autocomplete-source-id='algolia'], + [data-autocomplete-source-id='locations'], + [data-autocomplete-source-id='popular'] { + .fa-map-marker { + color: $brand-primary; + } + .aa-ItemContent { + align-items: first baseline; + display: flex; + justify-content: space-between; + width: 100%; + } + } } #header-desktop { diff --git a/assets/js/algolia-result.js b/assets/js/algolia-result.js index 373e30960b..0a13b756ed 100644 --- a/assets/js/algolia-result.js +++ b/assets/js/algolia-result.js @@ -223,7 +223,7 @@ function _iconFromRoute(route) { export function getPopularIcon(icon) { switch (icon) { case "airplane": - return TEMPLATES.fontAwesomeIcon.render({ icon: "fa-plane" }); + return TEMPLATES.fontAwesomeIcon.render({ icon: "fa-plane-departure" }); default: return Icons.getFeatureIcon(icon); } @@ -309,7 +309,9 @@ export function getUrl(hit, index) { return _contentUrl(hit); case "locations": - return `transit-near-me?address=${encodeURIComponent(hit.address)}`; + return `transit-near-me?address=${encodeURIComponent( + hit.street_address + )}`; case "usemylocation": return "#"; @@ -343,7 +345,7 @@ export function getTitle(hit, type) { switch (type) { case "locations": // eslint-disable-next-line no-case-declarations - const { address: text, highlighted_spans: spans } = hit; + const { street_address: text, highlighted_spans: spans } = hit; return highlightText(text, spans); case "stops": @@ -451,14 +453,21 @@ function _getCommuterRailZone(hit) { if (hit.zone) { return [`Zone ${hit.zone}`]; } + if (hit.icon === "station") { + // the north/south station popular result + return [`Zone 1A`]; + } return []; } function _stopIcons(hit, type) { + const isBusStop = type === "stops" && !hit.stop["station?"]; + const featuresToFilter = isBusStop + ? ["access", "parking_lot", "bus"] + : ["access", "parking_lot"]; const filteredFeatures = hit.features.filter( - feature => feature !== "access" && feature !== "parking_lot" + feature => !featuresToFilter.includes(feature) ); - const alertFeature = _getAlertIcon(hit, type); const allFeatures = alertFeature.concat(filteredFeatures); const allFeaturesSorted = _sortFeatures(allFeatures); @@ -466,6 +475,21 @@ function _stopIcons(hit, type) { const zoneIcon = _getCommuterRailZone(hit); + if (isBusStop) { + return hit.routes + .filter(route => route.type === 3) + .map(route => { + return route.display_name + .replace("Bus: ", "") + .split(", ") + .map( + num => + `${num}` + ) + .join(""); + }); + } + return allIcons.concat(zoneIcon); } diff --git a/assets/js/algolia-results.js b/assets/js/algolia-results.js index deba40d828..9085433b94 100644 --- a/assets/js/algolia-results.js +++ b/assets/js/algolia-results.js @@ -73,7 +73,10 @@ export class AlgoliaResults { results.locations.hits.forEach((hit, idx) => { const elem = document.getElementById(`hit-location-${idx}`); if (elem) { - elem.addEventListener("click", this._locationSearch(hit.address)); + elem.addEventListener( + "click", + this._locationSearch(hit.street_address) + ); } }); const useLocation = document.getElementById( diff --git a/assets/js/test/algolia-result_test.js b/assets/js/test/algolia-result_test.js index b2d9dfa9b4..0783c1c46a 100644 --- a/assets/js/test/algolia-result_test.js +++ b/assets/js/test/algolia-result_test.js @@ -166,7 +166,7 @@ describe("AlgoliaResult", () => { }; const locationHits = { bostonCommon: { - address: "Boston Common, Tremont Street, Boston, MA, USA", + street_address: "Boston Common, Tremont Street, Boston, MA, USA", highlighted_spans: [ { length: 2, offset: 3 }, { length: 5, offset: 9 } diff --git a/assets/js/test/algolia-results_test.js b/assets/js/test/algolia-results_test.js index a00c0ae66c..d81c2fad1a 100644 --- a/assets/js/test/algolia-results_test.js +++ b/assets/js/test/algolia-results_test.js @@ -137,7 +137,8 @@ describe("AlgoliaResults", () => { const hit = { stop: { id: "stop-id", - name: "pre_stop-name" + name: "pre_stop-name", + "station?": true }, _highlightResult: { stop: { @@ -164,7 +165,6 @@ describe("AlgoliaResults", () => { expect(result).to.be.a("string"); expect(result).to.have.string("/stops/" + hit.stop.id); expect(result).to.have.string(hit._highlightResult.stop.name.value); - expect(result).to.have.string("stop-icon"); expect(result).to.have.string("green-line-b-icon"); expect(result).to.have.string("bus-icon"); expect(result).to.have.string("Zone 8"); @@ -290,7 +290,8 @@ describe("AlgoliaResults", () => { hitTitle: "title1", stop: { id: "id1", - name: "name1" + name: "name1", + "station?": true }, _highlightResult: { stop: { @@ -308,7 +309,8 @@ describe("AlgoliaResults", () => { hitTitle: "title2", stop: { id: "id2", - name: "name2" + name: "name2", + "station?": true }, _highlightResult: { stop: { diff --git a/assets/js/trip-planner-location-controls.js b/assets/js/trip-planner-location-controls.js index 1e94d641b3..4818402731 100644 --- a/assets/js/trip-planner-location-controls.js +++ b/assets/js/trip-planner-location-controls.js @@ -320,12 +320,13 @@ export class TripPlannerLocControls { ); break; case "locations": - MapsHelpers.lookupPlace(hit.address).then(res => { + MapsHelpers.lookupPlace(hit.street_address).then(res => { + // this doesnt work const { latitude, longitude } = res; this.setStopValue(ac, hit); this.setAutocompleteValue( ac, - hit.address, + hit.street_address, lat, lng, latitude, diff --git a/assets/ts/ui/autocomplete/__autocomplete.d.ts b/assets/ts/ui/autocomplete/__autocomplete.d.ts index 2d9611a010..158db83cff 100644 --- a/assets/ts/ui/autocomplete/__autocomplete.d.ts +++ b/assets/ts/ui/autocomplete/__autocomplete.d.ts @@ -2,7 +2,6 @@ import { Hit } from "@algolia/client-search"; import { BaseItem } from "@algolia/autocomplete-core"; import { Route, RouteType, Stop } from "../../__v3api"; import { HighlightedSpan } from "../../helpers/text"; -import { TripPlannerLocControls } from "../../../js/trip-planner-location-controls"; type AlgoliaItem = Hit<{ index: string }> & BaseItem; @@ -35,12 +34,24 @@ type SearchResultItem = { export type LocationItem = { longitude: number; latitude: number; - address: string; + formatted: string; highlighted_spans: HighlightedSpan[]; + street_address: string; + municipality: string; + state: string; url: string; }; -export type PopularItem = typeof TripPlannerLocControls.POPULAR[number]; +export type PopularItem = { + icon: "airplane" | "station"; + name: string; + features: string[]; + latitude: number; + longitude: number; + url: string; + state: string; + municipality: string; +}; export type AutocompleteItem = RouteItem | StopItem | ContentItem; export type Item = AutocompleteItem | LocationItem | PopularItem; diff --git a/assets/ts/ui/autocomplete/__tests__/__snapshots__/config-test.ts.snap b/assets/ts/ui/autocomplete/__tests__/__snapshots__/config-test.ts.snap index 3113c63dff..bcee7c7c75 100644 --- a/assets/ts/ui/autocomplete/__tests__/__snapshots__/config-test.ts.snap +++ b/assets/ts/ui/autocomplete/__tests__/__snapshots__/config-test.ts.snap @@ -7,22 +7,28 @@ exports[`Algolia template handles route item 1`] = ` href="/schedules/111" >
- - - - -   - - +
+ +   + +
+
+ @@ -37,15 +43,24 @@ exports[`Location template renders 1`] = `
-
+
- South Station, Boston, MA - + Town, MA +
@@ -60,25 +75,49 @@ exports[`Popular template renders 1`] = `
- - - - - - - South Station - - +
+ + South Station + +
+
+ + + + + + Zone 1A + + +
+
+
+ Town, MA +
diff --git a/assets/ts/ui/autocomplete/__tests__/__snapshots__/templates-test.tsx.snap b/assets/ts/ui/autocomplete/__tests__/__snapshots__/templates-test.tsx.snap index 3113c63dff..bcee7c7c75 100644 --- a/assets/ts/ui/autocomplete/__tests__/__snapshots__/templates-test.tsx.snap +++ b/assets/ts/ui/autocomplete/__tests__/__snapshots__/templates-test.tsx.snap @@ -7,22 +7,28 @@ exports[`Algolia template handles route item 1`] = ` href="/schedules/111" >
- - - - -   - - +
+ +   + +
+
+ @@ -37,15 +43,24 @@ exports[`Location template renders 1`] = `
-
+
- South Station, Boston, MA - + Town, MA +
@@ -60,25 +75,49 @@ exports[`Popular template renders 1`] = `
- - - - - - - South Station - - +
+ + South Station + +
+
+ + + + + + Zone 1A + + +
+
+
+ Town, MA +
diff --git a/assets/ts/ui/autocomplete/__tests__/helpers-test.ts b/assets/ts/ui/autocomplete/__tests__/helpers-test.ts index 823200a959..0769613360 100644 --- a/assets/ts/ui/autocomplete/__tests__/helpers-test.ts +++ b/assets/ts/ui/autocomplete/__tests__/helpers-test.ts @@ -47,6 +47,9 @@ test("itemWithUrl gets a requested URL", () => { features: [], latitude: 42.365396, longitude: -71.017547, + municipality: "Town", + state: "MA", + url: "", urls: { "transit-near-me": "/transit-near-me/logan", "retail-sales-locations": "/retail-locations-somewhere", diff --git a/assets/ts/ui/autocomplete/__tests__/sources-test.ts b/assets/ts/ui/autocomplete/__tests__/sources-test.ts index f72b6ac6e2..3fb49cc30d 100644 --- a/assets/ts/ui/autocomplete/__tests__/sources-test.ts +++ b/assets/ts/ui/autocomplete/__tests__/sources-test.ts @@ -84,24 +84,31 @@ describe("popularLocationSource", () => { describe("algoliaSource", () => { test("defines a template", () => { - expect(algoliaSource("query").templates.item).toBeTruthy(); + expect(algoliaSource("query", {}).templates.item).toBeTruthy(); }); test("defines a getItems function", () => { const query = "new project"; - const indexes = ["drupal_test"]; + const indexes = { drupal_test: {} }; expect( algoliaSource(query, indexes).getItems({ query } as OnInputParams< AutocompleteItem >) ).toBeTruthy(); expect(global.fetch).toHaveBeenCalledWith("/search/query", { - body: JSON.stringify({ algoliaQuery: query, algoliaIndexes: indexes }), + body: JSON.stringify({ + algoliaQuery: query, + algoliaIndexesWithParams: indexes + }), headers: { "Content-Type": "application/json" }, method: "POST" }); }); test("has optional getItemUrl", () => { - expect(algoliaSource("question", [], false)).not.toContainKey("getItemUrl"); - expect(algoliaSource("question")).toContainKey("getItemUrl"); + expect( + algoliaSource("question", { stops: { hitsPerPage: 7 } }, false) + ).not.toContainKey("getItemUrl"); + expect( + algoliaSource("question", { stops: { hitsPerPage: 7 } }) + ).toContainKey("getItemUrl"); }); }); diff --git a/assets/ts/ui/autocomplete/__tests__/templates-test.tsx b/assets/ts/ui/autocomplete/__tests__/templates-test.tsx index e389866eac..4dc6e01317 100644 --- a/assets/ts/ui/autocomplete/__tests__/templates-test.tsx +++ b/assets/ts/ui/autocomplete/__tests__/templates-test.tsx @@ -203,9 +203,11 @@ test("Popular template renders", () => { longitude: -71.0, icon: "station", features: ["red_line", "bus", "commuter_rail", "access"], + municipality: "Town", + state: "MA", url: "/transit-near-me?latitude=42.352271&longitude=-71.055242&name=South+Station" - }; + } as PopularItem; const { asFragment } = renderMockTemplate( templateWithLink(PopularItemTemplate)( mockTemplateParam(popularItem, "South Station") @@ -221,6 +223,10 @@ test("Location template renders", () => { latitude: 42.4, longitude: -71.0, highlighted_spans: [], + formatted: "123 Main St, Town, MA, USA", + street_address: "123 Main St", + municipality: "Town", + state: "MA", url: "/transit-near-me?latitude=42.352271&longitude=-71.055242&address=South+Station,+Boston,+MA" }; diff --git a/assets/ts/ui/autocomplete/config.ts b/assets/ts/ui/autocomplete/config.ts index e5d70c233b..aec61227c8 100644 --- a/assets/ts/ui/autocomplete/config.ts +++ b/assets/ts/ui/autocomplete/config.ts @@ -75,7 +75,28 @@ const BASIC: Partial> = { getSources({ query, setIsOpen }): AutocompleteSource[] { if (!query) return [geolocationSource(setIsOpen, "transit-near-me")]; return debounced([ - algoliaSource(query), + algoliaSource(query, { + routes: { + hitsPerPage: 5 + }, + stops: { + hitsPerPage: 2 + }, + drupal: { + hitsPerPage: 2, + facetFilters: [ + [ + "_content_type:page", + "_content_type:search_result", + "_content_type:diversion", + "_content_type:landing_page", + "_content_type:person", + "_content_type:project", + "_content_type:project_update" + ] + ] + } + }), locationSource(query, 2, "transit-near-me") ]); } @@ -156,7 +177,7 @@ const TRIP_PLANNER = ({ const name: string = (item.route as Route)?.name || (item.stop as Stop)?.name || - ((item as unknown) as LocationItem).address || + ((item as unknown) as LocationItem).formatted || (item as ContentItem).content_title || ((item as unknown) as PopularItem).name || ""; @@ -193,7 +214,10 @@ const TRIP_PLANNER = ({ } ]); return debounced([ - { ...algoliaSource(query, ["stops"], false), onSelect }, + { + ...algoliaSource(query, { stops: { hitsPerPage: 5 } }, false), + onSelect + }, { ...locationSource(query, 5), onSelect } ]); } diff --git a/assets/ts/ui/autocomplete/sources.ts b/assets/ts/ui/autocomplete/sources.ts index c1b7c5ce14..43e6dbd4c9 100644 --- a/assets/ts/ui/autocomplete/sources.ts +++ b/assets/ts/ui/autocomplete/sources.ts @@ -89,7 +89,7 @@ export const popularLocationSource = ( */ export const algoliaSource = ( query: string, - indexes: string[] = ["routes", "stops", "drupal"], + indexesWithParams: Record>, withLink: boolean = true ): AutocompleteSource => ({ sourceId: "algolia", @@ -104,7 +104,7 @@ export const algoliaSource = ( }, body: JSON.stringify({ algoliaQuery: query, - algoliaIndexes: indexes + algoliaIndexesWithParams: indexesWithParams }) }) .then(res => res.json()) diff --git a/assets/ts/ui/autocomplete/templates/algolia.tsx b/assets/ts/ui/autocomplete/templates/algolia.tsx index a9e48ab7b3..168a1d796f 100644 --- a/assets/ts/ui/autocomplete/templates/algolia.tsx +++ b/assets/ts/ui/autocomplete/templates/algolia.tsx @@ -16,6 +16,10 @@ import { getIcon } from "../../../../js/algolia-result"; +// parse this from a stop's address until we can get it as a stop field +const stateAbbr = (address: string): string => + (address.split(",").pop() || "").substring(1, 3); + export function LinkForItem(props: { item: AutocompleteItem; query: string; @@ -74,41 +78,60 @@ const AlgoliaItemTemplate: SourceTemplates["item"] = ({ ? contentIcon(item) : getIcon(item, indexName); return ( -
- - {!isContentItem(item) && - featureIcons.map((feature: string) => ( +
+
+
- ))} - - - - {components.Highlight({ - hit: item, - attribute - })} - -   - {isRouteItem(item) && item.route.type === 3 && ( - +
+ {components.Highlight({ hit: item, - attribute: ["route", "long_name"] + attribute })} - )} - - + {isStopItem(item) && !item.stop["station?"] && ( + + #{item.stop.id} + + )} +   + {isRouteItem(item) && item.route.type === 3 && ( + + {components.Highlight({ + hit: item, + attribute: ["route", "long_name"] + })} + + )} +
+
+ {!isContentItem(item) && featureIcons.length > 0 && ( +
+ {featureIcons.map((feature: string) => ( + + ))} +
+ )} +
+ + {isStopItem(item) && ( +
+ {`${item.stop.municipality}, ${ + item.stop.address ? stateAbbr(item.stop.address) : "MA" + }`} +
+ )}
); }; diff --git a/assets/ts/ui/autocomplete/templates/geolocation.tsx b/assets/ts/ui/autocomplete/templates/geolocation.tsx index 2a559f797d..be01a5095a 100644 --- a/assets/ts/ui/autocomplete/templates/geolocation.tsx +++ b/assets/ts/ui/autocomplete/templates/geolocation.tsx @@ -68,8 +68,14 @@ export function GeolocationComponent(props: { if (loading) { return ( -
- {loading}