@fkui/logic 6.18.0 → 6.18.1
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/lib/cjs/index.js +58 -33
- package/lib/esm/index.js +58 -33
- package/package.json +3 -3
package/lib/cjs/index.js
CHANGED
|
@@ -198,6 +198,7 @@ class DecoratedError extends Error {
|
|
|
198
198
|
constructor(message, cause) {
|
|
199
199
|
super(message);
|
|
200
200
|
Object.setPrototypeOf(this, DecoratedError.prototype);
|
|
201
|
+
/* eslint-disable-next-line @typescript-eslint/restrict-plus-operands -- technical debt */
|
|
201
202
|
this.stack += `\nCaused by: ${cause.stack}`;
|
|
202
203
|
this.cause = cause;
|
|
203
204
|
}
|
|
@@ -220,7 +221,7 @@ class DecoratedError extends Error {
|
|
|
220
221
|
}
|
|
221
222
|
}
|
|
222
223
|
|
|
223
|
-
const DATE_REGEXP_WITH_DASH =
|
|
224
|
+
const DATE_REGEXP_WITH_DASH = /^\d{4}-\d{2}-\d{2}$/;
|
|
224
225
|
/**
|
|
225
226
|
* @public
|
|
226
227
|
*/
|
|
@@ -268,6 +269,7 @@ function validLimit(limit) {
|
|
|
268
269
|
* @param immediate - Whether the function should be called at the beginning of the delay (Before the timeout) instead of the end.
|
|
269
270
|
* Default is false.
|
|
270
271
|
*/
|
|
272
|
+
/* eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- technical debt */
|
|
271
273
|
function debounce(func, delay, immediate = false) {
|
|
272
274
|
let timeout = null;
|
|
273
275
|
return function functionToExecute(...args) {
|
|
@@ -2108,7 +2110,7 @@ function deepClone(value) {
|
|
|
2108
2110
|
* @param prefix - If set this is prefixed to all keys.
|
|
2109
2111
|
*/
|
|
2110
2112
|
function flatten(src, destination, prefix = "") {
|
|
2111
|
-
destination = destination
|
|
2113
|
+
destination = destination ?? {};
|
|
2112
2114
|
return Object.entries(src).reduce((result, [key, data]) => {
|
|
2113
2115
|
const nestedKey = `${prefix}${key}`;
|
|
2114
2116
|
if (typeof data === "string") {
|
|
@@ -2148,7 +2150,7 @@ function normalizeDateFormat(value) {
|
|
|
2148
2150
|
const match = supportedFormats
|
|
2149
2151
|
.map((pattern) => value.match(pattern))
|
|
2150
2152
|
.find(Boolean);
|
|
2151
|
-
if (!match
|
|
2153
|
+
if (!match?.groups) {
|
|
2152
2154
|
return undefined;
|
|
2153
2155
|
}
|
|
2154
2156
|
const { year, month, day } = match.groups;
|
|
@@ -2164,7 +2166,7 @@ function normalizeDateFormat(value) {
|
|
|
2164
2166
|
*/
|
|
2165
2167
|
function testLuhnChecksum(inputString) {
|
|
2166
2168
|
let sum = 0;
|
|
2167
|
-
if (
|
|
2169
|
+
if (/^\d+$/.test(inputString) === false) {
|
|
2168
2170
|
throw new Error("Luhn Checksum test only works on strings containing numbers");
|
|
2169
2171
|
}
|
|
2170
2172
|
inputString
|
|
@@ -2242,7 +2244,7 @@ function parseBankAccountNumber(value) {
|
|
|
2242
2244
|
: undefined;
|
|
2243
2245
|
}
|
|
2244
2246
|
|
|
2245
|
-
const BANKGIRO_REGEXP_HYPHEN = /^(\d{3,4})
|
|
2247
|
+
const BANKGIRO_REGEXP_HYPHEN = /^(\d{3,4})-?(\d{4})$/;
|
|
2246
2248
|
/**
|
|
2247
2249
|
* @public
|
|
2248
2250
|
*/
|
|
@@ -2250,6 +2252,7 @@ function parseBankgiro(value) {
|
|
|
2250
2252
|
if (isEmpty(value)) {
|
|
2251
2253
|
return undefined;
|
|
2252
2254
|
}
|
|
2255
|
+
/* eslint-disable-next-line @typescript-eslint/prefer-regexp-exec -- technical debt */
|
|
2253
2256
|
const match = value.match(BANKGIRO_REGEXP_HYPHEN);
|
|
2254
2257
|
if (!match) {
|
|
2255
2258
|
return undefined;
|
|
@@ -2550,8 +2553,9 @@ class FDate {
|
|
|
2550
2553
|
* @public
|
|
2551
2554
|
*/
|
|
2552
2555
|
static fromIso(value) {
|
|
2556
|
+
/* eslint-disable-next-line @typescript-eslint/prefer-regexp-exec -- technical debt */
|
|
2553
2557
|
const match = value.match(/^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/);
|
|
2554
|
-
if (match
|
|
2558
|
+
if (match?.groups) {
|
|
2555
2559
|
const date = new FDate(value);
|
|
2556
2560
|
const { month } = match.groups;
|
|
2557
2561
|
/* in dayjs (and native Date) when the day overflows (e.g. 31 feb)
|
|
@@ -2958,7 +2962,7 @@ function stripWhitespace(text) {
|
|
|
2958
2962
|
return text.replace(/\s+/g, "");
|
|
2959
2963
|
}
|
|
2960
2964
|
|
|
2961
|
-
const NUMBER_REGEXP$1 = /^(
|
|
2965
|
+
const NUMBER_REGEXP$1 = /^(-?\d+)([,.]\d+)?$/;
|
|
2962
2966
|
function replaceCommaWithDot(str) {
|
|
2963
2967
|
return str.replace(",", ".");
|
|
2964
2968
|
}
|
|
@@ -3041,7 +3045,7 @@ function resolveCentury(year, month, day, hasPlus, now) {
|
|
|
3041
3045
|
return (Number(nowCentury) - subtractCenturies).toString();
|
|
3042
3046
|
}
|
|
3043
3047
|
|
|
3044
|
-
const PERSONNUMMER_REGEXP = /^(?<century>\d{2})?(?<year>\d{2})(?<month>\d{2})(?<day>\d{2})(?<sign
|
|
3048
|
+
const PERSONNUMMER_REGEXP = /^(?<century>\d{2})?(?<year>\d{2})(?<month>\d{2})(?<day>\d{2})(?<sign>[-+])?(?<check>\d{4})$/;
|
|
3045
3049
|
function getDayWithoutSamordning(day) {
|
|
3046
3050
|
return (Number(day) % 60).toString().padStart(2, "0");
|
|
3047
3051
|
}
|
|
@@ -3069,6 +3073,7 @@ function parsePersonnummer(value, now = FDate.now()) {
|
|
|
3069
3073
|
if (!isSet(value)) {
|
|
3070
3074
|
return undefined;
|
|
3071
3075
|
}
|
|
3076
|
+
/* eslint-disable-next-line @typescript-eslint/prefer-regexp-exec -- technical debt */
|
|
3072
3077
|
const match = stripWhitespace(value).match(PERSONNUMMER_REGEXP);
|
|
3073
3078
|
if (!match) {
|
|
3074
3079
|
return undefined;
|
|
@@ -3125,14 +3130,14 @@ function formatPersonnummer(value) {
|
|
|
3125
3130
|
* @public
|
|
3126
3131
|
*/
|
|
3127
3132
|
function formatPersonnummerToDate(value) {
|
|
3128
|
-
const datePart = parseDate(parsePersonnummer(value)?.slice(0, 8)
|
|
3133
|
+
const datePart = parseDate(parsePersonnummer(value)?.slice(0, 8) ?? "");
|
|
3129
3134
|
if (!datePart) {
|
|
3130
3135
|
return undefined;
|
|
3131
3136
|
}
|
|
3132
3137
|
return FDate.fromIso(datePart);
|
|
3133
3138
|
}
|
|
3134
3139
|
|
|
3135
|
-
const PLUSGIRO_REGEXP = /^\d{1,7}
|
|
3140
|
+
const PLUSGIRO_REGEXP = /^\d{1,7}-?\d$/;
|
|
3136
3141
|
function hyphenShouldBeAdded(value) {
|
|
3137
3142
|
return value.length >= 2 && value.length <= 8;
|
|
3138
3143
|
}
|
|
@@ -3172,7 +3177,7 @@ function parsePlusgiro(value) {
|
|
|
3172
3177
|
/**
|
|
3173
3178
|
* @internal
|
|
3174
3179
|
*/
|
|
3175
|
-
const POSTAL_CODE_REGEXP = /^([1-9]
|
|
3180
|
+
const POSTAL_CODE_REGEXP = /^([1-9]\d{2}) ?(\d{2})$/;
|
|
3176
3181
|
/**
|
|
3177
3182
|
* @public
|
|
3178
3183
|
*/
|
|
@@ -3180,6 +3185,7 @@ function formatPostalCode(value) {
|
|
|
3180
3185
|
if (isEmpty(value)) {
|
|
3181
3186
|
return undefined;
|
|
3182
3187
|
}
|
|
3188
|
+
/* eslint-disable-next-line @typescript-eslint/prefer-regexp-exec -- technical debt */
|
|
3183
3189
|
const match = value.match(POSTAL_CODE_REGEXP);
|
|
3184
3190
|
if (match === null) {
|
|
3185
3191
|
return undefined;
|
|
@@ -3193,7 +3199,7 @@ function parsePostalCode(value) {
|
|
|
3193
3199
|
return formatPostalCode(value);
|
|
3194
3200
|
}
|
|
3195
3201
|
|
|
3196
|
-
const ORGANISATIONSNUMMER_REGEXP = /^(
|
|
3202
|
+
const ORGANISATIONSNUMMER_REGEXP = /^(\d{6})-?(\d{4})$/;
|
|
3197
3203
|
/**
|
|
3198
3204
|
* @public
|
|
3199
3205
|
*/
|
|
@@ -3201,6 +3207,7 @@ function parseOrganisationsnummer(value) {
|
|
|
3201
3207
|
if (isEmpty(value)) {
|
|
3202
3208
|
return undefined;
|
|
3203
3209
|
}
|
|
3210
|
+
/* eslint-disable-next-line @typescript-eslint/prefer-regexp-exec -- technical debt */
|
|
3204
3211
|
const match = value.match(ORGANISATIONSNUMMER_REGEXP);
|
|
3205
3212
|
if (!match) {
|
|
3206
3213
|
return undefined;
|
|
@@ -3221,6 +3228,7 @@ function formatPercent(modelValue, decimals) {
|
|
|
3221
3228
|
* @public
|
|
3222
3229
|
*/
|
|
3223
3230
|
function parsePercent(viewValue) {
|
|
3231
|
+
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- technical debt */
|
|
3224
3232
|
return parseNumber(viewValue);
|
|
3225
3233
|
}
|
|
3226
3234
|
|
|
@@ -3302,7 +3310,7 @@ function scrollTo(element, arg = 0) {
|
|
|
3302
3310
|
return Promise.resolve();
|
|
3303
3311
|
}
|
|
3304
3312
|
else {
|
|
3305
|
-
return scrollToSlow(element, arg.duration
|
|
3313
|
+
return scrollToSlow(element, arg.duration ?? 500, arg.offset ?? 0);
|
|
3306
3314
|
}
|
|
3307
3315
|
}
|
|
3308
3316
|
/**
|
|
@@ -3605,6 +3613,7 @@ function isValidatableFormElement(element) {
|
|
|
3605
3613
|
*/
|
|
3606
3614
|
function isVisibleInViewport(element) {
|
|
3607
3615
|
const rect = element.getBoundingClientRect();
|
|
3616
|
+
/* eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion -- technical debt */
|
|
3608
3617
|
return Boolean(rect.top >= 0 &&
|
|
3609
3618
|
rect.left >= 0 &&
|
|
3610
3619
|
rect.bottom <=
|
|
@@ -3796,8 +3805,10 @@ class DefaultTranslationProvider {
|
|
|
3796
3805
|
get currentLanguage() {
|
|
3797
3806
|
return this.language;
|
|
3798
3807
|
}
|
|
3799
|
-
|
|
3808
|
+
/* @todo technical debt, changeLanguage should accept just void */
|
|
3809
|
+
changeLanguage(language) {
|
|
3800
3810
|
this.language = language;
|
|
3811
|
+
return Promise.resolve();
|
|
3801
3812
|
}
|
|
3802
3813
|
translate(key, defaultValueOrArgs, args) {
|
|
3803
3814
|
if (this.language === "cimode") {
|
|
@@ -3958,8 +3969,9 @@ function getErrorMessages() {
|
|
|
3958
3969
|
* @internal
|
|
3959
3970
|
*/
|
|
3960
3971
|
function createFieldsetValidator(element, validationService) {
|
|
3961
|
-
/* eslint-disable-next-line no-new
|
|
3962
|
-
* refactored as to not rely of side-effects
|
|
3972
|
+
/* eslint-disable-next-line no-new, sonarjs/constructor-for-side-effects --
|
|
3973
|
+
* technical debt, this should be refactored as to not rely of side-effects
|
|
3974
|
+
* of the constructor */
|
|
3963
3975
|
new FieldsetValidationHandler(element, validationService);
|
|
3964
3976
|
}
|
|
3965
3977
|
class FieldsetValidationHandler {
|
|
@@ -3971,7 +3983,9 @@ class FieldsetValidationHandler {
|
|
|
3971
3983
|
Object.assign(this);
|
|
3972
3984
|
this.element = element;
|
|
3973
3985
|
this.validationService = validationService;
|
|
3974
|
-
element.addEventListener("focusin", (event) =>
|
|
3986
|
+
element.addEventListener("focusin", (event) => {
|
|
3987
|
+
this.onFocusIn(event);
|
|
3988
|
+
});
|
|
3975
3989
|
// Handle checking of input by using keyboard (space)
|
|
3976
3990
|
element.addEventListener("change", this.documentFocusIn.bind(this));
|
|
3977
3991
|
Array.from(this.element.querySelectorAll("input[type='checkbox'], input[type='radio']"))
|
|
@@ -4066,9 +4080,10 @@ function getElementType(element) {
|
|
|
4066
4080
|
if (element instanceof HTMLInputElement) {
|
|
4067
4081
|
return element.type === "checkbox"
|
|
4068
4082
|
? "checkbox"
|
|
4069
|
-
:
|
|
4070
|
-
|
|
4071
|
-
|
|
4083
|
+
: /* eslint-disable-next-line sonarjs/no-nested-conditional -- technical debt */
|
|
4084
|
+
element.type === "radio"
|
|
4085
|
+
? "radio"
|
|
4086
|
+
: "text";
|
|
4072
4087
|
}
|
|
4073
4088
|
else if (element instanceof HTMLTextAreaElement) {
|
|
4074
4089
|
return "textarea";
|
|
@@ -4263,6 +4278,7 @@ class ValidationServiceImpl {
|
|
|
4263
4278
|
});
|
|
4264
4279
|
});
|
|
4265
4280
|
}
|
|
4281
|
+
/* eslint-disable-next-line @typescript-eslint/require-await -- technical debt */
|
|
4266
4282
|
async isValid(src, root = document) {
|
|
4267
4283
|
/* nest inner sync function mostly because the code itself is sync
|
|
4268
4284
|
* (especially required by Array.every) but we still want the API to be
|
|
@@ -4349,6 +4365,7 @@ class ValidationServiceImpl {
|
|
|
4349
4365
|
const tagName = element.tagName.toLowerCase();
|
|
4350
4366
|
const ref = `${tagName}#${element.id}`;
|
|
4351
4367
|
element.removeEventListener("validity", once);
|
|
4368
|
+
/* eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- technical debt */
|
|
4352
4369
|
reject(`Element "${ref}" did not respond with validity event after 500ms`);
|
|
4353
4370
|
}, 500);
|
|
4354
4371
|
/* Dispatch request to validate, this is supposed to be picked up by
|
|
@@ -4449,7 +4466,9 @@ class ValidationServiceImpl {
|
|
|
4449
4466
|
return foundValidators.some((validator) => {
|
|
4450
4467
|
const config = validatorConfigs[validator.name];
|
|
4451
4468
|
const instantConfig = isSet(config) ? config.instant : undefined;
|
|
4452
|
-
return (
|
|
4469
|
+
return (
|
|
4470
|
+
/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- false positive */
|
|
4471
|
+
(validator.instant && instantConfig !== false) ||
|
|
4453
4472
|
instantConfig === true);
|
|
4454
4473
|
});
|
|
4455
4474
|
}
|
|
@@ -4517,9 +4536,7 @@ class ValidationServiceImpl {
|
|
|
4517
4536
|
if (element instanceof HTMLDivElement) {
|
|
4518
4537
|
return false;
|
|
4519
4538
|
}
|
|
4520
|
-
return Boolean(isRadiobuttonOrCheckbox(element)
|
|
4521
|
-
? element.checked
|
|
4522
|
-
: element.value);
|
|
4539
|
+
return Boolean(isRadiobuttonOrCheckbox(element) ? element.checked : element.value);
|
|
4523
4540
|
}
|
|
4524
4541
|
getValue(element) {
|
|
4525
4542
|
if ("value" in element) {
|
|
@@ -4560,7 +4577,7 @@ class ValidationServiceImpl {
|
|
|
4560
4577
|
}
|
|
4561
4578
|
}
|
|
4562
4579
|
validate(value, element, validator, validatorConfigs) {
|
|
4563
|
-
const validatorConfig = validatorConfigs[validator.name]
|
|
4580
|
+
const validatorConfig = validatorConfigs[validator.name] ?? {};
|
|
4564
4581
|
const isEnabled = validatorConfig.enabled === undefined ||
|
|
4565
4582
|
validatorConfig.enabled === true;
|
|
4566
4583
|
/**
|
|
@@ -4711,13 +4728,16 @@ const decimalValidator = {
|
|
|
4711
4728
|
name: "decimal",
|
|
4712
4729
|
validation(value, _element, config) {
|
|
4713
4730
|
const valueWithoutWhitespace = isSet(value)
|
|
4714
|
-
?
|
|
4731
|
+
? /* eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion -- technical debt */
|
|
4732
|
+
stripWhitespace(String(value))
|
|
4715
4733
|
: value;
|
|
4716
4734
|
const minDecimalsAsNumber = isSet(config.minDecimals)
|
|
4717
|
-
?
|
|
4735
|
+
? /* eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion -- technical debt */
|
|
4736
|
+
Number(config.minDecimals)
|
|
4718
4737
|
: undefined;
|
|
4719
4738
|
const maxDecimalsAsNumber = isSet(config.maxDecimals)
|
|
4720
|
-
?
|
|
4739
|
+
? /* eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion -- technical debt */
|
|
4740
|
+
Number(config.maxDecimals)
|
|
4721
4741
|
: undefined;
|
|
4722
4742
|
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- technical debt, should actually verfiy value instead */
|
|
4723
4743
|
if (config.minDecimals && isNaN(minDecimalsAsNumber)) {
|
|
@@ -4735,7 +4755,7 @@ const decimalValidator = {
|
|
|
4735
4755
|
const emailValidator = {
|
|
4736
4756
|
name: "email",
|
|
4737
4757
|
validation(value, _element, config) {
|
|
4738
|
-
const maxLength = config.maxLength
|
|
4758
|
+
const maxLength = config.maxLength ?? 254;
|
|
4739
4759
|
const EMAIL_REGEXP = new RegExp(`^(?=.{1,${maxLength}}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_\`a-z{|}~åäöÅÄÖ]+(\\.[-!#$%&'*+/0-9=?A-Z^_\`a-z{|}~åäöÅÄÖ]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$`);
|
|
4740
4760
|
return isEmpty(value) || EMAIL_REGEXP.test(value);
|
|
4741
4761
|
},
|
|
@@ -4744,6 +4764,7 @@ const emailValidator = {
|
|
|
4744
4764
|
/**
|
|
4745
4765
|
* @internal
|
|
4746
4766
|
*/
|
|
4767
|
+
/* eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- technical debt */
|
|
4747
4768
|
function numberValidator$1(value, config, name, compare) {
|
|
4748
4769
|
if (value === "") {
|
|
4749
4770
|
return true;
|
|
@@ -4770,12 +4791,13 @@ const greaterThanValidator = {
|
|
|
4770
4791
|
},
|
|
4771
4792
|
};
|
|
4772
4793
|
|
|
4773
|
-
const NUMBER_REGEXP = /^([-−]
|
|
4794
|
+
const NUMBER_REGEXP = /^([-−]?\d+)?$/;
|
|
4774
4795
|
const integerValidator = {
|
|
4775
4796
|
name: "integer",
|
|
4776
4797
|
validation(value) {
|
|
4777
4798
|
const valueWithoutWhitespace = isSet(value)
|
|
4778
|
-
?
|
|
4799
|
+
? /* eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion -- technical debt */
|
|
4800
|
+
stripWhitespace(String(value))
|
|
4779
4801
|
: value;
|
|
4780
4802
|
return (isEmpty(valueWithoutWhitespace) ||
|
|
4781
4803
|
NUMBER_REGEXP.test(valueWithoutWhitespace));
|
|
@@ -4921,12 +4943,13 @@ const organisationsnummerValidator = {
|
|
|
4921
4943
|
},
|
|
4922
4944
|
};
|
|
4923
4945
|
|
|
4924
|
-
const PERCENT_REGEXP = /^([-+]
|
|
4946
|
+
const PERCENT_REGEXP = /^([-+]?\d+)([,.]\d+)?$/;
|
|
4925
4947
|
const percentValidator = {
|
|
4926
4948
|
name: "percent",
|
|
4927
4949
|
validation(value) {
|
|
4928
4950
|
const valueWithoutWhitespace = isSet(value)
|
|
4929
|
-
?
|
|
4951
|
+
? /* eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion -- technical debt */
|
|
4952
|
+
stripWhitespace(String(value))
|
|
4930
4953
|
: value;
|
|
4931
4954
|
return (isEmpty(valueWithoutWhitespace) ||
|
|
4932
4955
|
PERCENT_REGEXP.test(valueWithoutWhitespace));
|
|
@@ -4950,6 +4973,7 @@ const personnummerLuhnValidator = {
|
|
|
4950
4973
|
const personnummerNotSame = {
|
|
4951
4974
|
name: "personnummerNotSame",
|
|
4952
4975
|
validation(value, _element, config) {
|
|
4976
|
+
/* eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion -- technical debt */
|
|
4953
4977
|
const valuePnr = parsePersonnummer(String(value));
|
|
4954
4978
|
if (!valuePnr) {
|
|
4955
4979
|
return true;
|
|
@@ -5122,6 +5146,7 @@ function waitForScreenReader(callback, delay = SCREEN_READER_DELAY) {
|
|
|
5122
5146
|
resolve(result);
|
|
5123
5147
|
}
|
|
5124
5148
|
catch (err) {
|
|
5149
|
+
/* eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- technical debt */
|
|
5125
5150
|
reject(err);
|
|
5126
5151
|
}
|
|
5127
5152
|
}, delay);
|
package/lib/esm/index.js
CHANGED
|
@@ -196,6 +196,7 @@ class DecoratedError extends Error {
|
|
|
196
196
|
constructor(message, cause) {
|
|
197
197
|
super(message);
|
|
198
198
|
Object.setPrototypeOf(this, DecoratedError.prototype);
|
|
199
|
+
/* eslint-disable-next-line @typescript-eslint/restrict-plus-operands -- technical debt */
|
|
199
200
|
this.stack += `\nCaused by: ${cause.stack}`;
|
|
200
201
|
this.cause = cause;
|
|
201
202
|
}
|
|
@@ -218,7 +219,7 @@ class DecoratedError extends Error {
|
|
|
218
219
|
}
|
|
219
220
|
}
|
|
220
221
|
|
|
221
|
-
const DATE_REGEXP_WITH_DASH =
|
|
222
|
+
const DATE_REGEXP_WITH_DASH = /^\d{4}-\d{2}-\d{2}$/;
|
|
222
223
|
/**
|
|
223
224
|
* @public
|
|
224
225
|
*/
|
|
@@ -266,6 +267,7 @@ function validLimit(limit) {
|
|
|
266
267
|
* @param immediate - Whether the function should be called at the beginning of the delay (Before the timeout) instead of the end.
|
|
267
268
|
* Default is false.
|
|
268
269
|
*/
|
|
270
|
+
/* eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- technical debt */
|
|
269
271
|
function debounce(func, delay, immediate = false) {
|
|
270
272
|
let timeout = null;
|
|
271
273
|
return function functionToExecute(...args) {
|
|
@@ -2106,7 +2108,7 @@ function deepClone(value) {
|
|
|
2106
2108
|
* @param prefix - If set this is prefixed to all keys.
|
|
2107
2109
|
*/
|
|
2108
2110
|
function flatten(src, destination, prefix = "") {
|
|
2109
|
-
destination = destination
|
|
2111
|
+
destination = destination ?? {};
|
|
2110
2112
|
return Object.entries(src).reduce((result, [key, data]) => {
|
|
2111
2113
|
const nestedKey = `${prefix}${key}`;
|
|
2112
2114
|
if (typeof data === "string") {
|
|
@@ -2146,7 +2148,7 @@ function normalizeDateFormat(value) {
|
|
|
2146
2148
|
const match = supportedFormats
|
|
2147
2149
|
.map((pattern) => value.match(pattern))
|
|
2148
2150
|
.find(Boolean);
|
|
2149
|
-
if (!match
|
|
2151
|
+
if (!match?.groups) {
|
|
2150
2152
|
return undefined;
|
|
2151
2153
|
}
|
|
2152
2154
|
const { year, month, day } = match.groups;
|
|
@@ -2162,7 +2164,7 @@ function normalizeDateFormat(value) {
|
|
|
2162
2164
|
*/
|
|
2163
2165
|
function testLuhnChecksum(inputString) {
|
|
2164
2166
|
let sum = 0;
|
|
2165
|
-
if (
|
|
2167
|
+
if (/^\d+$/.test(inputString) === false) {
|
|
2166
2168
|
throw new Error("Luhn Checksum test only works on strings containing numbers");
|
|
2167
2169
|
}
|
|
2168
2170
|
inputString
|
|
@@ -2240,7 +2242,7 @@ function parseBankAccountNumber(value) {
|
|
|
2240
2242
|
: undefined;
|
|
2241
2243
|
}
|
|
2242
2244
|
|
|
2243
|
-
const BANKGIRO_REGEXP_HYPHEN = /^(\d{3,4})
|
|
2245
|
+
const BANKGIRO_REGEXP_HYPHEN = /^(\d{3,4})-?(\d{4})$/;
|
|
2244
2246
|
/**
|
|
2245
2247
|
* @public
|
|
2246
2248
|
*/
|
|
@@ -2248,6 +2250,7 @@ function parseBankgiro(value) {
|
|
|
2248
2250
|
if (isEmpty(value)) {
|
|
2249
2251
|
return undefined;
|
|
2250
2252
|
}
|
|
2253
|
+
/* eslint-disable-next-line @typescript-eslint/prefer-regexp-exec -- technical debt */
|
|
2251
2254
|
const match = value.match(BANKGIRO_REGEXP_HYPHEN);
|
|
2252
2255
|
if (!match) {
|
|
2253
2256
|
return undefined;
|
|
@@ -2548,8 +2551,9 @@ class FDate {
|
|
|
2548
2551
|
* @public
|
|
2549
2552
|
*/
|
|
2550
2553
|
static fromIso(value) {
|
|
2554
|
+
/* eslint-disable-next-line @typescript-eslint/prefer-regexp-exec -- technical debt */
|
|
2551
2555
|
const match = value.match(/^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/);
|
|
2552
|
-
if (match
|
|
2556
|
+
if (match?.groups) {
|
|
2553
2557
|
const date = new FDate(value);
|
|
2554
2558
|
const { month } = match.groups;
|
|
2555
2559
|
/* in dayjs (and native Date) when the day overflows (e.g. 31 feb)
|
|
@@ -2956,7 +2960,7 @@ function stripWhitespace(text) {
|
|
|
2956
2960
|
return text.replace(/\s+/g, "");
|
|
2957
2961
|
}
|
|
2958
2962
|
|
|
2959
|
-
const NUMBER_REGEXP$1 = /^(
|
|
2963
|
+
const NUMBER_REGEXP$1 = /^(-?\d+)([,.]\d+)?$/;
|
|
2960
2964
|
function replaceCommaWithDot(str) {
|
|
2961
2965
|
return str.replace(",", ".");
|
|
2962
2966
|
}
|
|
@@ -3039,7 +3043,7 @@ function resolveCentury(year, month, day, hasPlus, now) {
|
|
|
3039
3043
|
return (Number(nowCentury) - subtractCenturies).toString();
|
|
3040
3044
|
}
|
|
3041
3045
|
|
|
3042
|
-
const PERSONNUMMER_REGEXP = /^(?<century>\d{2})?(?<year>\d{2})(?<month>\d{2})(?<day>\d{2})(?<sign
|
|
3046
|
+
const PERSONNUMMER_REGEXP = /^(?<century>\d{2})?(?<year>\d{2})(?<month>\d{2})(?<day>\d{2})(?<sign>[-+])?(?<check>\d{4})$/;
|
|
3043
3047
|
function getDayWithoutSamordning(day) {
|
|
3044
3048
|
return (Number(day) % 60).toString().padStart(2, "0");
|
|
3045
3049
|
}
|
|
@@ -3067,6 +3071,7 @@ function parsePersonnummer(value, now = FDate.now()) {
|
|
|
3067
3071
|
if (!isSet(value)) {
|
|
3068
3072
|
return undefined;
|
|
3069
3073
|
}
|
|
3074
|
+
/* eslint-disable-next-line @typescript-eslint/prefer-regexp-exec -- technical debt */
|
|
3070
3075
|
const match = stripWhitespace(value).match(PERSONNUMMER_REGEXP);
|
|
3071
3076
|
if (!match) {
|
|
3072
3077
|
return undefined;
|
|
@@ -3123,14 +3128,14 @@ function formatPersonnummer(value) {
|
|
|
3123
3128
|
* @public
|
|
3124
3129
|
*/
|
|
3125
3130
|
function formatPersonnummerToDate(value) {
|
|
3126
|
-
const datePart = parseDate(parsePersonnummer(value)?.slice(0, 8)
|
|
3131
|
+
const datePart = parseDate(parsePersonnummer(value)?.slice(0, 8) ?? "");
|
|
3127
3132
|
if (!datePart) {
|
|
3128
3133
|
return undefined;
|
|
3129
3134
|
}
|
|
3130
3135
|
return FDate.fromIso(datePart);
|
|
3131
3136
|
}
|
|
3132
3137
|
|
|
3133
|
-
const PLUSGIRO_REGEXP = /^\d{1,7}
|
|
3138
|
+
const PLUSGIRO_REGEXP = /^\d{1,7}-?\d$/;
|
|
3134
3139
|
function hyphenShouldBeAdded(value) {
|
|
3135
3140
|
return value.length >= 2 && value.length <= 8;
|
|
3136
3141
|
}
|
|
@@ -3170,7 +3175,7 @@ function parsePlusgiro(value) {
|
|
|
3170
3175
|
/**
|
|
3171
3176
|
* @internal
|
|
3172
3177
|
*/
|
|
3173
|
-
const POSTAL_CODE_REGEXP = /^([1-9]
|
|
3178
|
+
const POSTAL_CODE_REGEXP = /^([1-9]\d{2}) ?(\d{2})$/;
|
|
3174
3179
|
/**
|
|
3175
3180
|
* @public
|
|
3176
3181
|
*/
|
|
@@ -3178,6 +3183,7 @@ function formatPostalCode(value) {
|
|
|
3178
3183
|
if (isEmpty(value)) {
|
|
3179
3184
|
return undefined;
|
|
3180
3185
|
}
|
|
3186
|
+
/* eslint-disable-next-line @typescript-eslint/prefer-regexp-exec -- technical debt */
|
|
3181
3187
|
const match = value.match(POSTAL_CODE_REGEXP);
|
|
3182
3188
|
if (match === null) {
|
|
3183
3189
|
return undefined;
|
|
@@ -3191,7 +3197,7 @@ function parsePostalCode(value) {
|
|
|
3191
3197
|
return formatPostalCode(value);
|
|
3192
3198
|
}
|
|
3193
3199
|
|
|
3194
|
-
const ORGANISATIONSNUMMER_REGEXP = /^(
|
|
3200
|
+
const ORGANISATIONSNUMMER_REGEXP = /^(\d{6})-?(\d{4})$/;
|
|
3195
3201
|
/**
|
|
3196
3202
|
* @public
|
|
3197
3203
|
*/
|
|
@@ -3199,6 +3205,7 @@ function parseOrganisationsnummer(value) {
|
|
|
3199
3205
|
if (isEmpty(value)) {
|
|
3200
3206
|
return undefined;
|
|
3201
3207
|
}
|
|
3208
|
+
/* eslint-disable-next-line @typescript-eslint/prefer-regexp-exec -- technical debt */
|
|
3202
3209
|
const match = value.match(ORGANISATIONSNUMMER_REGEXP);
|
|
3203
3210
|
if (!match) {
|
|
3204
3211
|
return undefined;
|
|
@@ -3219,6 +3226,7 @@ function formatPercent(modelValue, decimals) {
|
|
|
3219
3226
|
* @public
|
|
3220
3227
|
*/
|
|
3221
3228
|
function parsePercent(viewValue) {
|
|
3229
|
+
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- technical debt */
|
|
3222
3230
|
return parseNumber(viewValue);
|
|
3223
3231
|
}
|
|
3224
3232
|
|
|
@@ -3300,7 +3308,7 @@ function scrollTo(element, arg = 0) {
|
|
|
3300
3308
|
return Promise.resolve();
|
|
3301
3309
|
}
|
|
3302
3310
|
else {
|
|
3303
|
-
return scrollToSlow(element, arg.duration
|
|
3311
|
+
return scrollToSlow(element, arg.duration ?? 500, arg.offset ?? 0);
|
|
3304
3312
|
}
|
|
3305
3313
|
}
|
|
3306
3314
|
/**
|
|
@@ -3603,6 +3611,7 @@ function isValidatableFormElement(element) {
|
|
|
3603
3611
|
*/
|
|
3604
3612
|
function isVisibleInViewport(element) {
|
|
3605
3613
|
const rect = element.getBoundingClientRect();
|
|
3614
|
+
/* eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion -- technical debt */
|
|
3606
3615
|
return Boolean(rect.top >= 0 &&
|
|
3607
3616
|
rect.left >= 0 &&
|
|
3608
3617
|
rect.bottom <=
|
|
@@ -3794,8 +3803,10 @@ class DefaultTranslationProvider {
|
|
|
3794
3803
|
get currentLanguage() {
|
|
3795
3804
|
return this.language;
|
|
3796
3805
|
}
|
|
3797
|
-
|
|
3806
|
+
/* @todo technical debt, changeLanguage should accept just void */
|
|
3807
|
+
changeLanguage(language) {
|
|
3798
3808
|
this.language = language;
|
|
3809
|
+
return Promise.resolve();
|
|
3799
3810
|
}
|
|
3800
3811
|
translate(key, defaultValueOrArgs, args) {
|
|
3801
3812
|
if (this.language === "cimode") {
|
|
@@ -3956,8 +3967,9 @@ function getErrorMessages() {
|
|
|
3956
3967
|
* @internal
|
|
3957
3968
|
*/
|
|
3958
3969
|
function createFieldsetValidator(element, validationService) {
|
|
3959
|
-
/* eslint-disable-next-line no-new
|
|
3960
|
-
* refactored as to not rely of side-effects
|
|
3970
|
+
/* eslint-disable-next-line no-new, sonarjs/constructor-for-side-effects --
|
|
3971
|
+
* technical debt, this should be refactored as to not rely of side-effects
|
|
3972
|
+
* of the constructor */
|
|
3961
3973
|
new FieldsetValidationHandler(element, validationService);
|
|
3962
3974
|
}
|
|
3963
3975
|
class FieldsetValidationHandler {
|
|
@@ -3969,7 +3981,9 @@ class FieldsetValidationHandler {
|
|
|
3969
3981
|
Object.assign(this);
|
|
3970
3982
|
this.element = element;
|
|
3971
3983
|
this.validationService = validationService;
|
|
3972
|
-
element.addEventListener("focusin", (event) =>
|
|
3984
|
+
element.addEventListener("focusin", (event) => {
|
|
3985
|
+
this.onFocusIn(event);
|
|
3986
|
+
});
|
|
3973
3987
|
// Handle checking of input by using keyboard (space)
|
|
3974
3988
|
element.addEventListener("change", this.documentFocusIn.bind(this));
|
|
3975
3989
|
Array.from(this.element.querySelectorAll("input[type='checkbox'], input[type='radio']"))
|
|
@@ -4064,9 +4078,10 @@ function getElementType(element) {
|
|
|
4064
4078
|
if (element instanceof HTMLInputElement) {
|
|
4065
4079
|
return element.type === "checkbox"
|
|
4066
4080
|
? "checkbox"
|
|
4067
|
-
:
|
|
4068
|
-
|
|
4069
|
-
|
|
4081
|
+
: /* eslint-disable-next-line sonarjs/no-nested-conditional -- technical debt */
|
|
4082
|
+
element.type === "radio"
|
|
4083
|
+
? "radio"
|
|
4084
|
+
: "text";
|
|
4070
4085
|
}
|
|
4071
4086
|
else if (element instanceof HTMLTextAreaElement) {
|
|
4072
4087
|
return "textarea";
|
|
@@ -4261,6 +4276,7 @@ class ValidationServiceImpl {
|
|
|
4261
4276
|
});
|
|
4262
4277
|
});
|
|
4263
4278
|
}
|
|
4279
|
+
/* eslint-disable-next-line @typescript-eslint/require-await -- technical debt */
|
|
4264
4280
|
async isValid(src, root = document) {
|
|
4265
4281
|
/* nest inner sync function mostly because the code itself is sync
|
|
4266
4282
|
* (especially required by Array.every) but we still want the API to be
|
|
@@ -4347,6 +4363,7 @@ class ValidationServiceImpl {
|
|
|
4347
4363
|
const tagName = element.tagName.toLowerCase();
|
|
4348
4364
|
const ref = `${tagName}#${element.id}`;
|
|
4349
4365
|
element.removeEventListener("validity", once);
|
|
4366
|
+
/* eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- technical debt */
|
|
4350
4367
|
reject(`Element "${ref}" did not respond with validity event after 500ms`);
|
|
4351
4368
|
}, 500);
|
|
4352
4369
|
/* Dispatch request to validate, this is supposed to be picked up by
|
|
@@ -4447,7 +4464,9 @@ class ValidationServiceImpl {
|
|
|
4447
4464
|
return foundValidators.some((validator) => {
|
|
4448
4465
|
const config = validatorConfigs[validator.name];
|
|
4449
4466
|
const instantConfig = isSet(config) ? config.instant : undefined;
|
|
4450
|
-
return (
|
|
4467
|
+
return (
|
|
4468
|
+
/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- false positive */
|
|
4469
|
+
(validator.instant && instantConfig !== false) ||
|
|
4451
4470
|
instantConfig === true);
|
|
4452
4471
|
});
|
|
4453
4472
|
}
|
|
@@ -4515,9 +4534,7 @@ class ValidationServiceImpl {
|
|
|
4515
4534
|
if (element instanceof HTMLDivElement) {
|
|
4516
4535
|
return false;
|
|
4517
4536
|
}
|
|
4518
|
-
return Boolean(isRadiobuttonOrCheckbox(element)
|
|
4519
|
-
? element.checked
|
|
4520
|
-
: element.value);
|
|
4537
|
+
return Boolean(isRadiobuttonOrCheckbox(element) ? element.checked : element.value);
|
|
4521
4538
|
}
|
|
4522
4539
|
getValue(element) {
|
|
4523
4540
|
if ("value" in element) {
|
|
@@ -4558,7 +4575,7 @@ class ValidationServiceImpl {
|
|
|
4558
4575
|
}
|
|
4559
4576
|
}
|
|
4560
4577
|
validate(value, element, validator, validatorConfigs) {
|
|
4561
|
-
const validatorConfig = validatorConfigs[validator.name]
|
|
4578
|
+
const validatorConfig = validatorConfigs[validator.name] ?? {};
|
|
4562
4579
|
const isEnabled = validatorConfig.enabled === undefined ||
|
|
4563
4580
|
validatorConfig.enabled === true;
|
|
4564
4581
|
/**
|
|
@@ -4709,13 +4726,16 @@ const decimalValidator = {
|
|
|
4709
4726
|
name: "decimal",
|
|
4710
4727
|
validation(value, _element, config) {
|
|
4711
4728
|
const valueWithoutWhitespace = isSet(value)
|
|
4712
|
-
?
|
|
4729
|
+
? /* eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion -- technical debt */
|
|
4730
|
+
stripWhitespace(String(value))
|
|
4713
4731
|
: value;
|
|
4714
4732
|
const minDecimalsAsNumber = isSet(config.minDecimals)
|
|
4715
|
-
?
|
|
4733
|
+
? /* eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion -- technical debt */
|
|
4734
|
+
Number(config.minDecimals)
|
|
4716
4735
|
: undefined;
|
|
4717
4736
|
const maxDecimalsAsNumber = isSet(config.maxDecimals)
|
|
4718
|
-
?
|
|
4737
|
+
? /* eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion -- technical debt */
|
|
4738
|
+
Number(config.maxDecimals)
|
|
4719
4739
|
: undefined;
|
|
4720
4740
|
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- technical debt, should actually verfiy value instead */
|
|
4721
4741
|
if (config.minDecimals && isNaN(minDecimalsAsNumber)) {
|
|
@@ -4733,7 +4753,7 @@ const decimalValidator = {
|
|
|
4733
4753
|
const emailValidator = {
|
|
4734
4754
|
name: "email",
|
|
4735
4755
|
validation(value, _element, config) {
|
|
4736
|
-
const maxLength = config.maxLength
|
|
4756
|
+
const maxLength = config.maxLength ?? 254;
|
|
4737
4757
|
const EMAIL_REGEXP = new RegExp(`^(?=.{1,${maxLength}}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_\`a-z{|}~åäöÅÄÖ]+(\\.[-!#$%&'*+/0-9=?A-Z^_\`a-z{|}~åäöÅÄÖ]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$`);
|
|
4738
4758
|
return isEmpty(value) || EMAIL_REGEXP.test(value);
|
|
4739
4759
|
},
|
|
@@ -4742,6 +4762,7 @@ const emailValidator = {
|
|
|
4742
4762
|
/**
|
|
4743
4763
|
* @internal
|
|
4744
4764
|
*/
|
|
4765
|
+
/* eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- technical debt */
|
|
4745
4766
|
function numberValidator$1(value, config, name, compare) {
|
|
4746
4767
|
if (value === "") {
|
|
4747
4768
|
return true;
|
|
@@ -4768,12 +4789,13 @@ const greaterThanValidator = {
|
|
|
4768
4789
|
},
|
|
4769
4790
|
};
|
|
4770
4791
|
|
|
4771
|
-
const NUMBER_REGEXP = /^([-−]
|
|
4792
|
+
const NUMBER_REGEXP = /^([-−]?\d+)?$/;
|
|
4772
4793
|
const integerValidator = {
|
|
4773
4794
|
name: "integer",
|
|
4774
4795
|
validation(value) {
|
|
4775
4796
|
const valueWithoutWhitespace = isSet(value)
|
|
4776
|
-
?
|
|
4797
|
+
? /* eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion -- technical debt */
|
|
4798
|
+
stripWhitespace(String(value))
|
|
4777
4799
|
: value;
|
|
4778
4800
|
return (isEmpty(valueWithoutWhitespace) ||
|
|
4779
4801
|
NUMBER_REGEXP.test(valueWithoutWhitespace));
|
|
@@ -4919,12 +4941,13 @@ const organisationsnummerValidator = {
|
|
|
4919
4941
|
},
|
|
4920
4942
|
};
|
|
4921
4943
|
|
|
4922
|
-
const PERCENT_REGEXP = /^([-+]
|
|
4944
|
+
const PERCENT_REGEXP = /^([-+]?\d+)([,.]\d+)?$/;
|
|
4923
4945
|
const percentValidator = {
|
|
4924
4946
|
name: "percent",
|
|
4925
4947
|
validation(value) {
|
|
4926
4948
|
const valueWithoutWhitespace = isSet(value)
|
|
4927
|
-
?
|
|
4949
|
+
? /* eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion -- technical debt */
|
|
4950
|
+
stripWhitespace(String(value))
|
|
4928
4951
|
: value;
|
|
4929
4952
|
return (isEmpty(valueWithoutWhitespace) ||
|
|
4930
4953
|
PERCENT_REGEXP.test(valueWithoutWhitespace));
|
|
@@ -4948,6 +4971,7 @@ const personnummerLuhnValidator = {
|
|
|
4948
4971
|
const personnummerNotSame = {
|
|
4949
4972
|
name: "personnummerNotSame",
|
|
4950
4973
|
validation(value, _element, config) {
|
|
4974
|
+
/* eslint-disable-next-line @typescript-eslint/no-unnecessary-type-conversion -- technical debt */
|
|
4951
4975
|
const valuePnr = parsePersonnummer(String(value));
|
|
4952
4976
|
if (!valuePnr) {
|
|
4953
4977
|
return true;
|
|
@@ -5120,6 +5144,7 @@ function waitForScreenReader(callback, delay = SCREEN_READER_DELAY) {
|
|
|
5120
5144
|
resolve(result);
|
|
5121
5145
|
}
|
|
5122
5146
|
catch (err) {
|
|
5147
|
+
/* eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- technical debt */
|
|
5123
5148
|
reject(err);
|
|
5124
5149
|
}
|
|
5125
5150
|
}, delay);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fkui/logic",
|
|
3
|
-
"version": "6.18.
|
|
3
|
+
"version": "6.18.1",
|
|
4
4
|
"description": "Logic",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fkui",
|
|
@@ -61,11 +61,11 @@
|
|
|
61
61
|
"watch": "rollup --config --watch"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"@fkui/date": "^6.18.
|
|
64
|
+
"@fkui/date": "^6.18.1"
|
|
65
65
|
},
|
|
66
66
|
"engines": {
|
|
67
67
|
"node": ">= 20",
|
|
68
68
|
"npm": ">= 7"
|
|
69
69
|
},
|
|
70
|
-
"gitHead": "
|
|
70
|
+
"gitHead": "d8465c63f3165e5dee90581430f0fbb76f3d636a"
|
|
71
71
|
}
|