@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.
- package/force-app/main/default/lwc/quanticResult/quanticResult.js +1 -1
- package/force-app/main/default/lwc/quanticResultHighlightedTextField/quanticResultHighlightedTextField.js +10 -1
- package/force-app/main/default/lwc/quanticResultLink/quanticResultLink.js +11 -4
- package/force-app/main/default/lwc/quanticResultQuickview/quanticResultQuickview.js +14 -2
- package/force-app/main/default/lwc/quanticSearchBoxInput/quanticSearchBoxInput.js +5 -0
- package/force-app/main/default/lwc/quanticTimeframeFacet/__tests__/quanticTimeframeFacet.test.js +3 -3
- package/force-app/main/default/lwc/quanticTimeframeFacet/quanticTimeframeFacet.js +3 -3
- package/force-app/main/default/lwc/quanticUtils/__tests__/quanticUtils.test.js +25 -15
- package/force-app/main/default/lwc/quanticUtils/__tests__/timeAndDateUtils.test.js +22 -0
- package/force-app/main/default/lwc/quanticUtils/quanticUtils.js +14 -350
- package/force-app/main/default/lwc/quanticUtils/timeAndDateUtils.js +407 -0
- package/force-app/main/default/staticresources/coveoheadless/case-assist/headless.js +15 -15
- package/force-app/main/default/staticresources/coveoheadless/definitions/api/knowledge/stream-answer-api.d.ts +1 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/api/platform-client.d.ts +5 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/core/generated-answer/headless-core-interactive-generated-answer-inline-link.d.ts +44 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/generated-answer/headless-interactive-generated-answer-inline-link.d.ts +15 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/smart-snippet/headless-smart-snippet-interactive-inline-links.d.ts +2 -11
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/generated-answer/generated-answer-analytics-actions.d.ts +5 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/generated-answer/generated-answer-selectors.d.ts +1 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/index.d.ts +2 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/utils/inline-link.d.ts +10 -0
- package/force-app/main/default/staticresources/coveoheadless/headless.js +18 -18
- package/force-app/main/default/staticresources/coveoheadless/insight/headless.js +16 -16
- package/force-app/main/default/staticresources/coveoheadless/recommendation/headless.js +14 -14
- package/force-app/main/default/staticresources/dompurify/purify.min.js +2 -2
- package/package.json +8 -7
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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:
|
package/force-app/main/default/lwc/quanticTimeframeFacet/__tests__/quanticTimeframeFacet.test.js
CHANGED
|
@@ -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
|
|
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 =
|
|
624
|
+
const startDate = DateUtils.formatDate(new Date(start));
|
|
625
625
|
|
|
626
626
|
const end = fromSearchApiDate(facetValue.end);
|
|
627
|
-
const endDate =
|
|
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
|
-
|
|
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('
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
*
|
|
324
|
-
*
|
|
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
|
|
407
|
-
|
|
408
|
-
|
|
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
|
-
|
|
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
|
-
|
|
493
|
-
|
|
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
|
/**
|