@cocreate/utils 1.42.0 → 1.42.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/CHANGELOG.md +7 -0
- package/index.cjs +3 -0
- package/package.json +3 -7
- package/src/index.js +3 -3
- package/src/operators copy.js +687 -0
- package/src/operators.js +407 -526
package/src/operators.js
CHANGED
|
@@ -3,578 +3,459 @@ import { uid } from "./uid.js";
|
|
|
3
3
|
import { queryElements } from "./queryElements.js";
|
|
4
4
|
import { getValueFromObject } from "./getValueFromObject.js";
|
|
5
5
|
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// $length: (element) => {element.getValue() || ""},
|
|
16
|
-
$innerWidth: () => window.innerWidth,
|
|
17
|
-
$innerHeight: () => window.innerHeight,
|
|
18
|
-
$href: () => window.location.href.replace(/\/$/, ""),
|
|
19
|
-
$origin: () => window.location.origin,
|
|
20
|
-
$protocol: () => window.location.protocol,
|
|
21
|
-
$hostname: () => window.location.hostname,
|
|
22
|
-
$host: () => window.location.host,
|
|
23
|
-
$port: () => window.location.port,
|
|
24
|
-
$pathname: () => window.location.pathname.replace(/\/$/, ""),
|
|
25
|
-
$hash: () => window.location.hash,
|
|
26
|
-
$subdomain: () => getSubdomain() || "",
|
|
27
|
-
$object_id: () => ObjectId().toString(),
|
|
28
|
-
"ObjectId()": () => ObjectId().toString(),
|
|
29
|
-
$relativePath: () => {
|
|
30
|
-
let currentPath = window.location.pathname.replace(/\/[^\/]*$/, ""); // Remove file or last segment from path
|
|
31
|
-
let depth = currentPath.split("/").filter(Boolean).length; // Count actual directory levels
|
|
32
|
-
return depth > 0 ? "../".repeat(depth) : "./";
|
|
33
|
-
},
|
|
34
|
-
$path: () => {
|
|
35
|
-
let path = window.location.pathname;
|
|
36
|
-
if (path.split("/").pop().includes(".")) {
|
|
37
|
-
path = path.replace(/\/[^\/]+$/, "/");
|
|
38
|
-
}
|
|
39
|
-
return path === "/" ? "" : path;
|
|
40
|
-
},
|
|
41
|
-
$param: (element, args) => args,
|
|
42
|
-
$getObjectValue: (element, args) => {
|
|
43
|
-
if (Array.isArray(args) && args.length >= 2) {
|
|
44
|
-
return getValueFromObject(args[0], args[1]);
|
|
45
|
-
}
|
|
46
|
-
return "";
|
|
47
|
-
},
|
|
48
|
-
$setValue: (element, args) => element.setValue(...args) || "",
|
|
49
|
-
$true: () => true,
|
|
50
|
-
$false: () => false,
|
|
51
|
-
// TODO: Handle uuid generation
|
|
52
|
-
// $uid: () => uid.generate(6),
|
|
53
|
-
$parse: (element, args) => {
|
|
54
|
-
let value = args || "";
|
|
55
|
-
try {
|
|
56
|
-
return JSON.parse(value);
|
|
57
|
-
} catch (e) {
|
|
58
|
-
return value;
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
// TODO: Implement number formatting
|
|
62
|
-
$numberFormat: (element, args) => {
|
|
63
|
-
let number = parseFloat(args[0]);
|
|
64
|
-
// Simple, fixed arg mapping:
|
|
65
|
-
// args[0] = locale (internationalization)
|
|
66
|
-
// args[1] = options (object)
|
|
67
|
-
// args[2] = number (if provided). If not provided, fall back to legacy behavior where args[0] might be the number.
|
|
68
|
-
if (!Array.isArray(args)) args = [args];
|
|
69
|
-
|
|
70
|
-
const locale = args[0] || undefined;
|
|
71
|
-
const options = args[1] || {};
|
|
72
|
-
const numCandidate = args[2] !== undefined ? args[2] : args[0];
|
|
73
|
-
|
|
74
|
-
number = parseFloat(numCandidate);
|
|
75
|
-
|
|
76
|
-
if (isNaN(number)) return String(numCandidate ?? "");
|
|
77
|
-
|
|
78
|
-
return new Intl.NumberFormat(locale, options).format(number);
|
|
79
|
-
},
|
|
80
|
-
$uid: (element, args) => uid(args[0]) || "",
|
|
81
|
-
|
|
82
|
-
})
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
// Operators that access a specific property of a target element
|
|
86
|
-
const propertyOperators = new Set([
|
|
87
|
-
"$scrollWidth",
|
|
88
|
-
"$scrollHeight",
|
|
89
|
-
"$offsetWidth",
|
|
90
|
-
"$offsetHeight",
|
|
91
|
-
"$id",
|
|
92
|
-
"$tagName",
|
|
93
|
-
"$className",
|
|
94
|
-
"$textContent",
|
|
95
|
-
"$innerHTML",
|
|
96
|
-
"$getValue",
|
|
97
|
-
"$reset"
|
|
98
|
-
]);
|
|
99
|
-
|
|
100
|
-
// Combine all known operator keys for the main parsing regex
|
|
101
|
-
const knownOperatorKeys = [
|
|
102
|
-
...customOperators.keys(),
|
|
103
|
-
...propertyOperators
|
|
104
|
-
].sort((a, b) => b.length - a.length);
|
|
6
|
+
// --- AST EVALUATION ENGINE (safeParse) ---
|
|
7
|
+
|
|
8
|
+
const mathConstants = { PI: Math.PI, E: Math.E };
|
|
9
|
+
const mathFunctions = {
|
|
10
|
+
abs: Math.abs, ceil: Math.ceil, floor: Math.floor, round: Math.round,
|
|
11
|
+
max: Math.max, min: Math.min, pow: Math.pow, sqrt: Math.sqrt,
|
|
12
|
+
log: Math.log, sin: Math.sin, cos: Math.cos, tan: Math.tan,
|
|
13
|
+
Number: (v) => Number(v) // Explicit numeric casting
|
|
14
|
+
};
|
|
105
15
|
|
|
106
16
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
17
|
+
* Reference class used by safeParse to track object properties for assignments.
|
|
18
|
+
* Prevents dot-notation from just returning values when we need to assign to them (e.g., el.value = 5)
|
|
109
19
|
*/
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
}
|
|
20
|
+
class Ref {
|
|
21
|
+
constructor(obj, prop) {
|
|
22
|
+
this.obj = obj;
|
|
23
|
+
this.prop = prop;
|
|
24
|
+
}
|
|
25
|
+
get() { return this.obj ? this.obj[this.prop] : undefined; }
|
|
26
|
+
set(val) {
|
|
27
|
+
if (this.obj) this.obj[this.prop] = val;
|
|
28
|
+
return val;
|
|
121
29
|
}
|
|
122
|
-
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function unref(val) {
|
|
33
|
+
return val instanceof Ref ? val.get() : val;
|
|
123
34
|
}
|
|
124
35
|
|
|
125
36
|
/**
|
|
126
|
-
*
|
|
127
|
-
* in the expression. This is the core logic for iterative parsing.
|
|
128
|
-
* * @param {string} expression The full expression string.
|
|
129
|
-
* @returns {{operator: string, args: string, fullMatch: string} | null} Details of the innermost function call, or null.
|
|
37
|
+
* Parses math, logic, ternaries, dot notation, and property assignments securely.
|
|
130
38
|
*/
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
let
|
|
135
|
-
|
|
136
|
-
let inSingleQuote = false;
|
|
137
|
-
let inDoubleQuote = false;
|
|
138
|
-
|
|
139
|
-
// First pass: Find the indices of the DEEPEST balanced parenthesis pair.
|
|
140
|
-
for (let i = 0; i < expression.length; i++) {
|
|
141
|
-
const char = expression[i];
|
|
39
|
+
function safeParse(expression, registry = new Map()) {
|
|
40
|
+
if (typeof expression !== "string") return expression;
|
|
41
|
+
|
|
42
|
+
let currentExpr = expression.trim();
|
|
43
|
+
if (!currentExpr) return null;
|
|
142
44
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
45
|
+
const tokenizerRegex = /('[^']*'|"[^"]*"|\d+(?:\.\d+)?|>=|<=|===|!==|==|!=|&&|\|\||[a-zA-Z_][a-zA-Z0-9_\.]*|[\+\-\*\/\%\(\)\?\:\>\<\!\,\=])/g;
|
|
46
|
+
const tokens = currentExpr.match(tokenizerRegex) || [];
|
|
47
|
+
let pos = 0;
|
|
48
|
+
|
|
49
|
+
function peek() { return tokens[pos]; }
|
|
50
|
+
function consume() { return tokens[pos++]; }
|
|
51
|
+
|
|
52
|
+
function parse() {
|
|
53
|
+
return parseAssignment();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function parseAssignment() {
|
|
57
|
+
let left = parseTernary();
|
|
58
|
+
if (peek() === "=") {
|
|
59
|
+
consume();
|
|
60
|
+
let right = unref(parseAssignment());
|
|
61
|
+
if (left instanceof Ref) {
|
|
62
|
+
return left.set(right); // Assign value to the actual object property
|
|
63
|
+
}
|
|
64
|
+
return right;
|
|
149
65
|
}
|
|
66
|
+
return left;
|
|
67
|
+
}
|
|
150
68
|
|
|
151
|
-
|
|
152
|
-
|
|
69
|
+
function parseTernary() {
|
|
70
|
+
let left = parseLogical();
|
|
71
|
+
if (peek() === "?") {
|
|
72
|
+
consume();
|
|
73
|
+
let trueExpr = parseTernary();
|
|
74
|
+
if (peek() === ":") {
|
|
75
|
+
consume();
|
|
76
|
+
let falseExpr = parseTernary();
|
|
77
|
+
return unref(left) ? unref(trueExpr) : unref(falseExpr);
|
|
78
|
+
}
|
|
153
79
|
}
|
|
80
|
+
return left;
|
|
81
|
+
}
|
|
154
82
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
83
|
+
function parseLogical() {
|
|
84
|
+
let left = parseComparison();
|
|
85
|
+
while (peek() === "&&" || peek() === "||") {
|
|
86
|
+
let op = consume();
|
|
87
|
+
let right = parseComparison();
|
|
88
|
+
if (op === "&&") left = unref(left) && unref(right);
|
|
89
|
+
if (op === "||") left = unref(left) || unref(right);
|
|
90
|
+
}
|
|
91
|
+
return left;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function parseComparison() {
|
|
95
|
+
let left = parseAdditive();
|
|
96
|
+
while ([">", "<", ">=", "<=", "===", "!==", "==", "!="].includes(peek())) {
|
|
97
|
+
let op = consume();
|
|
98
|
+
let right = parseAdditive();
|
|
99
|
+
let l = unref(left), r = unref(right);
|
|
100
|
+
if (op === ">") left = l > r;
|
|
101
|
+
if (op === "<") left = l < r;
|
|
102
|
+
if (op === ">=") left = l >= r;
|
|
103
|
+
if (op === "<=") left = l <= r;
|
|
104
|
+
if (op === "===") left = l === r;
|
|
105
|
+
if (op === "!==") left = l !== r;
|
|
106
|
+
if (op === "==") left = l == r;
|
|
107
|
+
if (op === "!=") left = l != r;
|
|
108
|
+
}
|
|
109
|
+
return left;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function parseAdditive() {
|
|
113
|
+
let left = parseMultiplicative();
|
|
114
|
+
while (["+", "-"].includes(peek())) {
|
|
115
|
+
let op = consume();
|
|
116
|
+
let right = parseMultiplicative();
|
|
117
|
+
if (op === "+") left = unref(left) + unref(right);
|
|
118
|
+
if (op === "-") left = unref(left) - unref(right);
|
|
119
|
+
}
|
|
120
|
+
return left;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function parseMultiplicative() {
|
|
124
|
+
let left = parsePrimary();
|
|
125
|
+
while (["*", "/", "%"].includes(peek())) {
|
|
126
|
+
let op = consume();
|
|
127
|
+
let right = parsePrimary();
|
|
128
|
+
if (op === "*") left = unref(left) * unref(right);
|
|
129
|
+
if (op === "/") left = unref(left) / unref(right);
|
|
130
|
+
if (op === "%") left = unref(left) % unref(right);
|
|
131
|
+
}
|
|
132
|
+
return left;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function parsePrimary() {
|
|
136
|
+
let token = consume();
|
|
137
|
+
if (!token) return undefined;
|
|
138
|
+
|
|
139
|
+
if (/^\d/.test(token)) return parseFloat(token);
|
|
140
|
+
|
|
141
|
+
if (token.startsWith("'") || token.startsWith('"')) {
|
|
142
|
+
return token.slice(1, -1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (token === "true") return true;
|
|
146
|
+
if (token === "false") return false;
|
|
147
|
+
|
|
148
|
+
if (token === "(") {
|
|
149
|
+
let expr = unref(parse());
|
|
150
|
+
if (peek() === ")") consume();
|
|
151
|
+
return expr;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (token === "-") return -unref(parsePrimary());
|
|
155
|
+
if (token === "!") return !unref(parsePrimary());
|
|
156
|
+
|
|
157
|
+
if (mathConstants.hasOwnProperty(token)) return mathConstants[token];
|
|
158
|
+
|
|
159
|
+
if (peek() === "(" && mathFunctions.hasOwnProperty(token)) {
|
|
160
|
+
consume();
|
|
161
|
+
let args = [];
|
|
162
|
+
if (peek() !== ")") {
|
|
163
|
+
args.push(unref(parse()));
|
|
164
|
+
while (peek() === ",") {
|
|
165
|
+
consume();
|
|
166
|
+
args.push(unref(parse()));
|
|
167
|
+
}
|
|
162
168
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
169
|
+
if (peek() === ")") consume();
|
|
170
|
+
return mathFunctions[token](...args);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// --- Context & Object Registry Traversal ---
|
|
174
|
+
let path = token.split(".");
|
|
175
|
+
let baseToken = path[0];
|
|
176
|
+
let val;
|
|
177
|
+
|
|
178
|
+
if (registry.has(baseToken)) {
|
|
179
|
+
val = registry.get(baseToken);
|
|
180
|
+
} else if (typeof window !== "undefined" && window[baseToken]) {
|
|
181
|
+
val = window[baseToken];
|
|
182
|
+
} else {
|
|
183
|
+
val = undefined;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (path.length === 1) return val;
|
|
187
|
+
|
|
188
|
+
for (let i = 1; i < path.length - 1; i++) {
|
|
189
|
+
if (val !== null && val !== undefined) {
|
|
190
|
+
val = val[path[i]];
|
|
191
|
+
} else {
|
|
192
|
+
return undefined;
|
|
167
193
|
}
|
|
168
|
-
balance--;
|
|
169
194
|
}
|
|
195
|
+
|
|
196
|
+
return new Ref(val, path[path.length - 1]);
|
|
170
197
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
const result = parse();
|
|
201
|
+
return unref(result);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.warn(`safeParse error: ${error.message} (Expr: "${expression}")`, error);
|
|
174
204
|
return null;
|
|
175
205
|
}
|
|
206
|
+
}
|
|
176
207
|
|
|
177
|
-
// Now we have the innermost argument content indices (deepestStart + 1 to deepestEnd - 1)
|
|
178
|
-
const rawArgs = expression.substring(deepestStart + 1, deepestEnd).trim();
|
|
179
|
-
|
|
180
|
-
// Step 2: Find the operator name backward from the deepestStart index.
|
|
181
|
-
let operatorStart = -1;
|
|
182
|
-
let nonWhitespaceFound = false;
|
|
183
208
|
|
|
184
|
-
|
|
209
|
+
// --- CORE OPERATOR ENGINE ---
|
|
210
|
+
|
|
211
|
+
const customOperators = new Map(
|
|
212
|
+
Object.entries({
|
|
213
|
+
$organization_id: () => localStorage.getItem("organization_id"),
|
|
214
|
+
$user_id: () => localStorage.getItem("user_id"),
|
|
215
|
+
$clientId: () => localStorage.getItem("clientId"),
|
|
216
|
+
$session_id: () => localStorage.getItem("session_id"),
|
|
217
|
+
$this: (element) => element,
|
|
218
|
+
$value: (element) => element.getValue() || "",
|
|
219
|
+
$innerWidth: () => window.innerWidth,
|
|
220
|
+
$innerHeight: () => window.innerHeight,
|
|
221
|
+
$href: () => window.location.href.replace(/\/$/, ""),
|
|
222
|
+
$origin: () => window.location.origin,
|
|
223
|
+
$protocol: () => window.location.protocol,
|
|
224
|
+
$hostname: () => window.location.hostname,
|
|
225
|
+
$host: () => window.location.host,
|
|
226
|
+
$port: () => window.location.port,
|
|
227
|
+
$pathname: () => window.location.pathname.replace(/\/$/, ""),
|
|
228
|
+
$hash: () => window.location.hash,
|
|
229
|
+
$subdomain: () => getSubdomain() || "",
|
|
230
|
+
$object_id: () => ObjectId().toString(),
|
|
231
|
+
"ObjectId()": () => ObjectId().toString(),
|
|
232
|
+
// Unwrap query results if only one element is found
|
|
233
|
+
$query: (element, args) => {
|
|
234
|
+
const results = queryElements({ element, selector: args });
|
|
235
|
+
return results.length === 1 ? results[0] : results;
|
|
236
|
+
},
|
|
237
|
+
$eval: (element, args, context) => safeParse(args, context.registry),
|
|
238
|
+
$relativePath: () => {
|
|
239
|
+
let currentPath = window.location.pathname.replace(/\/[^\/]*$/, "");
|
|
240
|
+
let depth = currentPath.split("/").filter(Boolean).length;
|
|
241
|
+
return depth > 0 ? "../".repeat(depth) : "./";
|
|
242
|
+
},
|
|
243
|
+
$path: () => {
|
|
244
|
+
let path = window.location.pathname;
|
|
245
|
+
if (path.split("/").pop().includes(".")) path = path.replace(/\/[^\/]+$/, "/");
|
|
246
|
+
return path === "/" ? "" : path;
|
|
247
|
+
},
|
|
248
|
+
$param: (element, args) => args,
|
|
249
|
+
$getObjectValue: (element, args) => {
|
|
250
|
+
if (Array.isArray(args) && args.length >= 2) return getValueFromObject(args[0], args[1]);
|
|
251
|
+
return "";
|
|
252
|
+
},
|
|
253
|
+
$setValue: (element, args) => element.setValue(...args) || "",
|
|
254
|
+
$true: () => true,
|
|
255
|
+
$false: () => false,
|
|
256
|
+
$parse: (element, args) => {
|
|
257
|
+
let value = args || "";
|
|
258
|
+
try { return JSON.parse(value); } catch (e) { return value; }
|
|
259
|
+
},
|
|
260
|
+
$numberFormat: (element, args) => {
|
|
261
|
+
let number = parseFloat(args[0]);
|
|
262
|
+
if (!Array.isArray(args)) args = [args];
|
|
263
|
+
const locale = args[0] || undefined;
|
|
264
|
+
const options = args[1] || {};
|
|
265
|
+
const numCandidate = args[2] !== undefined ? args[2] : args[0];
|
|
266
|
+
number = parseFloat(numCandidate);
|
|
267
|
+
if (isNaN(number)) return String(numCandidate ?? "");
|
|
268
|
+
return new Intl.NumberFormat(locale, options).format(number);
|
|
269
|
+
},
|
|
270
|
+
$uid: (element, args) => uid(args[0]) || "",
|
|
271
|
+
})
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
const isConstructor = (func, name) => {
|
|
275
|
+
try {
|
|
276
|
+
if (typeof func !== 'function') return false;
|
|
277
|
+
if (/^\s*class\s+/.test(func.toString())) return true;
|
|
278
|
+
if (!func.prototype) return false;
|
|
279
|
+
const n = name || func.name;
|
|
280
|
+
if (n && /^[A-Z]/.test(n)) return true;
|
|
281
|
+
} catch(e) {}
|
|
282
|
+
return false;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const findBareOperatorInPath = (path) => {
|
|
286
|
+
const trimmedPath = path.trim();
|
|
287
|
+
const match = trimmedPath.match(/^(\$[\w\-]+)/);
|
|
288
|
+
if (match) {
|
|
289
|
+
const key = match[1];
|
|
290
|
+
const remaining = trimmedPath.substring(key.length);
|
|
291
|
+
if (remaining.length === 0 || /^\s|\[|\./.test(remaining)) return key;
|
|
292
|
+
}
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const findInnermostFunctionCall = (expression) => {
|
|
297
|
+
let balance = 0, deepestStart = -1, deepestEnd = -1, deepestBalance = -1;
|
|
298
|
+
let inSingleQuote = false, inDoubleQuote = false;
|
|
299
|
+
for (let i = 0; i < expression.length; i++) {
|
|
185
300
|
const char = expression[i];
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
// Find the start of the operator name
|
|
196
|
-
// This regex captures letters, numbers, hyphens, and the required $
|
|
197
|
-
let isOperatorChar = /[\w\-\$]/.test(char);
|
|
198
|
-
|
|
199
|
-
if (!isOperatorChar) {
|
|
200
|
-
operatorStart = i + 1;
|
|
201
|
-
break;
|
|
301
|
+
if (char === '"' && !inSingleQuote) { inDoubleQuote = !inDoubleQuote; continue; }
|
|
302
|
+
else if (char === "'" && !inDoubleQuote) { inSingleQuote = !inDoubleQuote; continue; }
|
|
303
|
+
if (inSingleQuote || inDoubleQuote) continue;
|
|
304
|
+
if (char === '(') {
|
|
305
|
+
balance++;
|
|
306
|
+
if (balance > deepestBalance) { deepestBalance = balance; deepestStart = i; deepestEnd = -1; }
|
|
307
|
+
} else if (char === ')') {
|
|
308
|
+
if (balance === deepestBalance) deepestEnd = i;
|
|
309
|
+
balance--;
|
|
202
310
|
}
|
|
311
|
+
}
|
|
312
|
+
if (deepestStart === -1 || deepestEnd === -1 || deepestEnd <= deepestStart) return null;
|
|
313
|
+
const rawArgs = expression.substring(deepestStart + 1, deepestEnd).trim();
|
|
314
|
+
let operatorStart = -1, nonWhitespaceFound = false;
|
|
315
|
+
for (let i = deepestStart - 1; i >= 0; i--) {
|
|
316
|
+
const char = expression[i];
|
|
317
|
+
if (!nonWhitespaceFound) { if (/\s/.test(char)) continue; nonWhitespaceFound = true; }
|
|
318
|
+
if (!/[\w\-\$]/.test(char)) { operatorStart = i + 1; break; }
|
|
203
319
|
operatorStart = i;
|
|
204
320
|
}
|
|
205
|
-
|
|
206
321
|
if (operatorStart === -1) operatorStart = 0;
|
|
207
|
-
|
|
208
322
|
const operatorNameCandidate = expression.substring(operatorStart, deepestStart).trim();
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (knownOperatorKeys.includes(operatorNameCandidate)) {
|
|
212
|
-
// Construct the full match string: operatorNameCandidate(rawArgs)
|
|
213
|
-
const fullMatch = expression.substring(operatorStart, deepestEnd + 1);
|
|
214
|
-
|
|
215
|
-
return {
|
|
216
|
-
operator: operatorNameCandidate,
|
|
217
|
-
args: rawArgs,
|
|
218
|
-
fullMatch: fullMatch
|
|
219
|
-
};
|
|
323
|
+
if (/^\$[\w\-]+$/.test(operatorNameCandidate) || customOperators.has(operatorNameCandidate)) {
|
|
324
|
+
return { operator: operatorNameCandidate, args: rawArgs, fullMatch: expression.substring(operatorStart, deepestEnd + 1) };
|
|
220
325
|
}
|
|
221
|
-
|
|
222
|
-
return null; // Operator name invalid or not found
|
|
326
|
+
return null;
|
|
223
327
|
};
|
|
224
328
|
|
|
225
|
-
/**
|
|
226
|
-
* Main function to find the innermost operator.
|
|
227
|
-
* * Logic flow is updated to prioritize finding the innermost FUNCTION CALL,
|
|
228
|
-
* then fall back to finding a BARE OPERATOR if no function calls remain.
|
|
229
|
-
* * @param {string} expression The expression to parse.
|
|
230
|
-
* @returns {{operator: string, args: string, rawContent: string, fullMatch?: string} | {operator: null, args: string, rawContent: string}}
|
|
231
|
-
*/
|
|
232
329
|
const findInnermostOperator = (expression) => {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (result.endsWith(")")) {
|
|
240
|
-
result = result.substring(0, result.length - 1);
|
|
241
|
-
}
|
|
242
|
-
return result;
|
|
243
|
-
}
|
|
244
|
-
let args;
|
|
245
|
-
|
|
246
|
-
// --- 1. PRIORITY: Find Innermost FUNCTION CALL (Operator with Parentheses) ---
|
|
330
|
+
function stripParentheses(str) {
|
|
331
|
+
let result = str;
|
|
332
|
+
if (result.startsWith("(")) result = result.substring(1);
|
|
333
|
+
if (result.endsWith(")")) result = result.substring(0, result.length - 1);
|
|
334
|
+
return result;
|
|
335
|
+
}
|
|
247
336
|
const functionCall = findInnermostFunctionCall(expression);
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return {
|
|
253
|
-
operator: functionCall.operator,
|
|
254
|
-
args, // Arguments without parentheses
|
|
255
|
-
rawContent: functionCall.args, // The content inside the parentheses (arguments)
|
|
256
|
-
fullMatch: functionCall.fullMatch // The operator(args) string (the complete section to replace)
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// --- 2. FALLBACK: Find BARE OPERATOR (e.g., $value path) ---
|
|
261
|
-
|
|
262
|
-
// If no function calls are found, the entire expression is treated as the raw content
|
|
263
|
-
const rawContent = expression.trim();
|
|
264
|
-
|
|
265
|
-
// Now check the raw content to see if it starts with a bare operator ($value)
|
|
266
|
-
const innermostOperator = findBareOperatorInPath(rawContent, knownOperatorKeys);
|
|
267
|
-
|
|
268
|
-
if (innermostOperator) {
|
|
269
|
-
const operatorArgs = rawContent.substring(innermostOperator.length).trim();
|
|
270
|
-
args = stripParentheses(operatorArgs);
|
|
271
|
-
return {
|
|
272
|
-
operator: innermostOperator,
|
|
273
|
-
args, // Arguments without parentheses
|
|
274
|
-
rawContent: rawContent,
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
args = stripParentheses(rawContent);
|
|
280
|
-
|
|
281
|
-
// Fallback if no known operator is found
|
|
282
|
-
return {
|
|
283
|
-
operator: null,
|
|
284
|
-
args,
|
|
285
|
-
rawContent: rawContent
|
|
286
|
-
};
|
|
337
|
+
if (functionCall) return { operator: functionCall.operator, args: stripParentheses(functionCall.args), rawContent: functionCall.args, fullMatch: functionCall.fullMatch };
|
|
338
|
+
const rawContent = expression.trim(), innermostOperator = findBareOperatorInPath(rawContent);
|
|
339
|
+
if (innermostOperator) return { operator: innermostOperator, args: stripParentheses(rawContent.substring(innermostOperator.length).trim()), rawContent: rawContent };
|
|
340
|
+
return { operator: null, args: stripParentheses(rawContent), rawContent: rawContent };
|
|
287
341
|
};
|
|
288
342
|
|
|
289
|
-
function
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
return "ObjectId\\(\\)"; // Escape the parentheses
|
|
294
|
-
}
|
|
295
|
-
return key; // Should not happen with current keys, but fallback
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Synchronously processes a string, finding and replacing operators recursively.
|
|
300
|
-
* Assumes ALL underlying operations (getValue, queryElements) are synchronous.
|
|
301
|
-
* @param {Element | null} element - Context element.
|
|
302
|
-
* @param {string} value - String containing operators.
|
|
303
|
-
* @param {string[]} [exclude=[]] - Operator prefixes to ignore.
|
|
304
|
-
* @returns {string | {value: string, params: Promise[]}} - Processed string or an object containing the partially processed string and unresolved Promises.
|
|
305
|
-
*/
|
|
306
|
-
function processOperators(
|
|
307
|
-
element,
|
|
308
|
-
value,
|
|
309
|
-
exclude = [],
|
|
310
|
-
parent,
|
|
311
|
-
params = []
|
|
312
|
-
) {
|
|
313
|
-
// Early exit if no operators are possible or value is not a string
|
|
314
|
-
if (typeof value !== "string" || !value.includes("$")) {
|
|
315
|
-
return value;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
let processedValue = value;
|
|
319
|
-
let hasPromise = false;
|
|
320
|
-
let parsedValue = null
|
|
321
|
-
|
|
322
|
-
while (processedValue.includes("$")) {
|
|
323
|
-
|
|
324
|
-
// --- PROMISE TOKEN RESOLUTION ---
|
|
325
|
-
// If the processedValue starts with a resolved parameter token from the previous async step,
|
|
326
|
-
// substitute the token with its actual (now resolved) value from the params array.
|
|
343
|
+
function processOperators(element, value, exclude = [], parent, params = [], objectRegistry = new Map()) {
|
|
344
|
+
if (typeof value !== "string" || (!value.includes("$") && !value.includes("ObjectId()"))) return value;
|
|
345
|
+
let processedValue = value, hasPromise = false, unresolvedTokens = new Map();
|
|
346
|
+
while (processedValue.includes("$") || processedValue.includes("ObjectId()")) {
|
|
327
347
|
const paramMatch = processedValue.match(/^\$\$PARAM_(\d+)\$\$/);
|
|
328
348
|
if (paramMatch && Array.isArray(params) && params.length > 0) {
|
|
329
349
|
const index = parseInt(paramMatch[1], 10);
|
|
330
|
-
if (index < params.length) {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
350
|
+
if (index < params.length) { processedValue = processedValue.replace(paramMatch[0], params[index]); continue; }
|
|
351
|
+
}
|
|
352
|
+
const { operator, args, rawContent, fullMatch } = findInnermostOperator(processedValue);
|
|
353
|
+
if (!operator || (operator === "$param" && !args)) break;
|
|
354
|
+
const textToReplace = fullMatch || rawContent;
|
|
355
|
+
if (exclude.includes(operator)) {
|
|
356
|
+
const token = `__UNRESOLVED_${unresolvedTokens.size}__`;
|
|
357
|
+
unresolvedTokens.set(token, textToReplace);
|
|
358
|
+
processedValue = processedValue.replace(textToReplace, token);
|
|
359
|
+
continue;
|
|
336
360
|
}
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
if (params.some((p) => p instanceof Promise)) {
|
|
370
|
-
hasPromise = true;
|
|
371
|
-
break; // A nested call found a promise, stop and yield
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
let replacement = "";
|
|
375
|
-
if (operator === "$param") {
|
|
376
|
-
params.push(resolvedValue);
|
|
377
|
-
} else {
|
|
378
|
-
replacement = resolvedValue ?? "";
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
if (processedValue === textToReplace) {
|
|
382
|
-
processedValue = replacement;
|
|
383
|
-
break;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
processedValue = processedValue.replace(textToReplace, replacement);
|
|
387
|
-
|
|
388
|
-
if (!processedValue.includes("$")) {
|
|
389
|
-
// If there are still unresolved operators, we need to continue processing
|
|
390
|
-
break;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
} else {
|
|
394
|
-
// If operator is excluded, we need to advance past it to avoid infinite loop
|
|
395
|
-
break;
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
if (hasPromise) {
|
|
400
|
-
return { value: processedValue, params };
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
if (params.length) {
|
|
404
|
-
if (processedValue.trim() === "") {
|
|
405
|
-
return params;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
return processedValue;
|
|
361
|
+
let resolvedValue = resolveOperator(element, operator, args, parent, params, objectRegistry);
|
|
362
|
+
if (resolvedValue === undefined) {
|
|
363
|
+
const token = `__UNRESOLVED_${unresolvedTokens.size}__`;
|
|
364
|
+
unresolvedTokens.set(token, textToReplace);
|
|
365
|
+
processedValue = processedValue.replace(textToReplace, token);
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (resolvedValue instanceof Promise) {
|
|
369
|
+
const paramIndex = params.length;
|
|
370
|
+
params.push(resolvedValue);
|
|
371
|
+
processedValue = processedValue.replace(textToReplace, `$$PARAM_${paramIndex}$$`);
|
|
372
|
+
hasPromise = true;
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
if (params.some((p) => p instanceof Promise)) { hasPromise = true; break; }
|
|
376
|
+
let replacement = "";
|
|
377
|
+
if (operator === "$param") params.push(resolvedValue);
|
|
378
|
+
else if (resolvedValue !== null && (typeof resolvedValue === "object" || typeof resolvedValue === "function")) {
|
|
379
|
+
const token = `__OBJ_${objectRegistry.size}__`;
|
|
380
|
+
objectRegistry.set(token, resolvedValue);
|
|
381
|
+
replacement = token;
|
|
382
|
+
} else replacement = resolvedValue ?? "";
|
|
383
|
+
if (processedValue === textToReplace) { processedValue = replacement; break; }
|
|
384
|
+
processedValue = processedValue.replace(textToReplace, replacement);
|
|
385
|
+
}
|
|
386
|
+
for (const [token, originalText] of unresolvedTokens.entries()) processedValue = processedValue.replace(token, originalText);
|
|
387
|
+
if (typeof processedValue === "string") {
|
|
388
|
+
const exactMatch = processedValue.match(/^__OBJ_(\d+)__$/);
|
|
389
|
+
if (exactMatch && objectRegistry.has(processedValue)) processedValue = objectRegistry.get(processedValue);
|
|
390
|
+
}
|
|
391
|
+
if (hasPromise) return { value: processedValue, params, objectRegistry };
|
|
392
|
+
return processedValue;
|
|
410
393
|
}
|
|
411
394
|
|
|
412
|
-
async function processOperatorsAsync(
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
let result = processOperators(element, value, exclude, parent, params);
|
|
420
|
-
|
|
421
|
-
while (typeof result === "object" && result.params) {
|
|
422
|
-
const resolvedParams = await Promise.all(result.params);
|
|
423
|
-
// Note: The second argument passed to processOperators here is the partially processed string (result.value)
|
|
424
|
-
// which now contains the PARAM tokens. The third argument is the array of resolved values (resolvedParams)
|
|
425
|
-
// which will be used to replace those tokens in the subsequent processOperators call.
|
|
426
|
-
result = processOperators(
|
|
427
|
-
element,
|
|
428
|
-
result.value,
|
|
429
|
-
exclude,
|
|
430
|
-
parent,
|
|
431
|
-
resolvedParams
|
|
432
|
-
);
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
if (result instanceof Promise) {
|
|
436
|
-
return await result;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
return result;
|
|
395
|
+
async function processOperatorsAsync(element, value, exclude = [], parent, params = [], objectRegistry = new Map()) {
|
|
396
|
+
let result = processOperators(element, value, exclude, parent, params, objectRegistry);
|
|
397
|
+
while (typeof result === "object" && result.params) {
|
|
398
|
+
const resolvedParams = await Promise.all(result.params);
|
|
399
|
+
result = processOperators(element, result.value, exclude, parent, resolvedParams, result.objectRegistry || objectRegistry);
|
|
400
|
+
}
|
|
401
|
+
return result;
|
|
440
402
|
}
|
|
441
403
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
if (params.some((p) => p instanceof Promise)) {
|
|
463
|
-
return operator;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// Initialize an array of elements to operate on, starting with the single element reference if provided
|
|
467
|
-
let targetElements = element ? [element] : [];
|
|
468
|
-
|
|
469
|
-
// If the argument is a string and the operator is NOT a custom utility that expects raw data
|
|
470
|
-
// (like $param, $parse), we assume the argument is a selector.
|
|
471
|
-
if (args && typeof args === "string" && !customOperators.has(operator)) {
|
|
472
|
-
targetElements = queryElements({
|
|
473
|
-
element, // Use the context element as the base for querying
|
|
474
|
-
selector: args // Selector from args to find matching elements
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
// If no elements are found matching the selector in args, return args unmodified
|
|
478
|
-
if (!targetElements.length) return args;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// Generate a processed value by applying the operator to each of the target elements
|
|
482
|
-
let value = processValues(targetElements, operator, args, parent);
|
|
483
|
-
|
|
484
|
-
// If the result is a string and still contains unresolved operators, process them further
|
|
485
|
-
if (value && typeof value === "string" && value.includes("$")) {
|
|
486
|
-
// Resolve any remaining operators within the value string
|
|
487
|
-
value = processOperators(element, value, parent, params);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// Return the final processed value, fully resolved
|
|
491
|
-
return value;
|
|
404
|
+
function resolveOperator(element, operator, args, parent, params, objectRegistry) {
|
|
405
|
+
if (params.some((p) => p instanceof Promise)) return "";
|
|
406
|
+
if (args && typeof args === "string" && args.includes("$")) {
|
|
407
|
+
args = processOperators(element, args, [], operator, params, objectRegistry);
|
|
408
|
+
}
|
|
409
|
+
let targetElements = element ? [element] : [];
|
|
410
|
+
if (args && typeof args === "string") {
|
|
411
|
+
const objMatch = args.match(/^__OBJ_(\d+)__$/);
|
|
412
|
+
if (objMatch && objectRegistry.has(args)) {
|
|
413
|
+
targetElements = [objectRegistry.get(args)];
|
|
414
|
+
} else if (!customOperators.has(operator)) {
|
|
415
|
+
targetElements = queryElements({ element, selector: args });
|
|
416
|
+
if (!targetElements.length) return undefined;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
let value = processValues(targetElements, operator, args, parent, objectRegistry);
|
|
420
|
+
if (value && typeof value === "string" && value.includes("$")) {
|
|
421
|
+
value = processOperators(element, value, [], parent, params, objectRegistry);
|
|
422
|
+
}
|
|
423
|
+
return value;
|
|
492
424
|
}
|
|
493
425
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
// If the custom operator is NOT $param and has arguments, something is wrong with the flow.
|
|
522
|
-
// However, if it's a generic method on an element (like $setValue), the args are passed via spread.
|
|
523
|
-
if (customOperators.has(operator) && operator !== "$setValue" && operator !== "$getObjectValue" && args.length) {
|
|
524
|
-
// For simple custom operators that don't take multiple args, return an empty string.
|
|
525
|
-
return "";
|
|
526
|
-
}
|
|
527
|
-
// Invoke the function using the element and spread array arguments
|
|
528
|
-
rawValue = rawValue(el, ...args);
|
|
529
|
-
} else {
|
|
530
|
-
// Otherwise, invoke the function using the element and direct arguments
|
|
531
|
-
rawValue = rawValue(el, args);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// If the parent context requires parameter resolution
|
|
536
|
-
if (parent === "$param") {
|
|
537
|
-
// Return the first evaluated rawValue that is not null or undefined
|
|
538
|
-
if (rawValue) {
|
|
539
|
-
return rawValue;
|
|
540
|
-
}
|
|
541
|
-
} else {
|
|
542
|
-
if (
|
|
543
|
-
rawValue instanceof Promise ||
|
|
544
|
-
(typeof rawValue === "object" && rawValue !== null)
|
|
545
|
-
) {
|
|
546
|
-
return rawValue;
|
|
547
|
-
}
|
|
548
|
-
// Otherwise, append the stringified rawValue to the aggregated result, defaulting to an empty string if it's nullish
|
|
549
|
-
aggregatedString += String(rawValue ?? "");
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// Return the final aggregated string containing all processed values
|
|
554
|
-
return aggregatedString;
|
|
426
|
+
function processValues(elements, operator, args, parent, objectRegistry) {
|
|
427
|
+
let customOp = customOperators.get(operator);
|
|
428
|
+
let aggregatedString = "";
|
|
429
|
+
let hasValidProperty = customOp ? true : false;
|
|
430
|
+
const context = { registry: objectRegistry, element: elements[0] };
|
|
431
|
+
for (const el of elements) {
|
|
432
|
+
if (!el) continue;
|
|
433
|
+
let rawValue = customOp;
|
|
434
|
+
const propName = customOp ? null : operator.substring(1);
|
|
435
|
+
if (!customOp) {
|
|
436
|
+
if (propName in el) { hasValidProperty = true; rawValue = el[propName]; }
|
|
437
|
+
else continue;
|
|
438
|
+
}
|
|
439
|
+
if (typeof rawValue === "function") {
|
|
440
|
+
if (customOp) rawValue = Array.isArray(args) ? rawValue(el, ...args, context) : rawValue(el, args, context);
|
|
441
|
+
else {
|
|
442
|
+
if (isConstructor(rawValue, propName)) rawValue = Array.isArray(args) ? new rawValue(...args) : new rawValue(args);
|
|
443
|
+
else rawValue = Array.isArray(args) ? rawValue.apply(el, args) : rawValue.call(el, args);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (parent === "$param") { if (rawValue !== undefined && rawValue !== null) return rawValue; }
|
|
447
|
+
else {
|
|
448
|
+
if (rawValue instanceof Promise || (typeof rawValue === "object" && rawValue !== null) || typeof rawValue === "function") return rawValue;
|
|
449
|
+
aggregatedString += String(rawValue ?? "");
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return hasValidProperty ? aggregatedString : undefined;
|
|
555
453
|
}
|
|
556
454
|
|
|
557
|
-
/**
|
|
558
|
-
* Extracts the subdomain from the current window's hostname.
|
|
559
|
-
* @returns {string|null} - The subdomain part of the hostname if it exists, or null if there is none.
|
|
560
|
-
*/
|
|
561
455
|
function getSubdomain() {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
// Split the hostname into parts divided by dots ('.')
|
|
566
|
-
const parts = hostname.split(".");
|
|
567
|
-
|
|
568
|
-
// Check if the hostname has more than two parts and ensure the last part isn't a number (a common TLD check)
|
|
569
|
-
// A typical domain structure might look like "sub.domain.com",
|
|
570
|
-
// where "sub" is the subdomain, "domain" is the second-level domain, and "com" is the top-level domain.
|
|
571
|
-
if (parts.length > 2 && isNaN(parseInt(parts[parts.length - 1]))) {
|
|
572
|
-
// Join all parts except the last two (which are assumed to be the domain and TLD) to get the subdomain
|
|
573
|
-
return parts.slice(0, parts.length - 2).join(".");
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
// Return null if there's no valid subdomain structure
|
|
577
|
-
return null;
|
|
456
|
+
const hostname = window.location.hostname, parts = hostname.split(".");
|
|
457
|
+
if (parts.length > 2 && isNaN(parseInt(parts[parts.length - 1]))) return parts.slice(0, parts.length - 2).join(".");
|
|
458
|
+
return null;
|
|
578
459
|
}
|
|
579
460
|
|
|
580
|
-
export { processOperators, processOperatorsAsync };
|
|
461
|
+
export { processOperators, processOperatorsAsync, customOperators };
|