@salesforcedevs/dx-components 1.31.0 → 1.32.0-alpha.10
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/LICENSE +12 -0
- package/lwc.config.json +1 -0
- package/package.json +3 -2
- package/src/modules/dx/dropdownOption/dropdownOption.css +13 -8
- package/src/modules/dx/searchResults/searchResults.css +107 -6
- package/src/modules/dx/searchResults/searchResults.html +87 -91
- package/src/modules/dx/searchResults/searchResults.ts +174 -477
- package/src/modules/dx/sidebar/sidebar.css +1 -0
- package/src/modules/dx/sidebar/sidebar.html +1 -5
- package/src/modules/dx/sidebar/sidebar.ts +44 -29
- package/src/modules/dx/sidebarSearch/sidebarSearch.ts +159 -343
- package/src/modules/dx/sidebarSearchResult/sidebarSearchResult.css +57 -23
- package/src/modules/dx/sidebarSearchResult/sidebarSearchResult.ts +12 -4
- package/src/modules/dxUtils/data360Search/data360Search.ts +168 -0
|
@@ -1,526 +1,223 @@
|
|
|
1
1
|
import { LightningElement, api, track } from "lwc";
|
|
2
|
-
import
|
|
3
|
-
import { DateTime } from "luxon";
|
|
2
|
+
import debounce from "debounce";
|
|
4
3
|
import { track as trackGTM } from "dxUtils/analytics";
|
|
5
4
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
SearchEndpoint: typeof CoveoSDK.SearchEndpoint;
|
|
23
|
-
TemplateCache: typeof CoveoSDK.TemplateCache;
|
|
24
|
-
UnderscoreTemplate: typeof CoveoSDK.UnderscoreTemplate;
|
|
25
|
-
init: typeof CoveoSDK.init;
|
|
26
|
-
IQuerySuccessEventArgs: CoveoSDK.IQuerySuccessEventArgs;
|
|
27
|
-
TemplateHelpers: any;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
declare const Coveo: CoveoSearch;
|
|
31
|
-
|
|
32
|
-
function getPaginationState(event: CoveoSDK.IQuerySuccessEventArgs): {
|
|
33
|
-
numberOfPages: number;
|
|
34
|
-
currentPage: number;
|
|
35
|
-
} {
|
|
36
|
-
const pageSize = event.query.numberOfResults!;
|
|
37
|
-
const totalResults = event.results.totalCount!;
|
|
38
|
-
const numberOfPages = Math.ceil(totalResults / pageSize);
|
|
39
|
-
|
|
40
|
-
const currentPage = event.query.firstResult! / pageSize + 1;
|
|
41
|
-
|
|
42
|
-
return { numberOfPages, currentPage };
|
|
5
|
+
type Data360SearchResultItem,
|
|
6
|
+
fetchSearch,
|
|
7
|
+
getQueryFromUrl
|
|
8
|
+
} from "dxUtils/data360Search";
|
|
9
|
+
|
|
10
|
+
const SEARCH_DEBOUNCE_DELAY = 400;
|
|
11
|
+
|
|
12
|
+
/** Normalized result for template (with href, uniqueId, openInNewTab) */
|
|
13
|
+
interface SearchResultDisplay {
|
|
14
|
+
title: string;
|
|
15
|
+
href: string;
|
|
16
|
+
matchedText: string;
|
|
17
|
+
uniqueId: string;
|
|
18
|
+
resultIndex: number;
|
|
19
|
+
openInNewTab: string | undefined;
|
|
20
|
+
rel: string | undefined;
|
|
43
21
|
}
|
|
44
22
|
|
|
45
|
-
const isInternalDomain = (domain: string) =>
|
|
46
|
-
domain === "developer.salesforce.com" ||
|
|
47
|
-
domain === "developer-website-s.herokuapp.com";
|
|
48
|
-
|
|
49
|
-
const isTrailheadDomain = (domain: string) =>
|
|
50
|
-
domain === "trailhead.salesforce.com" ||
|
|
51
|
-
domain === "dev.trailhead.salesforce.com";
|
|
52
|
-
|
|
53
|
-
const buildTemplateHelperBadge = (value: keyof typeof CONTENT_TYPE_LABELS) => {
|
|
54
|
-
const style = getContentTypeColorVariables(value);
|
|
55
|
-
const label = CONTENT_TYPE_LABELS[value];
|
|
56
|
-
const { iconSprite, iconSymbol } = CONTENT_TYPE_ICONS[value];
|
|
57
|
-
return `
|
|
58
|
-
<div style="${style}" class="dx-badge">
|
|
59
|
-
<svg
|
|
60
|
-
aria-hidden="true"
|
|
61
|
-
style="display: inline; margin-bottom: 1px; fill: var(--color); height: var(--dx-g-icon-size-xs); width: var(--dx-g-icon-size-xs);"
|
|
62
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
63
|
-
part="svg"
|
|
64
|
-
>
|
|
65
|
-
<use xlink:href="/assets/icons/${iconSprite}-sprite/svg/symbols.svg#${iconSymbol}"></use>
|
|
66
|
-
</svg>
|
|
67
|
-
<span>${label}</span>
|
|
68
|
-
</div>
|
|
69
|
-
`;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const processParts = (parts: string[], internalFlag = false) => {
|
|
73
|
-
// filter /docs/ breadcrumb item from internal domains
|
|
74
|
-
const filterFn = internalFlag
|
|
75
|
-
? (part: string) => part !== "docs"
|
|
76
|
-
: (part: string) => part;
|
|
77
|
-
|
|
78
|
-
return parts.filter(filterFn).map((part) => {
|
|
79
|
-
// Remove special characters & .htm/.xml extension
|
|
80
|
-
part = part
|
|
81
|
-
.replace(/_/g, "")
|
|
82
|
-
.replace(/-/g, "")
|
|
83
|
-
.replace(/.html*/g, "")
|
|
84
|
-
.replace(/.xml/g, "")
|
|
85
|
-
.replace(/b2c/g, "B2C");
|
|
86
|
-
|
|
87
|
-
// Capitalize first letter of each word
|
|
88
|
-
part = part.replace(/\w\S*/g, (w) => {
|
|
89
|
-
return w.replace(/^\w/, (c) => c.toUpperCase());
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
return `<span class="breadcrumb-item">${decodeURI(part)}</span>`;
|
|
93
|
-
});
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const buildTemplateHelperUriBreadcrumbs = (value: string) => {
|
|
97
|
-
const url = new URL(value);
|
|
98
|
-
|
|
99
|
-
// exclude youtube links from breadcrumbs
|
|
100
|
-
const hostnamePattern = /^((www\.)?(youtube\.com|youtu\.be))$/;
|
|
101
|
-
|
|
102
|
-
// we don't want to show atlas docs because the url structure is mad ugly
|
|
103
|
-
if (hostnamePattern.test(url.hostname) || url.pathname.includes("atlas.")) {
|
|
104
|
-
return "";
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
let parts = url.pathname.split("/").filter((part) => part !== "");
|
|
108
|
-
|
|
109
|
-
// Remove language prefix from trailhead URLs
|
|
110
|
-
if (isTrailheadDomain(url.hostname)) {
|
|
111
|
-
parts = parts
|
|
112
|
-
.slice(1)
|
|
113
|
-
.filter((part) => part !== "content" && part !== "learn");
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const breadcrumbs = processParts(parts, isInternalDomain(url.hostname));
|
|
117
|
-
|
|
118
|
-
if (!isInternalDomain(url.hostname)) {
|
|
119
|
-
// Remove the first breadcrumb item if it's an internal domain (i.e drop developer.salesforce.com from developer.salesforce.com / B2C Commerce / Open Commerce API / Filtering)
|
|
120
|
-
breadcrumbs.unshift(
|
|
121
|
-
`<span class="breadcrumb-item">${url.hostname}</span>`
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
return `
|
|
125
|
-
<span class="breadcrumb">
|
|
126
|
-
${breadcrumbs.join(" / ")}
|
|
127
|
-
</span>
|
|
128
|
-
`;
|
|
129
|
-
} else if (breadcrumbs.length === 1) {
|
|
130
|
-
// Hide breadcrumbs if there is only one breadcrumb item
|
|
131
|
-
return "";
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// remove the last breadcrumb item (the search result title makes it redundant)
|
|
135
|
-
breadcrumbs.pop();
|
|
136
|
-
|
|
137
|
-
return `<span class="breadcrumb">/ ${breadcrumbs.join(" / ")} /</span>`;
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const buildTemplateHelperCommunityBreadcrumbs = () => {
|
|
141
|
-
return `<span class="breadcrumb"><span class="breadcrumb-item">trailhead.salesforce.com</span> / <span class="breadcrumb-item">trailblazer-community</span> / <span class="breadcrumb-item">feed</span> / </span>`;
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
const buildTemplateHelperBreadcrumbs = (value: string) => {
|
|
145
|
-
const parts = value.split("/").filter((part) => part !== "");
|
|
146
|
-
|
|
147
|
-
// Don't show breadcrumbs if there's only one part
|
|
148
|
-
if (parts.length === 1) {
|
|
149
|
-
return "";
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const breadcrumbs = processParts(parts);
|
|
153
|
-
|
|
154
|
-
// remove last breadcrumb item
|
|
155
|
-
breadcrumbs.pop();
|
|
156
|
-
|
|
157
|
-
return `
|
|
158
|
-
<span class="breadcrumb">/ ${breadcrumbs.join(" / ")} /</span>
|
|
159
|
-
`;
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
const buildTemplateHelperMetaBreadcrumbs = (value: string) => {
|
|
163
|
-
const parts = value.split("/").filter((part) => part !== "");
|
|
164
|
-
|
|
165
|
-
// Don't show breadcrumbs if there's only one part
|
|
166
|
-
if (parts.length === 1) {
|
|
167
|
-
return "";
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const breadcrumbs = processParts(parts);
|
|
171
|
-
|
|
172
|
-
return `
|
|
173
|
-
<span class="breadcrumb">/ ${breadcrumbs.join(" / ")}</span>
|
|
174
|
-
`;
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
const buildTemplateHelperPostedDate = (value: string) => {
|
|
178
|
-
const time = DateTime.fromMillis(Number(value)).toLocaleString(
|
|
179
|
-
DateTime.DATE_MED
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
return `Posted on: ${time} - `;
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
const buildTemplateHelperReplies = (value: string) => {
|
|
186
|
-
const number = Number(value);
|
|
187
|
-
|
|
188
|
-
return `<strong>${value} ${number > 1 ? "Replies" : "Reply"}</strong>`;
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
const buildTemplateHelperLikes = (value: string) => {
|
|
192
|
-
const number = Number(value);
|
|
193
|
-
|
|
194
|
-
return `<strong>${value} ${number > 1 ? "Likes" : "Like"}</strong>`;
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
// @ts-ignore Dark Magic (TM) we are overriding the 'title' field with a custom getter. We should really stop doing this.
|
|
198
23
|
export default class SearchResults extends LightningElement {
|
|
199
|
-
@
|
|
200
|
-
@
|
|
201
|
-
@
|
|
202
|
-
|
|
203
|
-
private
|
|
204
|
-
private isInitialized = false;
|
|
205
|
-
|
|
206
|
-
@track _query: string = "";
|
|
24
|
+
@track private _query = "";
|
|
25
|
+
@track private results: SearchResultDisplay[] = [];
|
|
26
|
+
@track private isLoading = false;
|
|
27
|
+
@track private hasSearched = false;
|
|
28
|
+
@track private didTrackThisSearch = false;
|
|
207
29
|
|
|
208
30
|
@api
|
|
209
31
|
get searchQuery() {
|
|
210
32
|
return this._query;
|
|
211
33
|
}
|
|
212
34
|
set searchQuery(q: string) {
|
|
213
|
-
|
|
214
|
-
if (
|
|
215
|
-
this.
|
|
35
|
+
const next = q ?? "";
|
|
36
|
+
if (next !== this._query) {
|
|
37
|
+
this._query = next;
|
|
38
|
+
this.syncUrlToQuery();
|
|
39
|
+
if (this._query.trim()) {
|
|
40
|
+
this.runSearch();
|
|
41
|
+
} else {
|
|
42
|
+
this.results = [];
|
|
43
|
+
this.hasSearched = false;
|
|
44
|
+
}
|
|
216
45
|
}
|
|
217
46
|
}
|
|
218
47
|
|
|
219
|
-
private
|
|
48
|
+
private get query(): string {
|
|
49
|
+
return this._query;
|
|
50
|
+
}
|
|
220
51
|
|
|
221
|
-
private get
|
|
222
|
-
return this.
|
|
52
|
+
private get totalResults(): number {
|
|
53
|
+
return this.results?.length ?? 0;
|
|
223
54
|
}
|
|
224
55
|
|
|
225
|
-
private
|
|
226
|
-
|
|
56
|
+
private get resultCountLabel(): string {
|
|
57
|
+
return this.totalResults.toLocaleString();
|
|
58
|
+
}
|
|
227
59
|
|
|
228
60
|
private get hasQuery(): boolean {
|
|
229
61
|
return this.query !== "";
|
|
230
62
|
}
|
|
231
63
|
|
|
232
|
-
private get
|
|
233
|
-
return
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
private updateSearchQuery() {
|
|
237
|
-
Coveo.state(this.root!, "q", this._query);
|
|
64
|
+
private get hasResults(): boolean {
|
|
65
|
+
return this.results.length > 0;
|
|
238
66
|
}
|
|
239
67
|
|
|
240
|
-
private
|
|
241
|
-
|
|
242
|
-
this.root!.querySelector(".CoveoBreadcrumb") as HTMLElement
|
|
243
|
-
) as any;
|
|
244
|
-
BreadcrumbManager.clearBreadcrumbs();
|
|
68
|
+
private get showNoResults(): boolean {
|
|
69
|
+
return this.hasSearched && !this.isLoading && !this.hasResults;
|
|
245
70
|
}
|
|
246
71
|
|
|
247
|
-
private
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
72
|
+
private syncUrlToQuery() {
|
|
73
|
+
const url = new URL(window.location.href);
|
|
74
|
+
if (this._query.trim()) {
|
|
75
|
+
url.searchParams.set("q", this._query.trim());
|
|
76
|
+
} else {
|
|
77
|
+
url.searchParams.delete("q");
|
|
78
|
+
}
|
|
79
|
+
const newHref = url.pathname + url.search + url.hash;
|
|
80
|
+
if (
|
|
81
|
+
window.location.pathname +
|
|
82
|
+
window.location.search +
|
|
83
|
+
window.location.hash !==
|
|
84
|
+
newHref
|
|
85
|
+
) {
|
|
86
|
+
window.history.replaceState(null, "", newHref);
|
|
87
|
+
}
|
|
260
88
|
}
|
|
261
89
|
|
|
262
|
-
private
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
// Accomodate for the global nav using 'keywords' param instead of 'q'
|
|
278
|
-
if (keywordsParam) {
|
|
279
|
-
this._query = searchParams.get("keywords")!;
|
|
280
|
-
searchParams.delete("keywords");
|
|
281
|
-
window.history.replaceState(null, "", url.href);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (this._query !== "") {
|
|
285
|
-
Coveo.state(this.root!, "q", this.searchQuery);
|
|
286
|
-
}
|
|
287
|
-
if (Coveo.state(this.root!, "q") === "") {
|
|
288
|
-
Coveo.state(this.root!, "sort", "date descending");
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
this.isInitialized = true;
|
|
292
|
-
}
|
|
293
|
-
);
|
|
294
|
-
|
|
295
|
-
Coveo.$$(root).on(Coveo.QueryEvents.querySuccess, (event: any) => {
|
|
296
|
-
const { currentPage, numberOfPages } = getPaginationState(
|
|
297
|
-
event.detail
|
|
298
|
-
);
|
|
299
|
-
this.currentPage = currentPage;
|
|
300
|
-
this.totalPages = numberOfPages;
|
|
301
|
-
this.totalResults = event.detail.results.totalCount;
|
|
302
|
-
|
|
303
|
-
this.query = event.detail.query.q ?? "";
|
|
304
|
-
this.hasFilters = event.detail.query?.facets?.some((f: any) => {
|
|
305
|
-
return f.currentValues.some((cv: any) => {
|
|
306
|
-
return cv.state === "selected";
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
// Note that this logic means that if someone clicks a search result before
|
|
310
|
-
// onetrust has loaded, and thus navigates away from the page, we will lose the tracking
|
|
311
|
-
if (document.readyState === "complete") {
|
|
312
|
-
this.trackSearchResults(event, this.query, this.totalResults);
|
|
313
|
-
} else {
|
|
314
|
-
const query = this.query;
|
|
315
|
-
const totalResults = this.totalResults;
|
|
316
|
-
window.addEventListener("load", () => {
|
|
317
|
-
this.trackSearchResults(event, query, totalResults);
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
Coveo.$$(root).on(Coveo.QueryEvents.deferredQuerySuccess, async () => {
|
|
323
|
-
// wait specified time to ensure breadcrumbs are rendered before processing them
|
|
324
|
-
await pollUntil(
|
|
325
|
-
() => {
|
|
326
|
-
const coveoResults =
|
|
327
|
-
this.root!.querySelector(".CoveoResult");
|
|
328
|
-
|
|
329
|
-
return Boolean(coveoResults);
|
|
330
|
-
},
|
|
331
|
-
20,
|
|
332
|
-
1000
|
|
90
|
+
private async doFetch() {
|
|
91
|
+
const query = this._query.trim();
|
|
92
|
+
if (!query) {
|
|
93
|
+
this.results = [];
|
|
94
|
+
this.isLoading = false;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
this.isLoading = true;
|
|
98
|
+
this.hasSearched = true;
|
|
99
|
+
this.didTrackThisSearch = false;
|
|
100
|
+
try {
|
|
101
|
+
const raw = await fetchSearch(query);
|
|
102
|
+
this.results = raw.map(
|
|
103
|
+
(item: Data360SearchResultItem, index: number) =>
|
|
104
|
+
this.normalizeResult(item, index)
|
|
333
105
|
);
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
106
|
+
this.trackSearchResultsOnce(query, this.results.length);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
console.error("Data 360 Search request failed", err);
|
|
109
|
+
this.results = [];
|
|
110
|
+
} finally {
|
|
111
|
+
this.isLoading = false;
|
|
112
|
+
}
|
|
339
113
|
}
|
|
340
114
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
115
|
+
private runSearch = debounce(() => {
|
|
116
|
+
this.doFetch();
|
|
117
|
+
}, SEARCH_DEBOUNCE_DELAY);
|
|
118
|
+
|
|
119
|
+
private normalizeResult(
|
|
120
|
+
item: Data360SearchResultItem,
|
|
121
|
+
index: number
|
|
122
|
+
): SearchResultDisplay {
|
|
123
|
+
const href = item.url ?? "";
|
|
124
|
+
const isExternal =
|
|
125
|
+
href.startsWith("http") &&
|
|
126
|
+
!href.includes("developer.salesforce.com") &&
|
|
127
|
+
!href.includes("developer-website-s.herokuapp.com");
|
|
128
|
+
return {
|
|
129
|
+
title: item.title ?? "",
|
|
130
|
+
href,
|
|
131
|
+
matchedText: item.matchedText ?? "",
|
|
132
|
+
uniqueId: href || `result-${index}`,
|
|
133
|
+
resultIndex: index + 1,
|
|
134
|
+
openInNewTab: isExternal ? "_blank" : undefined,
|
|
135
|
+
rel: isExternal ? "noopener noreferrer" : undefined
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private onSearchResultClick(e: MouseEvent) {
|
|
140
|
+
const anchor = e.currentTarget as HTMLAnchorElement;
|
|
141
|
+
const index = Number(anchor.dataset.index ?? "0");
|
|
142
|
+
const title = anchor.dataset.title ?? "";
|
|
143
|
+
const href = anchor.href ?? "";
|
|
144
|
+
trackGTM(anchor, "custEv_scopedSearchlinkClick", {
|
|
145
|
+
click_text: title,
|
|
146
|
+
click_url: href,
|
|
147
|
+
element_title: title,
|
|
148
|
+
element_type: "link",
|
|
149
|
+
content_category: "documentation",
|
|
150
|
+
search_term: this.query,
|
|
151
|
+
search_result_position: index
|
|
355
152
|
});
|
|
356
|
-
}
|
|
153
|
+
}
|
|
357
154
|
|
|
358
|
-
private
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
) => {
|
|
362
|
-
for (let i = 1; i < breadcrumbItems.length; i++) {
|
|
363
|
-
if (this.isTextWrapping(breadcrumb)) {
|
|
364
|
-
// if the previous element is an ellipsis, make it empty (in order to avoid multiple grouped ellipsis)
|
|
365
|
-
if (breadcrumbItems[i - 1]?.textContent === "...") {
|
|
366
|
-
breadcrumbItems[i].innerHTML = "";
|
|
367
|
-
} else {
|
|
368
|
-
breadcrumbItems[i].textContent = "...";
|
|
369
|
-
}
|
|
370
|
-
} else {
|
|
371
|
-
break; // Exit the loop if the breadcrumb is no longer overflowing
|
|
372
|
-
}
|
|
155
|
+
private trackSearchResultsOnce(term: string, resultCount: number) {
|
|
156
|
+
if (this.didTrackThisSearch) {
|
|
157
|
+
return;
|
|
373
158
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
"
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
// Check if the breadcrumb is overflowing by comparing it's height to the height of the first breadcrumb item
|
|
395
|
-
if (this.isTextWrapping(breadcrumb)) {
|
|
396
|
-
// it is overflowing, so we need to truncate long titles to 30 characters
|
|
397
|
-
this.truncateBreadcrumbText(breadcrumbItems);
|
|
398
|
-
|
|
399
|
-
// Iteratively check if the breadcrumb is still overflowing and replace text with '...' starting from the second breadcrumb item
|
|
400
|
-
this.addBreadcrumbEllipsis(breadcrumbItems, breadcrumb);
|
|
401
|
-
|
|
402
|
-
// After processing all breadcrumb items, if it's still overflowing, hide the breadcrumb element
|
|
403
|
-
if (this.isTextWrapping(breadcrumb)) {
|
|
404
|
-
breadcrumb.style.display = "none";
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
});
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
private restoreBreadcrumbs = (breadcrumbs: HTMLElement[]) => {
|
|
411
|
-
breadcrumbs.forEach((breadcrumb: HTMLElement, index: number) => {
|
|
412
|
-
// eslint-disable-next-line @lwc/lwc/no-inner-html
|
|
413
|
-
breadcrumb.innerHTML = this.originalBreadcrumbs[index];
|
|
414
|
-
});
|
|
415
|
-
};
|
|
416
|
-
|
|
417
|
-
private windowSizeIncreased = () =>
|
|
418
|
-
window.innerWidth > this.initialWindowWidth;
|
|
419
|
-
|
|
420
|
-
private processBreadcrumbs(root: HTMLElement) {
|
|
421
|
-
// Get all breadcrumbs from search results
|
|
422
|
-
const breadcrumbs = Array.from(
|
|
423
|
-
root.querySelectorAll(".breadcrumb")
|
|
424
|
-
) as HTMLElement[];
|
|
425
|
-
|
|
426
|
-
if (this.originalBreadcrumbs.length === 0) {
|
|
427
|
-
this.originalBreadcrumbs = breadcrumbs.map(
|
|
428
|
-
(breadcrumb) => breadcrumb.innerHTML
|
|
159
|
+
this.didTrackThisSearch = true;
|
|
160
|
+
if (document.readyState === "complete") {
|
|
161
|
+
trackGTM(this.template.host, "custEv_search", {
|
|
162
|
+
search_term: term,
|
|
163
|
+
search_category: "",
|
|
164
|
+
search_type: "site search",
|
|
165
|
+
search_result_count: resultCount
|
|
166
|
+
});
|
|
167
|
+
} else {
|
|
168
|
+
window.addEventListener(
|
|
169
|
+
"load",
|
|
170
|
+
() => {
|
|
171
|
+
trackGTM(this.template.host, "custEv_search", {
|
|
172
|
+
search_term: term,
|
|
173
|
+
search_category: "",
|
|
174
|
+
search_type: "site search",
|
|
175
|
+
search_result_count: resultCount
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
{ once: true }
|
|
429
179
|
);
|
|
430
180
|
}
|
|
181
|
+
}
|
|
431
182
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
this.
|
|
183
|
+
private onSearchInputChange(e: CustomEvent) {
|
|
184
|
+
const value = (e.detail as string) ?? "";
|
|
185
|
+
this._query = value;
|
|
186
|
+
this.syncUrlToQuery();
|
|
187
|
+
if (value.trim()) {
|
|
188
|
+
this.runSearch();
|
|
189
|
+
} else {
|
|
190
|
+
this.results = [];
|
|
191
|
+
this.hasSearched = false;
|
|
438
192
|
}
|
|
439
|
-
|
|
440
|
-
this.formatBreadcrumbs(breadcrumbs);
|
|
441
193
|
}
|
|
442
194
|
|
|
443
|
-
private
|
|
444
|
-
this.root = this.template.querySelector(".CoveoSearchInterface")!;
|
|
195
|
+
private hasRunInitialSearch = false;
|
|
445
196
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if (
|
|
449
|
-
|
|
450
|
-
|
|
197
|
+
connectedCallback() {
|
|
198
|
+
const q = getQueryFromUrl();
|
|
199
|
+
if (q) {
|
|
200
|
+
this._query = q;
|
|
201
|
+
this.hasRunInitialSearch = true;
|
|
202
|
+
this.doFetch();
|
|
451
203
|
}
|
|
452
|
-
|
|
453
|
-
Coveo.SearchEndpoint.configureCloudV2Endpoint(
|
|
454
|
-
this.coveoOrganizationId,
|
|
455
|
-
this.coveoPublicAccessToken,
|
|
456
|
-
`https://${this.coveoOrganizationId}.org.coveo.com/rest/search`
|
|
457
|
-
);
|
|
458
|
-
|
|
459
|
-
this.attachListeners(this.root);
|
|
460
|
-
|
|
461
|
-
Coveo.TemplateHelpers.registerTemplateHelper(
|
|
462
|
-
"badge",
|
|
463
|
-
buildTemplateHelperBadge
|
|
464
|
-
);
|
|
465
|
-
|
|
466
|
-
Coveo.TemplateHelpers.registerTemplateHelper(
|
|
467
|
-
"breadcrumbs",
|
|
468
|
-
buildTemplateHelperBreadcrumbs
|
|
469
|
-
);
|
|
470
|
-
|
|
471
|
-
Coveo.TemplateHelpers.registerTemplateHelper(
|
|
472
|
-
"metabreadcrumbs",
|
|
473
|
-
buildTemplateHelperMetaBreadcrumbs
|
|
474
|
-
);
|
|
475
|
-
|
|
476
|
-
Coveo.TemplateHelpers.registerTemplateHelper(
|
|
477
|
-
"uriBreadcrumbs",
|
|
478
|
-
buildTemplateHelperUriBreadcrumbs
|
|
479
|
-
);
|
|
480
|
-
|
|
481
|
-
Coveo.TemplateHelpers.registerTemplateHelper(
|
|
482
|
-
"communityBreadcrumbs",
|
|
483
|
-
buildTemplateHelperCommunityBreadcrumbs
|
|
484
|
-
);
|
|
485
|
-
|
|
486
|
-
Coveo.TemplateHelpers.registerTemplateHelper(
|
|
487
|
-
"postedDate",
|
|
488
|
-
buildTemplateHelperPostedDate
|
|
489
|
-
);
|
|
490
|
-
|
|
491
|
-
Coveo.TemplateHelpers.registerTemplateHelper(
|
|
492
|
-
"replies",
|
|
493
|
-
buildTemplateHelperReplies
|
|
494
|
-
);
|
|
495
|
-
|
|
496
|
-
Coveo.TemplateHelpers.registerTemplateHelper(
|
|
497
|
-
"likes",
|
|
498
|
-
buildTemplateHelperLikes
|
|
499
|
-
);
|
|
500
|
-
|
|
501
|
-
Coveo.init(this.root);
|
|
502
204
|
}
|
|
503
205
|
|
|
504
206
|
renderedCallback() {
|
|
505
|
-
if (
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
207
|
+
// Run search on first render if URL has q and we haven't yet (handles #q=, SPA nav, or late URL)
|
|
208
|
+
if (this.hasRunInitialSearch) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const q = getQueryFromUrl();
|
|
212
|
+
if (!q) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
this.hasRunInitialSearch = true;
|
|
216
|
+
if (this._query !== q) {
|
|
217
|
+
this._query = q;
|
|
218
|
+
}
|
|
219
|
+
if (!this.hasSearched && !this.isLoading) {
|
|
220
|
+
this.doFetch();
|
|
515
221
|
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
private trackSearchResults(event: Event, term: string, resultCount: any) {
|
|
519
|
-
trackGTM(event.target!, "custEv_search", {
|
|
520
|
-
search_term: term,
|
|
521
|
-
search_category: "",
|
|
522
|
-
search_type: "site search",
|
|
523
|
-
search_result_count: resultCount
|
|
524
|
-
});
|
|
525
222
|
}
|
|
526
223
|
}
|