@coveo/quantic 3.37.8 → 3.37.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.
Files changed (26) hide show
  1. package/force-app/main/default/lwc/quanticResult/quanticResult.js +1 -1
  2. package/force-app/main/default/lwc/quanticResultHighlightedTextField/quanticResultHighlightedTextField.js +10 -1
  3. package/force-app/main/default/lwc/quanticResultLink/quanticResultLink.js +11 -4
  4. package/force-app/main/default/lwc/quanticResultQuickview/quanticResultQuickview.js +14 -2
  5. package/force-app/main/default/lwc/quanticSearchBoxInput/quanticSearchBoxInput.js +5 -0
  6. package/force-app/main/default/lwc/quanticTimeframeFacet/__tests__/quanticTimeframeFacet.test.js +3 -3
  7. package/force-app/main/default/lwc/quanticTimeframeFacet/quanticTimeframeFacet.js +3 -3
  8. package/force-app/main/default/lwc/quanticUtils/__tests__/quanticUtils.test.js +25 -15
  9. package/force-app/main/default/lwc/quanticUtils/__tests__/timeAndDateUtils.test.js +22 -0
  10. package/force-app/main/default/lwc/quanticUtils/quanticUtils.js +14 -350
  11. package/force-app/main/default/lwc/quanticUtils/timeAndDateUtils.js +407 -0
  12. package/force-app/main/default/staticresources/coveoheadless/case-assist/headless.js +15 -15
  13. package/force-app/main/default/staticresources/coveoheadless/definitions/api/knowledge/stream-answer-api.d.ts +1 -1
  14. package/force-app/main/default/staticresources/coveoheadless/definitions/api/platform-client.d.ts +5 -0
  15. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/core/generated-answer/headless-core-interactive-generated-answer-inline-link.d.ts +44 -0
  16. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/generated-answer/headless-interactive-generated-answer-inline-link.d.ts +15 -0
  17. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/smart-snippet/headless-smart-snippet-interactive-inline-links.d.ts +2 -11
  18. package/force-app/main/default/staticresources/coveoheadless/definitions/features/generated-answer/generated-answer-analytics-actions.d.ts +5 -2
  19. package/force-app/main/default/staticresources/coveoheadless/definitions/features/generated-answer/generated-answer-selectors.d.ts +1 -0
  20. package/force-app/main/default/staticresources/coveoheadless/definitions/index.d.ts +2 -0
  21. package/force-app/main/default/staticresources/coveoheadless/definitions/utils/inline-link.d.ts +10 -0
  22. package/force-app/main/default/staticresources/coveoheadless/headless.js +18 -18
  23. package/force-app/main/default/staticresources/coveoheadless/insight/headless.js +16 -16
  24. package/force-app/main/default/staticresources/coveoheadless/recommendation/headless.js +14 -14
  25. package/force-app/main/default/staticresources/dompurify/purify.min.js +2 -2
  26. package/package.json +8 -7
@@ -94,7 +94,7 @@ export default class QuanticResult extends LightningElement {
94
94
 
95
95
  get videoTimeSpan() {
96
96
  return new TimeSpan(
97
- this.result.raw.ytvideoduration,
97
+ Number(this.result.raw.ytvideoduration),
98
98
  false
99
99
  ).getYoutubeFormatTimestamp();
100
100
  }
@@ -4,6 +4,7 @@ import {
4
4
  initializeWithHeadless,
5
5
  getHeadlessBundle,
6
6
  } from 'c/quanticHeadlessLoader';
7
+ import {unwrapLockerProxiedObject} from 'c/quanticUtils';
7
8
  import {LightningElement, api} from 'lwc';
8
9
 
9
10
  /** @typedef {import("coveo").Result} Result */
@@ -28,7 +29,13 @@ export default class QuanticResultHighlightedTextField extends LightningElement
28
29
  * @api
29
30
  * @type {Result}
30
31
  */
31
- @api result;
32
+ @api
33
+ get result() {
34
+ return this._result;
35
+ }
36
+ set result(result) {
37
+ this._result = unwrapLockerProxiedObject(result);
38
+ }
32
39
  /**
33
40
  * (Optional) The label to display.
34
41
  * @api
@@ -49,6 +56,8 @@ export default class QuanticResultHighlightedTextField extends LightningElement
49
56
  isInitialized = false;
50
57
  /** @type {boolean} */
51
58
  validated = false;
59
+ /** @type {Result} */
60
+ _result;
52
61
 
53
62
  connectedCallback() {
54
63
  this.validateProps();
@@ -4,7 +4,7 @@ import {
4
4
  getHeadlessBundle,
5
5
  getHeadlessEnginePromise,
6
6
  } from 'c/quanticHeadlessLoader';
7
- import {ResultUtils} from 'c/quanticUtils';
7
+ import {ResultUtils, unwrapLockerProxiedObject} from 'c/quanticUtils';
8
8
  import {NavigationMixin} from 'lightning/navigation';
9
9
  import {LightningElement, api} from 'lwc';
10
10
 
@@ -38,7 +38,13 @@ export default class QuanticResultLink extends NavigationMixin(
38
38
  * @api
39
39
  * @type {Result}
40
40
  */
41
- @api result;
41
+ @api
42
+ get result() {
43
+ return this._result;
44
+ }
45
+ set result(result) {
46
+ this._result = unwrapLockerProxiedObject(result);
47
+ }
42
48
  /**
43
49
  * Where to display the linked URL, as the name for a browsing context (a tab, window, or <iframe>).
44
50
  * The following keywords have special meanings for where to load the URL:
@@ -75,6 +81,8 @@ export default class QuanticResultLink extends NavigationMixin(
75
81
  engine;
76
82
  /** @type {AnyHeadless} */
77
83
  headless;
84
+ /** @type {Result} */
85
+ _result;
78
86
  /** @type {string} */
79
87
  salesforceRecordUrl;
80
88
 
@@ -113,8 +121,7 @@ export default class QuanticResultLink extends NavigationMixin(
113
121
  this.engine = engine;
114
122
  ResultUtils.bindClickEventsOnResult(
115
123
  this.engine,
116
- // Destructuring transforms the Proxy object created by Salesforce to a normal object so no unexpected behaviour will occur with the Headless library.
117
- {...this.result, raw: {...this.result.raw}},
124
+ this.result,
118
125
  this.template,
119
126
  this.headless.buildInteractiveResult
120
127
  );
@@ -8,7 +8,11 @@ import {
8
8
  HeadlessBundleNames,
9
9
  isHeadlessBundle,
10
10
  } from 'c/quanticHeadlessLoader';
11
- import {I18nUtils, getLastFocusableElement} from 'c/quanticUtils';
11
+ import {
12
+ I18nUtils,
13
+ getLastFocusableElement,
14
+ unwrapLockerProxiedObject,
15
+ } from 'c/quanticUtils';
12
16
  import {LightningElement, api, track} from 'lwc';
13
17
 
14
18
  /** @typedef {import("coveo").Result} Result */
@@ -42,7 +46,13 @@ export default class QuanticResultQuickview extends LightningElement {
42
46
  * @api
43
47
  * @type {ResultWithFolding}
44
48
  */
45
- @api result;
49
+ @api
50
+ get result() {
51
+ return this._result;
52
+ }
53
+ set result(result) {
54
+ this._result = unwrapLockerProxiedObject(result);
55
+ }
46
56
  /**
47
57
  * The maximum preview size to retrieve, in bytes. By default, the full preview is retrieved.
48
58
  * @api
@@ -84,6 +94,8 @@ export default class QuanticResultQuickview extends LightningElement {
84
94
 
85
95
  /** @type {Quickview} */
86
96
  quickview;
97
+ /** @type {ResultWithFolding} */
98
+ _result;
87
99
  /** @type {boolean} */
88
100
  isQuickviewOpen = false;
89
101
  /** @type {Function} */
@@ -223,6 +223,11 @@ export default class QuanticSearchBoxInput extends LightningElement {
223
223
  * @param {KeyboardEvent} event
224
224
  */
225
225
  onKeyDown(event) {
226
+ // Let the browser commit IME text before handling shortcuts like Enter during composition.
227
+ if (event.isComposing || event.keyCode === 229) {
228
+ return;
229
+ }
230
+
226
231
  // eslint-disable-next-line default-case
227
232
  switch (event.key) {
228
233
  case keys.ESC:
@@ -13,14 +13,14 @@ jest.mock('c/quanticUtils', () => ({
13
13
  },
14
14
  I18nUtils: {
15
15
  format: jest.fn(),
16
+ getLabelNameWithCount: jest.fn(),
17
+ },
18
+ DateUtils: {
16
19
  formatDate: jest.fn(
17
20
  (date) =>
18
21
  `${date.getUTCFullYear()}-${date.getUTCMonth() + 1}-${date.getUTCDate()}`
19
22
  ),
20
23
  getShortDatePattern: jest.fn(),
21
- getLabelNameWithCount: jest.fn(),
22
- },
23
- DateUtils: {
24
24
  fromLocalIsoDate: jest.fn((value) => value),
25
25
  toLocalSearchApiDate: jest.fn((value) => value),
26
26
  },
@@ -387,7 +387,7 @@ export default class QuanticTimeframeFacet extends LightningElement {
387
387
  }
388
388
 
389
389
  get datepickerFormat() {
390
- return I18nUtils.getShortDatePattern();
390
+ return DateUtils.getShortDatePattern();
391
391
  }
392
392
 
393
393
  /**
@@ -621,10 +621,10 @@ export default class QuanticTimeframeFacet extends LightningElement {
621
621
  }
622
622
 
623
623
  const start = fromSearchApiDate(facetValue.start);
624
- const startDate = I18nUtils.formatDate(new Date(start));
624
+ const startDate = DateUtils.formatDate(new Date(start));
625
625
 
626
626
  const end = fromSearchApiDate(facetValue.end);
627
- const endDate = I18nUtils.formatDate(new Date(end));
627
+ const endDate = DateUtils.formatDate(new Date(end));
628
628
 
629
629
  return `${startDate} - ${endDate}`;
630
630
  };
@@ -2,7 +2,7 @@ import {
2
2
  I18nUtils,
3
3
  buildTemplateTextFromResult,
4
4
  copyToClipboard,
5
- TimeSpan,
5
+ unwrapLockerProxiedObject,
6
6
  } from 'c/quanticUtils';
7
7
 
8
8
  describe('c/quanticUtils', () => {
@@ -202,22 +202,32 @@ describe('c/quanticUtils', () => {
202
202
  });
203
203
  });
204
204
 
205
- describe('TimeSpan', () => {
206
- describe('getYoutubeFormatTimestamp', () => {
207
- it('should return the correct YouTube timestamp format for timestamps of less than a minute', () => {
208
- const timeSpan = new TimeSpan(45000); // 45 seconds
209
- expect(timeSpan.getYoutubeFormatTimestamp()).toBe('0:45');
210
- });
205
+ describe('unwrapLockerObject', () => {
206
+ it('should deeply clone complex objects while preserving primitive values', () => {
207
+ const original = {
208
+ title: 'Example',
209
+ raw: {
210
+ foo: 'bar',
211
+ tags: ['a', {value: 'b'}],
212
+ },
213
+ score: 42,
214
+ };
211
215
 
212
- it('should return the correct YouTube timestamp format for timestamps of more than an hour', () => {
213
- const timeSpan = new TimeSpan(3661000); // 1 hour, 1 minute, and 1 second
214
- expect(timeSpan.getYoutubeFormatTimestamp()).toBe('1:01:01');
215
- });
216
+ const unwrapped = unwrapLockerProxiedObject(original);
216
217
 
217
- it('should return the correct YouTube timestamp format for timestamps of less than an hour', () => {
218
- const timeSpan = new TimeSpan(300000); // 5 minutes
219
- expect(timeSpan.getYoutubeFormatTimestamp()).toBe('05:00');
220
- });
218
+ expect(unwrapped).toEqual(original);
219
+ expect(unwrapped).not.toBe(original);
220
+ expect(unwrapped.raw).not.toBe(original.raw);
221
+ expect(unwrapped.raw.tags).not.toBe(original.raw.tags);
222
+ expect(unwrapped.raw.tags[1]).not.toBe(original.raw.tags[1]);
223
+ });
224
+
225
+ it('should return primitive values as-is', () => {
226
+ expect(unwrapLockerProxiedObject(undefined)).toBeUndefined();
227
+ expect(unwrapLockerProxiedObject(null)).toBeNull();
228
+ expect(unwrapLockerProxiedObject('value')).toBe('value');
229
+ expect(unwrapLockerProxiedObject(0)).toBe(0);
230
+ expect(unwrapLockerProxiedObject(false)).toBe(false);
221
231
  });
222
232
  });
223
233
  });
@@ -0,0 +1,22 @@
1
+ import {TimeSpan} from '../timeAndDateUtils';
2
+
3
+ describe('quanticUtils/timeAndDateUtils', () => {
4
+ describe('TimeSpan', () => {
5
+ describe('getYoutubeFormatTimestamp', () => {
6
+ it('should return the correct YouTube timestamp format for timestamps of less than a minute', () => {
7
+ const timeSpan = new TimeSpan(45000);
8
+ expect(timeSpan.getYoutubeFormatTimestamp()).toBe('0:45');
9
+ });
10
+
11
+ it('should return the correct YouTube timestamp format for timestamps of more than an hour', () => {
12
+ const timeSpan = new TimeSpan(3661000);
13
+ expect(timeSpan.getYoutubeFormatTimestamp()).toBe('1:01:01');
14
+ });
15
+
16
+ it('should return the correct YouTube timestamp format for timestamps of less than an hour', () => {
17
+ const timeSpan = new TimeSpan(300000);
18
+ expect(timeSpan.getYoutubeFormatTimestamp()).toBe('05:00');
19
+ });
20
+ });
21
+ });
22
+ });
@@ -1,33 +1,4 @@
1
1
  import LOCALE from '@salesforce/i18n/locale';
2
- import dayPattern from '@salesforce/label/c.quantic_DatePatternDay';
3
- import monthPattern from '@salesforce/label/c.quantic_DatePatternMonth';
4
- import yearPattern from '@salesforce/label/c.quantic_DatePatternYear';
5
- import nextDay from '@salesforce/label/c.quantic_NextDay';
6
- import nextDay_plural from '@salesforce/label/c.quantic_NextDay_plural';
7
- import nextHour from '@salesforce/label/c.quantic_NextHour';
8
- import nextHour_plural from '@salesforce/label/c.quantic_NextHour_plural';
9
- import nextMonth from '@salesforce/label/c.quantic_NextMonth';
10
- import nextMonth_plural from '@salesforce/label/c.quantic_NextMonth_plural';
11
- import nextQuarter from '@salesforce/label/c.quantic_NextQuarter';
12
- import nextQuarter_plural from '@salesforce/label/c.quantic_NextQuarter_plural';
13
- import nextWeek from '@salesforce/label/c.quantic_NextWeek';
14
- import nextWeek_plural from '@salesforce/label/c.quantic_NextWeek_plural';
15
- import nextYear from '@salesforce/label/c.quantic_NextYear';
16
- import nextYear_plural from '@salesforce/label/c.quantic_NextYear_plural';
17
- import pastDay from '@salesforce/label/c.quantic_PastDay';
18
- import pastDay_plural from '@salesforce/label/c.quantic_PastDay_plural';
19
-
20
- /** @typedef {import("coveo").RelativeDate} RelativeDate */
21
- import pastHour from '@salesforce/label/c.quantic_PastHour';
22
- import pastHour_plural from '@salesforce/label/c.quantic_PastHour_plural';
23
- import pastMonth from '@salesforce/label/c.quantic_PastMonth';
24
- import pastMonth_plural from '@salesforce/label/c.quantic_PastMonth_plural';
25
- import pastQuarter from '@salesforce/label/c.quantic_PastQuarter';
26
- import pastQuarter_plural from '@salesforce/label/c.quantic_PastQuarter_plural';
27
- import pastWeek from '@salesforce/label/c.quantic_PastWeek';
28
- import pastWeek_plural from '@salesforce/label/c.quantic_PastWeek_plural';
29
- import pastYear from '@salesforce/label/c.quantic_PastYear';
30
- import pastYear_plural from '@salesforce/label/c.quantic_PastYear_plural';
31
2
 
32
3
  /** @typedef {import("coveo").Result} Result */
33
4
  /** @typedef {import("coveo").SortCriterion} SortCriterion */
@@ -36,6 +7,7 @@ export * from './recentQueriesUtils';
36
7
  export * from './markdownUtils';
37
8
  export * from './facetDependenciesUtils';
38
9
  export * from './citationAnchoringUtils';
10
+ export * from './timeAndDateUtils';
39
11
 
40
12
  /**
41
13
  * Utility class for debouncing function calls.
@@ -227,38 +199,6 @@ export class I18nUtils {
227
199
  );
228
200
  }
229
201
 
230
- /**
231
- * Gets the short date pattern for the current locale.
232
- * @returns {string} The short date pattern.
233
- * @example `M/d/yyyy` for `en-US`, `d/M/yyyy` for `fr-FR`, etc.
234
- */
235
- static getShortDatePattern() {
236
- const date = new Date(2000, 2, 4); // month is zero-based
237
- const dateAsString = I18nUtils.formatDate(date);
238
-
239
- const day = I18nUtils.format(dayPattern);
240
- const month = I18nUtils.format(monthPattern);
241
- const year = I18nUtils.format(yearPattern);
242
-
243
- return dateAsString
244
- .replace('2000', year.repeat(4))
245
- .replace('00', year.repeat(2)) // for 2-digits year
246
- .replace('03', month.repeat(2))
247
- .replace('3', month) // for single-digit month
248
- .replace('04', day.repeat(2))
249
- .replace('4', day);
250
- }
251
-
252
- /**
253
- * Formats the date in the current locale.
254
- * @param {Date} date
255
- * @returns {string} The formatted date.
256
- */
257
- static formatDate(date) {
258
- const formattedDate = new Intl.DateTimeFormat(LOCALE).format(date);
259
- return formattedDate;
260
- }
261
-
262
202
  /**
263
203
  * @param {string} html
264
204
  * @returns {string}
@@ -320,303 +260,27 @@ export function parseXML(string) {
320
260
  }
321
261
 
322
262
  /**
323
- * Utility class for time-based calculations and formatting.
324
- * Provides methods to convert between different time units and format durations.
325
- */
326
- export class TimeSpan {
327
- constructor(time, isMilliseconds = true) {
328
- if (isMilliseconds) {
329
- this.milliseconds = time;
330
- } else {
331
- this.milliseconds = time * 1000;
332
- }
333
- }
334
-
335
- getMilliseconds() {
336
- return this.milliseconds;
337
- }
338
-
339
- getSeconds() {
340
- return this.getMilliseconds() / 1000;
341
- }
342
-
343
- getMinutes() {
344
- return this.getSeconds() / 60;
345
- }
346
-
347
- getHours() {
348
- return this.getMinutes() / 60;
349
- }
350
-
351
- getDays() {
352
- return this.getHours() / 24;
353
- }
354
-
355
- getWeeks() {
356
- return this.getDays() / 7;
357
- }
358
-
359
- getHHMMSS() {
360
- const hours = Math.floor(this.getHours());
361
- const minutes = Math.floor(this.getMinutes()) % 60;
362
- const seconds = Math.floor(this.getSeconds()) % 60;
363
- let hoursString, minutesString, secondsString;
364
- if (hours === 0) {
365
- hoursString = '';
366
- } else {
367
- hoursString = hours < 10 ? '0' + hours.toString() : hours.toString();
368
- }
369
- minutesString =
370
- minutes < 10 ? '0' + minutes.toString() : minutes.toString();
371
- secondsString =
372
- seconds < 10 ? '0' + seconds.toString() : seconds.toString();
373
- const hhmmss =
374
- (hoursString !== '' ? hoursString + ':' : '') +
375
- minutesString +
376
- ':' +
377
- secondsString;
378
- return hhmmss;
379
- }
380
-
381
- getYoutubeFormatTimestamp() {
382
- const hours = Math.floor(this.getHours());
383
- const minutes = Math.floor(this.getMinutes()) % 60;
384
- const seconds = Math.floor(this.getSeconds()) % 60;
385
-
386
- const formattedSeconds = seconds < 10 ? '0' + seconds : seconds;
387
-
388
- if (hours > 0) {
389
- const formattedMinutes = minutes < 10 ? '0' + minutes : minutes;
390
- return hours + ':' + formattedMinutes + ':' + formattedSeconds;
391
- }
392
- const formattedMinutes =
393
- minutes === 0 ? '0' : minutes < 10 ? '0' + minutes : minutes;
394
- return formattedMinutes + ':' + formattedSeconds;
395
- }
396
-
397
- getCleanHHMMSS() {
398
- return this.getHHMMSS().replace(/^0+/, '');
399
- }
400
- }
401
-
402
- /**
403
- * Utility class for date operations and formatting.
404
- * Handles conversion between different date formats and provides parsing utilities.
263
+ * Recursively clones objects to break Locker proxy chains.
264
+ * @param {any} value
265
+ * @returns {any}
405
266
  */
406
- export class DateUtils {
407
- /**
408
- * Converts a date string from the Coveo Search API format to the ISO-8601 format.
409
- * Replace `/` characters in date string with `-`.
410
- * Replace `@` characters in date string with `T`.
411
- * @param {string} dateString
412
- * @returns {string}
413
- */
414
- static fromSearchApiDate(dateString) {
415
- return dateString.replaceAll('/', '-').replaceAll('@', 'T');
416
- }
417
-
418
- /**
419
- * Converts a date object to the Search API format (`yyyy/MM/dd@hh:mm:ss`), using local time.
420
- * @param {Date} date The date object to convert.
421
- * @returns {string} The formatted date string.
422
- */
423
- static toLocalSearchApiDate(date) {
424
- const year = date.getFullYear().toString().padStart(4, '0');
425
- const month = (date.getMonth() + 1).toString().padStart(2, '0');
426
- const day = date.getDate().toString().padStart(2, '0');
427
- const hours = date.getHours().toString().padStart(2, '0');
428
- const minutes = date.getMinutes().toString().padStart(2, '0');
429
- const seconds = date.getSeconds().toString().padStart(2, '0');
430
-
431
- return `${year}/${month}/${day}@${hours}:${minutes}:${seconds}`;
432
- }
433
-
434
- /**
435
- * Converts a date to the ISO formatted local date.
436
- * @param {Date} date The date to convert.
437
- * @returns {string} The formatted date string.
438
- */
439
- static toLocalIsoDate(date) {
440
- const year = date.getFullYear().toString().padStart(4, '0');
441
- const month = (date.getMonth() + 1).toString().padStart(2, '0');
442
- const day = date.getDate().toString().padStart(2, '0');
443
-
444
- return `${year}-${month}-${day}T00:00:00`;
445
- }
446
-
447
- /**
448
- * Parses an ISO-formatted date string to a date object, using the specified local time.
449
- * @param {string} dateString The ISO formatted date string.
450
- * @param {number} hours The local hours to set on the date.
451
- * @param {number} minutes The local minutes to set on the date.
452
- * @param {number} seconds The local seconds to set on the date.
453
- * @throws {Error} If specified time is invalid.
454
- * @returns {Date} The parsed date.
455
- */
456
- static fromLocalIsoDate(dateString, hours, minutes, seconds) {
457
- const isTimeValid =
458
- hours >= 0 &&
459
- hours <= 23 &&
460
- minutes >= 0 &&
461
- minutes <= 59 &&
462
- seconds >= 0 &&
463
- seconds <= 59;
464
- if (!isTimeValid) {
465
- throw new Error(
466
- 'The specified time is invalid. It must be between 00:00:00 and 23:59:59.'
467
- );
468
- }
469
-
470
- const withoutTime = DateUtils.trimIsoTime(dateString);
471
- const time =
472
- hours.toString().padStart(2, '0') +
473
- ':' +
474
- minutes.toString().padStart(2, '0') +
475
- ':' +
476
- seconds.toString().padStart(2, '0');
477
-
478
- return new Date(`${withoutTime}T${time}`);
267
+ export function unwrapLockerProxiedObject(value) {
268
+ if (value === null || typeof value !== 'object') {
269
+ return value;
479
270
  }
480
271
 
481
- /**
482
- * Trims the time portion from an ISO 8601 date string.
483
- * @param {string} dateString
484
- * @returns {string}
485
- */
486
- static trimIsoTime(dateString) {
487
- const timeIdx = dateString.indexOf('T');
488
- return timeIdx !== -1 ? dateString.substring(0, timeIdx) : dateString;
272
+ if (Array.isArray(value)) {
273
+ return value.map((item) => unwrapLockerProxiedObject(item));
489
274
  }
490
275
 
491
- /**
492
- * @param {number} timestamp
493
- * @returns {boolean}
494
- */
495
- static isValidTimestamp(timestamp) {
496
- let isValid = true;
497
- try {
498
- // eslint-disable-next-line no-new
499
- new Date(timestamp);
500
- } catch (error) {
501
- isValid = false;
276
+ const unwrappedValue = {};
277
+ for (const key in value) {
278
+ if (Object.prototype.hasOwnProperty.call(value, key)) {
279
+ unwrappedValue[key] = unwrapLockerProxiedObject(value[key]);
502
280
  }
503
- return isValid;
504
- }
505
-
506
- /**
507
- * Parses a given timestamp into detailed date components.
508
- * @param {number} timestamp - The timestamp in milliseconds since January 1, 1970 (epoch time).
509
- * @returns {Object} An object containing the following date details:
510
- * - {number} year - The four-digit year (for example, 2024).
511
- * - {string} month - The full name of the month (for example, "August").
512
- * - {string} dayOfWeek - The abbreviated name of the day of the week (for example, "Mon").
513
- * - {number} day - The day of the month (for example, 26).
514
- * - {number} hours - The hour of the day in 24-hour format (0-23).
515
- * - {number} minutes - The minutes of the hour (0-59).
516
- */
517
- static parseTimestampToDateDetails(timestamp) {
518
- const daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
519
- const months = [
520
- 'January',
521
- 'February',
522
- 'March',
523
- 'April',
524
- 'May',
525
- 'June',
526
- 'July',
527
- 'August',
528
- 'September',
529
- 'October',
530
- 'November',
531
- 'December',
532
- ];
533
-
534
- const date = new Date(timestamp);
535
- const dayOfWeek = daysOfWeek[date.getDay()];
536
- const month = months[date.getMonth()];
537
- const day = date.getDate();
538
- const year = date.getFullYear();
539
- const hours = date.getHours();
540
- const minutes = date.getMinutes();
541
-
542
- return {
543
- year,
544
- month,
545
- dayOfWeek,
546
- day,
547
- hours,
548
- minutes,
549
- };
550
- }
551
- }
552
-
553
- /**
554
- * Converts a date string from the Coveo Search API format to the ISO-8601 format.
555
- * Replace `/` characters in date string with `-`.
556
- * Replace `@` characters in date string with `T`.
557
- * @param {string} dateString
558
- * @returns {string}
559
- */
560
- export function fromSearchApiDate(dateString) {
561
- return DateUtils.fromSearchApiDate(dateString);
562
- }
563
-
564
- /**
565
- * Formats relative date ranges into human-readable strings.
566
- * Supports past and future date ranges with proper pluralization.
567
- */
568
- export class RelativeDateFormatter {
569
- constructor() {
570
- this.singularIndex = 0;
571
- this.pluralIndex = 1;
572
-
573
- this.labels = {
574
- 'past-hour': [pastHour, pastHour_plural],
575
- 'past-day': [pastDay, pastDay_plural],
576
- 'past-week': [pastWeek, pastWeek_plural],
577
- 'past-month': [pastMonth, pastMonth_plural],
578
- 'past-quarter': [pastQuarter, pastQuarter_plural],
579
- 'past-year': [pastYear, pastYear_plural],
580
- 'next-hour': [nextHour, nextHour_plural],
581
- 'next-day': [nextDay, nextDay_plural],
582
- 'next-week': [nextWeek, nextWeek_plural],
583
- 'next-month': [nextMonth, nextMonth_plural],
584
- 'next-quarter': [nextQuarter, nextQuarter_plural],
585
- 'next-year': [nextYear, nextYear_plural],
586
- };
587
281
  }
588
282
 
589
- /**
590
- * Formats a relative date range into a human-readable string.
591
- * @param {RelativeDate} begin The beginning of the relative date range.
592
- * @param {RelativeDate} end The end of the relative date range.
593
- * @returns {string} The formatted human-readable date range.
594
- * @throws {Error} If the provided relative date range is invalid.
595
- * @example
596
- * begin = { period: 'past', unit: 'day', amount: 2 };
597
- * end = { period: 'now', unit: 'day', amount: 1 };
598
- * Output: "2 days ago - 1 day ago"
599
- */
600
- formatRange(begin, end) {
601
- const isPastRange = begin.period === 'past' && end.period === 'now';
602
- const isNextRange = begin.period === 'now' && end.period === 'next';
603
-
604
- if (!isPastRange && !isNextRange) {
605
- throw new Error(
606
- 'The provided relative date range is invalid. Either "begin" or "end" must have the "period" set to "now".'
607
- );
608
- }
609
-
610
- const relativeDate = isPastRange ? begin : end;
611
- const label =
612
- this.labels[`${relativeDate.period}-${relativeDate.unit}`][
613
- I18nUtils.isSingular(relativeDate.amount)
614
- ? this.singularIndex
615
- : this.pluralIndex
616
- ];
617
-
618
- return I18nUtils.format(label, relativeDate.amount);
619
- }
283
+ return unwrappedValue;
620
284
  }
621
285
 
622
286
  /**