@salesforcedevs/dx-components 1.3.218 → 1.3.219
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforcedevs/dx-components",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.219",
|
|
4
4
|
"description": "DX Lightning web components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"engines": {
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
"volta": {
|
|
45
45
|
"node": "16.19.1"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "c0f8f19b7bfc14f9a6ca02a2dc328f726d72270a"
|
|
48
48
|
}
|
|
@@ -422,11 +422,17 @@ a.CoveoResultLink,
|
|
|
422
422
|
width: fit-content;
|
|
423
423
|
}
|
|
424
424
|
|
|
425
|
-
.dx-
|
|
426
|
-
display:
|
|
425
|
+
.dx-result-info {
|
|
426
|
+
display: flex;
|
|
427
|
+
gap: 12px;
|
|
427
428
|
margin-bottom: var(--dx-g-spacing-smd);
|
|
428
429
|
}
|
|
429
430
|
|
|
431
|
+
.breadcrumb {
|
|
432
|
+
color: #555;
|
|
433
|
+
font-size: var(--dx-g-text-xs);
|
|
434
|
+
}
|
|
435
|
+
|
|
430
436
|
.no-results {
|
|
431
437
|
display: flex;
|
|
432
438
|
justify-content: center;
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
CONTENT_TYPE_ICONS
|
|
7
7
|
} from "dxConstants/contentTypes";
|
|
8
8
|
import { getContentTypeColorVariables } from "dxUtils/contentTypes";
|
|
9
|
+
import { pollUntil } from "dxUtils/async";
|
|
9
10
|
|
|
10
11
|
interface CoveoSearch {
|
|
11
12
|
state: typeof CoveoSDK.state;
|
|
@@ -40,16 +41,27 @@ const resultsTemplatesInnerHtml = `
|
|
|
40
41
|
<script
|
|
41
42
|
id="myDocumentResultTemplate"
|
|
42
43
|
class="result-template"
|
|
43
|
-
type="text/
|
|
44
|
+
type="text/underscore"
|
|
44
45
|
data-field-publicurl=""
|
|
45
46
|
>
|
|
46
47
|
<div class="dx-result">
|
|
47
|
-
<
|
|
48
|
+
<div class="dx-result-info">
|
|
49
|
+
<span class="CoveoFieldValue" data-field="@content_type" data-helper="badge" data-html-value="true"></span>
|
|
50
|
+
<% if (!raw.breadcrumbs && !raw.metabreadcrumbs) { %>
|
|
51
|
+
<span class="CoveoFieldValue" data-field="@uri" data-helper="uriBreadcrumbs" data-html-value="true"></span>
|
|
52
|
+
<% } else { %>
|
|
53
|
+
<% if (raw.uri.includes('/references/')) { %>
|
|
54
|
+
<span class="CoveoFieldValue" data-field="@metabreadcrumbs" data-helper="metabreadcrumbs" data-html-value="true"></span>
|
|
55
|
+
<% } else { %>
|
|
56
|
+
<span class="CoveoFieldValue" data-field="@breadcrumbs" data-helper="breadcrumbs" data-html-value="true"></span>
|
|
57
|
+
<% } %>
|
|
58
|
+
<% } %>
|
|
59
|
+
</div>
|
|
48
60
|
<p class="dx-result-title">
|
|
49
|
-
<
|
|
61
|
+
<span
|
|
50
62
|
class="CoveoResultLink"
|
|
51
|
-
data-field="@
|
|
52
|
-
></
|
|
63
|
+
data-field="@uri"
|
|
64
|
+
></span>
|
|
53
65
|
</p>
|
|
54
66
|
<p class="dx-result-excerpt CoveoExcerpt"></p>
|
|
55
67
|
</div>
|
|
@@ -57,10 +69,21 @@ const resultsTemplatesInnerHtml = `
|
|
|
57
69
|
<script
|
|
58
70
|
id="myDefaultResultTemplate"
|
|
59
71
|
class="result-template"
|
|
60
|
-
type="text/
|
|
72
|
+
type="text/underscore"
|
|
61
73
|
>
|
|
62
74
|
<div class="dx-result">
|
|
63
|
-
<
|
|
75
|
+
<div class="dx-result-info">
|
|
76
|
+
<span class="CoveoFieldValue" data-field="@content_type" data-helper="badge" data-html-value="true"></span>
|
|
77
|
+
<% if (!raw.breadcrumbs && !raw.metabreadcrumbs) { %>
|
|
78
|
+
<span class="CoveoFieldValue" data-field="@uri" data-helper="uriBreadcrumbs" data-html-value="true"></span>
|
|
79
|
+
<% } else { %>
|
|
80
|
+
<% if (raw.uri.includes('/references/')) { %>
|
|
81
|
+
<span class="CoveoFieldValue" data-field="@metabreadcrumbs" data-helper="metabreadcrumbs" data-html-value="true"></span>
|
|
82
|
+
<% } else { %>
|
|
83
|
+
<span class="CoveoFieldValue" data-field="@breadcrumbs" data-helper="breadcrumbs" data-html-value="true"></span>
|
|
84
|
+
<% } %>
|
|
85
|
+
<% } %>
|
|
86
|
+
</div>
|
|
64
87
|
<p class="dx-result-title">
|
|
65
88
|
<a class="CoveoResultLink"></a>
|
|
66
89
|
</p>
|
|
@@ -69,6 +92,14 @@ const resultsTemplatesInnerHtml = `
|
|
|
69
92
|
</script>
|
|
70
93
|
`;
|
|
71
94
|
|
|
95
|
+
const isInternalDomain = (domain: string) =>
|
|
96
|
+
domain === "developer.salesforce.com" ||
|
|
97
|
+
domain === "developer-website-s.herokuapp.com";
|
|
98
|
+
|
|
99
|
+
const isTrailheadDomain = (domain: string) =>
|
|
100
|
+
domain === "trailhead.salesforce.com" ||
|
|
101
|
+
domain === "dev.trailhead.salesforce.com";
|
|
102
|
+
|
|
72
103
|
const buildTemplateHelperBadge = (value: keyof typeof CONTENT_TYPE_LABELS) => {
|
|
73
104
|
const style = getContentTypeColorVariables(value);
|
|
74
105
|
const label = CONTENT_TYPE_LABELS[value];
|
|
@@ -88,6 +119,107 @@ const buildTemplateHelperBadge = (value: keyof typeof CONTENT_TYPE_LABELS) => {
|
|
|
88
119
|
`;
|
|
89
120
|
};
|
|
90
121
|
|
|
122
|
+
const processParts = (parts: string[], internalFlag = false) => {
|
|
123
|
+
// filter /docs/ breadcrumb item from internal domains
|
|
124
|
+
const filterFn = internalFlag
|
|
125
|
+
? (part: string) => part !== "docs"
|
|
126
|
+
: (part: string) => part;
|
|
127
|
+
|
|
128
|
+
return parts.filter(filterFn).map((part) => {
|
|
129
|
+
// Remove special characters & .htm/.xml extension
|
|
130
|
+
part = part
|
|
131
|
+
.replace(/_/g, " ")
|
|
132
|
+
.replace(/-/g, " ")
|
|
133
|
+
.replace(/.html*/g, " ")
|
|
134
|
+
.replace(/.xml/g, " ")
|
|
135
|
+
.replace(/b2c/g, "B2C");
|
|
136
|
+
|
|
137
|
+
// Capitalize first letter of each word
|
|
138
|
+
part = part.replace(/\w\S*/g, (w) => {
|
|
139
|
+
return w.replace(/^\w/, (c) => c.toUpperCase());
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return `<span class="breadcrumb-item">${decodeURI(part)}</span>`;
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const buildTemplateHelperUriBreadcrumbs = (value: string) => {
|
|
147
|
+
const url = new URL(value);
|
|
148
|
+
|
|
149
|
+
// exclude youtube links from breadcrumbs
|
|
150
|
+
const hostnamePattern = /^((www\.)?(youtube\.com|youtu\.be))$/;
|
|
151
|
+
|
|
152
|
+
// we don't want to show atlas docs because the url structure is mad ugly
|
|
153
|
+
if (hostnamePattern.test(url.hostname) || url.pathname.includes("atlas.")) {
|
|
154
|
+
return "";
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let parts = url.pathname.split("/").filter((part) => part !== "");
|
|
158
|
+
|
|
159
|
+
// Remove language prefix from trailhead URLs
|
|
160
|
+
if (isTrailheadDomain(url.hostname)) {
|
|
161
|
+
parts = parts
|
|
162
|
+
.slice(1)
|
|
163
|
+
.filter((part) => part !== "content" && part !== "learn");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const breadcrumbs = processParts(parts, isInternalDomain(url.hostname));
|
|
167
|
+
|
|
168
|
+
if (!isInternalDomain(url.hostname)) {
|
|
169
|
+
// 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)
|
|
170
|
+
breadcrumbs.unshift(
|
|
171
|
+
`<span class="breadcrumb-item">${url.hostname}</span>`
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
return `
|
|
175
|
+
<span class="breadcrumb">
|
|
176
|
+
${breadcrumbs.join(" / ")}
|
|
177
|
+
</span>
|
|
178
|
+
`;
|
|
179
|
+
} else if (breadcrumbs.length === 1) {
|
|
180
|
+
// Hide breadcrumbs if there is only one breadcrumb item
|
|
181
|
+
return "";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// remove the last breadcrumb item (the search result title makes it redundant)
|
|
185
|
+
breadcrumbs.pop();
|
|
186
|
+
|
|
187
|
+
return `<span class="breadcrumb">/ ${breadcrumbs.join(" / ")} /</span>`;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const buildTemplateHelperBreadcrumbs = (value: string) => {
|
|
191
|
+
const parts = value.split("/").filter((part) => part !== "");
|
|
192
|
+
|
|
193
|
+
// Don't show breadcrumbs if there's only one part
|
|
194
|
+
if (parts.length === 1) {
|
|
195
|
+
return "";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const breadcrumbs = processParts(parts);
|
|
199
|
+
|
|
200
|
+
// remove last breadcrumb item
|
|
201
|
+
breadcrumbs.pop();
|
|
202
|
+
|
|
203
|
+
return `
|
|
204
|
+
<span class="breadcrumb">/ ${breadcrumbs.join(" / ")} /</span>
|
|
205
|
+
`;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const buildTemplateHelperMetaBreadcrumbs = (value: string) => {
|
|
209
|
+
const parts = value.split("/").filter((part) => part !== "");
|
|
210
|
+
|
|
211
|
+
// Don't show breadcrumbs if there's only one part
|
|
212
|
+
if (parts.length === 1) {
|
|
213
|
+
return "";
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const breadcrumbs = processParts(parts);
|
|
217
|
+
|
|
218
|
+
return `
|
|
219
|
+
<span class="breadcrumb">/ ${breadcrumbs.join(" / ")}</span>
|
|
220
|
+
`;
|
|
221
|
+
};
|
|
222
|
+
|
|
91
223
|
// @ts-ignore Dark Magic (TM) we are overriding the 'title' field with a custom getter. We should really stop doing this.
|
|
92
224
|
export default class SearchResults extends LightningElement {
|
|
93
225
|
@api coveoOrganizationId!: string;
|
|
@@ -139,9 +271,12 @@ export default class SearchResults extends LightningElement {
|
|
|
139
271
|
BreadcrumbManager.clearBreadcrumbs();
|
|
140
272
|
}
|
|
141
273
|
|
|
142
|
-
private currentPage: number =
|
|
274
|
+
private currentPage: number = 25;
|
|
143
275
|
private totalPages: number = 1;
|
|
144
276
|
|
|
277
|
+
private originalBreadcrumbs: string[] = [];
|
|
278
|
+
private initialWindowWidth = window.innerWidth;
|
|
279
|
+
|
|
145
280
|
private goToPage(e: CustomEvent) {
|
|
146
281
|
const page = e.detail;
|
|
147
282
|
const Pager = Coveo.get(
|
|
@@ -179,6 +314,7 @@ export default class SearchResults extends LightningElement {
|
|
|
179
314
|
if (Coveo.state(this.root!, "q") === "") {
|
|
180
315
|
Coveo.state(this.root!, "sort", "date descending");
|
|
181
316
|
}
|
|
317
|
+
|
|
182
318
|
this.isInitialized = true;
|
|
183
319
|
}
|
|
184
320
|
);
|
|
@@ -204,6 +340,110 @@ export default class SearchResults extends LightningElement {
|
|
|
204
340
|
|
|
205
341
|
this.trackSearchResults(event, this.query, this.totalResults);
|
|
206
342
|
});
|
|
343
|
+
|
|
344
|
+
Coveo.$$(root).on(Coveo.QueryEvents.deferredQuerySuccess, async () => {
|
|
345
|
+
// wait specified time to ensure breadcrumbs are rendered before processing them
|
|
346
|
+
await pollUntil(
|
|
347
|
+
() => {
|
|
348
|
+
const coveoResults =
|
|
349
|
+
this.root!.querySelector(".CoveoResult");
|
|
350
|
+
|
|
351
|
+
return Boolean(coveoResults);
|
|
352
|
+
},
|
|
353
|
+
20,
|
|
354
|
+
1000
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
this.processBreadcrumbs(this.root!);
|
|
358
|
+
|
|
359
|
+
window.onresize = () => this.processBreadcrumbs(root);
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Checks if text is wrapping by comparing it with an element's text that doesn't wrap
|
|
364
|
+
private isTextWrapping = (
|
|
365
|
+
elementOne: HTMLElement,
|
|
366
|
+
elementTwo: HTMLElement
|
|
367
|
+
) => elementOne.offsetHeight > elementTwo.offsetHeight;
|
|
368
|
+
|
|
369
|
+
private truncateBreadcrumbText = (breadcrumbItems: HTMLElement[]) => {
|
|
370
|
+
breadcrumbItems.forEach((breadcrumbItem: HTMLElement) => {
|
|
371
|
+
const breadcrumbItemText = breadcrumbItem.textContent!;
|
|
372
|
+
if (breadcrumbItemText.length > 30) {
|
|
373
|
+
breadcrumbItem.textContent = `${breadcrumbItemText.substring(
|
|
374
|
+
0,
|
|
375
|
+
30
|
|
376
|
+
)}...`;
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
private addBreadcrumbEllipsis = (
|
|
382
|
+
breadcrumbItems: HTMLElement[],
|
|
383
|
+
breadcrumb: HTMLElement
|
|
384
|
+
) => {
|
|
385
|
+
for (let i = 1; i < breadcrumbItems.length; i++) {
|
|
386
|
+
if (this.isTextWrapping(breadcrumb, breadcrumbItems[0])) {
|
|
387
|
+
breadcrumbItems[i].textContent = "...";
|
|
388
|
+
} else {
|
|
389
|
+
break; // Exit the loop if the breadcrumb is no longer overflowing
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
private formatBreadcrumbs = (breadcrumbs: HTMLElement[]) => {
|
|
395
|
+
breadcrumbs?.forEach((breadcrumb: HTMLElement) => {
|
|
396
|
+
// Get all breadcrumb items that are separated by '/'
|
|
397
|
+
const breadcrumbItems: any =
|
|
398
|
+
breadcrumb.querySelectorAll(".breadcrumb-item");
|
|
399
|
+
|
|
400
|
+
// Check if the breadcrumb is overflowing by comparing it's height to the height of the first breadcrumb item
|
|
401
|
+
if (this.isTextWrapping(breadcrumb, breadcrumbItems[0])) {
|
|
402
|
+
// it is overflowing, so we need to truncate long titles to 30 characters
|
|
403
|
+
this.truncateBreadcrumbText(breadcrumbItems);
|
|
404
|
+
|
|
405
|
+
// Iteratively check if the breadcrumb is still overflowing and replace text with '...' starting from the second breadcrumb item
|
|
406
|
+
this.addBreadcrumbEllipsis(breadcrumbItems, breadcrumb);
|
|
407
|
+
|
|
408
|
+
// After processing all breadcrumb items, if it's still overflowing, hide the breadcrumb element
|
|
409
|
+
if (this.isTextWrapping(breadcrumb, breadcrumbItems[0])) {
|
|
410
|
+
breadcrumb.style.display = "none";
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
private restoreBreadcrumbs = (breadcrumbs: HTMLElement[]) => {
|
|
417
|
+
breadcrumbs.forEach((breadcrumb: HTMLElement, index: number) => {
|
|
418
|
+
// eslint-disable-next-line @lwc/lwc/no-inner-html
|
|
419
|
+
breadcrumb.innerHTML = this.originalBreadcrumbs[index];
|
|
420
|
+
});
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
private windowSizeIncreased = () =>
|
|
424
|
+
window.innerWidth > this.initialWindowWidth;
|
|
425
|
+
|
|
426
|
+
private processBreadcrumbs(root: HTMLElement) {
|
|
427
|
+
// Get all breadcrumbs from search results
|
|
428
|
+
const breadcrumbs = Array.from(
|
|
429
|
+
root.querySelectorAll(".breadcrumb")
|
|
430
|
+
) as HTMLElement[];
|
|
431
|
+
|
|
432
|
+
if (this.originalBreadcrumbs.length === 0) {
|
|
433
|
+
this.originalBreadcrumbs = breadcrumbs.map(
|
|
434
|
+
(breadcrumb) => breadcrumb.innerHTML
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (this.windowSizeIncreased()) {
|
|
439
|
+
/*
|
|
440
|
+
Reset the breadcrumbs to their original state and process them again.
|
|
441
|
+
The additional space means we can replace ellipsis with full text.
|
|
442
|
+
*/
|
|
443
|
+
this.restoreBreadcrumbs(breadcrumbs);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
this.formatBreadcrumbs(breadcrumbs);
|
|
207
447
|
}
|
|
208
448
|
|
|
209
449
|
private initializeCoveo() {
|
|
@@ -212,6 +452,7 @@ export default class SearchResults extends LightningElement {
|
|
|
212
452
|
const resultsList = this.template.querySelector(".CoveoResultList");
|
|
213
453
|
|
|
214
454
|
if (resultsList) {
|
|
455
|
+
// eslint-disable-next-line @lwc/lwc/no-inner-html
|
|
215
456
|
resultsList.innerHTML = resultsTemplatesInnerHtml;
|
|
216
457
|
}
|
|
217
458
|
|
|
@@ -227,6 +468,21 @@ export default class SearchResults extends LightningElement {
|
|
|
227
468
|
buildTemplateHelperBadge
|
|
228
469
|
);
|
|
229
470
|
|
|
471
|
+
Coveo.TemplateHelpers.registerTemplateHelper(
|
|
472
|
+
"breadcrumbs",
|
|
473
|
+
buildTemplateHelperBreadcrumbs
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
Coveo.TemplateHelpers.registerTemplateHelper(
|
|
477
|
+
"metabreadcrumbs",
|
|
478
|
+
buildTemplateHelperMetaBreadcrumbs
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
Coveo.TemplateHelpers.registerTemplateHelper(
|
|
482
|
+
"uriBreadcrumbs",
|
|
483
|
+
buildTemplateHelperUriBreadcrumbs
|
|
484
|
+
);
|
|
485
|
+
|
|
230
486
|
Coveo.init(this.root);
|
|
231
487
|
}
|
|
232
488
|
|
|
@@ -235,6 +491,7 @@ export default class SearchResults extends LightningElement {
|
|
|
235
491
|
if (Object.prototype.hasOwnProperty.call(window, "Coveo")) {
|
|
236
492
|
this.initializeCoveo();
|
|
237
493
|
} else {
|
|
494
|
+
// eslint-disable-next-line @lwc/lwc/no-document-query
|
|
238
495
|
const script = document.querySelector("script.coveo-script");
|
|
239
496
|
script?.addEventListener("load", () => {
|
|
240
497
|
this.initializeCoveo();
|