@cocreate/utils 1.41.1 → 1.42.0

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/src/index.js CHANGED
@@ -1,1365 +1,70 @@
1
- (function (root, factory) {
2
- if (typeof define === "function" && define.amd) {
3
- define([], function () {
4
- return factory(true);
5
- });
6
- } else if (typeof module === "object" && module.exports) {
7
- module.exports = factory(false);
8
- } else {
9
- root.returnExports = factory(true);
10
- }
11
- })(typeof self !== "undefined" ? self : this, function (isBrowser) {
12
- /*globals DOMParser*/
13
- function clickedElement() {
14
- document.addEventListener("click", (e) => {
15
- document.clickedElement = e.target;
16
- });
17
-
18
- try {
19
- let frames = document.querySelectorAll("iframe");
20
- for (let frame of frames) {
21
- try {
22
- let frameDocument = frame.contentDocument;
23
- if (!frameDocument.clickedElementListenerAdded) {
24
- frameDocument.addEventListener("click", (e) => {
25
- frameDocument.clickedElement = e.target;
26
- });
27
-
28
- // Mark the document to avoid adding duplicate listeners
29
- frameDocument.clickedElementListenerAdded = true;
30
- }
31
- } catch (iframeError) {
32
- console.log(
33
- `Cross-origin frame handling failed for: ${frame}`,
34
- iframeError
35
- );
36
- }
37
- }
38
- } catch (e) {
39
- console.log("Top-level frame document handling failed:", e);
40
- }
41
- }
42
-
43
- function getRelativePath(path) {
44
- // If no path provided, use window.location.pathname
45
- if (!path && isBrowser) {
46
- path = window.location.pathname.replace(/\/[^\/]*$/, ""); // Remove file from path
47
- }
48
-
49
- // For localhost/127.0.0.1, remove everything up to and including the first '/src'
50
- // so content AFTER '/src' is treated as the root
51
- if (
52
- isBrowser &&
53
- (location.hostname === "localhost" ||
54
- location.hostname === "127.0.0.1")
55
- ) {
56
- // Find the '/src' directory in the path and keep everything after it (exclude '/src' itself)
57
- const srcIndex = path.indexOf("/src");
58
- if (srcIndex !== -1) {
59
- path = path.slice(srcIndex + 4); // remove '/src' and everything before it
60
- }
61
- }
62
-
63
- if (!path.endsWith("/")) {
64
- path += "/";
65
- }
66
- let depth = path.split("/").filter(Boolean).length;
67
- return depth > 0 ? "../".repeat(depth) : "./";
68
- }
69
-
70
- // function getRelativePath(path) {
71
- // const isBrowser = typeof window !== 'undefined';
72
-
73
- // // If no path provided, use window.location.pathname
74
- // if (!path && isBrowser) {
75
- // path = window.location.pathname;
76
-
77
- // // FIX: Only remove the end segment if it looks like a file (has an extension like .html)
78
- // // This prevents stripping valid routes like /dashboard
79
- // if (/\.[^/]+$/.test(path)) {
80
- // path = path.replace(/\/[^\/]*$/, "");
81
- // }
82
- // }
83
-
84
- // // For localhost/127.0.0.1, remove everything up to and including the first '/src'
85
- // if (
86
- // isBrowser &&
87
- // (window.location.hostname === "localhost" ||
88
- // window.location.hostname === "127.0.0.1")
89
- // ) {
90
- // const srcIndex = path.indexOf("/src");
91
- // if (srcIndex !== -1) {
92
- // // If path is "/BeautySalon/src", this returns ""
93
- // path = path.slice(srcIndex + 4);
94
- // }
95
- // }
96
-
97
- // // Handle the empty string case here:
98
- // // "" does not end with "/", so it adds one -> "/"
99
- // if (!path || !path.endsWith("/")) {
100
- // path = (path || "") + "/";
101
- // }
102
-
103
- // // "/" splits to ['', ''], filter removes them -> length is 0
104
- // let depth = path.split("/").filter(Boolean).length;
105
-
106
- // // 0 depth returns "./"
107
- // return depth > 0 ? "../".repeat(depth) : "./";
108
- // }
109
-
110
- /**
111
- * Generates an ObjectId
112
- */
113
- let counter = 0;
114
- function ObjectId(inputId) {
115
- if (inputId && /^[0-9a-fA-F]{24}$/.test(inputId)) {
116
- // If a valid ObjectId is provided, return it as a custom ObjectId object
117
- return {
118
- timestamp: inputId.substring(0, 8),
119
- processId: inputId.substring(8, 20),
120
- counter: inputId.substring(20),
121
- toString: function () {
122
- return this.timestamp + this.processId + this.counter;
123
- }
124
- };
125
- } else if (inputId) {
126
- throw new Error("Invalid ObjectId provided.");
127
- }
128
-
129
- // Generate a new custom ObjectId
130
- const timestampHex = Math.floor(
131
- new Date(new Date().toISOString()).getTime() / 1000
132
- )
133
- .toString(16)
134
- .padStart(8, "0");
135
- const processIdHex = Math.floor(Math.random() * 0x100000000000)
136
- .toString(16)
137
- .padStart(12, "0");
138
-
139
- counter = (counter + 1) % 10000;
140
- if (counter < 2) {
141
- counter = Math.floor(Math.random() * (5000 - 100 + 1)) + 100;
142
- }
143
-
144
- const counterHex = counter.toString(16).padStart(4, "0");
145
-
146
- // Return the custom ObjectId object with a toString() method
147
- return {
148
- timestamp: timestampHex,
149
- processId: processIdHex,
150
- counter: counterHex,
151
- toString: function () {
152
- return this.timestamp + this.processId + this.counter;
153
- }
154
- };
155
- }
156
-
157
- function uid(length = 36) {
158
- let pattern = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
159
- if (length > 36) {
160
- length = 36; // If requested length is more than 36, set it to 36.
161
- }
162
-
163
- let uuid = pattern.replace(/[xy]/g, function (c) {
164
- var r = (Math.random() * 16) | 0;
165
- var v = c === 'x' ? r : (r & 0x3 | 0x8);
166
- return v.toString(16);
167
- }).substring(0, length); // Truncate to the requested length.
168
-
169
- return uuid;
170
- }
171
-
172
- function checkValue(value) {
173
- if (/{{\s*([\w\W]+)\s*}}/g.test(value)) return false;
174
- else return true;
175
- }
176
-
177
- function isValidDate(value) {
178
- if (
179
- typeof value === "string" &&
180
- value.length >= 20 &&
181
- value.length <= 24
182
- ) {
183
- // if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/i.test(value))
184
- if (
185
- /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?([-+]\d{2}:\d{2}|Z)?$/i.test(
186
- value
187
- )
188
- ) {
189
- return true;
190
- }
191
- }
192
-
193
- return false;
194
- }
195
-
196
- function dotNotationToObject(data, obj = {}) {
197
- try {
198
- let arrayGroup = {}; // Track groups by key paths (e.g., 'messages[a]')
199
-
200
- for (const key of Object.keys(data)) {
201
- let value = data[key];
202
- let newObject = obj;
203
- let oldObject = new Object(obj);
204
- let keys = key.split(".");
205
- let length = keys.length - 1;
206
-
207
- for (let i = 0; i < keys.length; i++) {
208
- // Check if the key ends with ']', indicating an array or grouping operation
209
- if (keys[i].endsWith("]")) {
210
- // Handle array push (e.g., messages[] -> push value)
211
- if (keys[i].endsWith("[]")) {
212
- let baseKey = keys[i].slice(0, -2); // Remove '[]'
213
-
214
- // Initialize newObject[baseKey] as an array if not an array or doesn't exist
215
- if (!Array.isArray(newObject[baseKey])) {
216
- newObject[baseKey] = [];
217
- }
218
-
219
- if (length == i) {
220
- // If value is an array, spread the array values into newObject[baseKey]
221
- if (Array.isArray(value)) {
222
- newObject[baseKey].push(...value);
223
- } else {
224
- // If value is not an array, just push the single value
225
- newObject[baseKey].push(value);
226
- }
227
- }
228
- }
229
- // Check for array index (e.g., messages[0])
230
- else if (/\[([0-9]+)\]/g.test(keys[i])) {
231
- let [k, index] = keys[i].split("[");
232
- index = index.slice(0, -1); // Get the index
233
-
234
- // Initialize newObject[k] as an array if it doesn't exist or is not an array
235
- if (!Array.isArray(newObject[k])) {
236
- newObject[k] = [];
237
- }
238
-
239
- if (length == i) {
240
- if (value === undefined) {
241
- newObject[k].splice(index, 1); // Remove element if value is undefined
242
- } else {
243
- newObject[k][index] = value; // Replace value at specified index
244
- }
245
- } else {
246
- newObject[k][index] = oldObject[k][index] || {}; // Initialize inner object
247
- newObject = newObject[k][index];
248
- oldObject = oldObject[k][index];
249
- }
250
- }
251
- // Handle letter-based groupings (e.g., messages[a].role)
252
- else if (/\[\w\]/g.test(keys[i])) {
253
- let [k, group] = keys[i].split("[");
254
- group = group.slice(0, -1); // Get the letter inside []
255
-
256
- // Initialize newObject[k] as an array if not an array or doesn't exist
257
- if (!Array.isArray(newObject[k])) {
258
- newObject[k] = [];
259
- }
260
-
261
- // If there's no object at this group index yet, push a new object
262
- let index;
263
- if (arrayGroup[keys.slice(0, i + 1).join(".")]) {
264
- // Reuse the existing index for the group
265
- index =
266
- arrayGroup[keys.slice(0, i + 1).join(".")];
267
- } else {
268
- // Create a new group and track the index
269
- index = newObject[k].length;
270
- arrayGroup[keys.slice(0, i + 1).join(".")] =
271
- index;
272
- newObject[k][index] = {};
273
- }
274
-
275
- // Move into the newly created or existing object for the group
276
- if (length == i) {
277
- newObject[k][index] = value; // Set value in the group
278
- } else {
279
- newObject = newObject[k][index]; // Continue with the group object
280
- }
281
- }
282
- }
283
- // Handle regular object keys (non-array keys)
284
- else {
285
- if (length == i) {
286
- if (value === undefined) {
287
- delete newObject[keys[i]]; // Delete key if value is undefined
288
- } else {
289
- newObject[keys[i]] = value; // Set value
290
- }
291
- } else {
292
- newObject[keys[i]] = oldObject[keys[i]] || {}; // Initialize inner object
293
- newObject = newObject[keys[i]];
294
- oldObject = oldObject[keys[i]];
295
- }
296
- }
297
- }
298
- }
299
- return obj;
300
- } catch (error) {
301
- console.log("Error converting dot notation to object", error);
302
- return false;
303
- }
304
- }
305
-
306
- /**
307
- * Flattens a deeply nested object or array into a single-level object
308
- * where keys are dot/bracket notation paths and values are the corresponding
309
- * primitive values (string, number, boolean, null, undefined) from the
310
- * original structure.
311
- *
312
- * @param {object|array} input The object or array to flatten.
313
- * @returns {object} A flat object with dot/bracket notation keys mapped to their primitive values.
314
- */
315
- function objectToDotNotation(input) {
316
- const results = {}; // Initialize an empty OBJECT to store key-value pairs
317
-
318
- // Helper function for recursion
319
- function traverse(currentValue, path) {
320
- // Base Case: Primitive values (or null/undefined)
321
- // We consider anything that's not an object or is null as a primitive endpoint.
322
- if (typeof currentValue !== "object" || currentValue === null) {
323
- // Only add if a path exists (handles the edge case where the initial input itself is primitive)
324
- if (path !== undefined && path !== null && path !== "") {
325
- results[path] = currentValue; // Assign the primitive value to the constructed path key
326
- }
327
- return; // Stop recursion for this branch
328
- }
329
-
330
- // Recursive Step: Array
331
- if (Array.isArray(currentValue)) {
332
- // Only traverse non-empty arrays if we're looking for primitive values
333
- // If you wanted empty arrays represented (e.g., 'notes': []), you'd add logic here.
334
- if (currentValue.length > 0) {
335
- currentValue.forEach((item, index) => {
336
- // Build the next path segment using bracket notation for arrays
337
- const nextPath = `${path}[${index}]`;
338
- traverse(item, nextPath);
339
- });
340
- } else if (path) {
341
- // Optional: represent empty arrays explicitly if needed
342
- // results[path] = []; // Uncomment this line if you want empty arrays included
343
- }
344
- }
345
- // Recursive Step: Object (and not null, not an array)
346
- else {
347
- const keys = Object.keys(currentValue);
348
- // Only traverse non-empty objects if we're looking for primitive values
349
- // If you wanted empty objects represented (e.g., 'metadata': {}), you'd add logic here.
350
- if (keys.length > 0) {
351
- keys.forEach((key) => {
352
- // Build the next path segment:
353
- // - Use dot notation if the current path is not empty.
354
- // - Just use the key if it's the first level.
355
- const nextPath = path ? `${path}.${key}` : key;
356
- traverse(currentValue[key], nextPath);
357
- });
358
- } else if (path) {
359
- // Optional: represent empty objects explicitly if needed
360
- // results[path] = {}; // Uncomment this line if you want empty objects included
361
- }
362
- }
363
- }
364
-
365
- // Start the traversal with the initial input and an empty path
366
- // Using an empty string '' ensures the first level keys don't start with '.'
367
- traverse(input, "");
368
-
369
- return results; // Return the populated results object
370
- }
371
-
372
- function getValueFromObject(object = {}, path = "", throwError = false) {
373
- try {
374
- if ((!Array.isArray(object) && !Object.keys(object).length) || !path) {
375
- if (throwError)
376
- throw new Error("Invalid input to getValueFromObject");
377
- return;
378
- }
379
-
380
- // remove leading dot if path is like `[0].src`
381
- path = path.replace(/\[(\d+)\]/g, ".$1").replace(/^\./, '');
382
-
383
- let data = object,
384
- subpath = path.split(".");
385
-
386
- for (let i = 0; i < subpath.length; i++) {
387
- if (throwError && !(subpath[i] in data))
388
- throw new Error("Key not found in object: " + subpath[i]);
389
-
390
- data = data[subpath[i]];
391
- if (!data) break;
392
- }
393
-
394
- return data;
395
- } catch (error) {
396
- // console.error("Error in getValueFromObject:", error);
397
- if (throwError) throw error;
398
- }
399
- }
400
-
401
- function createUpdate(update, data, globalOpertors) {
402
- let operatorKeys = {};
403
- if (globalOpertors) operatorKeys = { ...globalOpertors };
404
-
405
- Object.keys(data).forEach((key) => {
406
- if (key.startsWith("$")) {
407
- if (!key.includes("."))
408
- for (let oldkey of Object.keys(data[key]))
409
- operatorKeys[key + "." + oldkey] = data[key][oldkey];
410
- else operatorKeys[key] = data[key];
411
- } else if (
412
- typeof data[key] === "string" &&
413
- data[key].startsWith("$")
414
- ) {
415
- operatorKeys[data[key] + "." + key] = data[key];
416
- } else if (key.endsWith("]")) {
417
- const regex = /^(.*(?:\[\d+\].*?)?)\[(.*?)\](?:\[\])?$/;
418
- const match = key.match(regex);
419
- if (match[2] === "")
420
- operatorKeys["$push." + match[1]] = data[key];
421
- else {
422
- let index = parseInt(match[2], 10);
423
- if (index === NaN)
424
- operatorKeys[match[2] + "." + match[1]] = data[key];
425
- else operatorKeys[key] = data[key];
426
- }
427
- } else if (key.includes(".")) {
428
- operatorKeys[key] = data[key];
429
- } else if (data[key] === undefined) {
430
- delete update[key];
431
- } else update[key] = data[key];
432
- });
433
-
434
- return dotNotationToObjectUpdate(operatorKeys, update);
435
- }
436
-
437
- function dotNotationToObjectUpdate(data, object = {}) {
438
- try {
439
- for (const key of Object.keys(data)) {
440
- let newObject = object;
441
- let oldObject = new Object(newObject);
442
- let keys = key
443
- .replace(/\[(\d+)\]/g, ".$1")
444
- .split(".")
445
- .map((k) => (isNaN(k) ? k : Number(k)));
446
- let value = data[key];
447
- let operator;
448
- if (keys[0].startsWith("$")) operator = keys.shift();
449
-
450
- let length = keys.length - 1;
451
- for (let i = 0; i < keys.length; i++) {
452
- // if (/^\d+$/.test(keys[i])) keys[i] = parseInt(keys[i]);
453
-
454
- if (length == i) {
455
- if (operator) {
456
- let operators = [
457
- "$rename",
458
- "$inc",
459
- "$push",
460
- "$each",
461
- "$splice",
462
- "$unset",
463
- "$delete",
464
- "$slice",
465
- "$pop",
466
- "$shift",
467
- "$addToSet",
468
- "$pull"
469
- ];
470
- if (!operators.includes(operator)) continue;
471
- if (operator === "$rename") {
472
- newObject[value] = newObject[keys[i]];
473
- delete newObject[keys[i]];
474
- } else if (
475
- operator === "$delete" ||
476
- operator === "$unset" ||
477
- operator === "$slice"
478
- ) {
479
- if (typeof keys[i] === "number") {
480
- newObject.splice(keys[i], 1);
481
- } else {
482
- delete newObject[keys[i]];
483
- }
484
- } else if (operator === "$shift") {
485
- newObject[keys[i]].shift();
486
- } else if (operator === "$pop") {
487
- newObject[keys[i]].pop();
488
- } else if (operator === "$addToSet") {
489
- if (!newObject[keys[i]])
490
- newObject[keys[i]] = [];
491
- let exists;
492
- if (Array.isArray(value)) {
493
- exists = newObject[keys[i]].some(
494
- (item) =>
495
- Array.isArray(item) &&
496
- isEqualArray(item, value)
497
- );
498
- } else if (
499
- typeof value === "object" &&
500
- value !== null
501
- ) {
502
- exists = newObject[keys[i]].some(
503
- (item) =>
504
- typeof item === "object" &&
505
- isEqualObject(item, value)
506
- );
507
- } else {
508
- exists = newObject[keys[i]].includes(value);
509
- }
510
- if (!exists) newObject[keys[i]].push(value);
511
- } else if (operator === "$pull") {
512
- if (!newObject[keys[i]]) continue;
513
- if (Array.isArray(value)) {
514
- newObject[keys[i]] = newObject[
515
- keys[i]
516
- ].filter(
517
- (item) =>
518
- !Array.isArray(item) ||
519
- !isEqualArray(item, value)
520
- );
521
- } else if (
522
- typeof value === "object" &&
523
- value !== null
524
- ) {
525
- newObject[keys[i]] = newObject[
526
- keys[i]
527
- ].filter(
528
- (item) =>
529
- typeof item !== "object" ||
530
- !isEqualObject(item, value)
531
- );
532
- } else {
533
- newObject[keys[i]] = newObject[
534
- keys[i]
535
- ].filter((item) => item !== value);
536
- }
537
- } else if (
538
- operator === "$push" ||
539
- operator === "$splice"
540
- ) {
541
- if (
542
- typeof keys[i] === "number" &&
543
- newObject.length >= keys[i]
544
- )
545
- newObject.splice(keys[i], 0, value);
546
- else if (newObject[keys[i]])
547
- newObject[keys[i]].push(value);
548
- else newObject[keys[i]] = [value];
549
- } else if (operator === "$each") {
550
- if (!Array.isArray(value)) value = [value];
551
- if (typeof keys[i] === "number")
552
- newObject.splice(keys[i], 0, ...value);
553
- else newObject[keys[i]].push(...value);
554
- } else if (operator === "$inc") {
555
- if (
556
- !newObject[keys[i]] ||
557
- typeof newObject[keys[i]] !== "number"
558
- )
559
- newObject[keys[i]] = value;
560
- else newObject[keys[i]] += value;
561
- }
562
- } else if (value === undefined) {
563
- if (typeof keys[i] === "number")
564
- newObject.splice(keys[i], 1);
565
- else delete newObject[keys[i]];
566
- } else if (typeof keys[i] === "number") {
567
- newObject.splice(keys[i], 0, value);
568
- } else {
569
- newObject[keys[i]] = value;
570
- }
571
- } else if (
572
- typeof keys[i + 1] === "number" &&
573
- !Array.isArray(newObject[keys[i]])
574
- ) {
575
- newObject[keys[i]] = [];
576
- } else {
577
- newObject[keys[i]] = newObject[keys[i]] || {};
578
- // newObject[keys[i]] = oldObject[keys[i]] || {};
579
- // oldObject = oldObject[keys[i]];
580
- }
581
- newObject = newObject[keys[i]];
582
- }
583
- }
584
- return object;
585
- } catch (error) {
586
- console.log("Error converting dot notation to object", error);
587
- return false;
588
- }
589
- }
590
-
591
- /**
592
- * Converts a JavaScript object into a URL-encoded query string using
593
- * the standard URLSearchParams API (works in Node.js and modern browsers).
594
- * - Uses repeated keys for arrays.
595
- * - Skips null/undefined values.
596
- * - Converts other values to strings.
597
- *
598
- * @param {object | null | undefined} paramsObj The object to convert.
599
- * @returns {string} A URL-encoded query string starting with '?'
600
- * if params exist, otherwise an empty string.
601
- */
602
- function objectToSearchParams(paramsObj) {
603
- if (
604
- !paramsObj ||
605
- typeof paramsObj !== "object" ||
606
- Array.isArray(paramsObj)
607
- ) {
608
- return "";
609
- }
610
-
611
- // Filter out null/undefined values
612
- const filteredObj = {};
613
- for (const key in paramsObj) {
614
- if (Object.hasOwn(paramsObj, key)) {
615
- const value = paramsObj[key];
616
- if (value !== null && value !== undefined) {
617
- filteredObj[key] = value;
618
- }
619
- }
620
- }
621
-
622
- if (Object.keys(filteredObj).length === 0) {
623
- return "";
624
- }
625
-
626
- // --- CORE LOGIC ---
627
- // Create URLSearchParams directly from the filtered object
628
- // This works identically in modern Node.js and browsers.
629
- const searchParams = new URLSearchParams(filteredObj);
630
- const queryString = searchParams.toString();
631
- // --- END CORE LOGIC ---
632
-
633
- return queryString ? `?${queryString}` : "";
634
- }
635
-
636
- function domParser(str) {
637
- try {
638
- var mainTag = str.match(/\<(?<tag>[a-z0-9]+)(.*?)?\>/).groups.tag;
639
- } catch (e) {
640
- // console.log(e, 'find position: can not find the main tag');
641
- }
642
- let doc;
643
- switch (mainTag) {
644
- case "html":
645
- doc = new DOMParser().parseFromString(str, "text/html");
646
- return doc.documentElement;
647
- case "body":
648
- doc = new DOMParser().parseFromString(str, "text/html");
649
- return doc.body;
650
- case "head":
651
- doc = new DOMParser().parseFromString(str, "text/html");
652
- return doc.head;
653
-
654
- default:
655
- let con = document.createElement("dom-parser");
656
- con.innerHTML = str;
657
- return con;
658
- }
659
- }
660
-
661
- function parseTextToHtml(text) {
662
- let doc = new DOMParser().parseFromString(text, "text/html");
663
- if (doc.head.children[0]) return doc.head.children[0];
664
- else return doc.body.children[0];
665
- }
666
-
667
- function escapeHtml(html) {
668
- return html
669
- .replaceAll("&", "&amp")
670
- .replaceAll("<", "&lt")
671
- .replaceAll(">", "&gt;")
672
- .replaceAll("'", "&#39;")
673
- .replaceAll('"', "&quot;");
674
- }
675
-
676
- function cssPath(node, container) {
677
- let pathSplits = [];
678
- do {
679
- if (!node || !node.tagName) return false;
680
- let pathSplit = node.tagName.toLowerCase();
681
-
682
- if (node.id) {
683
- pathSplit += "#" + node.id;
684
- node = "";
685
- } else {
686
- let eid = node.getAttribute("eid");
687
- if (eid && !/{{\s*([\w\W]+)\s*}}/g.test(eid)) {
688
- pathSplit += `[eid="${eid}"]`;
689
- node = "";
690
- }
691
- // if (node.classList.length) {
692
- // node.classList.forEach((item) => {
693
- // if (item.indexOf(":") === -1) pathSplit += "." + item;
694
- // });
695
- // }
696
- else if (
697
- node.parentNode &&
698
- node.parentNode.children.length > 1
699
- ) {
700
- // TODO: improve array logic so ignores javascript generated html??
701
- let children = [];
702
- for (let child of node.parentNode.children) {
703
- if (child.tagName == node.tagName) children.push(child);
704
- }
705
- let index = Array.prototype.indexOf.call(children, node);
706
-
707
- pathSplit += `:nth-of-type(${index + 1})`;
708
- }
709
-
710
- node = node.parentNode;
711
- if (
712
- node == null ||
713
- node.tagName == "HTML" ||
714
- node.tagName == "BODY" ||
715
- node.tagName == "DOM-PARSER" ||
716
- node.nodeName == "#document" ||
717
- node.hasAttribute("contenteditable")
718
- )
719
- node = "";
720
- }
721
-
722
- pathSplits.unshift(pathSplit);
723
- } while (node);
724
- let path = pathSplits.join(" > ");
725
- if (path && path.includes("<")) {
726
- let index = path.lastIndexOf(" >");
727
- if (index != -1) path = path.slice(0, index);
728
- else {
729
- index = path.lastIndexOf("<");
730
- path = path.slice(0, index);
731
- }
732
- }
733
-
734
- return path;
735
- }
736
-
737
- // Define a list of query types that describe relationships or contexts in the DOM.
738
- const queryTypes = [
739
- "$closest", // Selects the closest ancestor matching the selector
740
- "$parent", // Selects the direct parent element
741
- "$next", // Selects the next sibling element
742
- "$previous", // Selects the previous sibling element
743
- "$document", // Selects the document containing the element
744
- "$frame", // Selects the frame or iframe containing the element
745
- "$top" // Selects the top-level document or window
746
- ];
747
-
748
- // Construct a regular expression pattern to match any of the query types.
749
- // Each query type begins with a dollar sign, which is escaped in the regex.
750
- const regexPatternString = `(?:${queryTypes
751
- .map((type) => type.replace("$", "\\$")) // Escape $ character for regex
752
- .join("|")})`;
753
-
754
- // Compile the regular expression pattern into a RegExp object.
755
- // This regex will be used to find the first occurrence of any query type within a string.
756
- const queryTypesRegex = new RegExp(regexPatternString);
757
-
758
- /**
759
- * Function to query DOM elements based on specified criteria.
760
- * @param {Object} params - Object containing parameters for querying elements.
761
- * @param {Element|Document} params.element - The root element or document to start the query from. Defaults to the entire document.
762
- * @param {string} params.prefix - Optional prefix used to construct the query.
763
- * @param {string} params.selector - The CSS selector or query string to use.
764
- * @returns {Array} - An array of elements that match the query.
765
- */
766
- function queryElements({ element = document, prefix, selector }) {
767
- try {
768
- // Initialize a Set to store unique elements.
769
- let elements = new Set();
770
-
771
- // If no selector is provided and the element is an element node.
772
- if (!selector && element.nodeType === 1) {
773
- // If no prefix is provided, derive one from the element's attributes.
774
- if (!prefix) {
775
- for (let attr of element.attributes) {
776
- // If an attribute with "-query" suffix is found, extract prefix.
777
- if (attr.name.endsWith("-query")) {
778
- prefix = attr.name.slice(0, -6);
779
- }
780
- }
781
- // If no valid prefix is found, exit the function.
782
- if (!prefix) return [];
783
- }
784
- // Get the selector using the derived prefix.
785
- selector = element.getAttribute(prefix + "-" + "query");
786
- if (!selector) return []; // Exit if no selector is found.
787
- }
788
-
789
- // Split complex selectors into individual ones, handling nested structures.
790
- let selectors = selector.split(/,(?![^()\[\]]*[)\]])/g);
791
- for (let i = 0; i < selectors.length; i++) {
792
- if (!selectors[i]) continue; // Skip empty selectors.
793
-
794
- let queriedElement = element; // Start query from the current element.
795
-
796
- // If media queries are included, verify and filter the selector accordingly.
797
- if (selectors[i].includes("@")) {
798
- selectors[i] = checkMediaQueries(selectors[i]);
799
- if (selectors[i] === false) continue; // Skip if media query is not matched.
800
- }
801
-
802
- let remainingSelector = selectors[i].trim(); // Trim any whitespace.
803
- let match;
804
-
805
- // Process each part of the selector that corresponds to specific query types/operators.
806
- while (
807
- (match = queryTypesRegex.exec(remainingSelector)) !== null
808
- ) {
809
- const matchIndex = match.index;
810
- const operator = match[0];
811
-
812
- // Process the part before the operator (if any).
813
- const part = remainingSelector
814
- .substring(0, matchIndex)
815
- .trim()
816
- .replace(/,$/, "");
817
- if (part) {
818
- queriedElement = querySelector(queriedElement, part);
819
- if (!queriedElement) break; // Exit loop if no element is found.
820
- }
821
-
822
- // Remove the processed part and operator from the remaining selector.
823
- remainingSelector = remainingSelector
824
- .substring(matchIndex + operator.length)
825
- .trim();
826
-
827
- // Handle the $closest operator specifically.
828
- if (operator === "$closest") {
829
- let [closest, remaining = ""] =
830
- remainingSelector.split(/\s+/, 2);
831
- queriedElement = queriedElement.closest(closest);
832
- remainingSelector = remaining.trim();
833
- } else {
834
- // Process other operators using the queryType function.
835
- queriedElement = queryType(queriedElement, operator);
836
- }
837
-
838
- if (!queriedElement) break; // Exit loop if no element is found.
839
- }
840
-
841
- if (!queriedElement) continue; // Skip if no element is found.
842
-
843
- // Process the remaining part after the last operator (if any).
844
- if (remainingSelector) {
845
- queriedElement = querySelector(
846
- queriedElement,
847
- remainingSelector
848
- );
849
- }
850
-
851
- // Add elements to the set.
852
- if (
853
- Array.isArray(queriedElement) ||
854
- queriedElement instanceof HTMLCollection ||
855
- queriedElement instanceof NodeList
856
- ) {
857
- for (let el of queriedElement) {
858
- if (el instanceof Element) {
859
- elements.add(el);
860
- }
861
- }
862
- } else if (queriedElement instanceof Element) {
863
- elements.add(queriedElement);
864
- }
865
- }
866
-
867
- return Array.from(elements); // Convert Set to Array and return found elements.
868
- } catch (e) {
869
- console.error(
870
- `CoCreate: Error in queryElements with selector: "${selector}".`,
871
- e
872
- );
873
- return [];
874
- }
875
- }
876
-
877
- function queryType(element, type) {
878
- if (!element) return null;
879
-
880
- switch (type) {
881
- case "$top":
882
- return window.top.document;
883
- case "$frame":
884
- // If element is a document, return the iframe element containing it
885
- if (element.nodeType === 9) return window.frameElement;
886
- // If element is an iframe, return it as is
887
- return element;
888
- case "$document":
889
- // If element is a document, return itself, else return `ownerDocument`
890
- return element.nodeType === 9 ? element : element.ownerDocument;
891
- case "$closest":
892
- // If closest find the first selector seperated by space
893
-
894
- return element.nodeType === 9 ? element : element.ownerDocument;
895
- case "$parent":
896
- // If it's a document, return the parent document (if inside an iframe)
897
- if (element.nodeType === 9) {
898
- return element.defaultView !== window.top
899
- ? element.defaultView.parent.document
900
- : null;
901
- }
902
- // Otherwise, return parent element
903
- return element.parentElement;
904
- case "$next":
905
- return element.nextElementSibling;
906
- case "$previous":
907
- return element.previousElementSibling;
908
- default:
909
- return null;
910
- }
911
- }
912
-
913
- function querySelector(element, selector) {
914
- if (!element) return null;
915
- return selector.endsWith("[]")
916
- ? element.querySelectorAll(selector.slice(0, -2))
917
- : element.querySelector(selector);
918
- }
919
-
920
- const mediaRanges = {
921
- xs: [0, 575],
922
- sm: [576, 768],
923
- md: [769, 992],
924
- lg: [993, 1200],
925
- xl: [1201, 0]
926
- };
927
-
928
- function checkMediaQueries(selector) {
929
- if (selector && selector.includes("@")) {
930
- const viewportWidth = window.innerWidth;
931
- let mediaViewport = false;
932
-
933
- let screenSizes = selector.split("@");
934
- selector = screenSizes.shift();
935
-
936
- for (let screenSize of screenSizes) {
937
- // Check if screenSize is a valid range in the 'ranges' object
938
- if (mediaRanges.hasOwnProperty(screenSize)) {
939
- const [minWidth, maxWidth] = mediaRanges[screenSize];
940
- if (
941
- viewportWidth >= minWidth &&
942
- viewportWidth <= maxWidth
943
- ) {
944
- mediaViewport = true;
945
- break;
946
- }
947
- }
948
- }
949
- if (!mediaViewport) return false;
950
- }
951
-
952
- return selector;
953
- }
954
-
955
- function queryData(data, query) {
956
- if (query.$and) {
957
- for (let i = 0; i < query.$and.length; i++) {
958
- if (!queryData(data, query.$and[i])) return false;
959
- }
960
- }
961
-
962
- if (query.$nor) {
963
- for (let i = 0; i < query.$nor.length; i++) {
964
- if (queryData(data, query.$nor[i])) return false;
965
- }
966
- }
967
-
968
- for (let key of Object.keys(query)) {
969
- if (key === "$and" || key === "$or") continue;
970
- if (!queryMatch(data, { [key]: query[key] })) return false;
971
- }
972
-
973
- if (query.$or) {
974
- for (let i = 0; i < query.$or.length; i++) {
975
- if (queryData(data, query.$or[i])) return true;
976
- }
977
- }
978
-
979
- return true;
980
- }
981
-
982
- function queryMatch(data, query) {
983
- for (let key of Object.keys(query)) {
984
- // if (!data.hasOwnProperty(key))
985
- // return false
986
-
987
- let dataValue;
988
- try {
989
- dataValue = getValueFromObject(data, key, true);
990
- } catch (error) {
991
- return false;
992
- }
993
-
994
- if (
995
- typeof query[key] === "string" ||
996
- typeof query[key] === "number" ||
997
- typeof query[key] === "boolean"
998
- ) {
999
- if (Array.isArray(dataValue))
1000
- return dataValue.includes(query[key]);
1001
- else return dataValue === query[key];
1002
- } else if (Array.isArray(query[key])) {
1003
- if (Array.isArray(dataValue)) {
1004
- return isEqualArray(dataValue, query[key]);
1005
- } else {
1006
- return false;
1007
- }
1008
- } else {
1009
- for (let property of Object.keys(query[key])) {
1010
- if (property === "$options") continue;
1011
- if (!property.startsWith("$")) {
1012
- if (typeof dataValue !== "object") {
1013
- return false;
1014
- } else
1015
- return queryMatch(
1016
- {
1017
- [property]: getValueFromObject(
1018
- dataValue,
1019
- property
1020
- )
1021
- },
1022
- { [property]: query[key][property] }
1023
- );
1024
- } else {
1025
- let queryValue = query[key][property];
1026
- if (isValidDate(queryValue) && isValidDate(dataValue)) {
1027
- queryValue = new Date(queryValue);
1028
- dataValue = new Date(dataValue);
1029
- }
1030
- let queryStatus = false;
1031
- switch (property) {
1032
- case "$eq":
1033
- if (
1034
- Array.isArray(dataValue) &&
1035
- Array.isArray(queryValue)
1036
- ) {
1037
- queryStatus = isEqualArray(
1038
- dataValue,
1039
- queryValue
1040
- );
1041
- } else {
1042
- queryStatus = dataValue === queryValue;
1043
- }
1044
- break;
1045
- case "$ne":
1046
- if (
1047
- Array.isArray(dataValue) &&
1048
- Array.isArray(queryValue)
1049
- ) {
1050
- queryStatus = !isEqualArray(
1051
- dataValue,
1052
- queryValue
1053
- );
1054
- } else {
1055
- queryStatus = dataValue !== queryValue;
1056
- }
1057
- break;
1058
- case "$not":
1059
- queryStatus = !queryMatch(data, {
1060
- [key]: query[key]["$not"]
1061
- });
1062
- break;
1063
- case "$lt":
1064
- queryStatus = dataValue < queryValue;
1065
- break;
1066
- case "$lte":
1067
- queryStatus = dataValue <= queryValue;
1068
- break;
1069
- case "$gt":
1070
- queryStatus = dataValue > queryValue;
1071
- break;
1072
- case "$gte":
1073
- queryStatus = dataValue >= queryValue;
1074
- break;
1075
- case "$in":
1076
- if (Array.isArray(dataValue)) {
1077
- queryStatus = dataValue.some((element) =>
1078
- queryValue.includes(element)
1079
- );
1080
- } else {
1081
- queryStatus =
1082
- queryValue.includes(dataValue);
1083
- }
1084
- break;
1085
- case "$nin":
1086
- if (Array.isArray(dataValue)) {
1087
- queryStatus = !dataValue.some((element) =>
1088
- queryValue.includes(element)
1089
- );
1090
- } else {
1091
- queryStatus =
1092
- !queryValue.includes(dataValue);
1093
- }
1094
- break;
1095
- case "$all":
1096
- if (
1097
- Array.isArray(dataValue) &&
1098
- Array.isArray(queryValue)
1099
- ) {
1100
- queryStatus = queryValue.every((element) =>
1101
- dataValue.includes(element)
1102
- );
1103
- }
1104
- break;
1105
- case "$elemMatch":
1106
- if (Array.isArray(data[key])) {
1107
- queryStatus = data[key].some((element) =>
1108
- queryMatch(
1109
- element,
1110
- query[key][property]
1111
- )
1112
- );
1113
- }
1114
- break;
1115
- case "$size":
1116
- if (Array.isArray(dataValue)) {
1117
- queryStatus =
1118
- dataValue.length === queryValue;
1119
- }
1120
- break;
1121
- case "$exists":
1122
- queryStatus = queryValue
1123
- ? data.hasOwnProperty(key)
1124
- : !data.hasOwnProperty(key);
1125
- break;
1126
- case "$regex":
1127
- if (typeof dataValue === "string") {
1128
- let regexFlag =
1129
- query[key]["$options"] || "";
1130
- let regex = new RegExp(
1131
- queryValue,
1132
- regexFlag
1133
- );
1134
- queryStatus = regex.test(dataValue);
1135
- }
1136
- break;
1137
- case "$type":
1138
- let dataType = typeof dataValue;
1139
- if (Array.isArray(dataValue)) {
1140
- dataType = "array";
1141
- }
1142
- queryStatus = dataType === queryValue;
1143
- break;
1144
- case "$mod":
1145
- if (
1146
- typeof dataValue === "number" &&
1147
- Array.isArray(queryValue) &&
1148
- queryValue.length === 2
1149
- ) {
1150
- const [divisor, remainder] = queryValue;
1151
- queryStatus =
1152
- dataValue % divisor === remainder;
1153
- }
1154
- break;
1155
- case "$where":
1156
- if (typeof queryValue === "function") {
1157
- try {
1158
- // queryStatus = queryValue.call(data);
1159
- } catch (error) {
1160
- console.error(
1161
- "Error in queryData $where function:",
1162
- error
1163
- );
1164
- }
1165
- }
1166
- break;
1167
-
1168
- default:
1169
- console.log("unknown operator");
1170
- break;
1171
- }
1172
- if (!queryStatus) return false;
1173
- }
1174
- }
1175
- return true;
1176
- }
1177
- }
1178
- }
1179
-
1180
- function isEqualArray(arr1, arr2) {
1181
- if (arr1.length !== arr2.length) {
1182
- return false;
1183
- }
1184
- for (let i = 0; i < arr1.length; i++) {
1185
- if (!isEqualObject(arr1[i], arr2[i])) {
1186
- return false;
1187
- }
1188
- }
1189
- return true;
1190
- }
1191
-
1192
- function isEqualObject(obj1, obj2) {
1193
- const keys1 = Object.keys(obj1);
1194
- const keys2 = Object.keys(obj2);
1195
-
1196
- if (keys1.length !== keys2.length) {
1197
- return false;
1198
- }
1199
-
1200
- for (const key of keys1) {
1201
- if (obj1[key] !== obj2[key]) {
1202
- return false;
1203
- }
1204
- }
1205
-
1206
- return true;
1207
- }
1208
-
1209
- function searchData(data, search) {
1210
- if (!search) return true;
1211
- if (!Array.isArray(search)) search = [search];
1212
- for (let i = 0; i < search.length; i++) {
1213
- let searchValue = search[i].value;
1214
- if (!Array.isArray(searchValue)) searchValue = [searchValue];
1215
- for (let key in data) {
1216
- let value = data[key];
1217
- let status = false;
1218
- switch (typeof value) {
1219
- case "number":
1220
- value = value.toString();
1221
- break;
1222
- case "object":
1223
- value = JSON.stringify(value);
1224
- break;
1225
- case "function":
1226
- value = value.toString();
1227
- break;
1228
- }
1229
- if (
1230
- search[i].caseSensitive != "true" ||
1231
- search[i].caseSensitive != true
1232
- )
1233
- value = value.toLowerCase();
1234
-
1235
- for (let i = 0; i < searchValue.length; i++) {
1236
- let searchString = searchValue[i];
1237
- if (
1238
- search[i].caseSensitive != "true" ||
1239
- search[i].caseSensitive != true
1240
- )
1241
- searchString = searchString.toLowerCase();
1242
-
1243
- if (searchString === "" && search[i].operator === "and") {
1244
- if (value !== "") return false;
1245
- }
1246
-
1247
- if (value.indexOf(searchString) > -1) status = true;
1248
-
1249
- if (status) return true;
1250
- else if (search[i].operator == "and") return false;
1251
- }
1252
- }
1253
- if (search[i].value.length && search[i].operator == "or")
1254
- return false;
1255
- }
1256
- return true;
1257
- }
1258
-
1259
- function sortData(data, sort) {
1260
- return data.sort((a, b) => {
1261
- for (let i = 0; i < sort.length; i++) {
1262
- let key = sort[i].key;
1263
- if (a[key] == null && b[key] == null) continue;
1264
- if (a[key] == null)
1265
- return sort[i].direction === "desc" ? -1 : 1;
1266
- if (b[key] == null)
1267
- return sort[i].direction === "desc" ? 1 : -1;
1268
-
1269
- if (typeof a[key] !== typeof b[key]) {
1270
- return typeof a[key] < typeof b[key] ? -1 : 1;
1271
- }
1272
-
1273
- if (a[key] !== b[key]) {
1274
- if (typeof a[key] === "string") {
1275
- return sort[i].direction === "desc"
1276
- ? b[key].localeCompare(a[key])
1277
- : a[key].localeCompare(b[key]);
1278
- } else {
1279
- // Assuming numeric or other comparable types
1280
- return sort[i].direction === "desc"
1281
- ? b[key] - a[key]
1282
- : a[key] - b[key];
1283
- }
1284
- }
1285
- }
1286
- return 0;
1287
- });
1288
- }
1289
-
1290
- function getAttributes(el) {
1291
- if (!el) return;
1292
-
1293
- let attributes = window.CoCreateConfig.attributes;
1294
- let object = {};
1295
-
1296
- for (let attribute of el.attributes) {
1297
- let variable = attributes[attribute.name];
1298
- if (variable) {
1299
- object[variable] = el.getAttribute(attribute.name);
1300
- }
1301
- }
1302
-
1303
- return object;
1304
- }
1305
-
1306
- function getAttributeNames(variables) {
1307
- let reversedObject = {};
1308
- for (const key of Object.keys(CoCreateConfig.attributes)) {
1309
- reversedObject[CoCreateConfig.attributes[key]] = key;
1310
- }
1311
-
1312
- let attributes = [];
1313
- for (const variable of variables) {
1314
- let attribute = reversedObject[variable];
1315
- if (attribute) attributes.push(attribute);
1316
- }
1317
- return attributes;
1318
- }
1319
-
1320
- function setAttributeNames(attributes, overWrite) {
1321
- let reversedObject = {};
1322
- for (const key of Object.keys(CoCreateConfig.attributes)) {
1323
- reversedObject[CoCreateConfig.attributes[key]] = key;
1324
- }
1325
-
1326
- for (const attribute of Object.keys(attributes)) {
1327
- const variable = attributes[attribute];
1328
- if (!reversedObject[variable] || overWrite != false)
1329
- reversedObject[variable] = attribute;
1330
- }
1331
-
1332
- let revertObject = {};
1333
- for (const key of Object.keys(reversedObject)) {
1334
- revertObject[reversedObject[key]] = key;
1335
- }
1336
- CoCreateConfig.attributes = revertObject;
1337
- }
1338
-
1339
- if (isBrowser) clickedElement();
1340
-
1341
- return {
1342
- getRelativePath,
1343
- ObjectId,
1344
- uid,
1345
- checkValue,
1346
- isValidDate,
1347
- dotNotationToObject,
1348
- objectToDotNotation,
1349
- getValueFromObject,
1350
- objectToSearchParams,
1351
- domParser,
1352
- parseTextToHtml,
1353
- escapeHtml,
1354
- cssPath,
1355
- queryElements,
1356
- checkMediaQueries,
1357
- queryData,
1358
- searchData,
1359
- sortData,
1360
- createUpdate,
1361
- getAttributes,
1362
- setAttributeNames,
1363
- getAttributeNames
1364
- };
1365
- });
1
+ export { getRelativePath } from "./getRelativePath.js";
2
+ export { ObjectId } from "./ObjectId.js";
3
+ export { uid } from "./uid.js";
4
+ export { checkValue } from "./checkValue.js";
5
+ export { isValidDate } from "./isValidDate.js";
6
+ export { objectToSearchParams } from "./objectToSearchParams.js";
7
+ export { dotNotationToObject } from "./dotNotationToObject.js";
8
+ export { objectToDotNotation } from "./objectToDotNotation.js";
9
+ export { getValueFromObject } from "./getValueFromObject.js";
10
+ export { createUpdate } from "./createUpdate.js";
11
+ export { domParser } from "./domParser.js";
12
+ export { parseTextToHtml } from "./parseTextToHtml.js";
13
+ export { escapeHtml } from "./escapeHtml.js";
14
+ export { cssPath } from "./cssPath.js";
15
+ export { queryElements, checkMediaQueries } from "./queryElements.js";
16
+ export { queryData, searchData, sortData } from "./dataQuery.js";
17
+ export { getAttributes, getAttributeNames, setAttributeNames } from "./attributes.js";
18
+ export { safeParse } from "./safeParse.js";
19
+ export { clickedElement } from "./clickedElement.js";
20
+ export { processOperators, processOperatorsAsync } from "./operators.js";
21
+
22
+ import { getRelativePath } from "./getRelativePath.js";
23
+ import { ObjectId } from "./ObjectId.js";
24
+ import { uid } from "./uid.js";
25
+ import { checkValue } from "./checkValue.js";
26
+ import { isValidDate } from "./isValidDate.js";
27
+ import { objectToSearchParams } from "./objectToSearchParams.js";
28
+ import { dotNotationToObject } from "./dotNotationToObject.js";
29
+ import { objectToDotNotation } from "./objectToDotNotation.js";
30
+ import { getValueFromObject } from "./getValueFromObject.js";
31
+ import { createUpdate } from "./createUpdate.js";
32
+ import { domParser } from "./domParser.js";
33
+ import { parseTextToHtml } from "./parseTextToHtml.js";
34
+ import { escapeHtml } from "./escapeHtml.js";
35
+ import { cssPath } from "./cssPath.js";
36
+ import { queryElements, checkMediaQueries } from "./queryElements.js";
37
+ import { queryData, searchData, sortData } from "./dataQuery.js";
38
+ import { getAttributes, getAttributeNames, setAttributeNames } from "./attributes.js";
39
+ import { safeParse } from "./safeParse.js";
40
+ import { processOperators, processOperatorsAsync } from "./operators.js";
41
+
42
+ const utils = {
43
+ getRelativePath,
44
+ ObjectId,
45
+ uid,
46
+ checkValue,
47
+ isValidDate,
48
+ dotNotationToObject,
49
+ objectToDotNotation,
50
+ getValueFromObject,
51
+ objectToSearchParams,
52
+ domParser,
53
+ parseTextToHtml,
54
+ escapeHtml,
55
+ cssPath,
56
+ queryElements,
57
+ checkMediaQueries,
58
+ queryData,
59
+ searchData,
60
+ sortData,
61
+ createUpdate,
62
+ getAttributes,
63
+ setAttributeNames,
64
+ getAttributeNames,
65
+ safeParse,
66
+ processOperators,
67
+ processOperatorsAsync
68
+ };
69
+
70
+ export default utils;