@cocreate/element-prototype 1.31.2 → 1.31.4
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/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/src/getAttribute.js +1 -1
- package/src/getValue.js +215 -162
- package/src/index.js +0 -2
- package/src/setValue.js +1 -1
- package/src/operators.js +0 -577
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [1.31.4](https://github.com/CoCreate-app/CoCreate-element-prototype/compare/v1.31.3...v1.31.4) (2026-03-01)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* update import paths to use @cocreate/utils and remove unused operators module ([b408215](https://github.com/CoCreate-app/CoCreate-element-prototype/commit/b408215d3ca5fea7504132ac7e838759794764fe))
|
|
7
|
+
|
|
8
|
+
## [1.31.3](https://github.com/CoCreate-app/CoCreate-element-prototype/compare/v1.31.2...v1.31.3) (2026-02-28)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* removed plugin attribute check from setValue function ([f391aad](https://github.com/CoCreate-app/CoCreate-element-prototype/commit/f391aadfcf39b827afedd84b00379265547adea1))
|
|
14
|
+
|
|
1
15
|
## [1.31.2](https://github.com/CoCreate-app/CoCreate-element-prototype/compare/v1.31.1...v1.31.2) (2026-02-25)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cocreate/element-prototype",
|
|
3
|
-
"version": "1.31.
|
|
3
|
+
"version": "1.31.4",
|
|
4
4
|
"description": "A simple element-prototype component in vanilla javascript. Easily configured using HTML5 data-attributes and/or JavaScript API.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"element-prototype",
|
package/src/getAttribute.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { processOperators, processOperatorsAsync } from "
|
|
1
|
+
import { processOperators, processOperatorsAsync } from "@cocreate/utils";
|
|
2
2
|
|
|
3
3
|
// Store a reference to the original getAttribute function
|
|
4
4
|
const originalGetAttribute = Element.prototype.getAttribute;
|
package/src/getValue.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { processOperators } from "
|
|
1
|
+
import { processOperators } from "@cocreate/utils";
|
|
2
2
|
|
|
3
3
|
const storage = new Map();
|
|
4
4
|
|
|
@@ -204,167 +204,220 @@ const handleCheckbox = (element, prefix = "", suffix = "") => {
|
|
|
204
204
|
* 3. FORMAT: Returns the final string/number representation.
|
|
205
205
|
*/
|
|
206
206
|
const handleDateTime = (element, value, valueType) => {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
207
|
+
const inputType = (element.getAttribute("type") || element.type || "").toLowerCase();
|
|
208
|
+
let date;
|
|
209
|
+
if (value === "$now") {
|
|
210
|
+
date = new Date();
|
|
211
|
+
} else if (value instanceof Date) {
|
|
212
|
+
// If it's already a date object, clone it to avoid mutating references
|
|
213
|
+
date = new Date(value.getTime());
|
|
214
|
+
} else if (value) {
|
|
215
|
+
if (
|
|
216
|
+
typeof value === "string" &&
|
|
217
|
+
/^\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}(?::\d{2}(?:\.\d{3})?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/.test(
|
|
218
|
+
value
|
|
219
|
+
)
|
|
220
|
+
) {
|
|
221
|
+
// Normalize datetime-local only when seconds are absent; preserve provided seconds.
|
|
222
|
+
if (inputType === "datetime-local" && /^[0-9T:-]{16}$/.test(value)) {
|
|
223
|
+
value = `${value}:00`;
|
|
224
|
+
}
|
|
225
|
+
date = new Date(value);
|
|
226
|
+
} else {
|
|
227
|
+
date = new Date(value);
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
console.warn("Provided date is invalid:", value);
|
|
231
|
+
return "";
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (date instanceof Date && !isNaN(date.getTime())) {
|
|
235
|
+
// These operations "floor" or "ceil" the date to a specific boundary.
|
|
236
|
+
switch (valueType) {
|
|
237
|
+
case "startOfDay":
|
|
238
|
+
date.setHours(0, 0, 0, 0);
|
|
239
|
+
break;
|
|
240
|
+
case "startOfWeek":
|
|
241
|
+
const startWkOff = parseInt(
|
|
242
|
+
element.getAttribute("week-start-day") || 0,
|
|
243
|
+
10
|
|
244
|
+
);
|
|
245
|
+
date.setDate(date.getDate() - date.getDay() + startWkOff);
|
|
246
|
+
date.setHours(0, 0, 0, 0);
|
|
247
|
+
break;
|
|
248
|
+
case "endOfWeek":
|
|
249
|
+
const endWkOff = parseInt(
|
|
250
|
+
element.getAttribute("week-start-day") || 0,
|
|
251
|
+
10
|
|
252
|
+
);
|
|
253
|
+
date.setDate(date.getDate() - date.getDay() + 6 + endWkOff);
|
|
254
|
+
date.setHours(23, 59, 59, 999);
|
|
255
|
+
break;
|
|
256
|
+
case "startOfMonth":
|
|
257
|
+
date.setDate(1);
|
|
258
|
+
date.setHours(0, 0, 0, 0);
|
|
259
|
+
break;
|
|
260
|
+
case "endOfMonth":
|
|
261
|
+
date.setMonth(date.getMonth() + 1, 0);
|
|
262
|
+
date.setHours(23, 59, 59, 999);
|
|
263
|
+
break;
|
|
264
|
+
case "startOfYear":
|
|
265
|
+
date.setMonth(0, 1);
|
|
266
|
+
date.setHours(0, 0, 0, 0);
|
|
267
|
+
break;
|
|
268
|
+
case "endOfYear":
|
|
269
|
+
date.setMonth(11, 31);
|
|
270
|
+
date.setHours(23, 59, 59, 999);
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// --- PHASE 3: MATH (Modify the Date)
|
|
275
|
+
// Helper to get integer value from attribute safely
|
|
276
|
+
const getVal = (attr) =>
|
|
277
|
+
parseInt(element.getAttribute(attr) || "0", 10);
|
|
278
|
+
|
|
279
|
+
// 1. Years
|
|
280
|
+
const addYears = getVal("add-years") - getVal("subtract-years");
|
|
281
|
+
if (addYears) date.setFullYear(date.getFullYear() + addYears);
|
|
282
|
+
|
|
283
|
+
// 2. Months
|
|
284
|
+
const addMonths = getVal("add-months") - getVal("subtract-months");
|
|
285
|
+
if (addMonths) date.setMonth(date.getMonth() + addMonths);
|
|
286
|
+
|
|
287
|
+
// 3. Weeks
|
|
288
|
+
const addWeeks = getVal("add-weeks") - getVal("subtract-weeks");
|
|
289
|
+
if (addWeeks) date.setDate(date.getDate() + addWeeks * 7);
|
|
290
|
+
|
|
291
|
+
// 4. Days
|
|
292
|
+
const addDays = getVal("add-days") - getVal("subtract-days");
|
|
293
|
+
if (addDays) date.setDate(date.getDate() + addDays);
|
|
294
|
+
|
|
295
|
+
// 5. Hours
|
|
296
|
+
const addHours = getVal("add-hours") - getVal("subtract-hours");
|
|
297
|
+
if (addHours) date.setHours(date.getHours() + addHours);
|
|
298
|
+
|
|
299
|
+
// 6. Minutes
|
|
300
|
+
const addMinutes = getVal("add-minutes") - getVal("subtract-minutes");
|
|
301
|
+
if (addMinutes) date.setMinutes(date.getMinutes() + addMinutes);
|
|
302
|
+
|
|
303
|
+
// 7. Seconds
|
|
304
|
+
const addSeconds = getVal("add-seconds") - getVal("subtract-seconds");
|
|
305
|
+
if (addSeconds) date.setSeconds(date.getSeconds() + addSeconds);
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
// --- PHASE 4: FORMATTING (Output the Result) ---
|
|
309
|
+
switch (valueType) {
|
|
310
|
+
case "getDayName":
|
|
311
|
+
const days = [
|
|
312
|
+
"Sunday",
|
|
313
|
+
"Monday",
|
|
314
|
+
"Tuesday",
|
|
315
|
+
"Wednesday",
|
|
316
|
+
"Thursday",
|
|
317
|
+
"Friday",
|
|
318
|
+
"Saturday",
|
|
319
|
+
];
|
|
320
|
+
return days[date.getDay()];
|
|
321
|
+
|
|
322
|
+
case "getMonthName":
|
|
323
|
+
const months = [
|
|
324
|
+
"January",
|
|
325
|
+
"February",
|
|
326
|
+
"March",
|
|
327
|
+
"April",
|
|
328
|
+
"May",
|
|
329
|
+
"June",
|
|
330
|
+
"July",
|
|
331
|
+
"August",
|
|
332
|
+
"September",
|
|
333
|
+
"October",
|
|
334
|
+
"November",
|
|
335
|
+
"December",
|
|
336
|
+
];
|
|
337
|
+
return months[date.getMonth()];
|
|
338
|
+
|
|
339
|
+
case "getYear":
|
|
340
|
+
case "getFullYear":
|
|
341
|
+
return date.getFullYear();
|
|
342
|
+
|
|
343
|
+
case "getTime":
|
|
344
|
+
let hours = date.getHours();
|
|
345
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
346
|
+
const ampm = hours >= 12 ? "PM" : "AM";
|
|
347
|
+
hours = hours % 12 || 12; // Convert 0 to 12 for midnight
|
|
348
|
+
return `${hours}:${minutes} ${ampm}`;
|
|
349
|
+
|
|
350
|
+
case "timeAgo": {
|
|
351
|
+
const diffMs = Date.now() - date.getTime();
|
|
352
|
+
const absSecs = Math.floor(Math.abs(diffMs) / 1000);
|
|
353
|
+
|
|
354
|
+
// --- LIMIT CHECK ---
|
|
355
|
+
const limitAttr = element.getAttribute("time-ago-limit");
|
|
356
|
+
if (limitAttr) {
|
|
357
|
+
const match = limitAttr.match(/^(\d+)([a-z]+)$/i);
|
|
358
|
+
if (match) {
|
|
359
|
+
const amount = parseInt(match[1], 10);
|
|
360
|
+
const limitUnit = match[2].toLowerCase();
|
|
361
|
+
let multiplier = 0;
|
|
362
|
+
|
|
363
|
+
switch (limitUnit) {
|
|
364
|
+
case 's': multiplier = 1; break; // seconds
|
|
365
|
+
case 'm': multiplier = 60; break; // minutes
|
|
366
|
+
case 'h': multiplier = 3600; break; // hours
|
|
367
|
+
case 'd': multiplier = 86400; break; // days
|
|
368
|
+
case 'w': multiplier = 604800; break; // weeks
|
|
369
|
+
case 'mo': multiplier = 2592000; break; // months
|
|
370
|
+
case 'y': multiplier = 31536000; break; // years
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const limitInSeconds = amount * multiplier;
|
|
374
|
+
|
|
375
|
+
// If the time elapsed is greater than the limit, fallback to standard date string
|
|
376
|
+
if (absSecs > limitInSeconds) {
|
|
377
|
+
const locale = element.getAttribute("locale") || "en-US";
|
|
378
|
+
return date.toLocaleString(locale);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
let value, unit;
|
|
384
|
+
if (absSecs < 60) { value = absSecs; unit = 'second'; }
|
|
385
|
+
else if (absSecs < 3600) { value = Math.floor(absSecs / 60); unit = 'minute'; }
|
|
386
|
+
else if (absSecs < 86400) { value = Math.floor(absSecs / 3600); unit = 'hour'; }
|
|
387
|
+
else if (absSecs < 604800) { value = Math.floor(absSecs / 86400); unit = 'day'; }
|
|
388
|
+
else if (absSecs < 2592000) { value = Math.floor(absSecs / 604800); unit = 'week'; }
|
|
389
|
+
else if (absSecs < 31536000) { value = Math.floor(absSecs / 2592000); unit = 'month'; }
|
|
390
|
+
else { value = Math.floor(absSecs / 31536000); unit = 'year'; }
|
|
391
|
+
|
|
392
|
+
const plural = value !== 1 ? 's' : '';
|
|
393
|
+
return `${value} ${unit}${plural}`;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
case "toUnixTimestamp":
|
|
397
|
+
return Math.floor(date.getTime() / 1000);
|
|
398
|
+
|
|
399
|
+
case "toLocaleString":
|
|
400
|
+
const locale = element.getAttribute("locale") || "en-US";
|
|
401
|
+
return date.toLocaleString(locale);
|
|
402
|
+
|
|
403
|
+
default:
|
|
404
|
+
// Handle generic methods if specified
|
|
405
|
+
if (valueType && typeof date[valueType] === "function") {
|
|
406
|
+
return date[valueType]();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
410
|
+
if (inputType === "datetime-local") {
|
|
411
|
+
// Return a datetime-local compatible string with seconds to avoid invalid values
|
|
412
|
+
return `${date.getFullYear()}-${pad(
|
|
413
|
+
date.getMonth() + 1
|
|
414
|
+
)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(
|
|
415
|
+
date.getMinutes()
|
|
416
|
+
)}:${pad(date.getSeconds())}`;
|
|
417
|
+
}
|
|
418
|
+
return date.toISOString();
|
|
419
|
+
}
|
|
420
|
+
}
|
|
368
421
|
};
|
|
369
422
|
|
|
370
423
|
/**
|
package/src/index.js
CHANGED
|
@@ -24,12 +24,10 @@ import { getAttribute } from "./getAttribute";
|
|
|
24
24
|
import { setValue } from "./setValue";
|
|
25
25
|
import { getValue } from "./getValue";
|
|
26
26
|
import { queryElements } from "./queryElements";
|
|
27
|
-
import { processOperators } from "./operators";
|
|
28
27
|
|
|
29
28
|
export default {
|
|
30
29
|
getAttribute,
|
|
31
30
|
getValue,
|
|
32
31
|
setValue,
|
|
33
|
-
processOperators,
|
|
34
32
|
queryElements
|
|
35
33
|
};
|
package/src/setValue.js
CHANGED
|
@@ -14,7 +14,7 @@ const setValue = (el, value, dispatch) => {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
if (value === null || value === undefined) return;
|
|
17
|
-
if (el.hasAttribute("component")
|
|
17
|
+
if (el.hasAttribute("component"))
|
|
18
18
|
return storage.set(el, value);
|
|
19
19
|
else if (typeof value === "object") value = JSON.stringify(value, null, 2);
|
|
20
20
|
|
package/src/operators.js
DELETED
|
@@ -1,577 +0,0 @@
|
|
|
1
|
-
import { ObjectId, queryElements, getValueFromObject, uid } from "@cocreate/utils";
|
|
2
|
-
|
|
3
|
-
// Operators handled directly for simple, synchronous value retrieval
|
|
4
|
-
const customOperators = new Map(
|
|
5
|
-
Object.entries({
|
|
6
|
-
$organization_id: () => localStorage.getItem("organization_id"),
|
|
7
|
-
$user_id: () => localStorage.getItem("user_id"),
|
|
8
|
-
$clientId: () => localStorage.getItem("clientId"),
|
|
9
|
-
$session_id: () => localStorage.getItem("session_id"),
|
|
10
|
-
$value: (element) => element.getValue() || "",
|
|
11
|
-
// TODO: get length of value
|
|
12
|
-
// $length: (element) => {element.getValue() || ""},
|
|
13
|
-
$innerWidth: () => window.innerWidth,
|
|
14
|
-
$innerHeight: () => window.innerHeight,
|
|
15
|
-
$href: () => window.location.href.replace(/\/$/, ""),
|
|
16
|
-
$origin: () => window.location.origin,
|
|
17
|
-
$protocol: () => window.location.protocol,
|
|
18
|
-
$hostname: () => window.location.hostname,
|
|
19
|
-
$host: () => window.location.host,
|
|
20
|
-
$port: () => window.location.port,
|
|
21
|
-
$pathname: () => window.location.pathname.replace(/\/$/, ""),
|
|
22
|
-
$hash: () => window.location.hash,
|
|
23
|
-
$subdomain: () => getSubdomain() || "",
|
|
24
|
-
$object_id: () => ObjectId().toString(),
|
|
25
|
-
"ObjectId()": () => ObjectId().toString(),
|
|
26
|
-
$relativePath: () => {
|
|
27
|
-
let currentPath = window.location.pathname.replace(/\/[^\/]*$/, ""); // Remove file or last segment from path
|
|
28
|
-
let depth = currentPath.split("/").filter(Boolean).length; // Count actual directory levels
|
|
29
|
-
return depth > 0 ? "../".repeat(depth) : "./";
|
|
30
|
-
},
|
|
31
|
-
$path: () => {
|
|
32
|
-
let path = window.location.pathname;
|
|
33
|
-
if (path.split("/").pop().includes(".")) {
|
|
34
|
-
path = path.replace(/\/[^\/]+$/, "/");
|
|
35
|
-
}
|
|
36
|
-
return path === "/" ? "" : path;
|
|
37
|
-
},
|
|
38
|
-
$param: (element, args) => args,
|
|
39
|
-
$getObjectValue: (element, args) => {
|
|
40
|
-
if (Array.isArray(args) && args.length >= 2) {
|
|
41
|
-
return getValueFromObject(args[0], args[1]);
|
|
42
|
-
}
|
|
43
|
-
return "";
|
|
44
|
-
},
|
|
45
|
-
$setValue: (element, args) => element.setValue(...args) || "",
|
|
46
|
-
$true: () => true,
|
|
47
|
-
$false: () => false,
|
|
48
|
-
// TODO: Handle uuid generation
|
|
49
|
-
// $uid: () => uid.generate(6),
|
|
50
|
-
$parse: (element, args) => {
|
|
51
|
-
let value = args || "";
|
|
52
|
-
try {
|
|
53
|
-
return JSON.parse(value);
|
|
54
|
-
} catch (e) {
|
|
55
|
-
return value;
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
// TODO: Implement number formatting
|
|
59
|
-
$numberFormat: (element, args) => {
|
|
60
|
-
let number = parseFloat(args[0]);
|
|
61
|
-
// Simple, fixed arg mapping:
|
|
62
|
-
// args[0] = locale (internationalization)
|
|
63
|
-
// args[1] = options (object)
|
|
64
|
-
// args[2] = number (if provided). If not provided, fall back to legacy behavior where args[0] might be the number.
|
|
65
|
-
if (!Array.isArray(args)) args = [args];
|
|
66
|
-
|
|
67
|
-
const locale = args[0] || undefined;
|
|
68
|
-
const options = args[1] || {};
|
|
69
|
-
const numCandidate = args[2] !== undefined ? args[2] : args[0];
|
|
70
|
-
|
|
71
|
-
number = parseFloat(numCandidate);
|
|
72
|
-
|
|
73
|
-
if (isNaN(number)) return String(numCandidate ?? "");
|
|
74
|
-
|
|
75
|
-
return new Intl.NumberFormat(locale, options).format(number);
|
|
76
|
-
},
|
|
77
|
-
$uid: (element, args) => uid(args[0]) || "",
|
|
78
|
-
|
|
79
|
-
})
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
// Operators that access a specific property of a target element
|
|
83
|
-
const propertyOperators = new Set([
|
|
84
|
-
"$scrollWidth",
|
|
85
|
-
"$scrollHeight",
|
|
86
|
-
"$offsetWidth",
|
|
87
|
-
"$offsetHeight",
|
|
88
|
-
"$id",
|
|
89
|
-
"$tagName",
|
|
90
|
-
"$className",
|
|
91
|
-
"$textContent",
|
|
92
|
-
"$innerHTML",
|
|
93
|
-
"$getValue",
|
|
94
|
-
"$reset"
|
|
95
|
-
]);
|
|
96
|
-
|
|
97
|
-
// Combine all known operator keys for the main parsing regex
|
|
98
|
-
const knownOperatorKeys = [
|
|
99
|
-
...customOperators.keys(),
|
|
100
|
-
...propertyOperators
|
|
101
|
-
].sort((a, b) => b.length - a.length);
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Helper function to check if a string path starts with a known bare operator.
|
|
105
|
-
* This logic is necessary to separate '$value' from '[0].src' when no parentheses remain.
|
|
106
|
-
*/
|
|
107
|
-
const findBareOperatorInPath = (path, knownOperatorKeys) => {
|
|
108
|
-
const trimmedPath = path.trim();
|
|
109
|
-
for (const key of knownOperatorKeys) {
|
|
110
|
-
if (trimmedPath.startsWith(key)) {
|
|
111
|
-
const remaining = trimmedPath.substring(key.length);
|
|
112
|
-
// Edge Case Fix: Ensure the operator is followed by a space, bracket, dot, or end-of-string.
|
|
113
|
-
// This prevents "valueThing" from being incorrectly split into "$value" + "Thing".
|
|
114
|
-
if (remaining.length === 0 || /^\s|\[|\./.test(remaining)) {
|
|
115
|
-
return key;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Finds the innermost function call (operator + its balanced parentheses argument)
|
|
124
|
-
* in the expression. This is the core logic for iterative parsing.
|
|
125
|
-
* * @param {string} expression The full expression string.
|
|
126
|
-
* @returns {{operator: string, args: string, fullMatch: string} | null} Details of the innermost function call, or null.
|
|
127
|
-
*/
|
|
128
|
-
const findInnermostFunctionCall = (expression) => {
|
|
129
|
-
let balance = 0;
|
|
130
|
-
let deepestStart = -1;
|
|
131
|
-
let deepestEnd = -1;
|
|
132
|
-
let deepestBalance = -1;
|
|
133
|
-
let inSingleQuote = false;
|
|
134
|
-
let inDoubleQuote = false;
|
|
135
|
-
|
|
136
|
-
// First pass: Find the indices of the DEEPEST balanced parenthesis pair.
|
|
137
|
-
for (let i = 0; i < expression.length; i++) {
|
|
138
|
-
const char = expression[i];
|
|
139
|
-
|
|
140
|
-
if (char === '"' && !inSingleQuote) {
|
|
141
|
-
inDoubleQuote = !inDoubleQuote;
|
|
142
|
-
continue;
|
|
143
|
-
} else if (char === "'" && !inDoubleQuote) {
|
|
144
|
-
inSingleQuote = !inDoubleQuote;
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (inSingleQuote || inDoubleQuote) {
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (char === '(') {
|
|
153
|
-
balance++;
|
|
154
|
-
// Track the index of the open parenthesis that belongs to the deepest balance level
|
|
155
|
-
if (balance > deepestBalance) {
|
|
156
|
-
deepestBalance = balance;
|
|
157
|
-
deepestStart = i;
|
|
158
|
-
deepestEnd = -1; // Reset end until match is found
|
|
159
|
-
}
|
|
160
|
-
} else if (char === ')') {
|
|
161
|
-
if (balance === deepestBalance) {
|
|
162
|
-
// This is the closing parenthesis that matches the deepest open parenthesis found
|
|
163
|
-
deepestEnd = i;
|
|
164
|
-
}
|
|
165
|
-
balance--;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// If we didn't find a balanced pair, or the match is invalid, exit.
|
|
170
|
-
if (deepestStart === -1 || deepestEnd === -1 || deepestEnd <= deepestStart) {
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Now we have the innermost argument content indices (deepestStart + 1 to deepestEnd - 1)
|
|
175
|
-
const rawArgs = expression.substring(deepestStart + 1, deepestEnd).trim();
|
|
176
|
-
|
|
177
|
-
// Step 2: Find the operator name backward from the deepestStart index.
|
|
178
|
-
let operatorStart = -1;
|
|
179
|
-
let nonWhitespaceFound = false;
|
|
180
|
-
|
|
181
|
-
for (let i = deepestStart - 1; i >= 0; i--) {
|
|
182
|
-
const char = expression[i];
|
|
183
|
-
|
|
184
|
-
// Skip trailing whitespace between operator and '('
|
|
185
|
-
if (!nonWhitespaceFound) {
|
|
186
|
-
if (/\s/.test(char)) {
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
nonWhitespaceFound = true;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Find the start of the operator name
|
|
193
|
-
// This regex captures letters, numbers, hyphens, and the required $
|
|
194
|
-
let isOperatorChar = /[\w\-\$]/.test(char);
|
|
195
|
-
|
|
196
|
-
if (!isOperatorChar) {
|
|
197
|
-
operatorStart = i + 1;
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
operatorStart = i;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (operatorStart === -1) operatorStart = 0;
|
|
204
|
-
|
|
205
|
-
const operatorNameCandidate = expression.substring(operatorStart, deepestStart).trim();
|
|
206
|
-
|
|
207
|
-
// Step 3: Validate the operator name
|
|
208
|
-
if (knownOperatorKeys.includes(operatorNameCandidate)) {
|
|
209
|
-
// Construct the full match string: operatorNameCandidate(rawArgs)
|
|
210
|
-
const fullMatch = expression.substring(operatorStart, deepestEnd + 1);
|
|
211
|
-
|
|
212
|
-
return {
|
|
213
|
-
operator: operatorNameCandidate,
|
|
214
|
-
args: rawArgs,
|
|
215
|
-
fullMatch: fullMatch
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return null; // Operator name invalid or not found
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Main function to find the innermost operator.
|
|
224
|
-
* * Logic flow is updated to prioritize finding the innermost FUNCTION CALL,
|
|
225
|
-
* then fall back to finding a BARE OPERATOR if no function calls remain.
|
|
226
|
-
* * @param {string} expression The expression to parse.
|
|
227
|
-
* @returns {{operator: string, args: string, rawContent: string, fullMatch?: string} | {operator: null, args: string, rawContent: string}}
|
|
228
|
-
*/
|
|
229
|
-
const findInnermostOperator = (expression) => {
|
|
230
|
-
// Helper function to strip leading and trailing parentheses from a string
|
|
231
|
-
function stripParentheses(str) {
|
|
232
|
-
let result = str;
|
|
233
|
-
if (result.startsWith("(")) {
|
|
234
|
-
result = result.substring(1);
|
|
235
|
-
}
|
|
236
|
-
if (result.endsWith(")")) {
|
|
237
|
-
result = result.substring(0, result.length - 1);
|
|
238
|
-
}
|
|
239
|
-
return result;
|
|
240
|
-
}
|
|
241
|
-
let args;
|
|
242
|
-
|
|
243
|
-
// --- 1. PRIORITY: Find Innermost FUNCTION CALL (Operator with Parentheses) ---
|
|
244
|
-
const functionCall = findInnermostFunctionCall(expression);
|
|
245
|
-
|
|
246
|
-
if (functionCall) {
|
|
247
|
-
// Return the full function expression details, including the full string section
|
|
248
|
-
args = stripParentheses(functionCall.args);
|
|
249
|
-
return {
|
|
250
|
-
operator: functionCall.operator,
|
|
251
|
-
args, // Arguments without parentheses
|
|
252
|
-
rawContent: functionCall.args, // The content inside the parentheses (arguments)
|
|
253
|
-
fullMatch: functionCall.fullMatch // The operator(args) string (the complete section to replace)
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// --- 2. FALLBACK: Find BARE OPERATOR (e.g., $value path) ---
|
|
258
|
-
|
|
259
|
-
// If no function calls are found, the entire expression is treated as the raw content
|
|
260
|
-
const rawContent = expression.trim();
|
|
261
|
-
|
|
262
|
-
// Now check the raw content to see if it starts with a bare operator ($value)
|
|
263
|
-
const innermostOperator = findBareOperatorInPath(rawContent, knownOperatorKeys);
|
|
264
|
-
|
|
265
|
-
if (innermostOperator) {
|
|
266
|
-
const operatorArgs = rawContent.substring(innermostOperator.length).trim();
|
|
267
|
-
args = stripParentheses(operatorArgs);
|
|
268
|
-
return {
|
|
269
|
-
operator: innermostOperator,
|
|
270
|
-
args, // Arguments without parentheses
|
|
271
|
-
rawContent: rawContent,
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
args = stripParentheses(rawContent);
|
|
277
|
-
|
|
278
|
-
// Fallback if no known operator is found
|
|
279
|
-
return {
|
|
280
|
-
operator: null,
|
|
281
|
-
args,
|
|
282
|
-
rawContent: rawContent
|
|
283
|
-
};
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
function escapeRegexKey(key) {
|
|
287
|
-
if (key.startsWith("$")) {
|
|
288
|
-
return "\\" + key; // Escape the leading $
|
|
289
|
-
} else if (key === "ObjectId()") {
|
|
290
|
-
return "ObjectId\\(\\)"; // Escape the parentheses
|
|
291
|
-
}
|
|
292
|
-
return key; // Should not happen with current keys, but fallback
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Synchronously processes a string, finding and replacing operators recursively.
|
|
297
|
-
* Assumes ALL underlying operations (getValue, queryElements) are synchronous.
|
|
298
|
-
* @param {Element | null} element - Context element.
|
|
299
|
-
* @param {string} value - String containing operators.
|
|
300
|
-
* @param {string[]} [exclude=[]] - Operator prefixes to ignore.
|
|
301
|
-
* @returns {string | {value: string, params: Promise[]}} - Processed string or an object containing the partially processed string and unresolved Promises.
|
|
302
|
-
*/
|
|
303
|
-
function processOperators(
|
|
304
|
-
element,
|
|
305
|
-
value,
|
|
306
|
-
exclude = [],
|
|
307
|
-
parent,
|
|
308
|
-
params = []
|
|
309
|
-
) {
|
|
310
|
-
// Early exit if no operators are possible or value is not a string
|
|
311
|
-
if (typeof value !== "string" || !value.includes("$")) {
|
|
312
|
-
return value;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
let processedValue = value;
|
|
316
|
-
let hasPromise = false;
|
|
317
|
-
let parsedValue = null
|
|
318
|
-
|
|
319
|
-
while (processedValue.includes("$")) {
|
|
320
|
-
|
|
321
|
-
// --- PROMISE TOKEN RESOLUTION ---
|
|
322
|
-
// If the processedValue starts with a resolved parameter token from the previous async step,
|
|
323
|
-
// substitute the token with its actual (now resolved) value from the params array.
|
|
324
|
-
const paramMatch = processedValue.match(/^\$\$PARAM_(\d+)\$\$/);
|
|
325
|
-
if (paramMatch && Array.isArray(params) && params.length > 0) {
|
|
326
|
-
const index = parseInt(paramMatch[1], 10);
|
|
327
|
-
if (index < params.length) {
|
|
328
|
-
const resolvedTokenValue = params[index];
|
|
329
|
-
processedValue = processedValue.replace(paramMatch[0], resolvedTokenValue);
|
|
330
|
-
// After replacement, we restart the loop to find the *next* innermost operator
|
|
331
|
-
continue;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
// --- END TOKEN RESOLUTION ---
|
|
335
|
-
|
|
336
|
-
const { operator, args, rawContent, fullMatch } = findInnermostOperator(processedValue);
|
|
337
|
-
|
|
338
|
-
if (!operator) {
|
|
339
|
-
break; // No more operators found
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
if (operator === "$param" && !args) {
|
|
343
|
-
break;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (operator && !exclude.includes(operator)) {
|
|
347
|
-
|
|
348
|
-
// --- Determine textToReplace ---
|
|
349
|
-
// The fullMatch property from findInnermostOperator ensures we correctly replace
|
|
350
|
-
// the whole expression (e.g., "$param(...)") or just the bare operator ($value path).
|
|
351
|
-
const textToReplace = fullMatch || rawContent;
|
|
352
|
-
// --- END textToReplace CALCULATION ---
|
|
353
|
-
|
|
354
|
-
let resolvedValue = resolveOperator(element, operator, args, parent, params);
|
|
355
|
-
|
|
356
|
-
if (resolvedValue instanceof Promise) {
|
|
357
|
-
const paramIndex = params.length; // Get the index *before* push
|
|
358
|
-
params.push(resolvedValue); // Store the Promise
|
|
359
|
-
|
|
360
|
-
// CRITICAL FIX: Replace the matched expression with a unique token, then break.
|
|
361
|
-
processedValue = processedValue.replace(textToReplace, `$$PARAM_${paramIndex}$$`);
|
|
362
|
-
hasPromise = true;
|
|
363
|
-
break; // Stop processing and yield
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
if (params.some((p) => p instanceof Promise)) {
|
|
367
|
-
hasPromise = true;
|
|
368
|
-
break; // A nested call found a promise, stop and yield
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
let replacement = "";
|
|
372
|
-
if (operator === "$param") {
|
|
373
|
-
params.push(resolvedValue);
|
|
374
|
-
} else {
|
|
375
|
-
replacement = resolvedValue ?? "";
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
if (processedValue === textToReplace) {
|
|
379
|
-
processedValue = replacement;
|
|
380
|
-
break;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
processedValue = processedValue.replace(textToReplace, replacement);
|
|
384
|
-
|
|
385
|
-
if (!processedValue.includes("$")) {
|
|
386
|
-
// If there are still unresolved operators, we need to continue processing
|
|
387
|
-
break;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
} else {
|
|
391
|
-
// If operator is excluded, we need to advance past it to avoid infinite loop
|
|
392
|
-
break;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
if (hasPromise) {
|
|
397
|
-
return { value: processedValue, params };
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
if (params.length) {
|
|
401
|
-
if (processedValue.trim() === "") {
|
|
402
|
-
return params;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
return processedValue;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
async function processOperatorsAsync(
|
|
410
|
-
element,
|
|
411
|
-
value,
|
|
412
|
-
exclude = [],
|
|
413
|
-
parent,
|
|
414
|
-
params = []
|
|
415
|
-
) {
|
|
416
|
-
let result = processOperators(element, value, exclude, parent, params);
|
|
417
|
-
|
|
418
|
-
while (typeof result === "object" && result.params) {
|
|
419
|
-
const resolvedParams = await Promise.all(result.params);
|
|
420
|
-
// Note: The second argument passed to processOperators here is the partially processed string (result.value)
|
|
421
|
-
// which now contains the PARAM tokens. The third argument is the array of resolved values (resolvedParams)
|
|
422
|
-
// which will be used to replace those tokens in the subsequent processOperators call.
|
|
423
|
-
result = processOperators(
|
|
424
|
-
element,
|
|
425
|
-
result.value,
|
|
426
|
-
exclude,
|
|
427
|
-
parent,
|
|
428
|
-
resolvedParams
|
|
429
|
-
);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
if (result instanceof Promise) {
|
|
433
|
-
return await result;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
return result;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Synchronously determines and executes the action for processing a single operator token.
|
|
441
|
-
* @param {HTMLElement|null} element - The context element from which to derive values or execute methods.
|
|
442
|
-
* @param {string} operator - The operator to apply, indicating what actions or property/method to evaluate.
|
|
443
|
-
* @param {string|Array} args - Arguments that may be used by the operator, which could be further processed if they contain a nested operator.
|
|
444
|
-
* @param {string} parent - Context in which the function is called, potentially affecting behavior or processing.
|
|
445
|
-
* @returns {string} The final resolved value after applying the operator to the given elements.
|
|
446
|
-
*/
|
|
447
|
-
function resolveOperator(element, operator, args, parent, params) {
|
|
448
|
-
// If a promise is already in the params, we must stop and wait for it to be resolved.
|
|
449
|
-
if (params.some((p) => p instanceof Promise)) {
|
|
450
|
-
return "";
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// If args contain any operators (indicated by '$'), process them recursively
|
|
454
|
-
if (args && typeof args === "string" && args.includes("$")) {
|
|
455
|
-
// Reprocess args to resolve any nested operators
|
|
456
|
-
args = processOperators(element, args, "", operator, params);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
if (params.some((p) => p instanceof Promise)) {
|
|
460
|
-
return operator;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// Initialize an array of elements to operate on, starting with the single element reference if provided
|
|
464
|
-
let targetElements = element ? [element] : [];
|
|
465
|
-
|
|
466
|
-
// If the argument is a string and the operator is NOT a custom utility that expects raw data
|
|
467
|
-
// (like $param, $parse), we assume the argument is a selector.
|
|
468
|
-
if (args && typeof args === "string" && !customOperators.has(operator)) {
|
|
469
|
-
targetElements = queryElements({
|
|
470
|
-
element, // Use the context element as the base for querying
|
|
471
|
-
selector: args // Selector from args to find matching elements
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
// If no elements are found matching the selector in args, return args unmodified
|
|
475
|
-
if (!targetElements.length) return args;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// Generate a processed value by applying the operator to each of the target elements
|
|
479
|
-
let value = processValues(targetElements, operator, args, parent);
|
|
480
|
-
|
|
481
|
-
// If the result is a string and still contains unresolved operators, process them further
|
|
482
|
-
if (value && typeof value === "string" && value.includes("$")) {
|
|
483
|
-
// Resolve any remaining operators within the value string
|
|
484
|
-
value = processOperators(element, value, parent, params);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// Return the final processed value, fully resolved
|
|
488
|
-
return value;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
/**
|
|
492
|
-
* Synchronously processes and aggregates values from a set of elements based on a specified operator.
|
|
493
|
-
* @param {Array<HTMLElement>} elements - Array of elements to be processed.
|
|
494
|
-
* @param {string} operator - The operator to apply to each element, indicating which property or method to use.
|
|
495
|
-
* @param {string|Array} args - Arguments that may be passed to the method if the operator corresponds to a function.
|
|
496
|
-
* @param {string} parent - Context in which the function is called, possibly influencing behavior (e.g., special handling for "$param").
|
|
497
|
-
* @returns {string} The combined string value obtained by processing elements with the specified operator.
|
|
498
|
-
*/
|
|
499
|
-
function processValues(elements, operator, args, parent) {
|
|
500
|
-
// Attempt to fetch a custom operator function associated with the operator
|
|
501
|
-
let customOp = customOperators.get(operator);
|
|
502
|
-
|
|
503
|
-
// Initialize an empty string to accumulate results from processing each element
|
|
504
|
-
let aggregatedString = "";
|
|
505
|
-
|
|
506
|
-
// Iterate over each element in the provided elements array
|
|
507
|
-
for (const el of elements) {
|
|
508
|
-
// If the element is null or undefined, skip to the next iteration
|
|
509
|
-
if (!el) continue;
|
|
510
|
-
|
|
511
|
-
// Determine the raw value from the custom operator or by accessing a property/method directly on the element
|
|
512
|
-
let rawValue = customOp || el?.[operator.substring(1)];
|
|
513
|
-
|
|
514
|
-
// Check if the rawValue is a function and process it using provided arguments
|
|
515
|
-
if (typeof rawValue === "function") {
|
|
516
|
-
// If arguments are provided as an array
|
|
517
|
-
if (Array.isArray(args)) {
|
|
518
|
-
// If the custom operator is NOT $param and has arguments, something is wrong with the flow.
|
|
519
|
-
// However, if it's a generic method on an element (like $setValue), the args are passed via spread.
|
|
520
|
-
if (customOperators.has(operator) && operator !== "$setValue" && operator !== "$getObjectValue" && args.length) {
|
|
521
|
-
// For simple custom operators that don't take multiple args, return an empty string.
|
|
522
|
-
return "";
|
|
523
|
-
}
|
|
524
|
-
// Invoke the function using the element and spread array arguments
|
|
525
|
-
rawValue = rawValue(el, ...args);
|
|
526
|
-
} else {
|
|
527
|
-
// Otherwise, invoke the function using the element and direct arguments
|
|
528
|
-
rawValue = rawValue(el, args);
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// If the parent context requires parameter resolution
|
|
533
|
-
if (parent === "$param") {
|
|
534
|
-
// Return the first evaluated rawValue that is not null or undefined
|
|
535
|
-
if (rawValue) {
|
|
536
|
-
return rawValue;
|
|
537
|
-
}
|
|
538
|
-
} else {
|
|
539
|
-
if (
|
|
540
|
-
rawValue instanceof Promise ||
|
|
541
|
-
(typeof rawValue === "object" && rawValue !== null)
|
|
542
|
-
) {
|
|
543
|
-
return rawValue;
|
|
544
|
-
}
|
|
545
|
-
// Otherwise, append the stringified rawValue to the aggregated result, defaulting to an empty string if it's nullish
|
|
546
|
-
aggregatedString += String(rawValue ?? "");
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
// Return the final aggregated string containing all processed values
|
|
551
|
-
return aggregatedString;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
/**
|
|
555
|
-
* Extracts the subdomain from the current window's hostname.
|
|
556
|
-
* @returns {string|null} - The subdomain part of the hostname if it exists, or null if there is none.
|
|
557
|
-
*/
|
|
558
|
-
function getSubdomain() {
|
|
559
|
-
// Retrieve the hostname from the current window's location
|
|
560
|
-
const hostname = window.location.hostname;
|
|
561
|
-
|
|
562
|
-
// Split the hostname into parts divided by dots ('.')
|
|
563
|
-
const parts = hostname.split(".");
|
|
564
|
-
|
|
565
|
-
// Check if the hostname has more than two parts and ensure the last part isn't a number (a common TLD check)
|
|
566
|
-
// A typical domain structure might look like "sub.domain.com",
|
|
567
|
-
// where "sub" is the subdomain, "domain" is the second-level domain, and "com" is the top-level domain.
|
|
568
|
-
if (parts.length > 2 && isNaN(parseInt(parts[parts.length - 1]))) {
|
|
569
|
-
// Join all parts except the last two (which are assumed to be the domain and TLD) to get the subdomain
|
|
570
|
-
return parts.slice(0, parts.length - 2).join(".");
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
// Return null if there's no valid subdomain structure
|
|
574
|
-
return null;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
export { processOperators, processOperatorsAsync };
|