@cocreate/element-prototype 1.35.0 → 2.0.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/.github/FUNDING.yml +1 -1
- package/.github/workflows/automated.yml +18 -7
- package/CHANGELOG.md +446 -415
- package/CONTRIBUTING.md +6 -6
- package/CoCreate.config.js +2 -2
- package/README.md +125 -18
- package/demo/index.html +2 -2
- package/docs/index.html +4 -4
- package/package.json +7 -7
- package/src/getAttribute.js +44 -10
- package/src/getValue.js +332 -554
- package/src/index.js +188 -1
- package/src/setValue.js +310 -255
- package/test/getAttributeAst.test.mjs +104 -0
- package/test/password-value.test.cjs +48 -0
- package/test/value-handler.test.cjs +320 -0
package/src/getValue.js
CHANGED
|
@@ -17,21 +17,20 @@
|
|
|
17
17
|
********************************************************************************/
|
|
18
18
|
|
|
19
19
|
import { astSync, astAsync } from "@cocreate/utils";
|
|
20
|
+
import { getValueAttribute, transformValue, parseJson, toBoolean, toArray } from "@cocreate/utils";
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
HTMLElement.prototype.getValue = function () {
|
|
24
|
-
return getValue(this);
|
|
22
|
+
HTMLElement.prototype.getValue = function (valueType) {
|
|
23
|
+
return getValue(this, valueType);
|
|
25
24
|
};
|
|
26
|
-
SVGElement.prototype.getValue = function () {
|
|
27
|
-
|
|
25
|
+
SVGElement.prototype.getValue = function (valueType) {
|
|
26
|
+
return getValue(this, valueType);
|
|
28
27
|
};
|
|
29
28
|
|
|
30
|
-
HTMLElement.prototype.getValueAsync = async function () {
|
|
31
|
-
|
|
29
|
+
HTMLElement.prototype.getValueAsync = async function (valueType) {
|
|
30
|
+
return await getValueAsync(this, valueType);
|
|
32
31
|
};
|
|
33
|
-
SVGElement.prototype.getValueAsync = async function () {
|
|
34
|
-
|
|
32
|
+
SVGElement.prototype.getValueAsync = async function (valueType) {
|
|
33
|
+
return await getValueAsync(this, valueType);
|
|
35
34
|
};
|
|
36
35
|
|
|
37
36
|
/**
|
|
@@ -42,121 +41,131 @@ SVGElement.prototype.getValueAsync = async function () {
|
|
|
42
41
|
* @returns {any} - The processed value.
|
|
43
42
|
*/
|
|
44
43
|
const getValue = (element, valueType) => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
44
|
+
if (!valueType) {
|
|
45
|
+
valueType = getValueAttribute(element, "type", "get-value") || "";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const evaluateValue = getValueAttribute(element, "evaluate", "get-value", true) !== "false";
|
|
49
|
+
const readValueAttribute = () => evaluateValue
|
|
50
|
+
? element.getAttribute("value")
|
|
51
|
+
: element.attributes.getNamedItem("value")?.value;
|
|
52
|
+
let value = element.value || readValueAttribute() || "";
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
(!["INPUT", "TEXTAREA", "SELECT"].includes(element.tagName) && (
|
|
56
|
+
element.hasAttribute("component") ||
|
|
57
|
+
((element.__cocreateEditor || element.hasAttribute("editor") ||
|
|
58
|
+
(element.hasAttribute("contenteditable") && element.getAttribute("contenteditable") !== "false")) &&
|
|
59
|
+
"value" in element)
|
|
60
|
+
)) ||
|
|
61
|
+
element.type === "file" ||
|
|
62
|
+
element.getAttribute("type") === "file"
|
|
63
|
+
) {
|
|
64
|
+
return element.value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let prefix = getValueAttribute(element, "prefix", "get-value") || "";
|
|
68
|
+
let suffix = getValueAttribute(element, "suffix", "get-value") || "";
|
|
69
|
+
|
|
70
|
+
const elementType = element.getAttribute("type") || element.type;
|
|
71
|
+
const tagName = element.tagName.toUpperCase();
|
|
72
|
+
|
|
73
|
+
switch (elementType) {
|
|
74
|
+
case "checkbox":
|
|
75
|
+
value = handleCheckbox(element, prefix, suffix, valueType);
|
|
76
|
+
break;
|
|
77
|
+
|
|
78
|
+
case "radio": {
|
|
79
|
+
const key = element.getAttribute("key");
|
|
80
|
+
let el = document.querySelector(`input[key="${key}"]:checked`);
|
|
81
|
+
if (el) {
|
|
82
|
+
value = el.value;
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
case "number":
|
|
88
|
+
value = value === "" ? "" : Number(value);
|
|
89
|
+
break;
|
|
90
|
+
case "range":
|
|
91
|
+
value = element.min
|
|
92
|
+
? [Number(element.min), Number(element.value)]
|
|
93
|
+
: Number(element.value);
|
|
94
|
+
break;
|
|
95
|
+
|
|
96
|
+
case "password":
|
|
97
|
+
value = btoa(value || "");
|
|
98
|
+
break;
|
|
99
|
+
|
|
100
|
+
case "email":
|
|
101
|
+
value = value.toLowerCase();
|
|
102
|
+
break;
|
|
103
|
+
|
|
104
|
+
case "url":
|
|
105
|
+
break;
|
|
106
|
+
|
|
107
|
+
case "time":
|
|
108
|
+
case "date":
|
|
109
|
+
case "datetime":
|
|
110
|
+
case "datetime-local":
|
|
111
|
+
value = handleDateTime(element, value, valueType);
|
|
112
|
+
break;
|
|
113
|
+
|
|
114
|
+
default:
|
|
115
|
+
switch (tagName) {
|
|
116
|
+
case "INPUT":
|
|
117
|
+
case "OPTION":
|
|
118
|
+
value = element.value;
|
|
119
|
+
break;
|
|
120
|
+
|
|
121
|
+
case "SELECT":
|
|
122
|
+
value = handleSelectedValues(element,
|
|
123
|
+
Array.from(element.selectedOptions, option => getValue(option)),
|
|
124
|
+
prefix, suffix);
|
|
125
|
+
break;
|
|
126
|
+
|
|
127
|
+
case "TEXTAREA":
|
|
128
|
+
value = element.hasAttribute("value")
|
|
129
|
+
? readValueAttribute()
|
|
130
|
+
: element.value;
|
|
131
|
+
break;
|
|
132
|
+
|
|
133
|
+
case "IFRAME":
|
|
134
|
+
value = readFrame(element);
|
|
135
|
+
break;
|
|
136
|
+
|
|
137
|
+
case "IMG":
|
|
138
|
+
value = element.src;
|
|
139
|
+
break;
|
|
140
|
+
|
|
141
|
+
default:
|
|
142
|
+
value = handleElement(element, valueType, evaluateValue);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (valueType === "boolean") {
|
|
149
|
+
return toBoolean(value);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (value) {
|
|
153
|
+
if (evaluateValue) {
|
|
154
|
+
value = astSync({ executeAST: value, node: element, exclude: ["$value"] });
|
|
155
|
+
}
|
|
156
|
+
value = transformValue(element, value, "get-value");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
160
|
+
if (prefix || suffix) {
|
|
161
|
+
value = prefix + value + suffix;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
value = parseJson(value, valueType);
|
|
166
|
+
value = toArray(value, valueType);
|
|
167
|
+
|
|
168
|
+
return value;
|
|
160
169
|
};
|
|
161
170
|
|
|
162
171
|
/**
|
|
@@ -167,151 +176,161 @@ const getValue = (element, valueType) => {
|
|
|
167
176
|
* @returns {Promise<any>} - A promise that resolves to the processed value.
|
|
168
177
|
*/
|
|
169
178
|
const getValueAsync = async (element, valueType) => {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
179
|
+
if (!valueType) {
|
|
180
|
+
valueType = getValueAttribute(element, "type", "get-value") || "";
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const evaluateValue = getValueAttribute(element, "evaluate", "get-value", true) !== "false";
|
|
184
|
+
const readValueAttribute = () => evaluateValue
|
|
185
|
+
? element.getAttribute("value")
|
|
186
|
+
: element.attributes.getNamedItem("value")?.value;
|
|
187
|
+
let value = element.value || readValueAttribute() || "";
|
|
188
|
+
|
|
189
|
+
if (
|
|
190
|
+
(!["INPUT", "TEXTAREA", "SELECT"].includes(element.tagName) && (
|
|
191
|
+
element.hasAttribute("component") ||
|
|
192
|
+
((element.__cocreateEditor || element.hasAttribute("editor") ||
|
|
193
|
+
(element.hasAttribute("contenteditable") && element.getAttribute("contenteditable") !== "false")) &&
|
|
194
|
+
"value" in element)
|
|
195
|
+
)) ||
|
|
196
|
+
element.type === "file" ||
|
|
197
|
+
element.getAttribute("type") === "file"
|
|
198
|
+
) {
|
|
199
|
+
return element.value;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let prefix = getValueAttribute(element, "prefix", "get-value") || "";
|
|
203
|
+
let suffix = getValueAttribute(element, "suffix", "get-value") || "";
|
|
204
|
+
|
|
205
|
+
const elementType = element.getAttribute("type") || element.type;
|
|
206
|
+
const tagName = element.tagName.toUpperCase();
|
|
207
|
+
|
|
208
|
+
switch (elementType) {
|
|
209
|
+
case "checkbox":
|
|
210
|
+
value = handleCheckbox(element, prefix, suffix, valueType);
|
|
211
|
+
break;
|
|
212
|
+
|
|
213
|
+
case "radio": {
|
|
214
|
+
const key = element.getAttribute("key");
|
|
215
|
+
let el = document.querySelector(`input[key="${key}"]:checked`);
|
|
216
|
+
if (el) {
|
|
217
|
+
value = el.value;
|
|
218
|
+
}
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
case "number":
|
|
223
|
+
value = Number(value);
|
|
224
|
+
break;
|
|
225
|
+
|
|
226
|
+
case "range":
|
|
227
|
+
value = element.min
|
|
228
|
+
? [Number(element.min), Number(element.value)]
|
|
229
|
+
: Number(element.value);
|
|
230
|
+
break;
|
|
231
|
+
|
|
232
|
+
case "password":
|
|
233
|
+
value = btoa(value || "");
|
|
234
|
+
break;
|
|
235
|
+
|
|
236
|
+
case "email":
|
|
237
|
+
value = value.toLowerCase();
|
|
238
|
+
break;
|
|
239
|
+
|
|
240
|
+
case "url":
|
|
241
|
+
break;
|
|
242
|
+
|
|
243
|
+
case "time":
|
|
244
|
+
case "date":
|
|
245
|
+
case "datetime":
|
|
246
|
+
case "datetime-local":
|
|
247
|
+
value = handleDateTime(element, value, valueType);
|
|
248
|
+
break;
|
|
249
|
+
|
|
250
|
+
default:
|
|
251
|
+
switch (tagName) {
|
|
252
|
+
case "INPUT":
|
|
253
|
+
case "OPTION":
|
|
254
|
+
value = element.value;
|
|
255
|
+
break;
|
|
256
|
+
|
|
257
|
+
case "SELECT":
|
|
258
|
+
value = handleSelectedValues(element,
|
|
259
|
+
await Promise.all(Array.from(element.selectedOptions, option => getValueAsync(option))),
|
|
260
|
+
prefix, suffix);
|
|
261
|
+
break;
|
|
262
|
+
|
|
263
|
+
case "TEXTAREA":
|
|
264
|
+
value = element.hasAttribute("value")
|
|
265
|
+
? readValueAttribute()
|
|
266
|
+
: element.value;
|
|
267
|
+
break;
|
|
268
|
+
|
|
269
|
+
case "IFRAME":
|
|
270
|
+
value = readFrame(element);
|
|
271
|
+
break;
|
|
272
|
+
|
|
273
|
+
default:
|
|
274
|
+
value = handleElement(element, valueType, evaluateValue);
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (valueType === "boolean") {
|
|
281
|
+
return toBoolean(value);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (value) {
|
|
285
|
+
if (evaluateValue) {
|
|
286
|
+
value = await astAsync({ executeAST: value, node: element, exclude: ["$value"] });
|
|
287
|
+
}
|
|
288
|
+
value = transformValue(element, value, "get-value");
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
292
|
+
if (prefix || suffix) {
|
|
293
|
+
value = prefix + value + suffix;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
value = parseJson(value, valueType);
|
|
298
|
+
value = toArray(value, valueType);
|
|
299
|
+
|
|
300
|
+
return value;
|
|
285
301
|
};
|
|
286
302
|
|
|
287
303
|
/**
|
|
288
304
|
* Processes a checkbox or a group of checkboxes to determine their checked values.
|
|
289
305
|
*/
|
|
290
|
-
const handleCheckbox = (element, prefix = "", suffix = "") => {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
+
const handleCheckbox = (element, prefix = "", suffix = "", valueType = "") => {
|
|
307
|
+
const inputs = element.getAttribute("key")
|
|
308
|
+
? document.querySelectorAll(
|
|
309
|
+
`input[type="${element.type}"][key="${element.getAttribute(
|
|
310
|
+
"key"
|
|
311
|
+
)}"]`
|
|
312
|
+
)
|
|
313
|
+
: [element];
|
|
314
|
+
|
|
315
|
+
if (inputs.length === 1) {
|
|
316
|
+
if (valueType.startsWith("array"))
|
|
317
|
+
return element.checked ? [element.value || true] : [];
|
|
318
|
+
return element.checked ? element.value || true : false;
|
|
319
|
+
} else {
|
|
320
|
+
return Array.from(inputs)
|
|
321
|
+
.filter((el) => el.checked)
|
|
322
|
+
.map((el) => `${prefix}${el.value}${suffix}`);
|
|
323
|
+
}
|
|
306
324
|
};
|
|
307
325
|
|
|
308
326
|
/**
|
|
309
327
|
* Handles and transforms a date/time value based on the specified valueType.
|
|
310
328
|
*/
|
|
311
329
|
const handleDateTime = (element, value, valueType) => {
|
|
330
|
+
if ((value === "" || value === null || value === undefined) && !element.hasAttribute("now")) return "";
|
|
312
331
|
const inputType = (element.getAttribute("type") || element.type || "").toLowerCase();
|
|
313
332
|
let date;
|
|
314
|
-
|
|
333
|
+
|
|
315
334
|
if (value === "$now" || element.hasAttribute("now")) {
|
|
316
335
|
date = new Date();
|
|
317
336
|
} else if (value instanceof Date) {
|
|
@@ -464,7 +483,7 @@ const handleDateTime = (element, value, valueType) => {
|
|
|
464
483
|
case "timeAgo": {
|
|
465
484
|
const diffMs = Date.now() - date.getTime();
|
|
466
485
|
const absSecs = Math.floor(Math.abs(diffMs) / 1000);
|
|
467
|
-
|
|
486
|
+
|
|
468
487
|
const limitAttr = element.getAttribute("time-ago-limit");
|
|
469
488
|
if (limitAttr) {
|
|
470
489
|
const match = limitAttr.match(/^(\d+)([a-z]+)$/i);
|
|
@@ -472,7 +491,7 @@ const handleDateTime = (element, value, valueType) => {
|
|
|
472
491
|
const amount = parseInt(match[1], 10);
|
|
473
492
|
const limitUnit = match[2].toLowerCase();
|
|
474
493
|
let multiplier = 0;
|
|
475
|
-
|
|
494
|
+
|
|
476
495
|
switch (limitUnit) {
|
|
477
496
|
case 's': multiplier = 1; break;
|
|
478
497
|
case 'm': multiplier = 60; break;
|
|
@@ -482,9 +501,9 @@ const handleDateTime = (element, value, valueType) => {
|
|
|
482
501
|
case 'mo': multiplier = 2592000; break;
|
|
483
502
|
case 'y': multiplier = 31536000; break;
|
|
484
503
|
}
|
|
485
|
-
|
|
504
|
+
|
|
486
505
|
const limitInSeconds = amount * multiplier;
|
|
487
|
-
|
|
506
|
+
|
|
488
507
|
if (absSecs > limitInSeconds) {
|
|
489
508
|
const locale = element.getAttribute("locale") || "en-US";
|
|
490
509
|
return date.toLocaleString(locale);
|
|
@@ -532,308 +551,67 @@ const handleDateTime = (element, value, valueType) => {
|
|
|
532
551
|
};
|
|
533
552
|
|
|
534
553
|
/**
|
|
535
|
-
*
|
|
554
|
+
* Collects processed option values, retaining select-level multi-value affixes.
|
|
536
555
|
*/
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
let optionValue = options[i].value;
|
|
543
|
-
if (prefix || suffix) {
|
|
544
|
-
optionValue = prefix + optionValue + suffix;
|
|
545
|
-
}
|
|
546
|
-
value.push(optionValue);
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
return value;
|
|
556
|
+
const handleSelectedValues = (element, values, prefix, suffix) => {
|
|
557
|
+
if (!element.hasAttribute("multiple")) return values.length ? values[0] : "";
|
|
558
|
+
return prefix || suffix
|
|
559
|
+
? values.map(value => prefix + value + suffix)
|
|
560
|
+
: values;
|
|
550
561
|
};
|
|
551
562
|
|
|
552
563
|
/**
|
|
553
564
|
* Retrieves a value from a standard element.
|
|
554
565
|
*/
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
if (excludeSelector) {
|
|
564
|
-
targetElement = element.cloneNode(true);
|
|
565
|
-
targetElement
|
|
566
|
-
.querySelectorAll(excludeSelector)
|
|
567
|
-
.forEach((el) => el.remove());
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
switch (valueType) {
|
|
571
|
-
case "text":
|
|
572
|
-
return targetElement.innerText;
|
|
573
|
-
case "outerHTML":
|
|
574
|
-
case "element":
|
|
575
|
-
case "node":
|
|
576
|
-
return targetElement.outerHTML;
|
|
577
|
-
default:
|
|
578
|
-
return targetElement.innerHTML;
|
|
579
|
-
}
|
|
566
|
+
const readFrame = (element) => {
|
|
567
|
+
if ("value" in element) return element.value;
|
|
568
|
+
if (element.hasAttribute("srcdoc")) return element.srcdoc;
|
|
569
|
+
const doc = element.contentDocument;
|
|
570
|
+
const declaration = doc.doctype
|
|
571
|
+
? new element.ownerDocument.defaultView.XMLSerializer().serializeToString(doc.doctype)
|
|
572
|
+
: "";
|
|
573
|
+
return declaration + doc.documentElement.outerHTML;
|
|
580
574
|
};
|
|
581
575
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
576
|
+
const handleElement = (element, valueType, evaluateValue) => {
|
|
577
|
+
const readDOM = [
|
|
578
|
+
"text", "innerText", "textContent", "innerHTML", "outerHTML", "element", "node"
|
|
579
|
+
].includes(valueType);
|
|
580
|
+
|
|
581
|
+
// Explicit DOM reads bypass stored values. Conversion types retain the
|
|
582
|
+
// normal attribute -> property -> DOM precedence, including empty values.
|
|
583
|
+
if (!readDOM) {
|
|
584
|
+
if (element.hasAttribute("value")) {
|
|
585
|
+
return evaluateValue
|
|
586
|
+
? element.getAttribute("value")
|
|
587
|
+
: element.attributes.getNamedItem("value")?.value;
|
|
588
|
+
}
|
|
589
|
+
if ("value" in element) return element.value;
|
|
590
|
+
}
|
|
595
591
|
|
|
596
|
-
|
|
597
|
-
|
|
592
|
+
let targetElement = element;
|
|
593
|
+
const excludeSelector = getValueAttribute(element, "remove-query", "get-value");
|
|
598
594
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
const regexAttribute = [
|
|
606
|
-
"value-replace",
|
|
607
|
-
"value-replaceall",
|
|
608
|
-
"value-test",
|
|
609
|
-
"value-match",
|
|
610
|
-
"value-split",
|
|
611
|
-
"value-lastindex",
|
|
612
|
-
"value-search",
|
|
613
|
-
"value-exec"
|
|
614
|
-
];
|
|
615
|
-
|
|
616
|
-
for (let i = 0; i < attributes.length; i++) {
|
|
617
|
-
if (value === null || value === undefined) break;
|
|
618
|
-
if (!regexAttribute.includes(attributes[i].name)) continue;
|
|
619
|
-
|
|
620
|
-
let regexAttributeValue = attributes[i].value;
|
|
621
|
-
if (!regexAttributeValue) continue;
|
|
622
|
-
|
|
623
|
-
let { regex, replacement } = regexParser(regexAttributeValue);
|
|
624
|
-
if (regex) regexAttributeValue = regex;
|
|
625
|
-
|
|
626
|
-
replacement =
|
|
627
|
-
replacement || element.getAttribute("value-replacement") || "";
|
|
628
|
-
|
|
629
|
-
switch (attributes[i].name) {
|
|
630
|
-
case "value-replace":
|
|
631
|
-
value = value.replace(regexAttributeValue, replacement);
|
|
632
|
-
break;
|
|
633
|
-
case "value-replaceall":
|
|
634
|
-
value = value.replaceAll(regexAttributeValue, replacement);
|
|
635
|
-
break;
|
|
636
|
-
case "value-test":
|
|
637
|
-
value = regex.test(value);
|
|
638
|
-
break;
|
|
639
|
-
case "value-match": {
|
|
640
|
-
const matches = value.match(regexAttributeValue);
|
|
641
|
-
if (matches) {
|
|
642
|
-
value = matches[1] || matches[0];
|
|
643
|
-
}
|
|
644
|
-
break;
|
|
645
|
-
}
|
|
646
|
-
case "value-split":
|
|
647
|
-
value = value.split(regexAttributeValue);
|
|
648
|
-
break;
|
|
649
|
-
case "value-lastindex":
|
|
650
|
-
regex.lastIndex = 0;
|
|
651
|
-
regex.test(value);
|
|
652
|
-
value = regex.lastIndex;
|
|
653
|
-
break;
|
|
654
|
-
case "value-search":
|
|
655
|
-
value = value.search(regexAttributeValue);
|
|
656
|
-
break;
|
|
657
|
-
case "value-exec": {
|
|
658
|
-
const execResult = regex.exec(value);
|
|
659
|
-
if (execResult) {
|
|
660
|
-
value = execResult[1] || execResult[2] || execResult[0];
|
|
661
|
-
}
|
|
662
|
-
break;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
return value;
|
|
668
|
-
} catch (error) {
|
|
669
|
-
console.warn(
|
|
670
|
-
"Warning: Transformation error during regex operation:",
|
|
671
|
-
error,
|
|
672
|
-
element
|
|
673
|
-
);
|
|
674
|
-
return value;
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
function regexParser(string) {
|
|
679
|
-
let regex, replacement;
|
|
680
|
-
let regexMatch = string.match(/\/(.+)\/([gimuy]*)/);
|
|
681
|
-
|
|
682
|
-
if (regexMatch) {
|
|
683
|
-
regex = new RegExp(regexMatch[1], regexMatch[2]);
|
|
684
|
-
const splitReplace = string.split(", ");
|
|
685
|
-
replacement =
|
|
686
|
-
splitReplace.length > 1 ? splitReplace[1].slice(1, -1) : "";
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
return { regex, replacement };
|
|
690
|
-
}
|
|
595
|
+
if (excludeSelector) {
|
|
596
|
+
targetElement = element.cloneNode(true);
|
|
597
|
+
targetElement
|
|
598
|
+
.querySelectorAll(excludeSelector)
|
|
599
|
+
.forEach((el) => el.remove());
|
|
600
|
+
}
|
|
691
601
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
return encodeURIComponent(value.replace(/ /g, "%20"));
|
|
707
|
-
|
|
708
|
-
case "base64":
|
|
709
|
-
case "atob":
|
|
710
|
-
try {
|
|
711
|
-
const encoder = new TextEncoder();
|
|
712
|
-
const uint8Array = encoder.encode(value);
|
|
713
|
-
return btoa(String.fromCharCode(...uint8Array));
|
|
714
|
-
} catch (error) {
|
|
715
|
-
console.warn(`Failed to encode as Base64: ${error.message}`);
|
|
716
|
-
return value;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
case "html-entities":
|
|
720
|
-
return value.replace(/[\u00A0-\u9999<>\&]/g, (i) => {
|
|
721
|
-
return `&#${i.charCodeAt(0)};`;
|
|
722
|
-
});
|
|
723
|
-
|
|
724
|
-
case "json":
|
|
725
|
-
try {
|
|
726
|
-
return JSON.stringify(value);
|
|
727
|
-
} catch (error) {
|
|
728
|
-
console.warn(`Failed to convert to JSON: ${error.message}`);
|
|
729
|
-
return value;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
default:
|
|
733
|
-
console.warn(`Unsupported encoding type: ${encodingType}`);
|
|
734
|
-
return value;
|
|
735
|
-
}
|
|
736
|
-
}
|
|
602
|
+
switch (valueType) {
|
|
603
|
+
case "text":
|
|
604
|
+
case "innerText":
|
|
605
|
+
return targetElement.innerText;
|
|
606
|
+
case "textContent":
|
|
607
|
+
return targetElement.textContent;
|
|
608
|
+
case "outerHTML":
|
|
609
|
+
case "element":
|
|
610
|
+
case "node":
|
|
611
|
+
return targetElement.outerHTML;
|
|
612
|
+
default:
|
|
613
|
+
return targetElement.innerHTML;
|
|
614
|
+
}
|
|
615
|
+
};
|
|
737
616
|
|
|
738
|
-
|
|
739
|
-
* Decodes calculated structures securely.
|
|
740
|
-
*/
|
|
741
|
-
function decodeValue(value, decodingType) {
|
|
742
|
-
if (!decodingType) {
|
|
743
|
-
return value;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
switch (decodingType.toLowerCase()) {
|
|
747
|
-
case "url":
|
|
748
|
-
case "uri":
|
|
749
|
-
return decodeURI(value);
|
|
750
|
-
|
|
751
|
-
case "uri-component":
|
|
752
|
-
return decodeURIComponent(value);
|
|
753
|
-
|
|
754
|
-
case "base64":
|
|
755
|
-
case "btoa":
|
|
756
|
-
try {
|
|
757
|
-
const decodedArray = Uint8Array.from(atob(value), (c) =>
|
|
758
|
-
c.charCodeAt(0)
|
|
759
|
-
);
|
|
760
|
-
const decoder = new TextDecoder();
|
|
761
|
-
return decoder.decode(decodedArray);
|
|
762
|
-
} catch (error) {
|
|
763
|
-
console.warn(`Failed to decode Base64: ${error.message}`);
|
|
764
|
-
return value;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
case "html-entities": {
|
|
768
|
-
const tempElement = document.createElement("div");
|
|
769
|
-
tempElement.innerHTML = value;
|
|
770
|
-
return tempElement.textContent;
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
case "json":
|
|
774
|
-
try {
|
|
775
|
-
return JSON.parse(value);
|
|
776
|
-
} catch (error) {
|
|
777
|
-
console.warn(`Failed to parse JSON: ${error.message}`);
|
|
778
|
-
return value;
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
default:
|
|
782
|
-
console.warn(`Unsupported decoding type: ${decodingType}`);
|
|
783
|
-
return value;
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
function parseJson(value, valueType) {
|
|
788
|
-
if (
|
|
789
|
-
value &&
|
|
790
|
-
(valueType === "object" ||
|
|
791
|
-
valueType === "json" ||
|
|
792
|
-
valueType.startsWith("array"))
|
|
793
|
-
) {
|
|
794
|
-
try {
|
|
795
|
-
value = JSON.parse(value);
|
|
796
|
-
} catch {
|
|
797
|
-
const jsonRegex = /(\{[\s\S]*}|\[[\s\S]*\])/;
|
|
798
|
-
const match = value.match(jsonRegex);
|
|
799
|
-
|
|
800
|
-
if (match) {
|
|
801
|
-
try {
|
|
802
|
-
value = JSON.parse(match[0]);
|
|
803
|
-
} catch {
|
|
804
|
-
console.warn(
|
|
805
|
-
"Warning: Failed to parse JSON after extraction. Returning original value."
|
|
806
|
-
);
|
|
807
|
-
}
|
|
808
|
-
} else {
|
|
809
|
-
console.warn(
|
|
810
|
-
"Warning: No valid JSON structure found in the string. Returning original value."
|
|
811
|
-
);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
return value;
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
function toArray(value, valueType) {
|
|
819
|
-
if (valueType.startsWith("array")) {
|
|
820
|
-
if (!Array.isArray(value)) {
|
|
821
|
-
if (typeof value === "object" && value !== null) {
|
|
822
|
-
if (valueType === "array.$keys") {
|
|
823
|
-
value = Object.keys(value);
|
|
824
|
-
} else if (valueType === "array.$values") {
|
|
825
|
-
value = Object.values(value);
|
|
826
|
-
} else if (valueType === "array.$entries") {
|
|
827
|
-
value = Object.entries(value);
|
|
828
|
-
} else {
|
|
829
|
-
value = [value];
|
|
830
|
-
}
|
|
831
|
-
} else {
|
|
832
|
-
value = [value];
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
return value;
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
export { getValue, getValueAsync, storage };
|
|
617
|
+
export { getValue, getValueAsync };
|