@askdialog/dialog-sdk 2.9.1 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -10
- package/dist/Dialog.d.ts +21 -6
- package/dist/Dialog.d.ts.map +1 -1
- package/dist/Dialog.js +72 -12
- package/dist/__tests__/dialogCurrentProduct.spec.d.ts +2 -0
- package/dist/__tests__/dialogCurrentProduct.spec.d.ts.map +1 -0
- package/dist/__tests__/dialogCurrentProduct.spec.js +134 -0
- package/dist/__tests__/dialogSearchAnalytics.spec.js +1 -0
- package/dist/__tests__/publicTypes.spec.js +2 -1
- package/dist/__tests__/searchController.spec.js +126 -35
- package/dist/__tests__/searchControllerAttribution.spec.js +30 -26
- package/dist/__tests__/searchImpressions.spec.js +3 -20
- package/dist/__tests__/searchService.spec.js +65 -60
- package/dist/config/config.development.js +1 -1
- package/dist/config/config.production.js +1 -1
- package/dist/config/index.js +1 -1
- package/dist/searchController.d.ts +1 -1
- package/dist/searchController.d.ts.map +1 -1
- package/dist/searchController.js +34 -24
- package/dist/services/search.d.ts +3 -2
- package/dist/services/search.d.ts.map +1 -1
- package/dist/services/search.js +4 -9
- package/dist/types/constructor.d.ts +15 -0
- package/dist/types/constructor.d.ts.map +1 -1
- package/dist/types/search.d.ts +24 -22
- package/dist/types/search.d.ts.map +1 -1
- package/dist/types/search.js +7 -4
- package/dist/types/searchAnalytics.d.ts +2 -0
- package/dist/types/searchAnalytics.d.ts.map +1 -1
- package/dist/types/searchController.d.ts +13 -5
- package/dist/types/searchController.d.ts.map +1 -1
- package/dist/utils/searchControllerAnalytics.d.ts +4 -4
- package/dist/utils/searchControllerAnalytics.d.ts.map +1 -1
- package/dist/utils/searchControllerAnalytics.js +14 -16
- package/dist/utils/searchImpressions.d.ts.map +1 -1
- package/dist/utils/searchImpressions.js +1 -6
- package/dist/utils/searchSections.d.ts +5 -0
- package/dist/utils/searchSections.d.ts.map +1 -0
- package/dist/utils/searchSections.js +21 -0
- package/package.json +1 -1
package/dist/searchController.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
// This controller is one cohesive unit: `selectResult` reports whether the
|
|
2
|
+
// navigate adapter handled the transition and the response is split into the
|
|
3
|
+
// products entry plus its sections, pushing the file past the default 200-line
|
|
4
|
+
// cap — raised modestly rather than split artificially.
|
|
5
|
+
/* eslint max-lines: ["error", 240] */
|
|
6
|
+
import { searchIndexName } from "./services/search";
|
|
1
7
|
import { SearchStatus, } from "./types/searchController";
|
|
2
8
|
import { createControllerAnalytics } from "./utils/searchControllerAnalytics";
|
|
9
|
+
import { buildSectionQueries, pickSectionResults, } from "./utils/searchSections";
|
|
3
10
|
const DEFAULT_DEBOUNCE_MS = 250;
|
|
4
11
|
const DEFAULT_HITS_PER_PAGE = 12;
|
|
5
12
|
// `SearchRequest` rejects queries under two visible characters (code points).
|
|
@@ -9,9 +16,12 @@ const INITIAL_STATE = {
|
|
|
9
16
|
query: "",
|
|
10
17
|
page: 0,
|
|
11
18
|
response: undefined,
|
|
19
|
+
sections: undefined,
|
|
12
20
|
error: undefined,
|
|
13
21
|
};
|
|
14
|
-
export function createSearchController({ search, analytics, navigate, debounceMs = DEFAULT_DEBOUNCE_MS, hitsPerPage = DEFAULT_HITS_PER_PAGE, locale,
|
|
22
|
+
export function createSearchController({ search, analytics, navigate, debounceMs = DEFAULT_DEBOUNCE_MS, hitsPerPage = DEFAULT_HITS_PER_PAGE, locale, sections = [], }) {
|
|
23
|
+
const indexName = searchIndexName("products", locale);
|
|
24
|
+
const resolveSections = () => typeof sections === "function" ? sections() : sections;
|
|
15
25
|
let state = INITIAL_STATE;
|
|
16
26
|
const listeners = new Set();
|
|
17
27
|
const controllerAnalytics = createControllerAnalytics(analytics);
|
|
@@ -34,39 +44,34 @@ export function createSearchController({ search, analytics, navigate, debounceMs
|
|
|
34
44
|
abortController?.abort();
|
|
35
45
|
abortController = undefined;
|
|
36
46
|
};
|
|
37
|
-
const buildRequest = (query, page) => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
request.countryCode = countryCode;
|
|
44
|
-
}
|
|
45
|
-
// Query unchanged since the last response: resend its queryId; a new
|
|
46
|
-
// query gets a fresh engine-generated one.
|
|
47
|
-
const previous = state.response;
|
|
48
|
-
if (previous?.query === query) {
|
|
49
|
-
request.queryId = previous.queryId;
|
|
50
|
-
}
|
|
51
|
-
return request;
|
|
52
|
-
};
|
|
47
|
+
const buildRequest = (query, page, requested) => ({
|
|
48
|
+
requests: [
|
|
49
|
+
{ indexName, query, page, hitsPerPage },
|
|
50
|
+
...buildSectionQueries(requested, query, locale, hitsPerPage),
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
53
|
const run = async (query, page) => {
|
|
54
54
|
cancelInFlight();
|
|
55
55
|
abortController = new AbortController();
|
|
56
56
|
const id = ++requestId;
|
|
57
|
-
const request = buildRequest(query, page);
|
|
58
57
|
setState({ status: SearchStatus.LOADING, query, page });
|
|
59
58
|
try {
|
|
60
|
-
const
|
|
59
|
+
const requested = resolveSections();
|
|
60
|
+
const response = await search(buildRequest(query, page, requested), {
|
|
61
61
|
signal: abortController.signal,
|
|
62
62
|
});
|
|
63
63
|
if (id !== requestId) {
|
|
64
64
|
return; // A newer request landed first: this response is stale.
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
const result = response.results.find((entry) => entry.index === indexName);
|
|
67
|
+
if (result === undefined) {
|
|
68
|
+
throw new Error(`Dialog search returned no ${indexName} entry`);
|
|
69
|
+
}
|
|
70
|
+
controllerAnalytics.onResponse(result);
|
|
67
71
|
setState({
|
|
68
|
-
status:
|
|
69
|
-
response,
|
|
72
|
+
status: result.nbHits === 0 ? SearchStatus.EMPTY : SearchStatus.SUCCESS,
|
|
73
|
+
response: result,
|
|
74
|
+
sections: pickSectionResults(response, requested, locale),
|
|
70
75
|
error: undefined,
|
|
71
76
|
});
|
|
72
77
|
}
|
|
@@ -77,7 +82,12 @@ export function createSearchController({ search, analytics, navigate, debounceMs
|
|
|
77
82
|
if (id !== requestId) {
|
|
78
83
|
return;
|
|
79
84
|
}
|
|
80
|
-
setState({
|
|
85
|
+
setState({
|
|
86
|
+
status: SearchStatus.ERROR,
|
|
87
|
+
error,
|
|
88
|
+
response: undefined,
|
|
89
|
+
sections: undefined,
|
|
90
|
+
});
|
|
81
91
|
}
|
|
82
92
|
};
|
|
83
93
|
// Shared by `setQuery`/`submit`: undefined = reset to idle (query too short).
|
|
@@ -146,7 +156,7 @@ export function createSearchController({ search, analytics, navigate, debounceMs
|
|
|
146
156
|
return false;
|
|
147
157
|
}
|
|
148
158
|
controllerAnalytics.select(response, index);
|
|
149
|
-
const url = response.hits[index].
|
|
159
|
+
const url = response.hits[index].url;
|
|
150
160
|
const runAdapter = options?.navigate ?? true;
|
|
151
161
|
if (runAdapter && navigate !== undefined && url !== undefined) {
|
|
152
162
|
navigate(url, response.hits[index]);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { SearchOptions, SearchRequest, SearchResponse } from "../types/search";
|
|
1
|
+
import { SearchIndex, SearchOptions, SearchRequest, SearchResponse } from "../types/search";
|
|
2
|
+
export declare const searchIndexName: (index: SearchIndex, locale: string) => string;
|
|
2
3
|
/**
|
|
3
4
|
* One POST per invocation — no debounce, cache, retry or request state; the
|
|
4
5
|
* caller owns cancellation through `options.signal`.
|
|
5
6
|
*/
|
|
6
|
-
export declare const
|
|
7
|
+
export declare const searchLexical: (apiKey: string, request: SearchRequest, options?: SearchOptions) => Promise<SearchResponse>;
|
|
7
8
|
//# sourceMappingURL=search.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/services/search.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/services/search.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,WAAW,EACX,aAAa,EACb,aAAa,EACb,cAAc,EACf,MAAM,iBAAiB,CAAC;AAMzB,eAAO,MAAM,eAAe,GAAI,OAAO,WAAW,EAAE,QAAQ,MAAM,KAAG,MACzB,CAAC;AAoB7C;;;GAGG;AACH,eAAO,MAAM,aAAa,GACxB,QAAQ,MAAM,EACd,SAAS,aAAa,EACtB,UAAU,aAAa,KACtB,OAAO,CAAC,cAAc,CAgBxB,CAAC"}
|
package/dist/services/search.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { config } from "../config";
|
|
2
2
|
import { DialogSearchError } from "../DialogSearchError";
|
|
3
3
|
import { toIso639LanguageCode } from "../utils/localization";
|
|
4
|
-
const SEARCH_PATH = "/public/search";
|
|
4
|
+
const SEARCH_PATH = "/public/search/lexical";
|
|
5
5
|
const API_KEY_HEADER = "x-dialog-api-key";
|
|
6
|
+
export const searchIndexName = (index, locale) => `${index}_${toIso639LanguageCode(locale)}`;
|
|
6
7
|
const toSearchError = async (response) => {
|
|
7
8
|
let body;
|
|
8
9
|
try {
|
|
@@ -17,24 +18,18 @@ const toSearchError = async (response) => {
|
|
|
17
18
|
: response.statusText;
|
|
18
19
|
return new DialogSearchError({ status: response.status, code, message });
|
|
19
20
|
};
|
|
20
|
-
// Every search path (Dialog instance, search controller, direct transport)
|
|
21
|
-
// funnels through here, so the locale reaches the wire as the bare ISO 639-1
|
|
22
|
-
// language whatever tag the caller holds.
|
|
23
|
-
const withNormalizedLocale = (request) => request.locale === undefined
|
|
24
|
-
? request
|
|
25
|
-
: { ...request, locale: toIso639LanguageCode(request.locale) };
|
|
26
21
|
/**
|
|
27
22
|
* One POST per invocation — no debounce, cache, retry or request state; the
|
|
28
23
|
* caller owns cancellation through `options.signal`.
|
|
29
24
|
*/
|
|
30
|
-
export const
|
|
25
|
+
export const searchLexical = async (apiKey, request, options) => {
|
|
31
26
|
const response = await fetch(`${config.monolithApiUrl}${SEARCH_PATH}`, {
|
|
32
27
|
method: "POST",
|
|
33
28
|
headers: {
|
|
34
29
|
"Content-Type": "application/json",
|
|
35
30
|
[API_KEY_HEADER]: apiKey,
|
|
36
31
|
},
|
|
37
|
-
body: JSON.stringify(
|
|
32
|
+
body: JSON.stringify(request),
|
|
38
33
|
signal: options?.signal,
|
|
39
34
|
});
|
|
40
35
|
if (!response.ok) {
|
|
@@ -5,6 +5,11 @@ export interface DialogCallbacks {
|
|
|
5
5
|
addToCart: (input: AddToCartInput) => Promise<void>;
|
|
6
6
|
getProduct: (productId: string, variantId?: string) => Promise<SimplifiedProduct>;
|
|
7
7
|
}
|
|
8
|
+
export interface CurrentProduct {
|
|
9
|
+
/** The catalog product id — must match the id in the Dialog product feed. */
|
|
10
|
+
id: string;
|
|
11
|
+
variantId?: string;
|
|
12
|
+
}
|
|
8
13
|
export interface DialogConstructor {
|
|
9
14
|
apiKey: string;
|
|
10
15
|
locale: string;
|
|
@@ -40,5 +45,15 @@ export interface DialogConstructor {
|
|
|
40
45
|
* Product links and recommendation browsing are unaffected.
|
|
41
46
|
*/
|
|
42
47
|
disableAddToCart?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* The product of the page the SDK is instantiated on, when it is a product
|
|
50
|
+
* page. The assistant uses it as the conversation's product context for
|
|
51
|
+
* entry points that carry no product of their own (floating bookmark,
|
|
52
|
+
* resume surface, free-text questions), so answers stay grounded on the
|
|
53
|
+
* product under the visitor's eyes after PDP-to-PDP navigation. On
|
|
54
|
+
* single-page storefronts, update it on navigation with
|
|
55
|
+
* `setCurrentProduct()` / `clearCurrentProduct()`.
|
|
56
|
+
*/
|
|
57
|
+
product?: CurrentProduct;
|
|
43
58
|
}
|
|
44
59
|
//# sourceMappingURL=constructor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constructor.d.ts","sourceRoot":"","sources":["../../src/types/constructor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,UAAU,EAAE,CACV,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,KACf,OAAO,CAAC,iBAAiB,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"constructor.d.ts","sourceRoot":"","sources":["../../src/types/constructor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,UAAU,EAAE,CACV,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,KACf,OAAO,CAAC,iBAAiB,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,6EAA6E;IAC7E,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B"}
|
package/dist/types/search.d.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
/** Logical indices the public search serves; the wire name adds the locale. */
|
|
2
|
+
export declare const SEARCH_INDICES: readonly ["products", "collections", "articles", "pages"];
|
|
3
|
+
export type SearchIndex = (typeof SEARCH_INDICES)[number];
|
|
4
|
+
export interface SearchQuery {
|
|
5
|
+
/** `<index>_<locale>` (e.g. `products_fr`), one shared locale per request; unknown or unserved → 404. */
|
|
6
|
+
indexName: string;
|
|
2
7
|
/** Trimmed server-side; must keep at least two visible characters (code points). */
|
|
3
8
|
query: string;
|
|
4
9
|
/** Zero-indexed results page. Defaults to 0 server-side. */
|
|
5
10
|
page?: number;
|
|
6
11
|
/** Between 1 and 100. Defaults to 20 server-side. */
|
|
7
12
|
hitsPerPage?: number;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
countryCode?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SearchRequest {
|
|
15
|
+
requests: SearchQuery[];
|
|
12
16
|
}
|
|
13
17
|
export interface SearchOptions {
|
|
14
18
|
/** Forwarded to fetch untouched: aborting rejects with the native AbortError. */
|
|
@@ -17,41 +21,39 @@ export interface SearchOptions {
|
|
|
17
21
|
export interface SearchPrice {
|
|
18
22
|
/** Decimal amount as a string, exactly as indexed (e.g. "24.90"). */
|
|
19
23
|
amount: string;
|
|
20
|
-
|
|
24
|
+
/** Absent when the price was indexed without a currency. */
|
|
25
|
+
currencyCode?: string;
|
|
21
26
|
}
|
|
22
27
|
export interface SearchPriceRange {
|
|
23
28
|
min: SearchPrice;
|
|
24
29
|
max: SearchPrice;
|
|
25
30
|
}
|
|
26
31
|
/**
|
|
27
|
-
*
|
|
28
|
-
* best-effort
|
|
32
|
+
* A flat, storefront-ready record: `objectID` plus the record's attributes.
|
|
33
|
+
* Every display field is best-effort — a hit may carry only its id;
|
|
34
|
+
* `priceRange` only comes from the products index.
|
|
29
35
|
*/
|
|
30
|
-
export interface
|
|
31
|
-
|
|
36
|
+
export interface SearchHit {
|
|
37
|
+
objectID: string;
|
|
32
38
|
title?: string;
|
|
33
39
|
url?: string;
|
|
34
|
-
/** Shopify URL handle, lets the storefront build `/products/{handle}` when `url` is absent (DEC-2543). */
|
|
35
40
|
handle?: string;
|
|
36
41
|
imageUrl?: string;
|
|
37
42
|
priceRange?: SearchPriceRange;
|
|
38
|
-
compareAtPriceRange?: SearchPriceRange;
|
|
39
|
-
inStock?: boolean;
|
|
40
43
|
}
|
|
41
|
-
export interface
|
|
42
|
-
|
|
43
|
-
score: number;
|
|
44
|
-
product: SearchProduct;
|
|
45
|
-
}
|
|
46
|
-
export interface SearchResponse {
|
|
47
|
-
/** Engine-generated id for search attribution analytics. */
|
|
48
|
-
queryId: string;
|
|
44
|
+
export interface SearchResult {
|
|
45
|
+
index: string;
|
|
49
46
|
hits: SearchHit[];
|
|
50
47
|
nbHits: number;
|
|
51
48
|
page: number;
|
|
52
49
|
nbPages: number;
|
|
53
50
|
hitsPerPage: number;
|
|
54
|
-
|
|
51
|
+
processingTimeMS: number;
|
|
55
52
|
query: string;
|
|
53
|
+
queryID: string;
|
|
54
|
+
}
|
|
55
|
+
/** One entry per request entry, in request order. */
|
|
56
|
+
export interface SearchResponse {
|
|
57
|
+
results: SearchResult[];
|
|
56
58
|
}
|
|
57
59
|
//# sourceMappingURL=search.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/types/search.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/types/search.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,eAAO,MAAM,cAAc,2DAKjB,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1D,MAAM,WAAW,WAAW;IAC1B,yGAAyG;IACzG,SAAS,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,KAAK,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,WAAW,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,iFAAiF;IACjF,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,WAAW,CAAC;IACjB,GAAG,EAAE,WAAW,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qDAAqD;AACrD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB"}
|
package/dist/types/search.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/** Logical indices the public search serves; the wire name adds the locale. */
|
|
2
|
+
export const SEARCH_INDICES = [
|
|
3
|
+
"products",
|
|
4
|
+
"collections",
|
|
5
|
+
"articles",
|
|
6
|
+
"pages",
|
|
7
|
+
];
|
|
@@ -4,6 +4,8 @@ export declare const SEARCH_TYPES: readonly ["lexical"];
|
|
|
4
4
|
export type SearchType = (typeof SEARCH_TYPES)[number];
|
|
5
5
|
export interface SearchAnalyticsEnvelope {
|
|
6
6
|
query_id: string;
|
|
7
|
+
/** Public index name the result came from (e.g. `products_fr`) — the Algolia Insights tuple's index. */
|
|
8
|
+
index: string;
|
|
7
9
|
/** Where results are displayed — not the integration technology. */
|
|
8
10
|
surface: SearchSurface;
|
|
9
11
|
/** Retrieval mode that produced the results. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"searchAnalytics.d.ts","sourceRoot":"","sources":["../../src/types/searchAnalytics.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,eAAe,uEAKlB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,eAAO,MAAM,YAAY,sBAAuB,CAAC;AAEjD,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"searchAnalytics.d.ts","sourceRoot":"","sources":["../../src/types/searchAnalytics.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,eAAe,uEAKlB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,eAAO,MAAM,YAAY,sBAAuB,CAAC;AAEjD,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,wGAAwG;IACxG,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,OAAO,EAAE,aAAa,CAAC;IACvB,gDAAgD;IAChD,WAAW,EAAE,UAAU,CAAC;IACxB,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,YAAY,EAAE,MAAM,CAAC;CACtB;AAID,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAMD,MAAM,WAAW,uBAAwB,SAAQ,uBAAuB;IACtE,KAAK,EAAE,gBAAgB,EAAE,CAAC;CAC3B;AAGD,MAAM,WAAW,wBAAyB,SAAQ,uBAAuB;IACvE,KAAK,EAAE,CAAC,gBAAgB,CAAC,CAAC;CAC3B;AAKD,MAAM,WAAW,6BAA8B,SAAQ,uBAAuB;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,8BACf,SAAQ,wBAAwB;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SearchHit, SearchOptions, SearchRequest, SearchResponse } from "./search";
|
|
1
|
+
import { SearchHit, SearchIndex, SearchOptions, SearchRequest, SearchResponse, SearchResult } from "./search";
|
|
2
2
|
import { SearchSurface, SelectSearchResultParams, ViewSearchResultsParams } from "./searchAnalytics";
|
|
3
3
|
export declare const SearchStatus: {
|
|
4
4
|
readonly IDLE: "idle";
|
|
@@ -9,14 +9,22 @@ export declare const SearchStatus: {
|
|
|
9
9
|
readonly ERROR: "error";
|
|
10
10
|
};
|
|
11
11
|
export type SearchStatus = (typeof SearchStatus)[keyof typeof SearchStatus];
|
|
12
|
+
export interface SearchSection {
|
|
13
|
+
index: Exclude<SearchIndex, "products">;
|
|
14
|
+
hitsPerPage?: number;
|
|
15
|
+
}
|
|
12
16
|
export interface SearchControllerState {
|
|
13
17
|
status: SearchStatus;
|
|
14
18
|
/** Last committed (run) query — not the text currently being typed. */
|
|
15
19
|
query: string;
|
|
16
20
|
/** Zero-indexed page of the last run. */
|
|
17
21
|
page: number;
|
|
18
|
-
/**
|
|
19
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Products entry of the last landed response; kept while the next page
|
|
24
|
+
* loads, cleared on error. Hits are flat records (`objectID`, `title`, …).
|
|
25
|
+
*/
|
|
26
|
+
response?: SearchResult;
|
|
27
|
+
sections?: Partial<Record<SearchIndex, SearchResult>>;
|
|
20
28
|
error?: unknown;
|
|
21
29
|
}
|
|
22
30
|
export type SearchFunction = (request: SearchRequest, options?: SearchOptions) => Promise<SearchResponse>;
|
|
@@ -44,8 +52,8 @@ export interface SearchControllerOptions {
|
|
|
44
52
|
navigate?: (url: string, hit: SearchHit) => void;
|
|
45
53
|
debounceMs?: number;
|
|
46
54
|
hitsPerPage?: number;
|
|
47
|
-
locale
|
|
48
|
-
|
|
55
|
+
locale: string;
|
|
56
|
+
sections?: readonly SearchSection[] | (() => readonly SearchSection[]);
|
|
49
57
|
}
|
|
50
58
|
/**
|
|
51
59
|
* Framework-agnostic stateful search behavior around the stateless
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"searchController.d.ts","sourceRoot":"","sources":["../../src/types/searchController.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,aAAa,EACb,aAAa,EACb,cAAc,
|
|
1
|
+
{"version":3,"file":"searchController.d.ts","sourceRoot":"","sources":["../../src/types/searchController.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,WAAW,EACX,aAAa,EACb,aAAa,EACb,cAAc,EACd,YAAY,EACb,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,aAAa,EACb,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,mBAAmB,CAAC;AAE3B,eAAO,MAAM,YAAY;;;;IAIvB,yGAAyG;;;CAGjG,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;AAE5E,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,OAAO,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,YAAY,CAAC;IACrB,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC;IACtD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,MAAM,cAAc,GAAG,CAC3B,OAAO,EAAE,aAAa,EACtB,OAAO,CAAC,EAAE,aAAa,KACpB,OAAO,CAAC,cAAc,CAAC,CAAC;AAE7B;;;;;GAKG;AACH,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,aAAa,CAAC;IACvB,sBAAsB,EAAE,CAAC,MAAM,EAAE,uBAAuB,KAAK,IAAI,CAAC;IAClE,uBAAuB,EAAE,CAAC,MAAM,EAAE,wBAAwB,KAAK,IAAI,CAAC;CACrE;AAED,MAAM,WAAW,uBAAuB;IACtC,0CAA0C;IAC1C,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,EAAE,yBAAyB,CAAC;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,KAAK,IAAI,CAAC;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,CAAC,MAAM,SAAS,aAAa,EAAE,CAAC,CAAC;CACxE;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,2EAA2E;IAC3E,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kEAAkE;IAClE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,oEAAoE;IACpE,KAAK,IAAI,IAAI,CAAC;IACd;;;OAGG;IACH,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrD;;;;;;;;;;;;;;;OAeG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC;IACvE,mEAAmE;IACnE,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACxE,QAAQ,IAAI,qBAAqB,CAAC;IAClC,kEAAkE;IAClE,OAAO,IAAI,IAAI,CAAC;CACjB"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SearchResult } from "../types/search";
|
|
2
2
|
import { SearchControllerAnalytics } from "../types/searchController";
|
|
3
3
|
/**
|
|
4
4
|
* Internal DEC-2448 wiring of the search controller: envelope computation,
|
|
5
5
|
* viewport impressions and selection attribution. Not part of the public API.
|
|
6
6
|
*/
|
|
7
7
|
export interface ControllerAnalyticsBinding {
|
|
8
|
-
onResponse(
|
|
9
|
-
observeResult(element: Element,
|
|
10
|
-
select(
|
|
8
|
+
onResponse(result: SearchResult): void;
|
|
9
|
+
observeResult(element: Element, result: SearchResult, index: number): void;
|
|
10
|
+
select(result: SearchResult, index: number): void;
|
|
11
11
|
dispose(): void;
|
|
12
12
|
}
|
|
13
13
|
export declare function createControllerAnalytics(analytics: SearchControllerAnalytics): ControllerAnalyticsBinding;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"searchControllerAnalytics.d.ts","sourceRoot":"","sources":["../../src/utils/searchControllerAnalytics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"searchControllerAnalytics.d.ts","sourceRoot":"","sources":["../../src/utils/searchControllerAnalytics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAK/C,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAMtE;;;GAGG;AACH,MAAM,WAAW,0BAA0B;IACzC,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IACvC,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3E,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAClD,OAAO,IAAI,IAAI,CAAC;CACjB;AAQD,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,yBAAyB,GACnC,0BAA0B,CAkD5B"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createSearchImpressionTracker, } from "./searchImpressions";
|
|
2
|
-
const resultItem = (
|
|
3
|
-
product_id:
|
|
2
|
+
const resultItem = (result, index) => ({
|
|
3
|
+
product_id: result.hits[index].objectID,
|
|
4
4
|
// 1-based and absolute across pages (page 2, first item, 20/page → 21).
|
|
5
|
-
position:
|
|
5
|
+
position: result.page * result.hitsPerPage + index + 1,
|
|
6
6
|
});
|
|
7
7
|
export function createControllerAnalytics(analytics) {
|
|
8
8
|
let impressions;
|
|
@@ -16,35 +16,33 @@ export function createControllerAnalytics(analytics) {
|
|
|
16
16
|
return impressions;
|
|
17
17
|
};
|
|
18
18
|
return {
|
|
19
|
-
onResponse(
|
|
19
|
+
onResponse(result) {
|
|
20
20
|
envelope = {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// across pagination and rotates with the query.
|
|
24
|
-
query_id: response.queryId,
|
|
21
|
+
query_id: result.queryID,
|
|
22
|
+
index: result.index,
|
|
25
23
|
surface: analytics.surface,
|
|
26
24
|
// The storefront search API is lexical-only today.
|
|
27
25
|
search_type: "lexical",
|
|
28
26
|
// Analytics pages are 1-based; the wire response is 0-based.
|
|
29
|
-
page:
|
|
30
|
-
total_hits:
|
|
31
|
-
query_length: [...
|
|
27
|
+
page: result.page + 1,
|
|
28
|
+
total_hits: result.nbHits,
|
|
29
|
+
query_length: [...result.query].length,
|
|
32
30
|
};
|
|
33
31
|
tracker().setContext(envelope);
|
|
34
|
-
if (
|
|
32
|
+
if (result.nbHits === 0) {
|
|
35
33
|
// A rendered no-results state is the view event with zero items; it
|
|
36
34
|
// shows above the fold, no viewport gating needed.
|
|
37
35
|
analytics.trackViewSearchResults({ ...envelope, items: [] });
|
|
38
36
|
}
|
|
39
37
|
},
|
|
40
|
-
observeResult(element,
|
|
41
|
-
tracker().observe(element, resultItem(
|
|
38
|
+
observeResult(element, result, index) {
|
|
39
|
+
tracker().observe(element, resultItem(result, index));
|
|
42
40
|
},
|
|
43
|
-
select(
|
|
41
|
+
select(result, index) {
|
|
44
42
|
if (envelope === undefined) {
|
|
45
43
|
return;
|
|
46
44
|
}
|
|
47
|
-
const item = resultItem(
|
|
45
|
+
const item = resultItem(result, index);
|
|
48
46
|
// The click forces the item's impression so CTR by position stays ≤100%.
|
|
49
47
|
tracker().forceImpression(item);
|
|
50
48
|
analytics.trackSelectSearchResult({ ...envelope, items: [item] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"searchImpressions.d.ts","sourceRoot":"","sources":["../../src/utils/searchImpressions.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,gBAAgB,EAChB,uBAAuB,EACxB,MAAM,0BAA0B,CAAC;AAWlC,MAAM,WAAW,8BAA8B;IAC7C,gFAAgF;IAChF,IAAI,EAAE,CAAC,MAAM,EAAE,uBAAuB,KAAK,IAAI,CAAC;IAChD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,uBAAuB;IACtC,uFAAuF;IACvF,UAAU,CAAC,QAAQ,EAAE,uBAAuB,GAAG,IAAI,CAAC;IACpD,gEAAgE;IAChE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACxD,kFAAkF;IAClF,eAAe,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC9C,KAAK,IAAI,IAAI,CAAC;IACd,UAAU,IAAI,IAAI,CAAC;CACpB;AAED,wBAAgB,6BAA6B,CAAC,EAC5C,IAAI,EACJ,mBAAkD,EAClD,OAA0B,EAC1B,WAAmC,EACnC,gBAA8C,GAC/C,EAAE,8BAA8B,GAAG,uBAAuB,
|
|
1
|
+
{"version":3,"file":"searchImpressions.d.ts","sourceRoot":"","sources":["../../src/utils/searchImpressions.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,gBAAgB,EAChB,uBAAuB,EACxB,MAAM,0BAA0B,CAAC;AAWlC,MAAM,WAAW,8BAA8B;IAC7C,gFAAgF;IAChF,IAAI,EAAE,CAAC,MAAM,EAAE,uBAAuB,KAAK,IAAI,CAAC;IAChD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,uBAAuB;IACtC,uFAAuF;IACvF,UAAU,CAAC,QAAQ,EAAE,uBAAuB,GAAG,IAAI,CAAC;IACpD,gEAAgE;IAChE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACxD,kFAAkF;IAClF,eAAe,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC9C,KAAK,IAAI,IAAI,CAAC;IACd,UAAU,IAAI,IAAI,CAAC;CACpB;AAED,wBAAgB,6BAA6B,CAAC,EAC5C,IAAI,EACJ,mBAAkD,EAClD,OAA0B,EAC1B,WAAmC,EACnC,gBAA8C,GAC/C,EAAE,8BAA8B,GAAG,uBAAuB,CA+H1D"}
|
|
@@ -98,12 +98,7 @@ export function createSearchImpressionTracker({ emit, visibilityThreshold = DEFA
|
|
|
98
98
|
setContext(nextEnvelope) {
|
|
99
99
|
// Pending impressions belong to the envelope they qualified under.
|
|
100
100
|
flush();
|
|
101
|
-
|
|
102
|
-
// query_id): dropping older keys bounds the set across search-as-you-type.
|
|
103
|
-
if (envelope !== undefined &&
|
|
104
|
-
envelope.query_id !== nextEnvelope.query_id) {
|
|
105
|
-
seen.clear();
|
|
106
|
-
}
|
|
101
|
+
seen.clear();
|
|
107
102
|
envelope = nextEnvelope;
|
|
108
103
|
stopObserving();
|
|
109
104
|
},
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { SearchIndex, SearchQuery, SearchResponse, SearchResult } from "../types/search";
|
|
2
|
+
import { SearchSection } from "../types/searchController";
|
|
3
|
+
export declare const buildSectionQueries: (sections: readonly SearchSection[], query: string, locale: string, hitsPerPage: number) => SearchQuery[];
|
|
4
|
+
export declare const pickSectionResults: (response: SearchResponse, sections: readonly SearchSection[], locale: string) => Partial<Record<SearchIndex, SearchResult>> | undefined;
|
|
5
|
+
//# sourceMappingURL=searchSections.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"searchSections.d.ts","sourceRoot":"","sources":["../../src/utils/searchSections.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EACX,WAAW,EACX,cAAc,EACd,YAAY,EACb,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE1D,eAAO,MAAM,mBAAmB,GAC9B,UAAU,SAAS,aAAa,EAAE,EAClC,OAAO,MAAM,EACb,QAAQ,MAAM,EACd,aAAa,MAAM,KAClB,WAAW,EAMT,CAAC;AAEN,eAAO,MAAM,kBAAkB,GAC7B,UAAU,cAAc,EACxB,UAAU,SAAS,aAAa,EAAE,EAClC,QAAQ,MAAM,KACb,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,GAAG,SAgB/C,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { searchIndexName } from "../services/search";
|
|
2
|
+
export const buildSectionQueries = (sections, query, locale, hitsPerPage) => sections.map((section) => ({
|
|
3
|
+
indexName: searchIndexName(section.index, locale),
|
|
4
|
+
query,
|
|
5
|
+
page: 0,
|
|
6
|
+
hitsPerPage: section.hitsPerPage ?? hitsPerPage,
|
|
7
|
+
}));
|
|
8
|
+
export const pickSectionResults = (response, sections, locale) => {
|
|
9
|
+
if (sections.length === 0) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
const picked = {};
|
|
13
|
+
for (const section of sections) {
|
|
14
|
+
const sectionIndexName = searchIndexName(section.index, locale);
|
|
15
|
+
const entry = response.results.find((candidate) => candidate.index === sectionIndexName);
|
|
16
|
+
if (entry !== undefined) {
|
|
17
|
+
picked[section.index] = entry;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return picked;
|
|
21
|
+
};
|