@salesforcedevs/dx-components 1.3.204 → 1.3.206-alpha.11

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.204",
3
+ "version": "1.3.206-alpha.11",
4
4
  "description": "DX Lightning web components",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -43,6 +43,5 @@
43
43
  },
44
44
  "volta": {
45
45
  "node": "16.19.1"
46
- },
47
- "gitHead": "d424645944682ab94a53cad2c0994ad4f739824b"
46
+ }
48
47
  }
@@ -386,7 +386,13 @@ a.CoveoResultLink,
386
386
  width: fit-content;
387
387
  }
388
388
 
389
- .dx-badge {
390
- display: block;
389
+ .dx-result-info {
390
+ display: flex;
391
+ gap: 12px;
391
392
  margin-bottom: var(--dx-g-spacing-smd);
392
393
  }
394
+
395
+ .breadcrumb {
396
+ color: #555;
397
+ font-size: var(--dx-g-text-xs);
398
+ }
@@ -84,7 +84,6 @@
84
84
  class="CoveoResultList"
85
85
  data-layout="list"
86
86
  data-wait-animation="fade"
87
- data-auto-select-fields-to-include="true"
88
87
  lwc:dom="manual"
89
88
  ></div>
90
89
  <div class="coveo-results-footer" aria-hidden="true">
@@ -1,5 +1,6 @@
1
1
  import { LightningElement, api, track } from "lwc";
2
2
  import type * as CoveoSDK from "coveo-search-ui";
3
+ import debounce from "debounce";
3
4
  import { track as trackGTM } from "dxUtils/analytics";
4
5
  import {
5
6
  CONTENT_TYPE_LABELS,
@@ -44,7 +45,11 @@ const resultsTemplatesInnerHtml = `
44
45
  data-field-publicurl=""
45
46
  >
46
47
  <div class="dx-result">
47
- <span class="CoveoFieldValue" data-field="@content_type" data-helper="badge" data-html-value="true"></span>
48
+ <div class="dx-result-info">
49
+ <span class="CoveoFieldValue" data-field="@content_type" data-helper="badge" data-html-value="true"></span>
50
+ <span class="CoveoFieldValue" data-field="@uri" data-helper="uriBreadcrumbs" data-html-value="true"></span>
51
+ <span class="CoveoFieldValue" data-field="@breadcrumbs" data-helper="breadcrumbs" data-html-value="true"></span>
52
+ </div>
48
53
  <p class="dx-result-title">
49
54
  <a
50
55
  class="CoveoResultLink"
@@ -60,7 +65,11 @@ const resultsTemplatesInnerHtml = `
60
65
  type="text/html"
61
66
  >
62
67
  <div class="dx-result">
63
- <span class="CoveoFieldValue" data-field="@content_type" data-helper="badge" data-html-value="true"></span>
68
+ <div class="dx-result-info">
69
+ <span class="CoveoFieldValue" data-field="@content_type" data-helper="badge" data-html-value="true"></span>
70
+ <span class="CoveoFieldValue" data-field="@uri" data-helper="uriBreadcrumbs" data-html-value="true"></span>
71
+ <span class="CoveoFieldValue" data-field="@breadcrumbs" data-helper="breadcrumbs" data-html-value="true"></span>
72
+ </div>
64
73
  <p class="dx-result-title">
65
74
  <a class="CoveoResultLink"></a>
66
75
  </p>
@@ -88,6 +97,83 @@ const buildTemplateHelperBadge = (value: keyof typeof CONTENT_TYPE_LABELS) => {
88
97
  `;
89
98
  };
90
99
 
100
+ const processParts = (parts: string[]) => {
101
+ return parts.map((part) => {
102
+ // Remove special characters & .htm/.xml extension
103
+ part = part
104
+ .replace(/_/g, " ")
105
+ .replace(/-/g, " ")
106
+ .replace(/.htm/g, " ")
107
+ .replace(/.xml/g, " ");
108
+
109
+ // Capitalize first letter of each word
110
+ part = part.replace(/\w\S*/g, (w) => {
111
+ return w.replace(/^\w/, (c) => c.toUpperCase());
112
+ });
113
+
114
+ return `<span class="breadcrumb-item">${decodeURI(part)}</span>`;
115
+ });
116
+ };
117
+
118
+ const buildTemplateHelperUriBreadcrumbs = (value: string) => {
119
+ const url = new URL(value);
120
+
121
+ // Only generate breadcrumbs for external links using the URL
122
+ const hostnamePattern =
123
+ /^((www\.)?developer\.salesforce\.com|(www\.)?developer-website-s\.herokuapp\.com|(www\.)?youtube\.com)$/;
124
+ if (hostnamePattern.test(url.hostname)) {
125
+ return "";
126
+ }
127
+
128
+ let parts = url.pathname.split("/").filter((part) => part !== "");
129
+
130
+ // Remove language prefix from trailhead URLs
131
+ if (
132
+ url.hostname === "trailhead.salesforce.com" ||
133
+ url.hostname === "dev.trailhead.salesforce.com"
134
+ ) {
135
+ parts = parts
136
+ .slice(1)
137
+ .filter((part) => part !== "content" && part !== "learn");
138
+ }
139
+
140
+ const breadcrumbs = processParts(parts);
141
+
142
+ breadcrumbs.unshift(`<span class="breadcrumb-item">${url.hostname}</span>`);
143
+
144
+ return `
145
+ <span class="breadcrumb">
146
+ ${breadcrumbs.join(" / ")}
147
+ </span>
148
+ `;
149
+ };
150
+
151
+ const buildTemplateHelperBreadcrumbs = (value: string) => {
152
+ let parts: string[] = [];
153
+
154
+ if (value.includes("developer.salesforce.com")) {
155
+ const url = new URL(value);
156
+ parts = url.pathname.split("/").filter((part) => part !== "");
157
+ } else {
158
+ parts = value.split("/").filter((part) => part !== "");
159
+ }
160
+
161
+ // Don't show breadcrumbs if there's only one part or two (two because we remove the last item in the next step)
162
+ if (parts.length === 1 || parts.length === 2) {
163
+ return "";
164
+ }
165
+
166
+ const breadcrumbs = processParts(parts);
167
+
168
+ // Remove the last breadcrumb item
169
+ breadcrumbs.pop();
170
+ console.log(breadcrumbs);
171
+
172
+ return `
173
+ <span class="breadcrumb">/ ${breadcrumbs.join(" / ")} /</span>
174
+ `;
175
+ };
176
+
91
177
  // @ts-ignore Dark Magic (TM) we are overriding the 'title' field with a custom getter. We should really stop doing this.
92
178
  export default class SearchResults extends LightningElement {
93
179
  @api coveoOrganizationId!: string;
@@ -134,9 +220,12 @@ export default class SearchResults extends LightningElement {
134
220
  BreadcrumbManager.clearBreadcrumbs();
135
221
  }
136
222
 
137
- private currentPage: number = 1;
223
+ private currentPage: number = 25;
138
224
  private totalPages: number = 1;
139
225
 
226
+ private originalBreadcrumbs: string[] = [];
227
+ private windowWidth = window.innerWidth;
228
+
140
229
  private goToPage(e: CustomEvent) {
141
230
  const page = e.detail;
142
231
  const Pager = Coveo.get(
@@ -174,6 +263,7 @@ export default class SearchResults extends LightningElement {
174
263
  if (Coveo.state(this.root!, "q") === "") {
175
264
  Coveo.state(this.root!, "sort", "date descending");
176
265
  }
266
+
177
267
  this.isInitialized = true;
178
268
  }
179
269
  );
@@ -193,6 +283,98 @@ export default class SearchResults extends LightningElement {
193
283
  });
194
284
  this.trackSearchResults(event, this.query, this.totalResults);
195
285
  });
286
+
287
+ Coveo.$$(root).on(Coveo.QueryEvents.deferredQuerySuccess, () => {
288
+ this.processBreadcrumbs(root);
289
+
290
+ window.onresize = debounce(() => {
291
+ this.processBreadcrumbs(root);
292
+ }, 10);
293
+ });
294
+ }
295
+
296
+ // Checks if text is wrapping by comparing it with an element's text that doesn't wrap
297
+ private isTextWrapping = (
298
+ elementOne: HTMLElement,
299
+ elementTwo: HTMLElement
300
+ ) => elementOne.offsetHeight > elementTwo.offsetHeight;
301
+
302
+ private truncateBreadcrumbText = (breadcrumbItems: HTMLElement[]) => {
303
+ breadcrumbItems.forEach((breadcrumbItem: HTMLElement) => {
304
+ const breadcrumbItemText = breadcrumbItem.textContent!;
305
+ if (breadcrumbItemText.length > 30) {
306
+ breadcrumbItem.textContent = `${breadcrumbItemText.substring(
307
+ 0,
308
+ 30
309
+ )}...`;
310
+ }
311
+ });
312
+ };
313
+
314
+ private addBreadcrumbEllipsis = (
315
+ breadcrumbItems: HTMLElement[],
316
+ breadcrumb: HTMLElement
317
+ ) => {
318
+ for (let i = 1; i < breadcrumbItems.length; i++) {
319
+ if (this.isTextWrapping(breadcrumb, breadcrumbItems[0])) {
320
+ breadcrumbItems[i].textContent = "...";
321
+ } else {
322
+ break; // Exit the loop if the breadcrumb is no longer overflowing
323
+ }
324
+ }
325
+ };
326
+
327
+ private formatBreadcrumbs = (breadcrumbs: HTMLElement[]) => {
328
+ breadcrumbs?.forEach((breadcrumb: HTMLElement) => {
329
+ // Get all breadcrumb items that are separated by '/'
330
+ const breadcrumbItems: any =
331
+ breadcrumb.querySelectorAll(".breadcrumb-item");
332
+
333
+ // Check if the breadcrumb is overflowing by comparing it's height to the height of the first breadcrumb item
334
+ if (this.isTextWrapping(breadcrumb, breadcrumbItems[0])) {
335
+ // it is overflowing, so we need to truncate long titles to 30 characters
336
+ this.truncateBreadcrumbText(breadcrumbItems);
337
+
338
+ // Iteratively check if the breadcrumb is still overflowing and replace text with '...' starting from the second breadcrumb item
339
+ this.addBreadcrumbEllipsis(breadcrumbItems, breadcrumb);
340
+
341
+ // After processing all breadcrumb items, if it's still overflowing, hide the breadcrumb element
342
+ if (this.isTextWrapping(breadcrumb, breadcrumbItems[0])) {
343
+ breadcrumb.style.display = "none";
344
+ }
345
+ }
346
+ });
347
+ };
348
+
349
+ private restoreBreadcrumbs = (breadcrumbs: HTMLElement[]) => {
350
+ breadcrumbs.forEach((breadcrumb: HTMLElement, index: number) => {
351
+ // eslint-disable-next-line @lwc/lwc/no-inner-html
352
+ breadcrumb.innerHTML = this.originalBreadcrumbs[index];
353
+ });
354
+ };
355
+
356
+ private windowSizeIncreased = () => window.innerWidth > this.windowWidth;
357
+
358
+ private processBreadcrumbs(root: HTMLElement) {
359
+ // Get all breadcrumbs from search results
360
+ const breadcrumbs = root.querySelectorAll(
361
+ ".breadcrumb"
362
+ // eslint-disable-next-line no-undef
363
+ ) as NodeListOf<HTMLElement>;
364
+
365
+ if (this.originalBreadcrumbs.length === 0) {
366
+ this.originalBreadcrumbs = Array.from(breadcrumbs).map(
367
+ // eslint-disable-next-line @lwc/lwc/no-inner-html
368
+ (breadcrumb) => breadcrumb.innerHTML
369
+ );
370
+ }
371
+
372
+ if (this.windowSizeIncreased()) {
373
+ // reset the breadcrumbs to their original state
374
+ this.restoreBreadcrumbs(Array.from(breadcrumbs));
375
+ }
376
+
377
+ this.formatBreadcrumbs(Array.from(breadcrumbs));
196
378
  }
197
379
 
198
380
  private initializeCoveo() {
@@ -200,6 +382,7 @@ export default class SearchResults extends LightningElement {
200
382
 
201
383
  const resultsList = this.template.querySelector(".CoveoResultList");
202
384
  if (resultsList) {
385
+ // eslint-disable-next-line @lwc/lwc/no-inner-html
203
386
  resultsList.innerHTML = resultsTemplatesInnerHtml;
204
387
  }
205
388
 
@@ -215,6 +398,16 @@ export default class SearchResults extends LightningElement {
215
398
  buildTemplateHelperBadge
216
399
  );
217
400
 
401
+ Coveo.TemplateHelpers.registerTemplateHelper(
402
+ "uriBreadcrumbs",
403
+ buildTemplateHelperUriBreadcrumbs
404
+ );
405
+
406
+ Coveo.TemplateHelpers.registerTemplateHelper(
407
+ "breadcrumbs",
408
+ buildTemplateHelperBreadcrumbs
409
+ );
410
+
218
411
  Coveo.init(this.root);
219
412
  }
220
413
 
@@ -223,6 +416,7 @@ export default class SearchResults extends LightningElement {
223
416
  if (Object.prototype.hasOwnProperty.call(window, "Coveo")) {
224
417
  this.initializeCoveo();
225
418
  } else {
419
+ // eslint-disable-next-line @lwc/lwc/no-document-query
226
420
  const script = document.querySelector("script.coveo-script");
227
421
  script?.addEventListener("load", () => {
228
422
  this.initializeCoveo();
package/LICENSE DELETED
@@ -1,12 +0,0 @@
1
- Copyright (c) 2020, Salesforce.com, Inc.
2
- All rights reserved.
3
-
4
- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
-
6
- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
-
8
- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9
-
10
- * Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11
-
12
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.