@osimatic/helpers-js 1.4.24 → 1.4.25
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/.claude/settings.local.json +2 -1
- package/chartjs.js +1 -1
- package/date_time.js +25 -16
- package/draw.js +3 -2
- package/duration.js +12 -15
- package/event_bus.js +2 -2
- package/file.js +1 -1
- package/form_helper.js +1 -1
- package/http_client.js +2 -0
- package/jwt.js +18 -6
- package/media.js +1 -1
- package/number.js +2 -3
- package/package.json +3 -2
- package/social_network.js +5 -0
- package/string.js +11 -2
- package/tests/chartjs.test.js +273 -0
- package/tests/date_time/DatePeriod.test.js +179 -0
- package/tests/date_time/DateTime.test.js +492 -0
- package/tests/date_time/SqlDate.test.js +205 -0
- package/tests/date_time/SqlDateTime.test.js +326 -0
- package/tests/date_time/SqlTime.test.js +162 -0
- package/tests/date_time/TimestampUnix.test.js +262 -0
- package/tests/draw.test.js +271 -0
- package/tests/duration.test.js +365 -0
- package/tests/event_bus.test.js +268 -0
- package/tests/file.test.js +358 -0
- package/tests/form_date.test.js +417 -0
- package/tests/form_helper.test.js +415 -0
- package/tests/http_client.test.js +570 -0
- package/tests/jwt.test.js +804 -0
- package/tests/media.test.js +458 -0
- package/tests/network.test.js +489 -0
- package/tests/number.test.js +448 -0
- package/tests/open_street_map.test.js +388 -0
- package/tests/shopping_cart.test.js +355 -0
- package/tests/social_network.test.js +333 -0
- package/tests/string.test.js +473 -0
- package/tests/user.test.js +204 -0
- package/tests/util.test.js +99 -0
- package/tests/visitor.test.js +508 -0
- package/tmpclaude-00a6-cwd +1 -0
- package/tmpclaude-0526-cwd +1 -0
- package/tmpclaude-0973-cwd +1 -0
- package/tmpclaude-0b61-cwd +1 -0
- package/tmpclaude-146f-cwd +1 -0
- package/tmpclaude-223d-cwd +1 -0
- package/tmpclaude-2330-cwd +1 -0
- package/tmpclaude-282a-cwd +1 -0
- package/tmpclaude-2846-cwd +1 -0
- package/tmpclaude-28a6-cwd +1 -0
- package/tmpclaude-2b5a-cwd +1 -0
- package/tmpclaude-2def-cwd +1 -0
- package/tmpclaude-3906-cwd +1 -0
- package/tmpclaude-3b32-cwd +1 -0
- package/tmpclaude-3da9-cwd +1 -0
- package/tmpclaude-3dc3-cwd +1 -0
- package/tmpclaude-3e3b-cwd +1 -0
- package/tmpclaude-43b6-cwd +1 -0
- package/tmpclaude-4495-cwd +1 -0
- package/tmpclaude-462f-cwd +1 -0
- package/tmpclaude-4b29-cwd +1 -0
- package/tmpclaude-4db5-cwd +1 -0
- package/tmpclaude-4e01-cwd +1 -0
- package/tmpclaude-5101-cwd +1 -0
- package/tmpclaude-524f-cwd +1 -0
- package/tmpclaude-5636-cwd +1 -0
- package/tmpclaude-5cdd-cwd +1 -0
- package/tmpclaude-5f1f-cwd +1 -0
- package/tmpclaude-6078-cwd +1 -0
- package/tmpclaude-622e-cwd +1 -0
- package/tmpclaude-6802-cwd +1 -0
- package/tmpclaude-6e36-cwd +1 -0
- package/tmpclaude-7793-cwd +1 -0
- package/tmpclaude-7f96-cwd +1 -0
- package/tmpclaude-8566-cwd +1 -0
- package/tmpclaude-8874-cwd +1 -0
- package/tmpclaude-8915-cwd +1 -0
- package/tmpclaude-8c8b-cwd +1 -0
- package/tmpclaude-94df-cwd +1 -0
- package/tmpclaude-9859-cwd +1 -0
- package/tmpclaude-9ac5-cwd +1 -0
- package/tmpclaude-9f18-cwd +1 -0
- package/tmpclaude-a202-cwd +1 -0
- package/tmpclaude-a741-cwd +1 -0
- package/tmpclaude-ab5f-cwd +1 -0
- package/tmpclaude-b008-cwd +1 -0
- package/tmpclaude-b0a1-cwd +1 -0
- package/tmpclaude-b63d-cwd +1 -0
- package/tmpclaude-b681-cwd +1 -0
- package/tmpclaude-b72d-cwd +1 -0
- package/tmpclaude-b92f-cwd +1 -0
- package/tmpclaude-bc49-cwd +1 -0
- package/tmpclaude-bc50-cwd +1 -0
- package/tmpclaude-bccf-cwd +1 -0
- package/tmpclaude-be55-cwd +1 -0
- package/tmpclaude-c228-cwd +1 -0
- package/tmpclaude-c717-cwd +1 -0
- package/tmpclaude-c7ce-cwd +1 -0
- package/tmpclaude-cf3e-cwd +1 -0
- package/tmpclaude-d142-cwd +1 -0
- package/tmpclaude-d5bc-cwd +1 -0
- package/tmpclaude-d6ae-cwd +1 -0
- package/tmpclaude-d77a-cwd +1 -0
- package/tmpclaude-d8da-cwd +1 -0
- package/tmpclaude-dbdb-cwd +1 -0
- package/tmpclaude-de61-cwd +1 -0
- package/tmpclaude-de81-cwd +1 -0
- package/tmpclaude-df9d-cwd +1 -0
- package/tmpclaude-e786-cwd +1 -0
- package/tmpclaude-f01d-cwd +1 -0
- package/tmpclaude-f2a9-cwd +1 -0
- package/tmpclaude-fc36-cwd +1 -0
- package/tmpclaude-ffef-cwd +1 -0
- package/visitor.js +2 -2
package/chartjs.js
CHANGED
|
@@ -298,7 +298,7 @@ class Chartjs {
|
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
static getAutoGranularity(data) {
|
|
301
|
-
const dates = Object.keys(data);
|
|
301
|
+
const dates = Object.keys(data).sort();
|
|
302
302
|
const days = (new Date(dates[dates.length - 1]) - new Date(dates[0])) / (1000 * 60 * 60 * 24);
|
|
303
303
|
if (days > 90) return 'month';
|
|
304
304
|
if (days > 30) return 'week';
|
package/date_time.js
CHANGED
|
@@ -206,9 +206,7 @@ class DateTime {
|
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
static getLastDayOfYear(date) {
|
|
209
|
-
date.
|
|
210
|
-
date.setUTCMonth(11);
|
|
211
|
-
return new Date(date);
|
|
209
|
+
return new Date(Date.UTC(date.getUTCFullYear(), 11, 31));
|
|
212
210
|
}
|
|
213
211
|
|
|
214
212
|
static getFirstDayOfWeekAndYear(year, week) {
|
|
@@ -308,7 +306,7 @@ class DatePeriod {
|
|
|
308
306
|
return partOfDay === DatePeriod.PART_OF_DAY_MORNING || partOfDay === DatePeriod.PART_OF_DAY_AFTERNOON;
|
|
309
307
|
}
|
|
310
308
|
|
|
311
|
-
static getNbDayBetweenTwo(jsDate1, jsDate2,
|
|
309
|
+
static getNbDayBetweenTwo(jsDate1, jsDate2, considerTime=false, timeZone="Europe/Paris", returnDecimal=false) {
|
|
312
310
|
//jsDate1.set
|
|
313
311
|
if (jsDate1 == null || jsDate2 == null) {
|
|
314
312
|
return 0;
|
|
@@ -317,7 +315,7 @@ class DatePeriod {
|
|
|
317
315
|
let timestamp1 = jsDate1.getTime() / 1000;
|
|
318
316
|
let timestamp2 = jsDate2.getTime() / 1000;
|
|
319
317
|
|
|
320
|
-
if (!
|
|
318
|
+
if (!considerTime) {
|
|
321
319
|
let jsMidnightDate1 = new Date(jsDate1.toLocaleDateString('en-US', {timeZone: timeZone})+' 00:00:00');
|
|
322
320
|
let jsMidnightDate2 = new Date(jsDate2.toLocaleDateString('en-US', {timeZone: timeZone})+' 00:00:00');
|
|
323
321
|
timestamp1 = jsMidnightDate1.getTime() / 1000;
|
|
@@ -327,7 +325,9 @@ class DatePeriod {
|
|
|
327
325
|
//jsDate1.setUTCHours(0, 0, 0);
|
|
328
326
|
//jsDate2.setUTCHours(0, 0, 0);
|
|
329
327
|
}
|
|
330
|
-
|
|
328
|
+
|
|
329
|
+
const daysDiff = (timestamp2-timestamp1)/86400;
|
|
330
|
+
return returnDecimal ? daysDiff : parseInt(Math.round(daysDiff));
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
static getPeriodLabels(data, period, locale = 'fr-FR', timeZone = 'Europe/Paris') {
|
|
@@ -392,23 +392,32 @@ class TimestampUnix {
|
|
|
392
392
|
}
|
|
393
393
|
|
|
394
394
|
static getYear(timestamp, timeZone="Europe/Paris") {
|
|
395
|
-
|
|
395
|
+
//const sqlDate = this.getSqlDate(timestamp, timeZone);
|
|
396
|
+
//return parseInt(sqlDate.substring(0, 4));
|
|
397
|
+
return parseInt(this.parse(timestamp).toLocaleDateString('fr-FR', {year: 'numeric', timeZone: timeZone}));
|
|
396
398
|
}
|
|
397
399
|
static getMonth(timestamp, timeZone="Europe/Paris") {
|
|
398
|
-
|
|
400
|
+
//const sqlDate = this.getSqlDate(timestamp, timeZone);
|
|
401
|
+
//return parseInt(sqlDate.substring(5, 7));
|
|
402
|
+
return parseInt(this.parse(timestamp).toLocaleDateString('fr-FR', {month: 'numeric', timeZone: timeZone}));
|
|
399
403
|
}
|
|
400
404
|
static getDayOfMonth(timestamp, timeZone="Europe/Paris") {
|
|
401
|
-
|
|
405
|
+
//const sqlDate = this.getSqlDate(timestamp, timeZone);
|
|
406
|
+
//return parseInt(sqlDate.substring(8, 10));
|
|
407
|
+
return parseInt(this.parse(timestamp).toLocaleDateString('fr-FR', {day: 'numeric', timeZone: timeZone}));
|
|
402
408
|
}
|
|
403
409
|
|
|
404
410
|
static getHour(timestamp, timeZone="Europe/Paris") {
|
|
405
|
-
|
|
411
|
+
const sqlTime = this.getSqlTime(timestamp, timeZone);
|
|
412
|
+
return parseInt(sqlTime.substring(0, 2));
|
|
406
413
|
}
|
|
407
414
|
static getMinute(timestamp, timeZone="Europe/Paris") {
|
|
408
|
-
|
|
415
|
+
const sqlTime = this.getSqlTime(timestamp, timeZone);
|
|
416
|
+
return parseInt(sqlTime.substring(3, 5));
|
|
409
417
|
}
|
|
410
418
|
static getSecond(timestamp, timeZone="Europe/Paris") {
|
|
411
|
-
|
|
419
|
+
const sqlTime = this.getSqlTime(timestamp, timeZone);
|
|
420
|
+
return parseInt(sqlTime.substring(6, 8));
|
|
412
421
|
}
|
|
413
422
|
|
|
414
423
|
static getSqlDateTime(timestamp, timeZone="Europe/Paris") {
|
|
@@ -556,18 +565,18 @@ class SqlDateTime {
|
|
|
556
565
|
return DateTime.getSqlDateTime(new Date());
|
|
557
566
|
}
|
|
558
567
|
|
|
559
|
-
static getSqlDate(sqlDateTime) {
|
|
568
|
+
static getSqlDate(sqlDateTime, timeZone="UTC") {
|
|
560
569
|
if (sqlDateTime == null) {
|
|
561
570
|
return null;
|
|
562
571
|
}
|
|
563
|
-
return DateTime.getSqlDate(this.parse(sqlDateTime));
|
|
572
|
+
return DateTime.getSqlDate(this.parse(sqlDateTime), timeZone);
|
|
564
573
|
}
|
|
565
574
|
|
|
566
|
-
static getSqlTime(sqlDateTime) {
|
|
575
|
+
static getSqlTime(sqlDateTime, timeZone="UTC") {
|
|
567
576
|
if (sqlDateTime == null) {
|
|
568
577
|
return null;
|
|
569
578
|
}
|
|
570
|
-
return DateTime.getSqlTime(this.parse(sqlDateTime));
|
|
579
|
+
return DateTime.getSqlTime(this.parse(sqlDateTime), timeZone);
|
|
571
580
|
}
|
|
572
581
|
|
|
573
582
|
static parse(sqlDateTime) {
|
package/draw.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
class HexColor {
|
|
2
2
|
static check(hexColor) {
|
|
3
|
-
|
|
3
|
+
const match = hexColor.match(/^[0-9a-fA-F]{6}$/);
|
|
4
|
+
return match ? match.length > 0 : false;
|
|
4
5
|
}
|
|
5
6
|
|
|
6
7
|
static convertToRgb(hexColor) {
|
|
@@ -9,7 +10,7 @@ class HexColor {
|
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
if (hexColor.length === 3) {
|
|
12
|
-
hexColor = hexColor+hexColor;
|
|
13
|
+
hexColor = hexColor[0]+hexColor[0]+hexColor[1]+hexColor[1]+hexColor[2]+hexColor[2];
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
if (!HexColor.check(hexColor)) {
|
package/duration.js
CHANGED
|
@@ -72,30 +72,24 @@ class Duration {
|
|
|
72
72
|
if (withMinutes) {
|
|
73
73
|
let nbMinutes = this.getNbMinutesRemainingOfDurationInSeconds(durationInSeconds);
|
|
74
74
|
strMinute += ' ';
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
strMinute += 'min';
|
|
83
|
-
}
|
|
75
|
+
if (fullLabel) {
|
|
76
|
+
strMinute += nbMinutes.toString()+(withMinuteLabel ? ' minute'+(nbMinutes>1?'s':'') : '');
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
strMinute += nbMinutes.toString().padStart(2, '0')+(withMinuteLabel ? 'min' : '');
|
|
84
80
|
}
|
|
85
81
|
}
|
|
86
|
-
|
|
82
|
+
|
|
87
83
|
// Secondes
|
|
88
84
|
let strSeconde = '';
|
|
89
85
|
if (withSeconds) {
|
|
90
86
|
let nbSecondes = this.getNbSecondsRemainingOfDurationInSeconds(durationInSeconds);
|
|
91
87
|
strSeconde += ' ';
|
|
92
|
-
//strSeconde += sprintf('%02d', nbSecondes);
|
|
93
|
-
strSeconde += nbSecondes.toString().padStart(2, '0');
|
|
94
88
|
if (fullLabel) {
|
|
95
|
-
strSeconde += ' seconde'+(nbSecondes>1?'s':'');
|
|
89
|
+
strSeconde += nbSecondes.toString()+' seconde'+(nbSecondes>1?'s':'');
|
|
96
90
|
}
|
|
97
91
|
else {
|
|
98
|
-
strSeconde += 's';
|
|
92
|
+
strSeconde += nbSecondes.toString().padStart(2, '0')+'s';
|
|
99
93
|
}
|
|
100
94
|
}
|
|
101
95
|
|
|
@@ -191,7 +185,10 @@ class Duration {
|
|
|
191
185
|
}
|
|
192
186
|
|
|
193
187
|
static getNbMinutesOfHundredthOfAnHour(durationAsHundredthOfAnHour) {
|
|
194
|
-
|
|
188
|
+
// Extraire la partie décimale et convertir en minutes
|
|
189
|
+
// Ex: 1.5h -> 0.5 * 60 = 30 minutes
|
|
190
|
+
const decimalPart = durationAsHundredthOfAnHour - Math.floor(durationAsHundredthOfAnHour);
|
|
191
|
+
return Math.floor(decimalPart * 60);
|
|
195
192
|
}
|
|
196
193
|
|
|
197
194
|
|
package/event_bus.js
CHANGED
package/file.js
CHANGED
|
@@ -52,7 +52,7 @@ class File {
|
|
|
52
52
|
fileSizeInBytes = fileSizeInBytes / 1024;
|
|
53
53
|
i++;
|
|
54
54
|
}
|
|
55
|
-
while (fileSizeInBytes
|
|
55
|
+
while (fileSizeInBytes >= 1024);
|
|
56
56
|
//var size = Math.max(fileSizeInBytes, 0.1).toFixed(fractionDigits);
|
|
57
57
|
let size = Math.max(fileSizeInBytes, 0.1);
|
|
58
58
|
return (new Intl.NumberFormat(locale, {
|
package/form_helper.js
CHANGED
|
@@ -294,7 +294,7 @@ class FormHelper {
|
|
|
294
294
|
if (typeof errors[property] == 'function') {
|
|
295
295
|
continue;
|
|
296
296
|
}
|
|
297
|
-
if (typeof errors[property]['error_description'] !== 'undefined') {
|
|
297
|
+
if (errors[property] != null && typeof errors[property]['error_description'] !== 'undefined') {
|
|
298
298
|
errorLabels.push(errors[property]['error_description']);
|
|
299
299
|
continue;
|
|
300
300
|
}
|
package/http_client.js
CHANGED
package/jwt.js
CHANGED
|
@@ -2,9 +2,15 @@ class JwtToken {
|
|
|
2
2
|
static parseJwt (token) {
|
|
3
3
|
let base64Url = token.split('.')[1];
|
|
4
4
|
let base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
|
5
|
-
let jsonPayload
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
let jsonPayload;
|
|
6
|
+
try {
|
|
7
|
+
jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
|
|
8
|
+
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
|
9
|
+
}).join(''));
|
|
10
|
+
} catch (e) {
|
|
11
|
+
// Fallback for characters that can't be decoded
|
|
12
|
+
jsonPayload = atob(base64);
|
|
13
|
+
}
|
|
8
14
|
|
|
9
15
|
return JSON.parse(jsonPayload);
|
|
10
16
|
}
|
|
@@ -75,7 +81,9 @@ class JwtSession {
|
|
|
75
81
|
static login(data, redirectUrl, onComplete) {
|
|
76
82
|
console.log('JwtSession.login()');
|
|
77
83
|
JwtSession.setToken(data['access_token'] || data['token']);
|
|
78
|
-
|
|
84
|
+
if (data['refresh_token'] != null) {
|
|
85
|
+
JwtSession.setRefreshToken(data['refresh_token']);
|
|
86
|
+
}
|
|
79
87
|
|
|
80
88
|
JwtSession.removeItemInStorage('real_users');
|
|
81
89
|
|
|
@@ -191,7 +199,9 @@ class JwtSession {
|
|
|
191
199
|
|
|
192
200
|
// on enregistre la session de l'utilisateur simulé
|
|
193
201
|
JwtSession.setToken(loginData['access_token'] || loginData['token']);
|
|
194
|
-
|
|
202
|
+
if (loginData['refresh_token'] != null) {
|
|
203
|
+
JwtSession.setRefreshToken(loginData['refresh_token']);
|
|
204
|
+
}
|
|
195
205
|
|
|
196
206
|
if (typeof onComplete == 'function') {
|
|
197
207
|
onComplete();
|
|
@@ -216,7 +226,9 @@ class JwtSession {
|
|
|
216
226
|
JwtSession.setItemInStorage('real_users', JSON.stringify(realUsers));
|
|
217
227
|
|
|
218
228
|
JwtSession.setToken(loginData['access_token'] || loginData['token']);
|
|
219
|
-
|
|
229
|
+
if (loginData['refresh_token'] != null) {
|
|
230
|
+
JwtSession.setRefreshToken(loginData['refresh_token']);
|
|
231
|
+
}
|
|
220
232
|
|
|
221
233
|
if (typeof onComplete == 'function') {
|
|
222
234
|
onComplete();
|
package/media.js
CHANGED
|
@@ -137,7 +137,7 @@ class VideoMedia {
|
|
|
137
137
|
//Source : https://www.npmjs.com/package/mic-check
|
|
138
138
|
class UserMedia {
|
|
139
139
|
static hasGetUserMedia() {
|
|
140
|
-
return !!(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);
|
|
140
|
+
return !!(typeof navigator !== 'undefined' && (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia));
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
/** SystemPermissionDenied => (macOS) browser does not have permission to access cam/mic */
|
package/number.js
CHANGED
|
@@ -78,11 +78,10 @@ Number.padLeft2 = Number.padLeft2 || function(n) {
|
|
|
78
78
|
return n > 9 ? "" + n : "0" + n;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
Number.prototype.roundDecimal = Number.prototype.roundDecimal || function(precision) {
|
|
81
|
+
Number.prototype.roundDecimal = Number.prototype.roundDecimal || function(precision=2) {
|
|
82
82
|
return Number.roundDecimal(this, precision);
|
|
83
83
|
}
|
|
84
|
-
Number.roundDecimal = Number.roundDecimal || function(number, precision) {
|
|
85
|
-
precision = precision || 2;
|
|
84
|
+
Number.roundDecimal = Number.roundDecimal || function(number, precision=2) {
|
|
86
85
|
let tmp = Math.pow(10, precision);
|
|
87
86
|
return Math.round(number*tmp) / tmp;
|
|
88
87
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@osimatic/helpers-js",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.25",
|
|
4
4
|
"main": "main.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "jest",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"ilib": "^14.16.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"jest": "^30.2.0"
|
|
19
|
+
"jest": "^30.2.0",
|
|
20
|
+
"whatwg-fetch": "^3.6.20"
|
|
20
21
|
},
|
|
21
22
|
"jest": {
|
|
22
23
|
"testEnvironment": "node",
|
package/social_network.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require('./string');
|
|
2
|
+
|
|
1
3
|
class SocialNetwork {
|
|
2
4
|
|
|
3
5
|
// ---------- Facebook ----------
|
|
@@ -40,6 +42,9 @@ class SocialNetwork {
|
|
|
40
42
|
/*
|
|
41
43
|
<a target="_blank" title="Twitter" href="https://twitter.com/share?url=<?php the_permalink(); ?>&text=<?php the_title(); ?>&via=korben" rel="nofollow" onclick="javascript:window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=400,width=700');return false;"><img src="http://korben.info/wp-content/themes/korben2013/hab/twitter_icon.png" alt="Twitter" /></a>
|
|
42
44
|
*/
|
|
45
|
+
if (typeof text === 'string' && typeof text.escapeHtml === 'function') {
|
|
46
|
+
text = text.escapeHtml();
|
|
47
|
+
}
|
|
43
48
|
return ''
|
|
44
49
|
+'<a href="https://twitter.com/share" class="twitter-share-button" data-url="'+url+'" data-text="'+text+'" data-lang="'+lang+'" data-hashtags="'+hashtags+'">Tweeter</a>'
|
|
45
50
|
+'<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>'
|
package/string.js
CHANGED
|
@@ -31,7 +31,7 @@ String.prototype.reverseString = String.prototype.reverseString || function() {
|
|
|
31
31
|
|
|
32
32
|
String.prototype.truncateOnWord = String.prototype.truncateOnWord || function(limit, fromLeft) {
|
|
33
33
|
if (fromLeft) {
|
|
34
|
-
return this.reverseString(
|
|
34
|
+
return this.reverseString().truncateOnWord(limit, false).reverseString();
|
|
35
35
|
}
|
|
36
36
|
let TRIM_CHARS = '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u3000\uFEFF';
|
|
37
37
|
let words = this.split(RegExp('(?=['+TRIM_CHARS+'])'));
|
|
@@ -62,15 +62,24 @@ String.prototype.truncateString = String.prototype.truncateString || function(le
|
|
|
62
62
|
switch(from) {
|
|
63
63
|
case 'left':
|
|
64
64
|
str2 = split ? this.truncateOnWord(length, true) : this.slice(this.length - length);
|
|
65
|
+
if (split) {
|
|
66
|
+
str2 = str2.trimStart();
|
|
67
|
+
return ellipsis + ' ' + str2;
|
|
68
|
+
}
|
|
65
69
|
return ellipsis + str2;
|
|
66
70
|
case 'middle':
|
|
67
71
|
len1 = Math.ceil(length / 2);
|
|
68
72
|
len2 = Math.floor(length / 2);
|
|
69
73
|
str1 = split ? this.truncateOnWord(len1) : this.slice(0, len1);
|
|
70
74
|
str2 = split ? this.truncateOnWord(len2, true) : this.slice(this.length - len2);
|
|
71
|
-
|
|
75
|
+
// Enlever les espaces de fin de str1 et de début de str2 pour éviter les doubles espaces
|
|
76
|
+
return str1.trimEnd() + ' ' + ellipsis + ' ' + str2.trimStart();
|
|
72
77
|
default:
|
|
73
78
|
str1 = split ? this.truncateOnWord(length) : this.slice(0, length);
|
|
79
|
+
if (split) {
|
|
80
|
+
str1 = str1.trimEnd();
|
|
81
|
+
return str1 + ' ' + ellipsis;
|
|
82
|
+
}
|
|
74
83
|
return str1 + ellipsis;
|
|
75
84
|
}
|
|
76
85
|
};
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
const { Chartjs } = require('../chartjs');
|
|
2
|
+
|
|
3
|
+
describe('Chartjs', () => {
|
|
4
|
+
describe('groupByPeriod', () => {
|
|
5
|
+
test('should group data by day (default)', () => {
|
|
6
|
+
const data = {
|
|
7
|
+
'2024-01-15': { views: 10, clicks: 5 },
|
|
8
|
+
'2024-01-16': { views: 20, clicks: 8 },
|
|
9
|
+
'2024-01-17': { views: 15, clicks: 6 }
|
|
10
|
+
};
|
|
11
|
+
const metrics = ['views', 'clicks'];
|
|
12
|
+
|
|
13
|
+
const result = Chartjs.groupByPeriod(data, 'day', metrics);
|
|
14
|
+
|
|
15
|
+
expect(result).toHaveLength(3);
|
|
16
|
+
expect(result[0]).toEqual({ label: '2024-01-15', views: 10, clicks: 5 });
|
|
17
|
+
expect(result[1]).toEqual({ label: '2024-01-16', views: 20, clicks: 8 });
|
|
18
|
+
expect(result[2]).toEqual({ label: '2024-01-17', views: 15, clicks: 6 });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('should group data by month', () => {
|
|
22
|
+
const data = {
|
|
23
|
+
'2024-01-15': { views: 10, clicks: 5 },
|
|
24
|
+
'2024-01-20': { views: 20, clicks: 8 },
|
|
25
|
+
'2024-02-05': { views: 15, clicks: 6 }
|
|
26
|
+
};
|
|
27
|
+
const metrics = ['views', 'clicks'];
|
|
28
|
+
|
|
29
|
+
const result = Chartjs.groupByPeriod(data, 'month', metrics);
|
|
30
|
+
|
|
31
|
+
expect(result).toHaveLength(2);
|
|
32
|
+
expect(result[0].label).toBe('2024-01');
|
|
33
|
+
expect(result[0].views).toBe(15); // Average of 10 and 20
|
|
34
|
+
expect(result[0].clicks).toBe(6.5); // Average of 5 and 8
|
|
35
|
+
expect(result[1].label).toBe('2024-02');
|
|
36
|
+
expect(result[1].views).toBe(15);
|
|
37
|
+
expect(result[1].clicks).toBe(6);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('should group data by week', () => {
|
|
41
|
+
const data = {
|
|
42
|
+
'2024-01-01': { views: 10 },
|
|
43
|
+
'2024-01-02': { views: 20 },
|
|
44
|
+
'2024-01-08': { views: 15 },
|
|
45
|
+
'2024-01-09': { views: 25 }
|
|
46
|
+
};
|
|
47
|
+
const metrics = ['views'];
|
|
48
|
+
|
|
49
|
+
const result = Chartjs.groupByPeriod(data, 'week', metrics);
|
|
50
|
+
|
|
51
|
+
// Results should be grouped by weeks
|
|
52
|
+
expect(result.length).toBeGreaterThan(0);
|
|
53
|
+
// Check that labels contain week format (YYYY-SX)
|
|
54
|
+
result.forEach(item => {
|
|
55
|
+
expect(item.label).toMatch(/^\d{4}-S\d+$/);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('should handle single metric', () => {
|
|
60
|
+
const data = {
|
|
61
|
+
'2024-01-15': { views: 10 },
|
|
62
|
+
'2024-01-16': { views: 20 }
|
|
63
|
+
};
|
|
64
|
+
const metrics = ['views'];
|
|
65
|
+
|
|
66
|
+
const result = Chartjs.groupByPeriod(data, 'day', metrics);
|
|
67
|
+
|
|
68
|
+
expect(result).toHaveLength(2);
|
|
69
|
+
expect(result[0]).toEqual({ label: '2024-01-15', views: 10 });
|
|
70
|
+
expect(result[1]).toEqual({ label: '2024-01-16', views: 20 });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('should handle multiple metrics', () => {
|
|
74
|
+
const data = {
|
|
75
|
+
'2024-01-15': { views: 10, clicks: 5, conversions: 2 }
|
|
76
|
+
};
|
|
77
|
+
const metrics = ['views', 'clicks', 'conversions'];
|
|
78
|
+
|
|
79
|
+
const result = Chartjs.groupByPeriod(data, 'day', metrics);
|
|
80
|
+
|
|
81
|
+
expect(result).toHaveLength(1);
|
|
82
|
+
expect(result[0]).toEqual({
|
|
83
|
+
label: '2024-01-15',
|
|
84
|
+
views: 10,
|
|
85
|
+
clicks: 5,
|
|
86
|
+
conversions: 2
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('should handle empty data', () => {
|
|
91
|
+
const data = {};
|
|
92
|
+
const metrics = ['views'];
|
|
93
|
+
|
|
94
|
+
const result = Chartjs.groupByPeriod(data, 'day', metrics);
|
|
95
|
+
|
|
96
|
+
expect(result).toEqual([]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('should average values when grouping by month', () => {
|
|
100
|
+
const data = {
|
|
101
|
+
'2024-01-10': { score: 100 },
|
|
102
|
+
'2024-01-15': { score: 200 },
|
|
103
|
+
'2024-01-20': { score: 300 }
|
|
104
|
+
};
|
|
105
|
+
const metrics = ['score'];
|
|
106
|
+
|
|
107
|
+
const result = Chartjs.groupByPeriod(data, 'month', metrics);
|
|
108
|
+
|
|
109
|
+
expect(result).toHaveLength(1);
|
|
110
|
+
expect(result[0].label).toBe('2024-01');
|
|
111
|
+
expect(result[0].score).toBe(200); // Average of 100, 200, 300
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('should handle missing metric values', () => {
|
|
115
|
+
const data = {
|
|
116
|
+
'2024-01-15': { views: 10 },
|
|
117
|
+
'2024-01-16': { views: 20, clicks: 5 }
|
|
118
|
+
};
|
|
119
|
+
const metrics = ['views', 'clicks'];
|
|
120
|
+
|
|
121
|
+
const result = Chartjs.groupByPeriod(data, 'day', metrics);
|
|
122
|
+
|
|
123
|
+
expect(result).toHaveLength(2);
|
|
124
|
+
expect(result[0]).toEqual({ label: '2024-01-15', views: 10, clicks: NaN });
|
|
125
|
+
expect(result[1]).toEqual({ label: '2024-01-16', views: 20, clicks: 5 });
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('should handle dates across different years', () => {
|
|
129
|
+
const data = {
|
|
130
|
+
'2023-12-30': { count: 5 },
|
|
131
|
+
'2024-01-02': { count: 10 }
|
|
132
|
+
};
|
|
133
|
+
const metrics = ['count'];
|
|
134
|
+
|
|
135
|
+
const result = Chartjs.groupByPeriod(data, 'month', metrics);
|
|
136
|
+
|
|
137
|
+
expect(result).toHaveLength(2);
|
|
138
|
+
expect(result[0].label).toBe('2023-12');
|
|
139
|
+
expect(result[1].label).toBe('2024-01');
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('getAutoGranularity', () => {
|
|
144
|
+
test('should return day_of_month for data spanning 30 days or less', () => {
|
|
145
|
+
const data = {
|
|
146
|
+
'2024-01-01': {},
|
|
147
|
+
'2024-01-15': {},
|
|
148
|
+
'2024-01-30': {}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const result = Chartjs.getAutoGranularity(data);
|
|
152
|
+
|
|
153
|
+
expect(result).toBe('day_of_month');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('should return week for data spanning 31-90 days', () => {
|
|
157
|
+
const data = {
|
|
158
|
+
'2024-01-01': {},
|
|
159
|
+
'2024-03-15': {} // 74 days
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const result = Chartjs.getAutoGranularity(data);
|
|
163
|
+
|
|
164
|
+
expect(result).toBe('week');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('should return month for data spanning more than 90 days', () => {
|
|
168
|
+
const data = {
|
|
169
|
+
'2024-01-01': {},
|
|
170
|
+
'2024-05-01': {} // 121 days
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const result = Chartjs.getAutoGranularity(data);
|
|
174
|
+
|
|
175
|
+
expect(result).toBe('month');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('should return day_of_month for single day', () => {
|
|
179
|
+
const data = {
|
|
180
|
+
'2024-01-01': {}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const result = Chartjs.getAutoGranularity(data);
|
|
184
|
+
|
|
185
|
+
expect(result).toBe('day_of_month');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('should return day_of_month for 2 days', () => {
|
|
189
|
+
const data = {
|
|
190
|
+
'2024-01-01': {},
|
|
191
|
+
'2024-01-02': {}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const result = Chartjs.getAutoGranularity(data);
|
|
195
|
+
|
|
196
|
+
expect(result).toBe('day_of_month');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('should return week for exactly 31 days', () => {
|
|
200
|
+
const data = {
|
|
201
|
+
'2024-01-01': {},
|
|
202
|
+
'2024-02-01': {} // 31 days
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const result = Chartjs.getAutoGranularity(data);
|
|
206
|
+
|
|
207
|
+
expect(result).toBe('week');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('should return week for exactly 90 days', () => {
|
|
211
|
+
const data = {
|
|
212
|
+
'2024-01-01': {},
|
|
213
|
+
'2024-03-31': {} // 90 days
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const result = Chartjs.getAutoGranularity(data);
|
|
217
|
+
|
|
218
|
+
expect(result).toBe('week');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test('should return month for exactly 91 days', () => {
|
|
222
|
+
const data = {
|
|
223
|
+
'2024-01-01': {},
|
|
224
|
+
'2024-04-01': {} // 91 days
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const result = Chartjs.getAutoGranularity(data);
|
|
228
|
+
|
|
229
|
+
expect(result).toBe('month');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test('should handle dates in random order', () => {
|
|
233
|
+
const data = {
|
|
234
|
+
'2024-03-01': {},
|
|
235
|
+
'2024-01-01': {},
|
|
236
|
+
'2024-02-01': {}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const result = Chartjs.getAutoGranularity(data);
|
|
240
|
+
|
|
241
|
+
// Should calculate from first to last date (Jan 1 to Mar 1 = ~59 days)
|
|
242
|
+
expect(result).toBe('week');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test('should return month for data spanning a full year', () => {
|
|
246
|
+
const data = {
|
|
247
|
+
'2024-01-01': {},
|
|
248
|
+
'2024-12-31': {} // 365 days
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const result = Chartjs.getAutoGranularity(data);
|
|
252
|
+
|
|
253
|
+
expect(result).toBe('month');
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe('Chartjs class structure', () => {
|
|
258
|
+
test('should be a class', () => {
|
|
259
|
+
expect(typeof Chartjs).toBe('function');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('should have all expected static methods', () => {
|
|
263
|
+
expect(typeof Chartjs.init).toBe('function');
|
|
264
|
+
expect(typeof Chartjs.createStackedChart).toBe('function');
|
|
265
|
+
expect(typeof Chartjs.createBarChart).toBe('function');
|
|
266
|
+
expect(typeof Chartjs.createLineChart).toBe('function');
|
|
267
|
+
expect(typeof Chartjs.createDoughnutChart).toBe('function');
|
|
268
|
+
expect(typeof Chartjs.groupByPeriod).toBe('function');
|
|
269
|
+
expect(typeof Chartjs.getPeriodLabels).toBe('function');
|
|
270
|
+
expect(typeof Chartjs.getAutoGranularity).toBe('function');
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
});
|