@streamline-pulse/formkrafter-core 0.8.0 → 0.10.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/dist/index.d.ts +2 -0
- package/dist/index.js +1155 -1030
- package/dist/index.js.map +7 -5
- package/dist/utils/recap.d.ts +20 -0
- package/dist/utils/remote.d.ts +3 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -194,781 +194,624 @@ function normalizeOptions(raw, labelKey = "label", valueKey = "value") {
|
|
|
194
194
|
return String(raw ?? "").split(`
|
|
195
195
|
`).map((line) => line.trim()).filter(Boolean).map((line) => ({ label: line, value: line }));
|
|
196
196
|
}
|
|
197
|
-
// lib/
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
evalNode(ctx, node.expression);
|
|
332
|
-
return;
|
|
333
|
-
case "Literal":
|
|
334
|
-
return node.value;
|
|
335
|
-
case "TemplateLiteral": {
|
|
336
|
-
const quasis = node.quasis;
|
|
337
|
-
const expressions = node.expressions;
|
|
338
|
-
let text = "";
|
|
339
|
-
quasis.forEach((quasi, index) => {
|
|
340
|
-
text += quasi.value.cooked ?? "";
|
|
341
|
-
if (index < expressions.length) {
|
|
342
|
-
text += String(evalNode(ctx, expressions[index]));
|
|
343
|
-
}
|
|
344
|
-
});
|
|
345
|
-
return text;
|
|
346
|
-
}
|
|
347
|
-
case "Identifier":
|
|
348
|
-
return lookup(ctx, node.name);
|
|
349
|
-
case "ThisExpression":
|
|
350
|
-
return;
|
|
351
|
-
case "ChainExpression":
|
|
352
|
-
return evalNode(ctx, node.expression);
|
|
353
|
-
case "MemberExpression":
|
|
354
|
-
return readMember(ctx, node);
|
|
355
|
-
case "CallExpression": {
|
|
356
|
-
const callee = node.callee;
|
|
357
|
-
const args = [];
|
|
358
|
-
for (const argument of node.arguments) {
|
|
359
|
-
if (argument.type === "SpreadElement") {
|
|
360
|
-
const spread = evalNode(ctx, argument.argument);
|
|
361
|
-
if (Array.isArray(spread))
|
|
362
|
-
args.push(...spread);
|
|
363
|
-
} else {
|
|
364
|
-
args.push(evalNode(ctx, argument));
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
if (callee.type === "MemberExpression") {
|
|
368
|
-
const { object, prop, optional } = memberTarget(ctx, callee);
|
|
369
|
-
if (object === null || object === undefined) {
|
|
370
|
-
if (optional || node.optional === true)
|
|
371
|
-
return;
|
|
372
|
-
throw new Error(`Cannot call "${String(prop)}" of ${String(object)}`);
|
|
373
|
-
}
|
|
374
|
-
const fn3 = object[prop];
|
|
375
|
-
if (typeof fn3 !== "function") {
|
|
376
|
-
if (node.optional === true && fn3 == null)
|
|
377
|
-
return;
|
|
378
|
-
throw new Error(`"${String(prop)}" is not a function`);
|
|
379
|
-
}
|
|
380
|
-
if (fn3 === Function || fn3 === eval) {
|
|
381
|
-
throw new Error("This function is not allowed");
|
|
382
|
-
}
|
|
383
|
-
return fn3.apply(object, args);
|
|
384
|
-
}
|
|
385
|
-
const fn2 = evalNode(ctx, callee);
|
|
386
|
-
if (typeof fn2 !== "function") {
|
|
387
|
-
if (node.optional === true && fn2 == null)
|
|
388
|
-
return;
|
|
389
|
-
throw new Error("Value is not a function");
|
|
390
|
-
}
|
|
391
|
-
if (fn2 === Function || fn2 === eval) {
|
|
392
|
-
throw new Error("This function is not allowed");
|
|
393
|
-
}
|
|
394
|
-
return fn2(...args);
|
|
395
|
-
}
|
|
396
|
-
case "ArrowFunctionExpression": {
|
|
397
|
-
const params = node.params;
|
|
398
|
-
const body = node.body;
|
|
399
|
-
const capturedScopes = [...ctx.scopes];
|
|
400
|
-
return (...args) => {
|
|
401
|
-
const callScope = new Map;
|
|
402
|
-
params.forEach((param, index) => {
|
|
403
|
-
if (param.type !== "Identifier") {
|
|
404
|
-
throw new Error("Only simple parameters are supported");
|
|
405
|
-
}
|
|
406
|
-
callScope.set(param.name, args[index]);
|
|
407
|
-
});
|
|
408
|
-
const callCtx = {
|
|
409
|
-
steps: ctx.steps,
|
|
410
|
-
scopes: [...capturedScopes, callScope]
|
|
411
|
-
};
|
|
412
|
-
const result = evalNode(callCtx, body);
|
|
413
|
-
ctx.steps = callCtx.steps;
|
|
414
|
-
if (result instanceof ReturnSignal)
|
|
415
|
-
return result.value;
|
|
416
|
-
return node.expression === true ? result : undefined;
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
case "BinaryExpression": {
|
|
420
|
-
const left = evalNode(ctx, node.left);
|
|
421
|
-
const right = evalNode(ctx, node.right);
|
|
422
|
-
switch (node.operator) {
|
|
423
|
-
case "===":
|
|
424
|
-
return left === right;
|
|
425
|
-
case "!==":
|
|
426
|
-
return left !== right;
|
|
427
|
-
case "==":
|
|
428
|
-
return left == right;
|
|
429
|
-
case "!=":
|
|
430
|
-
return left != right;
|
|
431
|
-
case "<":
|
|
432
|
-
return left < right;
|
|
433
|
-
case "<=":
|
|
434
|
-
return left <= right;
|
|
435
|
-
case ">":
|
|
436
|
-
return left > right;
|
|
437
|
-
case ">=":
|
|
438
|
-
return left >= right;
|
|
439
|
-
case "+":
|
|
440
|
-
return left + right;
|
|
441
|
-
case "-":
|
|
442
|
-
return left - right;
|
|
443
|
-
case "*":
|
|
444
|
-
return left * right;
|
|
445
|
-
case "/":
|
|
446
|
-
return left / right;
|
|
447
|
-
case "%":
|
|
448
|
-
return left % right;
|
|
449
|
-
case "**":
|
|
450
|
-
return left ** right;
|
|
451
|
-
default:
|
|
452
|
-
throw new Error(`Unsupported operator "${String(node.operator)}"`);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
case "LogicalExpression": {
|
|
456
|
-
const left = evalNode(ctx, node.left);
|
|
457
|
-
switch (node.operator) {
|
|
458
|
-
case "&&":
|
|
459
|
-
return left && evalNode(ctx, node.right);
|
|
460
|
-
case "||":
|
|
461
|
-
return left || evalNode(ctx, node.right);
|
|
462
|
-
case "??":
|
|
463
|
-
return left ?? evalNode(ctx, node.right);
|
|
464
|
-
default:
|
|
465
|
-
throw new Error(`Unsupported operator "${String(node.operator)}"`);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
case "UnaryExpression": {
|
|
469
|
-
const argument = evalNode(ctx, node.argument);
|
|
470
|
-
switch (node.operator) {
|
|
471
|
-
case "!":
|
|
472
|
-
return !argument;
|
|
473
|
-
case "-":
|
|
474
|
-
return -argument;
|
|
475
|
-
case "+":
|
|
476
|
-
return +argument;
|
|
477
|
-
case "typeof":
|
|
478
|
-
return typeof argument;
|
|
479
|
-
default:
|
|
480
|
-
throw new Error(`Unsupported operator "${String(node.operator)}"`);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
case "ConditionalExpression":
|
|
484
|
-
return evalNode(ctx, node.test) ? evalNode(ctx, node.consequent) : evalNode(ctx, node.alternate);
|
|
485
|
-
case "AssignmentExpression": {
|
|
486
|
-
const target = node.left;
|
|
487
|
-
if (target.type !== "Identifier" || node.operator !== "=") {
|
|
488
|
-
throw new Error("Only simple assignments are supported");
|
|
489
|
-
}
|
|
490
|
-
const value = evalNode(ctx, node.right);
|
|
491
|
-
assign(ctx, target.name, value);
|
|
492
|
-
return value;
|
|
493
|
-
}
|
|
494
|
-
case "ArrayExpression": {
|
|
495
|
-
const values = [];
|
|
496
|
-
for (const element of node.elements) {
|
|
497
|
-
if (!element)
|
|
498
|
-
continue;
|
|
499
|
-
if (element.type === "SpreadElement") {
|
|
500
|
-
const spread = evalNode(ctx, element.argument);
|
|
501
|
-
if (Array.isArray(spread))
|
|
502
|
-
values.push(...spread);
|
|
503
|
-
} else {
|
|
504
|
-
values.push(evalNode(ctx, element));
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
return values;
|
|
508
|
-
}
|
|
509
|
-
case "ObjectExpression": {
|
|
510
|
-
const result = {};
|
|
511
|
-
for (const property of node.properties) {
|
|
512
|
-
if (property.type === "SpreadElement") {
|
|
513
|
-
const spread = evalNode(ctx, property.argument);
|
|
514
|
-
if (spread && typeof spread === "object") {
|
|
515
|
-
for (const [key2, value] of Object.entries(spread)) {
|
|
516
|
-
result[String(guardProp(key2))] = value;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
continue;
|
|
520
|
-
}
|
|
521
|
-
const keyNode = property.key;
|
|
522
|
-
const key = property.computed ? guardProp(evalNode(ctx, keyNode)) : guardProp(keyNode.type === "Identifier" ? keyNode.name : keyNode.value);
|
|
523
|
-
result[String(key)] = evalNode(ctx, property.value);
|
|
524
|
-
}
|
|
525
|
-
return result;
|
|
526
|
-
}
|
|
527
|
-
default:
|
|
528
|
-
throw new Error(`Unsupported syntax: ${node.type}`);
|
|
529
|
-
}
|
|
197
|
+
// lib/i18n.ts
|
|
198
|
+
var en = {
|
|
199
|
+
"palette.search": "Search bricks…",
|
|
200
|
+
"palette.empty": "No brick matches “{query}”",
|
|
201
|
+
"builder.undo": "Undo",
|
|
202
|
+
"builder.redo": "Redo",
|
|
203
|
+
"builder.editLocale": "Editing language",
|
|
204
|
+
"builder.copyJson": "Copy JSON",
|
|
205
|
+
"builder.importJson": "Import JSON",
|
|
206
|
+
"builder.importInvalid": "Invalid form JSON",
|
|
207
|
+
"builder.pasteHint": "Press Ctrl+V to paste a spec",
|
|
208
|
+
"builder.copied": "Copied!",
|
|
209
|
+
"builder.downloadJson": "Download .json",
|
|
210
|
+
"builder.hint": "Select a brick to edit its properties",
|
|
211
|
+
"drop.hint": "Drop a brick here",
|
|
212
|
+
"empty.title": "Your form is empty",
|
|
213
|
+
"empty.hint": "Drag a brick from the palette to get started",
|
|
214
|
+
"actions.move": "Move",
|
|
215
|
+
"actions.duplicate": "Duplicate",
|
|
216
|
+
"actions.delete": "Delete",
|
|
217
|
+
"stepper.back": "← Back",
|
|
218
|
+
"stepper.next": "Next →",
|
|
219
|
+
"stepper.submit": "Submit",
|
|
220
|
+
"panel.cfg.validateSteps": "Validate each step",
|
|
221
|
+
"panel.cfg.allowStepClick": "Allow clicking steps to jump",
|
|
222
|
+
"panel.cfg.showSubmit": "Submit button on last step",
|
|
223
|
+
"panel.cfg.validateTabs": "Validate tab before leaving it",
|
|
224
|
+
"signature.clear": "Clear",
|
|
225
|
+
"panel.tab.config": "Config",
|
|
226
|
+
"panel.tab.validation": "Validation",
|
|
227
|
+
"panel.tab.styles": "Styles",
|
|
228
|
+
"panel.tab.rules": "Rules",
|
|
229
|
+
"panel.key": "Key",
|
|
230
|
+
"panel.label": "Label",
|
|
231
|
+
"panel.placeholder": "Placeholder",
|
|
232
|
+
"panel.prefix": "Prefix",
|
|
233
|
+
"panel.suffix": "Suffix",
|
|
234
|
+
"panel.accept": "Accepted file types",
|
|
235
|
+
"panel.mask": "Input mask (9=digit, a=letter, *=any)",
|
|
236
|
+
"panel.uploadUrl": "Upload URL",
|
|
237
|
+
"panel.cfg.multiple": "Allow multiple files",
|
|
238
|
+
"panel.optionsSource": "Options source",
|
|
239
|
+
"panel.options": "Options (one per line)",
|
|
240
|
+
"panel.content": "Content",
|
|
241
|
+
"panel.dataKey": "Data key",
|
|
242
|
+
"panel.url": "URL",
|
|
243
|
+
"panel.headers": "Headers ({token} reads form data)",
|
|
244
|
+
"panel.searchParam": "Server search param",
|
|
245
|
+
"panel.labelKey": "Label key",
|
|
246
|
+
"panel.valueKey": "Value key",
|
|
247
|
+
"panel.errorMessage": "Error message",
|
|
248
|
+
"panel.required": "Required",
|
|
249
|
+
"panel.minLength": "Min length",
|
|
250
|
+
"panel.maxLength": "Max length",
|
|
251
|
+
"panel.min": "Min",
|
|
252
|
+
"panel.max": "Max",
|
|
253
|
+
"panel.pattern": "Pattern (regex)",
|
|
254
|
+
"panel.minItems": "Min items",
|
|
255
|
+
"panel.maxItems": "Max items",
|
|
256
|
+
"panel.emailFormat": "Email format",
|
|
257
|
+
"panel.urlFormat": "URL format",
|
|
258
|
+
"panel.custom": "Custom (JavaScript)",
|
|
259
|
+
"panel.fallbackMessage": "Fallback message",
|
|
260
|
+
"panel.styleProperty": "property",
|
|
261
|
+
"panel.styleValue": "value",
|
|
262
|
+
"file.choose": "Choose a file…",
|
|
263
|
+
"file.uploading": "Uploading…",
|
|
264
|
+
"file.remove": "Remove file",
|
|
265
|
+
"file.addAnother": "+ Add a file",
|
|
266
|
+
"address.street": "Street",
|
|
267
|
+
"address.city": "City",
|
|
268
|
+
"address.zip": "Postal code",
|
|
269
|
+
"address.country": "Country",
|
|
270
|
+
"grid.addRow": "+ Add row",
|
|
271
|
+
"grid.moveUp": "Move row up",
|
|
272
|
+
"grid.moveDown": "Move row down",
|
|
273
|
+
"grid.removeRow": "Remove row",
|
|
274
|
+
"grid.empty": "No rows yet",
|
|
275
|
+
"grid.columns": "Row template",
|
|
276
|
+
"select.search": "Search…",
|
|
277
|
+
"select.empty": "No options",
|
|
278
|
+
"select.clear": "Clear",
|
|
279
|
+
"rules.when": "When",
|
|
280
|
+
"rules.then": "Then",
|
|
281
|
+
"rules.addRule": "+ Add rule",
|
|
282
|
+
"rules.addEffect": "+ Effect",
|
|
283
|
+
"rules.removeRule": "Remove rule",
|
|
284
|
+
"rules.removeEffect": "Remove effect",
|
|
285
|
+
"rules.mode.simple": "simple",
|
|
286
|
+
"rules.mode.json": "json",
|
|
287
|
+
"rules.mode.js": "JS",
|
|
288
|
+
"category.Inputs": "Inputs",
|
|
289
|
+
"category.Layout": "Layout",
|
|
290
|
+
"category.Data": "Data",
|
|
291
|
+
"brick.text.name": "Text",
|
|
292
|
+
"brick.email.name": "Email",
|
|
293
|
+
"brick.password.name": "Password",
|
|
294
|
+
"brick.url.name": "URL",
|
|
295
|
+
"brick.phone.name": "Phone",
|
|
296
|
+
"brick.textarea.name": "Text area",
|
|
297
|
+
"brick.number.name": "Number",
|
|
298
|
+
"brick.date.name": "Date",
|
|
299
|
+
"brick.time.name": "Time",
|
|
300
|
+
"brick.datetime.name": "Date / Time",
|
|
301
|
+
"brick.select.name": "Select",
|
|
302
|
+
"brick.multi-select.name": "Multi select",
|
|
303
|
+
"brick.radio.name": "Radio",
|
|
304
|
+
"brick.select-boxes.name": "Select boxes",
|
|
305
|
+
"brick.checkbox.name": "Checkbox",
|
|
306
|
+
"brick.tags.name": "Tags",
|
|
307
|
+
"brick.signature.name": "Signature",
|
|
308
|
+
"brick.data-grid.name": "Data grid",
|
|
309
|
+
"brick.address.name": "Address",
|
|
310
|
+
"brick.file.name": "File",
|
|
311
|
+
"brick.hidden.name": "Hidden",
|
|
312
|
+
"brick.content.name": "Content",
|
|
313
|
+
"brick.recap.name": "Recap",
|
|
314
|
+
"brick.nested-form.name": "Nested form",
|
|
315
|
+
"brick.code.name": "Code",
|
|
316
|
+
"nestedForm.ref": "Referenced form",
|
|
317
|
+
"nestedForm.missing": "Set the specRef config to reference a form.",
|
|
318
|
+
"nestedForm.loading": "Loading nested forms…",
|
|
319
|
+
"panel.specRef": "Referenced form (specRef)",
|
|
320
|
+
"panel.optionsRef": "Options catalog (optionsRef)",
|
|
321
|
+
"recap.empty": "Nothing to summarize yet.",
|
|
322
|
+
"recap.yes": "Yes",
|
|
323
|
+
"recap.no": "No",
|
|
324
|
+
"panel.cfg.showEmpty": "Show empty fields",
|
|
325
|
+
"panel.cfg.groupBySections": "Group by section",
|
|
326
|
+
"brick.group.name": "Group",
|
|
327
|
+
"brick.row.name": "Row",
|
|
328
|
+
"brick.column.name": "Column",
|
|
329
|
+
"brick.stepper.name": "Stepper",
|
|
330
|
+
"brick.tabs.name": "Tabs"
|
|
530
331
|
};
|
|
531
|
-
var
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
332
|
+
var frFkTranslations = {
|
|
333
|
+
"palette.search": "Rechercher une brique…",
|
|
334
|
+
"palette.empty": "Aucune brique ne correspond à « {query} »",
|
|
335
|
+
"builder.undo": "Annuler",
|
|
336
|
+
"builder.editLocale": "Langue d'édition",
|
|
337
|
+
"builder.redo": "Rétablir",
|
|
338
|
+
"builder.copyJson": "Copier le JSON",
|
|
339
|
+
"builder.importJson": "Importer le JSON",
|
|
340
|
+
"builder.importInvalid": "JSON de formulaire invalide",
|
|
341
|
+
"builder.pasteHint": "Collez avec Ctrl+V pour importer une spec",
|
|
342
|
+
"builder.copied": "Copié !",
|
|
343
|
+
"builder.downloadJson": "Télécharger le .json",
|
|
344
|
+
"builder.hint": "Sélectionnez une brique pour éditer ses propriétés",
|
|
345
|
+
"drop.hint": "Déposez une brique ici",
|
|
346
|
+
"empty.title": "Votre formulaire est vide",
|
|
347
|
+
"empty.hint": "Glissez une brique depuis la palette pour commencer",
|
|
348
|
+
"actions.move": "Déplacer",
|
|
349
|
+
"actions.duplicate": "Dupliquer",
|
|
350
|
+
"actions.delete": "Supprimer",
|
|
351
|
+
"stepper.back": "← Précédent",
|
|
352
|
+
"stepper.next": "Suivant →",
|
|
353
|
+
"stepper.submit": "Envoyer",
|
|
354
|
+
"panel.cfg.validateSteps": "Valider chaque étape",
|
|
355
|
+
"panel.cfg.allowStepClick": "Autoriser le saut d’étapes au clic",
|
|
356
|
+
"panel.cfg.showSubmit": "Bouton d’envoi à la dernière étape",
|
|
357
|
+
"panel.cfg.validateTabs": "Valider l’onglet avant de le quitter",
|
|
358
|
+
"signature.clear": "Effacer",
|
|
359
|
+
"panel.tab.config": "Config",
|
|
360
|
+
"panel.tab.validation": "Validation",
|
|
361
|
+
"panel.tab.styles": "Styles",
|
|
362
|
+
"panel.tab.rules": "Règles",
|
|
363
|
+
"panel.key": "Clé",
|
|
364
|
+
"panel.label": "Libellé",
|
|
365
|
+
"panel.placeholder": "Placeholder",
|
|
366
|
+
"panel.prefix": "Préfixe",
|
|
367
|
+
"panel.suffix": "Suffixe",
|
|
368
|
+
"panel.accept": "Types de fichiers acceptés",
|
|
369
|
+
"panel.mask": "Masque de saisie (9=chiffre, a=lettre, *=tout)",
|
|
370
|
+
"panel.uploadUrl": "URL d'upload",
|
|
371
|
+
"panel.cfg.multiple": "Autoriser plusieurs fichiers",
|
|
372
|
+
"panel.optionsSource": "Source des options",
|
|
373
|
+
"panel.options": "Options (une par ligne)",
|
|
374
|
+
"panel.content": "Contenu",
|
|
375
|
+
"panel.dataKey": "Clé de données",
|
|
376
|
+
"panel.url": "URL",
|
|
377
|
+
"panel.headers": "En-têtes ({token} lit les données du formulaire)",
|
|
378
|
+
"panel.searchParam": "Paramètre de recherche serveur",
|
|
379
|
+
"panel.labelKey": "Clé du libellé",
|
|
380
|
+
"panel.valueKey": "Clé de la valeur",
|
|
381
|
+
"panel.errorMessage": "Message d'erreur",
|
|
382
|
+
"panel.required": "Obligatoire",
|
|
383
|
+
"panel.minLength": "Longueur min",
|
|
384
|
+
"panel.maxLength": "Longueur max",
|
|
385
|
+
"panel.min": "Min",
|
|
386
|
+
"panel.max": "Max",
|
|
387
|
+
"panel.pattern": "Motif (regex)",
|
|
388
|
+
"panel.minItems": "Éléments min",
|
|
389
|
+
"panel.maxItems": "Éléments max",
|
|
390
|
+
"panel.emailFormat": "Format email",
|
|
391
|
+
"panel.urlFormat": "Format URL",
|
|
392
|
+
"panel.custom": "Personnalisé (JavaScript)",
|
|
393
|
+
"panel.fallbackMessage": "Message par défaut",
|
|
394
|
+
"panel.styleProperty": "propriété",
|
|
395
|
+
"panel.styleValue": "valeur",
|
|
396
|
+
"file.choose": "Choisir un fichier…",
|
|
397
|
+
"file.uploading": "Envoi en cours…",
|
|
398
|
+
"file.remove": "Retirer le fichier",
|
|
399
|
+
"file.addAnother": "+ Ajouter un fichier",
|
|
400
|
+
"address.street": "Rue",
|
|
401
|
+
"address.city": "Ville",
|
|
402
|
+
"address.zip": "Code postal",
|
|
403
|
+
"address.country": "Pays",
|
|
404
|
+
"grid.addRow": "+ Ajouter une ligne",
|
|
405
|
+
"grid.moveUp": "Monter la ligne",
|
|
406
|
+
"grid.moveDown": "Descendre la ligne",
|
|
407
|
+
"grid.removeRow": "Supprimer la ligne",
|
|
408
|
+
"grid.empty": "Aucune ligne",
|
|
409
|
+
"grid.columns": "Modèle de ligne",
|
|
410
|
+
"select.search": "Rechercher…",
|
|
411
|
+
"select.empty": "Aucune option",
|
|
412
|
+
"select.clear": "Effacer",
|
|
413
|
+
"rules.when": "Quand",
|
|
414
|
+
"rules.then": "Alors",
|
|
415
|
+
"rules.addRule": "+ Ajouter une règle",
|
|
416
|
+
"rules.addEffect": "+ Effet",
|
|
417
|
+
"rules.removeRule": "Supprimer la règle",
|
|
418
|
+
"rules.removeEffect": "Supprimer l'effet",
|
|
419
|
+
"rules.mode.simple": "simple",
|
|
420
|
+
"rules.mode.json": "json",
|
|
421
|
+
"rules.mode.js": "JS",
|
|
422
|
+
"category.Inputs": "Champs",
|
|
423
|
+
"category.Layout": "Mise en page",
|
|
424
|
+
"category.Data": "Données",
|
|
425
|
+
"brick.text.name": "Texte",
|
|
426
|
+
"brick.email.name": "Email",
|
|
427
|
+
"brick.password.name": "Mot de passe",
|
|
428
|
+
"brick.url.name": "URL",
|
|
429
|
+
"brick.phone.name": "Téléphone",
|
|
430
|
+
"brick.textarea.name": "Zone de texte",
|
|
431
|
+
"brick.number.name": "Nombre",
|
|
432
|
+
"brick.date.name": "Date",
|
|
433
|
+
"brick.time.name": "Heure",
|
|
434
|
+
"brick.datetime.name": "Date / Heure",
|
|
435
|
+
"brick.select.name": "Liste déroulante",
|
|
436
|
+
"brick.multi-select.name": "Sélection multiple",
|
|
437
|
+
"brick.radio.name": "Boutons radio",
|
|
438
|
+
"brick.select-boxes.name": "Cases à cocher",
|
|
439
|
+
"brick.checkbox.name": "Case à cocher",
|
|
440
|
+
"brick.tags.name": "Étiquettes",
|
|
441
|
+
"brick.signature.name": "Signature",
|
|
442
|
+
"brick.data-grid.name": "Grille de données",
|
|
443
|
+
"brick.address.name": "Adresse",
|
|
444
|
+
"brick.file.name": "Fichier",
|
|
445
|
+
"brick.hidden.name": "Caché",
|
|
446
|
+
"brick.content.name": "Contenu",
|
|
447
|
+
"brick.recap.name": "Récapitulatif",
|
|
448
|
+
"brick.nested-form.name": "Formulaire imbriqué",
|
|
449
|
+
"brick.code.name": "Code",
|
|
450
|
+
"nestedForm.ref": "Formulaire référencé",
|
|
451
|
+
"nestedForm.missing": "Renseignez la config specRef pour référencer un formulaire.",
|
|
452
|
+
"nestedForm.loading": "Chargement des formulaires imbriqués…",
|
|
453
|
+
"panel.specRef": "Formulaire référencé (specRef)",
|
|
454
|
+
"panel.optionsRef": "Catalogue d'options (optionsRef)",
|
|
455
|
+
"recap.empty": "Rien à récapituler pour le moment.",
|
|
456
|
+
"recap.yes": "Oui",
|
|
457
|
+
"recap.no": "Non",
|
|
458
|
+
"panel.cfg.showEmpty": "Afficher les champs vides",
|
|
459
|
+
"panel.cfg.groupBySections": "Regrouper par section",
|
|
460
|
+
"brick.group.name": "Groupe",
|
|
461
|
+
"brick.row.name": "Ligne",
|
|
462
|
+
"brick.column.name": "Colonne",
|
|
463
|
+
"brick.stepper.name": "Étapes",
|
|
464
|
+
"brick.tabs.name": "Onglets"
|
|
547
465
|
};
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
}
|
|
553
|
-
const ctx = {
|
|
554
|
-
steps: 0,
|
|
555
|
-
scopes: [
|
|
556
|
-
new Map(Object.entries({ ...SAFE_BUILTINS, ...globals })),
|
|
557
|
-
new Map
|
|
558
|
-
]
|
|
559
|
-
};
|
|
560
|
-
const result = evalNode(ctx, ast);
|
|
561
|
-
return result instanceof ReturnSignal ? result.value : undefined;
|
|
466
|
+
var GLOBAL_KEY = Symbol.for("formkrafter.i18n");
|
|
467
|
+
var globalStore = globalThis;
|
|
468
|
+
var state = globalStore[GLOBAL_KEY] ??= { current: { ...en } };
|
|
469
|
+
function setFkTranslations(overrides = {}) {
|
|
470
|
+
state.current = { ...en, ...overrides };
|
|
562
471
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
try {
|
|
568
|
-
parse2(js2, options);
|
|
569
|
-
return { valid: true };
|
|
570
|
-
} catch (error) {
|
|
571
|
-
return {
|
|
572
|
-
valid: false,
|
|
573
|
-
error: error instanceof Error ? error : new Error(String(error))
|
|
574
|
-
};
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
eval(js2, scope2 = {}) {
|
|
578
|
-
return runSandboxed(js2, scope2);
|
|
472
|
+
function fkT(key, params) {
|
|
473
|
+
let text = state.current[key] ?? key;
|
|
474
|
+
for (const [name, value] of Object.entries(params ?? {})) {
|
|
475
|
+
text = text.replace(`{${name}}`, String(value));
|
|
579
476
|
}
|
|
477
|
+
return text;
|
|
580
478
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
class FetchDataSourceService {
|
|
584
|
-
defaults;
|
|
585
|
-
cache = new Map;
|
|
586
|
-
constructor(defaults = {}) {
|
|
587
|
-
this.defaults = defaults;
|
|
588
|
-
}
|
|
589
|
-
fetchOptions(url, options) {
|
|
590
|
-
const headers = { ...this.defaults.headers, ...options?.headers };
|
|
591
|
-
const init = {};
|
|
592
|
-
if (Object.keys(headers).length)
|
|
593
|
-
init.headers = headers;
|
|
594
|
-
if (this.defaults.credentials)
|
|
595
|
-
init.credentials = this.defaults.credentials;
|
|
596
|
-
const key = `${url}|${JSON.stringify(init)}`;
|
|
597
|
-
let pending = this.cache.get(key);
|
|
598
|
-
if (!pending) {
|
|
599
|
-
pending = fetch(url, Object.keys(init).length ? init : undefined).then((response) => {
|
|
600
|
-
if (!response.ok) {
|
|
601
|
-
throw new Error(`Data source request failed (${response.status}): ${url}`);
|
|
602
|
-
}
|
|
603
|
-
return response.json();
|
|
604
|
-
}).then((json) => Array.isArray(json) ? json : []).catch((error) => {
|
|
605
|
-
this.cache.delete(key);
|
|
606
|
-
throw error;
|
|
607
|
-
});
|
|
608
|
-
this.cache.set(key, pending);
|
|
609
|
-
}
|
|
610
|
-
return pending;
|
|
611
|
-
}
|
|
479
|
+
function fkTOr(key, fallback) {
|
|
480
|
+
return state.current[key] ?? fallback;
|
|
612
481
|
}
|
|
613
482
|
|
|
614
|
-
// lib/
|
|
615
|
-
|
|
616
|
-
async upload(file) {
|
|
617
|
-
const encode = globalThis.btoa;
|
|
618
|
-
if (!encode)
|
|
619
|
-
throw new Error("No base64 encoder available");
|
|
620
|
-
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
621
|
-
let binary = "";
|
|
622
|
-
for (const byte of bytes)
|
|
623
|
-
binary += String.fromCharCode(byte);
|
|
624
|
-
return {
|
|
625
|
-
name: file.name,
|
|
626
|
-
type: file.type,
|
|
627
|
-
size: file.size,
|
|
628
|
-
url: `data:${file.type};base64,${encode(binary)}`
|
|
629
|
-
};
|
|
630
|
-
}
|
|
631
|
-
}
|
|
483
|
+
// lib/brick/utils.ts
|
|
484
|
+
import jsonLogic from "json-logic-js";
|
|
632
485
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
constructor(defaults = {}) {
|
|
636
|
-
this.defaults = defaults;
|
|
637
|
-
}
|
|
638
|
-
get fetchImplementation() {
|
|
639
|
-
return this.defaults.fetch ?? globalThis.fetch.bind(globalThis);
|
|
640
|
-
}
|
|
641
|
-
resolveUrl(options) {
|
|
642
|
-
const url = options?.url ?? this.defaults.url;
|
|
643
|
-
if (!url) {
|
|
644
|
-
throw new Error("No upload URL configured — set it on the brick (uploadUrl) or on UrlFileUploadService");
|
|
645
|
-
}
|
|
646
|
-
return url;
|
|
647
|
-
}
|
|
648
|
-
mergedHeaders(options) {
|
|
649
|
-
return { ...this.defaults.headers, ...options?.headers };
|
|
650
|
-
}
|
|
651
|
-
async upload(file, options) {
|
|
652
|
-
const url = this.resolveUrl(options);
|
|
653
|
-
const blob = typeof Blob !== "undefined" && file instanceof Blob ? file : new Blob([await file.arrayBuffer()], { type: file.type });
|
|
654
|
-
const body = new FormData;
|
|
655
|
-
body.append(this.defaults.fieldName ?? "file", blob, file.name);
|
|
656
|
-
const response = await this.fetchImplementation(url, {
|
|
657
|
-
method: "POST",
|
|
658
|
-
body,
|
|
659
|
-
headers: this.mergedHeaders(options),
|
|
660
|
-
credentials: this.defaults.credentials
|
|
661
|
-
});
|
|
662
|
-
if (!response.ok) {
|
|
663
|
-
throw new Error(`Upload failed (${response.status})`);
|
|
664
|
-
}
|
|
665
|
-
const payload = await response.json().catch(() => ({}));
|
|
666
|
-
const uploadedUrl = payload.url ?? payload.path ?? payload.location ?? payload.href;
|
|
667
|
-
return {
|
|
668
|
-
name: file.name,
|
|
669
|
-
type: file.type,
|
|
670
|
-
size: file.size,
|
|
671
|
-
url: typeof uploadedUrl === "string" ? uploadedUrl : ""
|
|
672
|
-
};
|
|
673
|
-
}
|
|
674
|
-
async remove(file, options) {
|
|
675
|
-
if (!file.url)
|
|
676
|
-
return;
|
|
677
|
-
const target = /^https?:\/\//.test(file.url) ? file.url : this.resolveUrl(options);
|
|
678
|
-
const response = await this.fetchImplementation(target, {
|
|
679
|
-
method: "DELETE",
|
|
680
|
-
headers: {
|
|
681
|
-
"content-type": "application/json",
|
|
682
|
-
...this.mergedHeaders(options)
|
|
683
|
-
},
|
|
684
|
-
credentials: this.defaults.credentials,
|
|
685
|
-
body: JSON.stringify({ url: file.url, name: file.name })
|
|
686
|
-
});
|
|
687
|
-
if (!response.ok) {
|
|
688
|
-
throw new Error(`Delete failed (${response.status})`);
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
}
|
|
486
|
+
// lib/services/js_runner_service.ts
|
|
487
|
+
import { parse as parse2 } from "acorn";
|
|
692
488
|
|
|
693
|
-
// lib/services/
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
return ref;
|
|
703
|
-
return `${this.defaults.baseUrl.replace(/\/$/, "")}/${ref.replace(/^\//, "")}`;
|
|
704
|
-
}
|
|
705
|
-
fetchSpec(ref) {
|
|
706
|
-
const url = this.resolveUrl(ref);
|
|
707
|
-
let pending = this.cache.get(url);
|
|
708
|
-
if (!pending) {
|
|
709
|
-
const fetchImplementation = this.defaults.fetch ?? globalThis.fetch.bind(globalThis);
|
|
710
|
-
pending = fetchImplementation(url, {
|
|
711
|
-
headers: this.defaults.headers,
|
|
712
|
-
credentials: this.defaults.credentials
|
|
713
|
-
}).then(async (response) => {
|
|
714
|
-
if (!response.ok) {
|
|
715
|
-
throw new Error(`Failed to load spec "${ref}" (${response.status})`);
|
|
716
|
-
}
|
|
717
|
-
return await response.json();
|
|
718
|
-
});
|
|
719
|
-
pending.catch(() => this.cache.delete(url));
|
|
720
|
-
this.cache.set(url, pending);
|
|
721
|
-
}
|
|
722
|
-
return pending;
|
|
489
|
+
// lib/services/sandbox_interpreter.ts
|
|
490
|
+
import { parse } from "acorn";
|
|
491
|
+
var FORBIDDEN_PROPS = new Set(["__proto__", "constructor", "prototype"]);
|
|
492
|
+
var MAX_STEPS = 1e5;
|
|
493
|
+
|
|
494
|
+
class ReturnSignal {
|
|
495
|
+
value;
|
|
496
|
+
constructor(value) {
|
|
497
|
+
this.value = value;
|
|
723
498
|
}
|
|
724
499
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
cache = new Map;
|
|
730
|
-
constructor(defaults = {}) {
|
|
731
|
-
this.defaults = defaults;
|
|
732
|
-
}
|
|
733
|
-
resolveUrl(ref) {
|
|
734
|
-
if (/^https?:\/\//.test(ref) || !this.defaults.baseUrl)
|
|
735
|
-
return ref;
|
|
736
|
-
return `${this.defaults.baseUrl.replace(/\/$/, "")}/${ref.replace(/^\//, "")}`;
|
|
500
|
+
var lookup = (ctx, name) => {
|
|
501
|
+
for (let i = ctx.scopes.length - 1;i >= 0; i--) {
|
|
502
|
+
if (ctx.scopes[i].has(name))
|
|
503
|
+
return ctx.scopes[i].get(name);
|
|
737
504
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
throw new Error(`Failed to load options "${ref}" (${response.status})`);
|
|
749
|
-
}
|
|
750
|
-
return await response.json();
|
|
751
|
-
});
|
|
752
|
-
pending.catch(() => this.cache.delete(url));
|
|
753
|
-
this.cache.set(url, pending);
|
|
505
|
+
throw new Error(`Unknown identifier "${name}"`);
|
|
506
|
+
};
|
|
507
|
+
var declareVar = (ctx, name, value) => {
|
|
508
|
+
ctx.scopes[ctx.scopes.length - 1].set(name, value);
|
|
509
|
+
};
|
|
510
|
+
var assign = (ctx, name, value) => {
|
|
511
|
+
for (let i = ctx.scopes.length - 1;i >= 0; i--) {
|
|
512
|
+
if (ctx.scopes[i].has(name)) {
|
|
513
|
+
ctx.scopes[i].set(name, value);
|
|
514
|
+
return;
|
|
754
515
|
}
|
|
755
|
-
return pending;
|
|
756
516
|
}
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
// lib/services/index.ts
|
|
760
|
-
var GLOBAL_KEY = Symbol.for("formkrafter.core.services");
|
|
761
|
-
var globalStore = globalThis;
|
|
762
|
-
var services = globalStore[GLOBAL_KEY] ??= {
|
|
763
|
-
jsRunnerService: new SandboxJsRunnerService,
|
|
764
|
-
dataSourceService: new FetchDataSourceService,
|
|
765
|
-
fileUploadService: new Base64FileUploadService,
|
|
766
|
-
specSourceService: new FetchSpecSourceService,
|
|
767
|
-
optionSourceService: new FetchOptionSourceService
|
|
517
|
+
throw new Error(`Cannot assign to unknown identifier "${name}"`);
|
|
768
518
|
};
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
return services.jsRunnerService.eval(code, { dataMap: data ?? {} });
|
|
776
|
-
} catch (error) {
|
|
777
|
-
return error instanceof Error ? error : new Error(String(error));
|
|
519
|
+
var guardProp = (prop) => {
|
|
520
|
+
if (typeof prop === "number")
|
|
521
|
+
return prop;
|
|
522
|
+
const name = String(prop);
|
|
523
|
+
if (FORBIDDEN_PROPS.has(name)) {
|
|
524
|
+
throw new Error(`Access to "${name}" is not allowed`);
|
|
778
525
|
}
|
|
526
|
+
return name;
|
|
779
527
|
};
|
|
780
|
-
var
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
528
|
+
var memberTarget = (ctx, node) => {
|
|
529
|
+
const object = evalNode(ctx, node.object);
|
|
530
|
+
const prop = node.computed ? guardProp(evalNode(ctx, node.property)) : guardProp(node.property.name);
|
|
531
|
+
return { object, prop, optional: node.optional === true };
|
|
532
|
+
};
|
|
533
|
+
var readMember = (ctx, node) => {
|
|
534
|
+
const { object, prop, optional } = memberTarget(ctx, node);
|
|
535
|
+
if (object === null || object === undefined) {
|
|
536
|
+
if (optional)
|
|
537
|
+
return;
|
|
538
|
+
throw new Error(`Cannot read "${String(prop)}" of ${String(object)}`);
|
|
790
539
|
}
|
|
791
|
-
return
|
|
540
|
+
return object[prop];
|
|
792
541
|
};
|
|
793
|
-
var
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
542
|
+
var evalNode = (ctx, node) => {
|
|
543
|
+
if (++ctx.steps > MAX_STEPS) {
|
|
544
|
+
throw new Error("Sandbox execution budget exceeded");
|
|
545
|
+
}
|
|
546
|
+
switch (node.type) {
|
|
547
|
+
case "Program":
|
|
548
|
+
case "BlockStatement": {
|
|
549
|
+
for (const statement of node.body) {
|
|
550
|
+
const result = evalNode(ctx, statement);
|
|
551
|
+
if (result instanceof ReturnSignal)
|
|
552
|
+
return result;
|
|
553
|
+
}
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
case "ReturnStatement":
|
|
557
|
+
return new ReturnSignal(node.argument ? evalNode(ctx, node.argument) : undefined);
|
|
558
|
+
case "IfStatement": {
|
|
559
|
+
if (evalNode(ctx, node.test)) {
|
|
560
|
+
return evalNode(ctx, node.consequent);
|
|
561
|
+
}
|
|
562
|
+
if (node.alternate)
|
|
563
|
+
return evalNode(ctx, node.alternate);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
case "VariableDeclaration": {
|
|
567
|
+
for (const declaration of node.declarations) {
|
|
568
|
+
const id = declaration.id;
|
|
569
|
+
if (id.type !== "Identifier") {
|
|
570
|
+
throw new Error("Only simple variable names are supported");
|
|
571
|
+
}
|
|
572
|
+
declareVar(ctx, id.name, declaration.init ? evalNode(ctx, declaration.init) : undefined);
|
|
573
|
+
}
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
case "ExpressionStatement":
|
|
577
|
+
evalNode(ctx, node.expression);
|
|
578
|
+
return;
|
|
579
|
+
case "Literal":
|
|
580
|
+
return node.value;
|
|
581
|
+
case "TemplateLiteral": {
|
|
582
|
+
const quasis = node.quasis;
|
|
583
|
+
const expressions = node.expressions;
|
|
584
|
+
let text = "";
|
|
585
|
+
quasis.forEach((quasi, index) => {
|
|
586
|
+
text += quasi.value.cooked ?? "";
|
|
587
|
+
if (index < expressions.length) {
|
|
588
|
+
text += String(evalNode(ctx, expressions[index]));
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
return text;
|
|
592
|
+
}
|
|
593
|
+
case "Identifier":
|
|
594
|
+
return lookup(ctx, node.name);
|
|
595
|
+
case "ThisExpression":
|
|
596
|
+
return;
|
|
597
|
+
case "ChainExpression":
|
|
598
|
+
return evalNode(ctx, node.expression);
|
|
599
|
+
case "MemberExpression":
|
|
600
|
+
return readMember(ctx, node);
|
|
601
|
+
case "CallExpression": {
|
|
602
|
+
const callee = node.callee;
|
|
603
|
+
const args = [];
|
|
604
|
+
for (const argument of node.arguments) {
|
|
605
|
+
if (argument.type === "SpreadElement") {
|
|
606
|
+
const spread = evalNode(ctx, argument.argument);
|
|
607
|
+
if (Array.isArray(spread))
|
|
608
|
+
args.push(...spread);
|
|
609
|
+
} else {
|
|
610
|
+
args.push(evalNode(ctx, argument));
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
if (callee.type === "MemberExpression") {
|
|
614
|
+
const { object, prop, optional } = memberTarget(ctx, callee);
|
|
615
|
+
if (object === null || object === undefined) {
|
|
616
|
+
if (optional || node.optional === true)
|
|
617
|
+
return;
|
|
618
|
+
throw new Error(`Cannot call "${String(prop)}" of ${String(object)}`);
|
|
619
|
+
}
|
|
620
|
+
const fn3 = object[prop];
|
|
621
|
+
if (typeof fn3 !== "function") {
|
|
622
|
+
if (node.optional === true && fn3 == null)
|
|
623
|
+
return;
|
|
624
|
+
throw new Error(`"${String(prop)}" is not a function`);
|
|
625
|
+
}
|
|
626
|
+
if (fn3 === Function || fn3 === eval) {
|
|
627
|
+
throw new Error("This function is not allowed");
|
|
628
|
+
}
|
|
629
|
+
return fn3.apply(object, args);
|
|
630
|
+
}
|
|
631
|
+
const fn2 = evalNode(ctx, callee);
|
|
632
|
+
if (typeof fn2 !== "function") {
|
|
633
|
+
if (node.optional === true && fn2 == null)
|
|
634
|
+
return;
|
|
635
|
+
throw new Error("Value is not a function");
|
|
636
|
+
}
|
|
637
|
+
if (fn2 === Function || fn2 === eval) {
|
|
638
|
+
throw new Error("This function is not allowed");
|
|
639
|
+
}
|
|
640
|
+
return fn2(...args);
|
|
641
|
+
}
|
|
642
|
+
case "ArrowFunctionExpression": {
|
|
643
|
+
const params = node.params;
|
|
644
|
+
const body = node.body;
|
|
645
|
+
const capturedScopes = [...ctx.scopes];
|
|
646
|
+
return (...args) => {
|
|
647
|
+
const callScope = new Map;
|
|
648
|
+
params.forEach((param, index) => {
|
|
649
|
+
if (param.type !== "Identifier") {
|
|
650
|
+
throw new Error("Only simple parameters are supported");
|
|
651
|
+
}
|
|
652
|
+
callScope.set(param.name, args[index]);
|
|
653
|
+
});
|
|
654
|
+
const callCtx = {
|
|
655
|
+
steps: ctx.steps,
|
|
656
|
+
scopes: [...capturedScopes, callScope]
|
|
657
|
+
};
|
|
658
|
+
const result = evalNode(callCtx, body);
|
|
659
|
+
ctx.steps = callCtx.steps;
|
|
660
|
+
if (result instanceof ReturnSignal)
|
|
661
|
+
return result.value;
|
|
662
|
+
return node.expression === true ? result : undefined;
|
|
809
663
|
};
|
|
810
664
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
665
|
+
case "BinaryExpression": {
|
|
666
|
+
const left = evalNode(ctx, node.left);
|
|
667
|
+
const right = evalNode(ctx, node.right);
|
|
668
|
+
switch (node.operator) {
|
|
669
|
+
case "===":
|
|
670
|
+
return left === right;
|
|
671
|
+
case "!==":
|
|
672
|
+
return left !== right;
|
|
673
|
+
case "==":
|
|
674
|
+
return left == right;
|
|
675
|
+
case "!=":
|
|
676
|
+
return left != right;
|
|
677
|
+
case "<":
|
|
678
|
+
return left < right;
|
|
679
|
+
case "<=":
|
|
680
|
+
return left <= right;
|
|
681
|
+
case ">":
|
|
682
|
+
return left > right;
|
|
683
|
+
case ">=":
|
|
684
|
+
return left >= right;
|
|
685
|
+
case "+":
|
|
686
|
+
return left + right;
|
|
687
|
+
case "-":
|
|
688
|
+
return left - right;
|
|
689
|
+
case "*":
|
|
690
|
+
return left * right;
|
|
691
|
+
case "/":
|
|
692
|
+
return left / right;
|
|
693
|
+
case "%":
|
|
694
|
+
return left % right;
|
|
695
|
+
case "**":
|
|
696
|
+
return left ** right;
|
|
697
|
+
default:
|
|
698
|
+
throw new Error(`Unsupported operator "${String(node.operator)}"`);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
case "LogicalExpression": {
|
|
702
|
+
const left = evalNode(ctx, node.left);
|
|
703
|
+
switch (node.operator) {
|
|
704
|
+
case "&&":
|
|
705
|
+
return left && evalNode(ctx, node.right);
|
|
706
|
+
case "||":
|
|
707
|
+
return left || evalNode(ctx, node.right);
|
|
708
|
+
case "??":
|
|
709
|
+
return left ?? evalNode(ctx, node.right);
|
|
710
|
+
default:
|
|
711
|
+
throw new Error(`Unsupported operator "${String(node.operator)}"`);
|
|
712
|
+
}
|
|
838
713
|
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
continue;
|
|
714
|
+
case "UnaryExpression": {
|
|
715
|
+
const argument = evalNode(ctx, node.argument);
|
|
716
|
+
switch (node.operator) {
|
|
717
|
+
case "!":
|
|
718
|
+
return !argument;
|
|
719
|
+
case "-":
|
|
720
|
+
return -argument;
|
|
721
|
+
case "+":
|
|
722
|
+
return +argument;
|
|
723
|
+
case "typeof":
|
|
724
|
+
return typeof argument;
|
|
725
|
+
default:
|
|
726
|
+
throw new Error(`Unsupported operator "${String(node.operator)}"`);
|
|
853
727
|
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
errors[key] = result;
|
|
862
|
-
else if (result instanceof Error)
|
|
863
|
-
errors[key] = result.message;
|
|
864
|
-
else {
|
|
865
|
-
const resolved = resolveLocalizedText(validation2.message, locale);
|
|
866
|
-
errors[key] = typeof resolved === "string" ? resolved : defaultValidationMessage("custom", locale);
|
|
728
|
+
}
|
|
729
|
+
case "ConditionalExpression":
|
|
730
|
+
return evalNode(ctx, node.test) ? evalNode(ctx, node.consequent) : evalNode(ctx, node.alternate);
|
|
731
|
+
case "AssignmentExpression": {
|
|
732
|
+
const target = node.left;
|
|
733
|
+
if (target.type !== "Identifier" || node.operator !== "=") {
|
|
734
|
+
throw new Error("Only simple assignments are supported");
|
|
867
735
|
}
|
|
736
|
+
const value = evalNode(ctx, node.right);
|
|
737
|
+
assign(ctx, target.name, value);
|
|
738
|
+
return value;
|
|
868
739
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
};
|
|
882
|
-
var hiddenKeys = (brickSpec, dataMap) => {
|
|
883
|
-
const hidden = new Set;
|
|
884
|
-
const walk = (brick, parentHidden) => {
|
|
885
|
-
const isHidden = parentHidden || (brick.rules?.length ? getAffectedProperties(brick.rules, dataMap).hidden === true : false);
|
|
886
|
-
const key = brick.configs?.key;
|
|
887
|
-
if (isHidden && key)
|
|
888
|
-
hidden.add(key);
|
|
889
|
-
if (brick.type === "collection")
|
|
890
|
-
return;
|
|
891
|
-
for (const child of brick.children ?? [])
|
|
892
|
-
walk(child, isHidden);
|
|
893
|
-
};
|
|
894
|
-
walk(brickSpec, false);
|
|
895
|
-
return hidden;
|
|
896
|
-
};
|
|
897
|
-
var validateBrickSpecDataDetailed = (brickSpec, data, locale) => {
|
|
898
|
-
const errors = {};
|
|
899
|
-
const validate = compiledValidator(brickSpec, locale);
|
|
900
|
-
if (validate && !validate(data)) {
|
|
901
|
-
for (const error of validate.errors ?? []) {
|
|
902
|
-
const key = keyOfError(error);
|
|
903
|
-
if (key && !(key in errors) && error.message) {
|
|
904
|
-
errors[key] = error.message;
|
|
740
|
+
case "ArrayExpression": {
|
|
741
|
+
const values = [];
|
|
742
|
+
for (const element of node.elements) {
|
|
743
|
+
if (!element)
|
|
744
|
+
continue;
|
|
745
|
+
if (element.type === "SpreadElement") {
|
|
746
|
+
const spread = evalNode(ctx, element.argument);
|
|
747
|
+
if (Array.isArray(spread))
|
|
748
|
+
values.push(...spread);
|
|
749
|
+
} else {
|
|
750
|
+
values.push(evalNode(ctx, element));
|
|
751
|
+
}
|
|
905
752
|
}
|
|
753
|
+
return values;
|
|
906
754
|
}
|
|
755
|
+
case "ObjectExpression": {
|
|
756
|
+
const result = {};
|
|
757
|
+
for (const property of node.properties) {
|
|
758
|
+
if (property.type === "SpreadElement") {
|
|
759
|
+
const spread = evalNode(ctx, property.argument);
|
|
760
|
+
if (spread && typeof spread === "object") {
|
|
761
|
+
for (const [key2, value] of Object.entries(spread)) {
|
|
762
|
+
result[String(guardProp(key2))] = value;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
const keyNode = property.key;
|
|
768
|
+
const key = property.computed ? guardProp(evalNode(ctx, keyNode)) : guardProp(keyNode.type === "Identifier" ? keyNode.name : keyNode.value);
|
|
769
|
+
result[String(key)] = evalNode(ctx, property.value);
|
|
770
|
+
}
|
|
771
|
+
return result;
|
|
772
|
+
}
|
|
773
|
+
default:
|
|
774
|
+
throw new Error(`Unsupported syntax: ${node.type}`);
|
|
907
775
|
}
|
|
908
|
-
for (const [key, message] of Object.entries(customErrors(brickSpec, data, locale))) {
|
|
909
|
-
if (!(key in errors))
|
|
910
|
-
errors[key] = message;
|
|
911
|
-
}
|
|
912
|
-
const dataMap = typeof data === "object" && data !== null ? data : undefined;
|
|
913
|
-
for (const key of hiddenKeys(brickSpec, dataMap)) {
|
|
914
|
-
delete errors[key];
|
|
915
|
-
}
|
|
916
|
-
return { valid: Object.keys(errors).length === 0, errors };
|
|
917
|
-
};
|
|
918
|
-
var validateBrickSpecData = (brickSpec, data, locale) => validateBrickSpecDataDetailed(brickSpec, data, locale).valid;
|
|
919
|
-
var stripEmpty = (data) => {
|
|
920
|
-
if (typeof data !== "object" || data === null)
|
|
921
|
-
return {};
|
|
922
|
-
return Object.fromEntries(Object.entries(data).filter(([, value]) => value !== "" && value !== null && value !== undefined));
|
|
923
776
|
};
|
|
924
|
-
var
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
777
|
+
var SAFE_BUILTINS = {
|
|
778
|
+
undefined: undefined,
|
|
779
|
+
NaN: NaN,
|
|
780
|
+
Infinity: Infinity,
|
|
781
|
+
Math,
|
|
782
|
+
JSON,
|
|
783
|
+
Number,
|
|
784
|
+
String,
|
|
785
|
+
Boolean,
|
|
786
|
+
Array,
|
|
787
|
+
Object,
|
|
788
|
+
Date,
|
|
789
|
+
parseInt,
|
|
790
|
+
parseFloat,
|
|
791
|
+
isNaN,
|
|
792
|
+
isFinite
|
|
938
793
|
};
|
|
939
|
-
|
|
940
|
-
const
|
|
941
|
-
|
|
942
|
-
|
|
794
|
+
function runSandboxed(code, globals = {}) {
|
|
795
|
+
const ast = parse(code, {
|
|
796
|
+
ecmaVersion: "latest",
|
|
797
|
+
allowReturnOutsideFunction: true
|
|
798
|
+
});
|
|
799
|
+
const ctx = {
|
|
800
|
+
steps: 0,
|
|
801
|
+
scopes: [
|
|
802
|
+
new Map(Object.entries({ ...SAFE_BUILTINS, ...globals })),
|
|
803
|
+
new Map
|
|
804
|
+
]
|
|
943
805
|
};
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
const key = brick.configs?.key;
|
|
948
|
-
if (!key)
|
|
949
|
-
continue;
|
|
950
|
-
const rows = present[key];
|
|
951
|
-
if (!Array.isArray(rows))
|
|
952
|
-
continue;
|
|
953
|
-
const rowSpec = rowSpecOf(brick);
|
|
954
|
-
rows.forEach((row, index) => {
|
|
955
|
-
const rowResult = validateFormData(rowSpec, row, locale);
|
|
956
|
-
for (const [field, message] of Object.entries(rowResult.errors)) {
|
|
957
|
-
const prefixed = `${key}[${index}].${field}`;
|
|
958
|
-
if (!(prefixed in errors))
|
|
959
|
-
errors[prefixed] = message;
|
|
960
|
-
}
|
|
961
|
-
});
|
|
962
|
-
}
|
|
963
|
-
return { valid: Object.keys(errors).length === 0, errors };
|
|
964
|
-
};
|
|
965
|
-
// lib/services/unsafe_js_runner_service.ts
|
|
966
|
-
import { parse as parse3 } from "acorn";
|
|
806
|
+
const result = evalNode(ctx, ast);
|
|
807
|
+
return result instanceof ReturnSignal ? result.value : undefined;
|
|
808
|
+
}
|
|
967
809
|
|
|
968
|
-
|
|
810
|
+
// lib/services/js_runner_service.ts
|
|
811
|
+
class SandboxJsRunnerService {
|
|
969
812
|
validateJs(js2, options = { ecmaVersion: "latest", allowReturnOutsideFunction: true }) {
|
|
970
813
|
try {
|
|
971
|
-
|
|
814
|
+
parse2(js2, options);
|
|
972
815
|
return { valid: true };
|
|
973
816
|
} catch (error) {
|
|
974
817
|
return {
|
|
@@ -977,301 +820,579 @@ class UnsafeEvalJsRunnerService {
|
|
|
977
820
|
};
|
|
978
821
|
}
|
|
979
822
|
}
|
|
980
|
-
eval(
|
|
981
|
-
|
|
982
|
-
`);
|
|
983
|
-
const fn = `(() => {${prelude}
|
|
984
|
-
${js}})();`;
|
|
985
|
-
const validation = this.validateJs(fn, { ecmaVersion: "latest" });
|
|
986
|
-
if (!validation.valid)
|
|
987
|
-
throw validation.error;
|
|
988
|
-
return eval(fn);
|
|
823
|
+
eval(js2, scope2 = {}) {
|
|
824
|
+
return runSandboxed(js2, scope2);
|
|
989
825
|
}
|
|
990
826
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
827
|
+
|
|
828
|
+
// lib/services/data_source_service.ts
|
|
829
|
+
class FetchDataSourceService {
|
|
830
|
+
defaults;
|
|
831
|
+
cache = new Map;
|
|
832
|
+
constructor(defaults = {}) {
|
|
833
|
+
this.defaults = defaults;
|
|
834
|
+
}
|
|
835
|
+
fetchOptions(url, options) {
|
|
836
|
+
const headers = { ...this.defaults.headers, ...options?.headers };
|
|
837
|
+
const init = {};
|
|
838
|
+
if (Object.keys(headers).length)
|
|
839
|
+
init.headers = headers;
|
|
840
|
+
if (this.defaults.credentials)
|
|
841
|
+
init.credentials = this.defaults.credentials;
|
|
842
|
+
const key = `${url}|${JSON.stringify(init)}`;
|
|
843
|
+
let pending = this.cache.get(key);
|
|
844
|
+
if (!pending) {
|
|
845
|
+
pending = fetch(url, Object.keys(init).length ? init : undefined).then((response) => {
|
|
846
|
+
if (!response.ok) {
|
|
847
|
+
throw new Error(`Data source request failed (${response.status}): ${url}`);
|
|
848
|
+
}
|
|
849
|
+
return response.json();
|
|
850
|
+
}).then((json) => Array.isArray(json) ? json : []).catch((error) => {
|
|
851
|
+
this.cache.delete(key);
|
|
852
|
+
throw error;
|
|
853
|
+
});
|
|
854
|
+
this.cache.set(key, pending);
|
|
855
|
+
}
|
|
856
|
+
return pending;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// lib/services/file_upload_service.ts
|
|
861
|
+
class Base64FileUploadService {
|
|
862
|
+
async upload(file) {
|
|
863
|
+
const encode = globalThis.btoa;
|
|
864
|
+
if (!encode)
|
|
865
|
+
throw new Error("No base64 encoder available");
|
|
866
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
867
|
+
let binary = "";
|
|
868
|
+
for (const byte of bytes)
|
|
869
|
+
binary += String.fromCharCode(byte);
|
|
870
|
+
return {
|
|
871
|
+
name: file.name,
|
|
872
|
+
type: file.type,
|
|
873
|
+
size: file.size,
|
|
874
|
+
url: `data:${file.type};base64,${encode(binary)}`
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
class UrlFileUploadService {
|
|
880
|
+
defaults;
|
|
881
|
+
constructor(defaults = {}) {
|
|
882
|
+
this.defaults = defaults;
|
|
883
|
+
}
|
|
884
|
+
get fetchImplementation() {
|
|
885
|
+
return this.defaults.fetch ?? globalThis.fetch.bind(globalThis);
|
|
886
|
+
}
|
|
887
|
+
resolveUrl(options) {
|
|
888
|
+
const url = options?.url ?? this.defaults.url;
|
|
889
|
+
if (!url) {
|
|
890
|
+
throw new Error("No upload URL configured — set it on the brick (uploadUrl) or on UrlFileUploadService");
|
|
891
|
+
}
|
|
892
|
+
return url;
|
|
893
|
+
}
|
|
894
|
+
mergedHeaders(options) {
|
|
895
|
+
return { ...this.defaults.headers, ...options?.headers };
|
|
896
|
+
}
|
|
897
|
+
async upload(file, options) {
|
|
898
|
+
const url = this.resolveUrl(options);
|
|
899
|
+
const blob = typeof Blob !== "undefined" && file instanceof Blob ? file : new Blob([await file.arrayBuffer()], { type: file.type });
|
|
900
|
+
const body = new FormData;
|
|
901
|
+
body.append(this.defaults.fieldName ?? "file", blob, file.name);
|
|
902
|
+
const response = await this.fetchImplementation(url, {
|
|
903
|
+
method: "POST",
|
|
904
|
+
body,
|
|
905
|
+
headers: this.mergedHeaders(options),
|
|
906
|
+
credentials: this.defaults.credentials
|
|
907
|
+
});
|
|
908
|
+
if (!response.ok) {
|
|
909
|
+
throw new Error(`Upload failed (${response.status})`);
|
|
910
|
+
}
|
|
911
|
+
const payload = await response.json().catch(() => ({}));
|
|
912
|
+
const uploadedUrl = payload.url ?? payload.path ?? payload.location ?? payload.href;
|
|
913
|
+
return {
|
|
914
|
+
name: file.name,
|
|
915
|
+
type: file.type,
|
|
916
|
+
size: file.size,
|
|
917
|
+
url: typeof uploadedUrl === "string" ? uploadedUrl : ""
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
async remove(file, options) {
|
|
921
|
+
if (!file.url)
|
|
922
|
+
return;
|
|
923
|
+
const target = /^https?:\/\//.test(file.url) ? file.url : this.resolveUrl(options);
|
|
924
|
+
const response = await this.fetchImplementation(target, {
|
|
925
|
+
method: "DELETE",
|
|
926
|
+
headers: {
|
|
927
|
+
"content-type": "application/json",
|
|
928
|
+
...this.mergedHeaders(options)
|
|
929
|
+
},
|
|
930
|
+
credentials: this.defaults.credentials,
|
|
931
|
+
body: JSON.stringify({ url: file.url, name: file.name })
|
|
932
|
+
});
|
|
933
|
+
if (!response.ok) {
|
|
934
|
+
throw new Error(`Delete failed (${response.status})`);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// lib/services/spec_source_service.ts
|
|
940
|
+
class FetchSpecSourceService {
|
|
941
|
+
defaults;
|
|
942
|
+
cache = new Map;
|
|
943
|
+
constructor(defaults = {}) {
|
|
944
|
+
this.defaults = defaults;
|
|
945
|
+
}
|
|
946
|
+
resolveUrl(ref) {
|
|
947
|
+
if (/^https?:\/\//.test(ref) || !this.defaults.baseUrl)
|
|
948
|
+
return ref;
|
|
949
|
+
return `${this.defaults.baseUrl.replace(/\/$/, "")}/${ref.replace(/^\//, "")}`;
|
|
950
|
+
}
|
|
951
|
+
fetchSpec(ref) {
|
|
952
|
+
const url = this.resolveUrl(ref);
|
|
953
|
+
let pending = this.cache.get(url);
|
|
954
|
+
if (!pending) {
|
|
955
|
+
const fetchImplementation = this.defaults.fetch ?? globalThis.fetch.bind(globalThis);
|
|
956
|
+
pending = fetchImplementation(url, {
|
|
957
|
+
headers: this.defaults.headers,
|
|
958
|
+
credentials: this.defaults.credentials
|
|
959
|
+
}).then(async (response) => {
|
|
960
|
+
if (!response.ok) {
|
|
961
|
+
throw new Error(`Failed to load spec "${ref}" (${response.status})`);
|
|
962
|
+
}
|
|
963
|
+
return await response.json();
|
|
964
|
+
});
|
|
965
|
+
pending.catch(() => this.cache.delete(url));
|
|
966
|
+
this.cache.set(url, pending);
|
|
967
|
+
}
|
|
968
|
+
return pending;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// lib/services/option_source_service.ts
|
|
973
|
+
class FetchOptionSourceService {
|
|
974
|
+
defaults;
|
|
975
|
+
cache = new Map;
|
|
976
|
+
constructor(defaults = {}) {
|
|
977
|
+
this.defaults = defaults;
|
|
978
|
+
}
|
|
979
|
+
resolveUrl(ref) {
|
|
980
|
+
if (/^https?:\/\//.test(ref) || !this.defaults.baseUrl)
|
|
981
|
+
return ref;
|
|
982
|
+
return `${this.defaults.baseUrl.replace(/\/$/, "")}/${ref.replace(/^\//, "")}`;
|
|
983
|
+
}
|
|
984
|
+
fetchOptions(ref) {
|
|
985
|
+
const url = this.resolveUrl(ref);
|
|
986
|
+
let pending = this.cache.get(url);
|
|
987
|
+
if (!pending) {
|
|
988
|
+
const fetchImplementation = this.defaults.fetch ?? globalThis.fetch.bind(globalThis);
|
|
989
|
+
pending = fetchImplementation(url, {
|
|
990
|
+
headers: this.defaults.headers,
|
|
991
|
+
credentials: this.defaults.credentials
|
|
992
|
+
}).then(async (response) => {
|
|
993
|
+
if (!response.ok) {
|
|
994
|
+
throw new Error(`Failed to load options "${ref}" (${response.status})`);
|
|
995
|
+
}
|
|
996
|
+
return await response.json();
|
|
997
|
+
});
|
|
998
|
+
pending.catch(() => this.cache.delete(url));
|
|
999
|
+
this.cache.set(url, pending);
|
|
1000
|
+
}
|
|
1001
|
+
return pending;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// lib/services/index.ts
|
|
1006
|
+
var GLOBAL_KEY2 = Symbol.for("formkrafter.core.services");
|
|
1007
|
+
var globalStore2 = globalThis;
|
|
1008
|
+
var services = globalStore2[GLOBAL_KEY2] ??= {
|
|
1009
|
+
jsRunnerService: new SandboxJsRunnerService,
|
|
1010
|
+
dataSourceService: new FetchDataSourceService,
|
|
1011
|
+
fileUploadService: new Base64FileUploadService,
|
|
1012
|
+
specSourceService: new FetchSpecSourceService,
|
|
1013
|
+
optionSourceService: new FetchOptionSourceService
|
|
1125
1014
|
};
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
"
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
"address.zip": "Code postal",
|
|
1197
|
-
"address.country": "Pays",
|
|
1198
|
-
"grid.addRow": "+ Ajouter une ligne",
|
|
1199
|
-
"grid.moveUp": "Monter la ligne",
|
|
1200
|
-
"grid.moveDown": "Descendre la ligne",
|
|
1201
|
-
"grid.removeRow": "Supprimer la ligne",
|
|
1202
|
-
"grid.empty": "Aucune ligne",
|
|
1203
|
-
"grid.columns": "Modèle de ligne",
|
|
1204
|
-
"select.search": "Rechercher…",
|
|
1205
|
-
"select.empty": "Aucune option",
|
|
1206
|
-
"select.clear": "Effacer",
|
|
1207
|
-
"rules.when": "Quand",
|
|
1208
|
-
"rules.then": "Alors",
|
|
1209
|
-
"rules.addRule": "+ Ajouter une règle",
|
|
1210
|
-
"rules.addEffect": "+ Effet",
|
|
1211
|
-
"rules.removeRule": "Supprimer la règle",
|
|
1212
|
-
"rules.removeEffect": "Supprimer l'effet",
|
|
1213
|
-
"rules.mode.simple": "simple",
|
|
1214
|
-
"rules.mode.json": "json",
|
|
1215
|
-
"rules.mode.js": "JS",
|
|
1216
|
-
"category.Inputs": "Champs",
|
|
1217
|
-
"category.Layout": "Mise en page",
|
|
1218
|
-
"category.Data": "Données",
|
|
1219
|
-
"brick.text.name": "Texte",
|
|
1220
|
-
"brick.email.name": "Email",
|
|
1221
|
-
"brick.password.name": "Mot de passe",
|
|
1222
|
-
"brick.url.name": "URL",
|
|
1223
|
-
"brick.phone.name": "Téléphone",
|
|
1224
|
-
"brick.textarea.name": "Zone de texte",
|
|
1225
|
-
"brick.number.name": "Nombre",
|
|
1226
|
-
"brick.date.name": "Date",
|
|
1227
|
-
"brick.time.name": "Heure",
|
|
1228
|
-
"brick.datetime.name": "Date / Heure",
|
|
1229
|
-
"brick.select.name": "Liste déroulante",
|
|
1230
|
-
"brick.multi-select.name": "Sélection multiple",
|
|
1231
|
-
"brick.radio.name": "Boutons radio",
|
|
1232
|
-
"brick.select-boxes.name": "Cases à cocher",
|
|
1233
|
-
"brick.checkbox.name": "Case à cocher",
|
|
1234
|
-
"brick.tags.name": "Étiquettes",
|
|
1235
|
-
"brick.signature.name": "Signature",
|
|
1236
|
-
"brick.data-grid.name": "Grille de données",
|
|
1237
|
-
"brick.address.name": "Adresse",
|
|
1238
|
-
"brick.file.name": "Fichier",
|
|
1239
|
-
"brick.hidden.name": "Caché",
|
|
1240
|
-
"brick.content.name": "Contenu",
|
|
1241
|
-
"brick.recap.name": "Récapitulatif",
|
|
1242
|
-
"brick.nested-form.name": "Formulaire imbriqué",
|
|
1243
|
-
"brick.code.name": "Code",
|
|
1244
|
-
"nestedForm.ref": "Formulaire référencé",
|
|
1245
|
-
"nestedForm.missing": "Renseignez la config specRef pour référencer un formulaire.",
|
|
1246
|
-
"nestedForm.loading": "Chargement des formulaires imbriqués…",
|
|
1247
|
-
"panel.specRef": "Formulaire référencé (specRef)",
|
|
1248
|
-
"panel.optionsRef": "Catalogue d'options (optionsRef)",
|
|
1249
|
-
"recap.empty": "Rien à récapituler pour le moment.",
|
|
1250
|
-
"recap.yes": "Oui",
|
|
1251
|
-
"recap.no": "Non",
|
|
1252
|
-
"panel.cfg.showEmpty": "Afficher les champs vides",
|
|
1253
|
-
"panel.cfg.groupBySections": "Regrouper par section",
|
|
1254
|
-
"brick.group.name": "Groupe",
|
|
1255
|
-
"brick.row.name": "Ligne",
|
|
1256
|
-
"brick.column.name": "Colonne",
|
|
1257
|
-
"brick.stepper.name": "Étapes",
|
|
1258
|
-
"brick.tabs.name": "Onglets"
|
|
1015
|
+
services.specSourceService ??= new FetchSpecSourceService;
|
|
1016
|
+
services.optionSourceService ??= new FetchOptionSourceService;
|
|
1017
|
+
|
|
1018
|
+
// lib/brick/utils.ts
|
|
1019
|
+
var evalBrickCode = (code, data) => {
|
|
1020
|
+
try {
|
|
1021
|
+
return services.jsRunnerService.eval(code, { dataMap: data ?? {} });
|
|
1022
|
+
} catch (error) {
|
|
1023
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
var evalRule = (rule, dataMap) => {
|
|
1027
|
+
if (rule.type === "jsonLogic") {
|
|
1028
|
+
if (rule.logic == null)
|
|
1029
|
+
return false;
|
|
1030
|
+
return jsonLogic.apply(rule.logic, dataMap) === true;
|
|
1031
|
+
}
|
|
1032
|
+
if (rule.type === "javaScript") {
|
|
1033
|
+
if (!rule.code)
|
|
1034
|
+
return false;
|
|
1035
|
+
return evalBrickCode(rule.code, dataMap) === true;
|
|
1036
|
+
}
|
|
1037
|
+
return false;
|
|
1038
|
+
};
|
|
1039
|
+
var getActiveRules = (rules, dataMap) => rules?.filter((rule) => evalRule(rule, dataMap));
|
|
1040
|
+
var getActiveEffect = (rules, dataMap) => getActiveRules(rules, dataMap)?.map((rule) => rule.effects).flat().filter((effect) => effect != null);
|
|
1041
|
+
var getAffectedProperties = (rules, dataMap) => {
|
|
1042
|
+
return getActiveEffect(rules, dataMap)?.map((effect) => {
|
|
1043
|
+
let value;
|
|
1044
|
+
if (effect.property) {
|
|
1045
|
+
if (effect.property.type === "boolean")
|
|
1046
|
+
value = effect.boolean;
|
|
1047
|
+
else if (effect.property.type === "logic" && effect.logic != null)
|
|
1048
|
+
value = jsonLogic.apply(effect.logic, dataMap);
|
|
1049
|
+
else if (effect.property.type === "code" && effect.code)
|
|
1050
|
+
value = evalBrickCode(effect.code, dataMap);
|
|
1051
|
+
else if (effect.property.type === "string")
|
|
1052
|
+
value = effect.string;
|
|
1053
|
+
return {
|
|
1054
|
+
[effect.property.target]: value
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
return {};
|
|
1058
|
+
}).flat().reduce((acc, item) => ({ ...acc, ...item }), {}) ?? { hidden: false };
|
|
1059
|
+
};
|
|
1060
|
+
|
|
1061
|
+
// lib/utils/recap.ts
|
|
1062
|
+
var formatRecapValue = (brick, value) => {
|
|
1063
|
+
if (Array.isArray(value)) {
|
|
1064
|
+
return value.map((item) => formatRecapValue(brick, item)).filter(Boolean).join(", ");
|
|
1065
|
+
}
|
|
1066
|
+
if (value === true)
|
|
1067
|
+
return fkTOr("recap.yes", "Yes");
|
|
1068
|
+
if (value === false)
|
|
1069
|
+
return fkTOr("recap.no", "No");
|
|
1070
|
+
if (value && typeof value === "object") {
|
|
1071
|
+
const record = value;
|
|
1072
|
+
if (typeof record.name === "string" && "url" in record)
|
|
1073
|
+
return record.name;
|
|
1074
|
+
return Object.values(record).filter((part) => typeof part === "string" && part).join(" ");
|
|
1075
|
+
}
|
|
1076
|
+
const raw = String(value ?? "");
|
|
1077
|
+
if (brick.configs?.options != null) {
|
|
1078
|
+
const labelKey = typeof brick.configs.labelKey === "string" ? brick.configs.labelKey : "label";
|
|
1079
|
+
const valueKey = typeof brick.configs.valueKey === "string" ? brick.configs.valueKey : "value";
|
|
1080
|
+
const match = normalizeOptions(brick.configs.options, labelKey, valueKey).find((option) => option.value === raw);
|
|
1081
|
+
if (match)
|
|
1082
|
+
return match.label;
|
|
1083
|
+
}
|
|
1084
|
+
return raw;
|
|
1259
1085
|
};
|
|
1260
|
-
var
|
|
1261
|
-
var
|
|
1262
|
-
var
|
|
1263
|
-
|
|
1264
|
-
|
|
1086
|
+
var isEmptyRecapValue = (raw) => raw === undefined || raw === null || raw === "" || Array.isArray(raw) && raw.length === 0;
|
|
1087
|
+
var brickLabel = (brick, locale) => String(resolveLocalizedText(brick.configs?.label, locale) ?? brick.name ?? brick.configs?.key ?? "");
|
|
1088
|
+
var collectionInputs = (spec) => {
|
|
1089
|
+
const inputs = [];
|
|
1090
|
+
for (const child of spec.children ?? []) {
|
|
1091
|
+
const key = child.configs?.key;
|
|
1092
|
+
if (child.type === "input" && key && !key.startsWith("_")) {
|
|
1093
|
+
inputs.push(child);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
return inputs;
|
|
1097
|
+
};
|
|
1098
|
+
var collectRecapItems = (spec, dataMap, locale, showEmpty, groupBySections = false) => {
|
|
1099
|
+
if (!spec)
|
|
1100
|
+
return [];
|
|
1101
|
+
const items = [];
|
|
1102
|
+
for (const child of spec.children ?? []) {
|
|
1103
|
+
if (child.id === "recap" || child.type === "action")
|
|
1104
|
+
continue;
|
|
1105
|
+
if (getAffectedProperties(child.rules, dataMap).hidden === true)
|
|
1106
|
+
continue;
|
|
1107
|
+
const key = child.configs?.key;
|
|
1108
|
+
const label = brickLabel(child, locale);
|
|
1109
|
+
if (child.type === "collection") {
|
|
1110
|
+
const rows = key && Array.isArray(dataMap?.[key]) ? dataMap[key] : [];
|
|
1111
|
+
if (!rows.length && !showEmpty)
|
|
1112
|
+
continue;
|
|
1113
|
+
const inputs = collectionInputs(child);
|
|
1114
|
+
items.push({
|
|
1115
|
+
kind: "collection",
|
|
1116
|
+
label,
|
|
1117
|
+
columns: inputs.map((input) => brickLabel(input, locale)),
|
|
1118
|
+
rows: rows.map((row) => inputs.map((input) => {
|
|
1119
|
+
const raw2 = row?.[input.configs.key];
|
|
1120
|
+
return isEmptyRecapValue(raw2) ? "—" : formatRecapValue(input, raw2);
|
|
1121
|
+
}))
|
|
1122
|
+
});
|
|
1123
|
+
continue;
|
|
1124
|
+
}
|
|
1125
|
+
if (child.type === "panel") {
|
|
1126
|
+
const sectionLabel = resolveLocalizedText(child.configs?.label, locale);
|
|
1127
|
+
if (groupBySections && typeof sectionLabel === "string" && sectionLabel.trim()) {
|
|
1128
|
+
const sectionItems = collectRecapItems(child, dataMap, locale, showEmpty);
|
|
1129
|
+
if (sectionItems.length) {
|
|
1130
|
+
items.push({ kind: "section", label: sectionLabel });
|
|
1131
|
+
items.push(...sectionItems);
|
|
1132
|
+
}
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
1135
|
+
items.push(...collectRecapItems(child, dataMap, locale, showEmpty, groupBySections));
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
if (child.type !== "input" || !key || key.startsWith("_"))
|
|
1139
|
+
continue;
|
|
1140
|
+
const raw = dataMap?.[key];
|
|
1141
|
+
if (isEmptyRecapValue(raw) && !showEmpty)
|
|
1142
|
+
continue;
|
|
1143
|
+
items.push({
|
|
1144
|
+
kind: "field",
|
|
1145
|
+
label,
|
|
1146
|
+
value: isEmptyRecapValue(raw) ? "—" : formatRecapValue(child, raw)
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
return items;
|
|
1150
|
+
};
|
|
1151
|
+
// lib/utils/remote.ts
|
|
1152
|
+
function interpolateTemplate(template, dataMap) {
|
|
1153
|
+
return template.replace(/\{(\w+)\}/g, (_, token) => {
|
|
1154
|
+
const value = dataMap?.[token];
|
|
1155
|
+
return value === undefined || value === null ? "" : String(value);
|
|
1156
|
+
});
|
|
1265
1157
|
}
|
|
1266
|
-
function
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1158
|
+
function parseHeaderLines(raw, dataMap) {
|
|
1159
|
+
if (typeof raw !== "string" || !raw.trim())
|
|
1160
|
+
return;
|
|
1161
|
+
const headers = {};
|
|
1162
|
+
for (const line of raw.split(`
|
|
1163
|
+
`)) {
|
|
1164
|
+
const separator = line.indexOf(":");
|
|
1165
|
+
if (separator <= 0)
|
|
1166
|
+
continue;
|
|
1167
|
+
const name = line.slice(0, separator).trim();
|
|
1168
|
+
const value = interpolateTemplate(line.slice(separator + 1).trim(), dataMap);
|
|
1169
|
+
if (name)
|
|
1170
|
+
headers[name] = value;
|
|
1270
1171
|
}
|
|
1271
|
-
return
|
|
1172
|
+
return Object.keys(headers).length ? headers : undefined;
|
|
1272
1173
|
}
|
|
1273
|
-
function
|
|
1274
|
-
|
|
1174
|
+
function appendSearchParam(url, param, term) {
|
|
1175
|
+
if (!term)
|
|
1176
|
+
return url;
|
|
1177
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
1178
|
+
return `${url}${separator}${encodeURIComponent(param)}=${encodeURIComponent(term)}`;
|
|
1179
|
+
}
|
|
1180
|
+
// lib/brick/brick.ts
|
|
1181
|
+
class Brick {
|
|
1182
|
+
type;
|
|
1183
|
+
dataType;
|
|
1184
|
+
id;
|
|
1185
|
+
name;
|
|
1186
|
+
configsForm;
|
|
1187
|
+
category;
|
|
1188
|
+
styles;
|
|
1189
|
+
unWrapData;
|
|
1190
|
+
isPrivate;
|
|
1191
|
+
validators;
|
|
1192
|
+
defaultConfigs;
|
|
1193
|
+
constructor(params) {
|
|
1194
|
+
this.type = params.type;
|
|
1195
|
+
this.dataType = params.dataType;
|
|
1196
|
+
this.id = params.id;
|
|
1197
|
+
this.name = params.name;
|
|
1198
|
+
this.configsForm = params.configsForm;
|
|
1199
|
+
this.category = params.category;
|
|
1200
|
+
this.styles = params.styles;
|
|
1201
|
+
this.unWrapData = params.unWrapData ?? false;
|
|
1202
|
+
this.isPrivate = params.isPrivate ?? false;
|
|
1203
|
+
this.validators = params.validators ?? [];
|
|
1204
|
+
this.defaultConfigs = params.defaultConfigs;
|
|
1205
|
+
}
|
|
1206
|
+
getBrickEmptySpec() {
|
|
1207
|
+
return {
|
|
1208
|
+
type: this.type,
|
|
1209
|
+
dataType: this.dataType,
|
|
1210
|
+
id: this.id,
|
|
1211
|
+
name: this.name,
|
|
1212
|
+
...this.category !== undefined ? { category: this.category } : {},
|
|
1213
|
+
...this.configsForm !== undefined ? { configsForm: this.configsForm } : {},
|
|
1214
|
+
...this.styles !== undefined ? { styles: this.styles } : {},
|
|
1215
|
+
...this.defaultConfigs !== undefined ? { configs: { ...this.defaultConfigs } } : {},
|
|
1216
|
+
validators: this.validators
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
// lib/validators/validator.ts
|
|
1221
|
+
import { Ajv } from "ajv";
|
|
1222
|
+
import ajvErrors from "ajv-errors";
|
|
1223
|
+
import addFormats from "ajv-formats";
|
|
1224
|
+
var ajv = new Ajv({ allErrors: true });
|
|
1225
|
+
addFormats(ajv, ["email", "uri"]);
|
|
1226
|
+
ajvErrors(ajv);
|
|
1227
|
+
var validatorCache = new WeakMap;
|
|
1228
|
+
var compiledValidator = (brickSpec, locale) => {
|
|
1229
|
+
let byLocale = validatorCache.get(brickSpec);
|
|
1230
|
+
if (!byLocale) {
|
|
1231
|
+
byLocale = new Map;
|
|
1232
|
+
validatorCache.set(brickSpec, byLocale);
|
|
1233
|
+
}
|
|
1234
|
+
const cacheKey = locale ?? "";
|
|
1235
|
+
let validate = byLocale.get(cacheKey);
|
|
1236
|
+
if (!validate) {
|
|
1237
|
+
const schema = buildValidationSchema(brickSpec, locale);
|
|
1238
|
+
if (!schema)
|
|
1239
|
+
return;
|
|
1240
|
+
try {
|
|
1241
|
+
validate = ajv.compile(schema);
|
|
1242
|
+
} catch {
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
byLocale.set(cacheKey, validate);
|
|
1246
|
+
}
|
|
1247
|
+
return validate;
|
|
1248
|
+
};
|
|
1249
|
+
var customErrors = (brickSpec, data, locale) => {
|
|
1250
|
+
const errors = {};
|
|
1251
|
+
const dataMap = data ?? {};
|
|
1252
|
+
for (const brick of iterateSchemaBricks(brickSpec)) {
|
|
1253
|
+
const key = brick.configs?.key;
|
|
1254
|
+
if (!key)
|
|
1255
|
+
continue;
|
|
1256
|
+
for (const validation2 of brick.validations ?? []) {
|
|
1257
|
+
if (validation2.validator !== "custom" || !validation2.customValidator) {
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
const result = evalBrickCode(`const value = dataMap?.[${JSON.stringify(key)}];
|
|
1261
|
+
${validation2.customValidator}`, dataMap);
|
|
1262
|
+
if (result === true || result === undefined || result === null)
|
|
1263
|
+
continue;
|
|
1264
|
+
if (key in errors)
|
|
1265
|
+
continue;
|
|
1266
|
+
if (typeof result === "string")
|
|
1267
|
+
errors[key] = result;
|
|
1268
|
+
else if (result instanceof Error)
|
|
1269
|
+
errors[key] = result.message;
|
|
1270
|
+
else {
|
|
1271
|
+
const resolved = resolveLocalizedText(validation2.message, locale);
|
|
1272
|
+
errors[key] = typeof resolved === "string" ? resolved : defaultValidationMessage("custom", locale);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
return errors;
|
|
1277
|
+
};
|
|
1278
|
+
var keyOfError = (error) => {
|
|
1279
|
+
if (error.keyword === "required") {
|
|
1280
|
+
return error.params.missingProperty;
|
|
1281
|
+
}
|
|
1282
|
+
if (error.keyword === "errorMessage") {
|
|
1283
|
+
const inner = error.params.errors?.[0];
|
|
1284
|
+
return inner ? keyOfError(inner) : undefined;
|
|
1285
|
+
}
|
|
1286
|
+
return error.instancePath.split("/").filter(Boolean)[0];
|
|
1287
|
+
};
|
|
1288
|
+
var hiddenKeys = (brickSpec, dataMap) => {
|
|
1289
|
+
const hidden = new Set;
|
|
1290
|
+
const walk = (brick, parentHidden) => {
|
|
1291
|
+
const isHidden = parentHidden || (brick.rules?.length ? getAffectedProperties(brick.rules, dataMap).hidden === true : false);
|
|
1292
|
+
const key = brick.configs?.key;
|
|
1293
|
+
if (isHidden && key)
|
|
1294
|
+
hidden.add(key);
|
|
1295
|
+
if (brick.type === "collection")
|
|
1296
|
+
return;
|
|
1297
|
+
for (const child of brick.children ?? [])
|
|
1298
|
+
walk(child, isHidden);
|
|
1299
|
+
};
|
|
1300
|
+
walk(brickSpec, false);
|
|
1301
|
+
return hidden;
|
|
1302
|
+
};
|
|
1303
|
+
var validateBrickSpecDataDetailed = (brickSpec, data, locale) => {
|
|
1304
|
+
const errors = {};
|
|
1305
|
+
const validate = compiledValidator(brickSpec, locale);
|
|
1306
|
+
if (validate && !validate(data)) {
|
|
1307
|
+
for (const error of validate.errors ?? []) {
|
|
1308
|
+
const key = keyOfError(error);
|
|
1309
|
+
if (key && !(key in errors) && error.message) {
|
|
1310
|
+
errors[key] = error.message;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
for (const [key, message] of Object.entries(customErrors(brickSpec, data, locale))) {
|
|
1315
|
+
if (!(key in errors))
|
|
1316
|
+
errors[key] = message;
|
|
1317
|
+
}
|
|
1318
|
+
const dataMap = typeof data === "object" && data !== null ? data : undefined;
|
|
1319
|
+
for (const key of hiddenKeys(brickSpec, dataMap)) {
|
|
1320
|
+
delete errors[key];
|
|
1321
|
+
}
|
|
1322
|
+
return { valid: Object.keys(errors).length === 0, errors };
|
|
1323
|
+
};
|
|
1324
|
+
var validateBrickSpecData = (brickSpec, data, locale) => validateBrickSpecDataDetailed(brickSpec, data, locale).valid;
|
|
1325
|
+
var stripEmpty = (data) => {
|
|
1326
|
+
if (typeof data !== "object" || data === null)
|
|
1327
|
+
return {};
|
|
1328
|
+
return Object.fromEntries(Object.entries(data).filter(([, value]) => value !== "" && value !== null && value !== undefined));
|
|
1329
|
+
};
|
|
1330
|
+
var rowSpecCache = new WeakMap;
|
|
1331
|
+
var rowSpecOf = (collection) => {
|
|
1332
|
+
let rowSpec = rowSpecCache.get(collection);
|
|
1333
|
+
if (!rowSpec) {
|
|
1334
|
+
rowSpec = {
|
|
1335
|
+
type: "panel",
|
|
1336
|
+
id: "collection-row",
|
|
1337
|
+
name: "Row",
|
|
1338
|
+
configs: { key: `${collection.configs?.key ?? "collection"}_row` },
|
|
1339
|
+
children: collection.children ?? []
|
|
1340
|
+
};
|
|
1341
|
+
rowSpecCache.set(collection, rowSpec);
|
|
1342
|
+
}
|
|
1343
|
+
return rowSpec;
|
|
1344
|
+
};
|
|
1345
|
+
var validateFormData = (brickSpec, data, locale) => {
|
|
1346
|
+
const present = stripEmpty(data);
|
|
1347
|
+
const errors = {
|
|
1348
|
+
...validateBrickSpecDataDetailed(brickSpec, present, locale).errors
|
|
1349
|
+
};
|
|
1350
|
+
for (const brick of iterateSchemaBricks(brickSpec)) {
|
|
1351
|
+
if (brick.type !== "collection")
|
|
1352
|
+
continue;
|
|
1353
|
+
const key = brick.configs?.key;
|
|
1354
|
+
if (!key)
|
|
1355
|
+
continue;
|
|
1356
|
+
const rows = present[key];
|
|
1357
|
+
if (!Array.isArray(rows))
|
|
1358
|
+
continue;
|
|
1359
|
+
const rowSpec = rowSpecOf(brick);
|
|
1360
|
+
rows.forEach((row, index) => {
|
|
1361
|
+
const rowResult = validateFormData(rowSpec, row, locale);
|
|
1362
|
+
for (const [field, message] of Object.entries(rowResult.errors)) {
|
|
1363
|
+
const prefixed = `${key}[${index}].${field}`;
|
|
1364
|
+
if (!(prefixed in errors))
|
|
1365
|
+
errors[prefixed] = message;
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
return { valid: Object.keys(errors).length === 0, errors };
|
|
1370
|
+
};
|
|
1371
|
+
// lib/services/unsafe_js_runner_service.ts
|
|
1372
|
+
import { parse as parse3 } from "acorn";
|
|
1373
|
+
|
|
1374
|
+
class UnsafeEvalJsRunnerService {
|
|
1375
|
+
validateJs(js2, options = { ecmaVersion: "latest", allowReturnOutsideFunction: true }) {
|
|
1376
|
+
try {
|
|
1377
|
+
parse3(js2, options);
|
|
1378
|
+
return { valid: true };
|
|
1379
|
+
} catch (error) {
|
|
1380
|
+
return {
|
|
1381
|
+
valid: false,
|
|
1382
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
eval(js, scope = {}) {
|
|
1387
|
+
const prelude = Object.entries(scope).map(([name, value]) => `const ${name} = ${JSON.stringify(value)};`).join(`
|
|
1388
|
+
`);
|
|
1389
|
+
const fn = `(() => {${prelude}
|
|
1390
|
+
${js}})();`;
|
|
1391
|
+
const validation = this.validateJs(fn, { ecmaVersion: "latest" });
|
|
1392
|
+
if (!validation.valid)
|
|
1393
|
+
throw validation.error;
|
|
1394
|
+
return eval(fn);
|
|
1395
|
+
}
|
|
1275
1396
|
}
|
|
1276
1397
|
// lib/ops/pointer.ts
|
|
1277
1398
|
function pointerFromPath(path) {
|
|
@@ -2025,11 +2146,13 @@ export {
|
|
|
2025
2146
|
registerValidationMessages,
|
|
2026
2147
|
pointerOfUid,
|
|
2027
2148
|
pointerFromPath,
|
|
2149
|
+
parseHeaderLines,
|
|
2028
2150
|
normalizeOptions,
|
|
2029
2151
|
moveBrick,
|
|
2030
2152
|
iterateSchemaBricks,
|
|
2031
2153
|
iterateBricks,
|
|
2032
2154
|
isLocalizedObject,
|
|
2155
|
+
interpolateTemplate,
|
|
2033
2156
|
hasNestedForms,
|
|
2034
2157
|
getBrickData,
|
|
2035
2158
|
getBrickAt,
|
|
@@ -2042,7 +2165,9 @@ export {
|
|
|
2042
2165
|
duplicateBrick,
|
|
2043
2166
|
defaultValidationMessage,
|
|
2044
2167
|
convertFormioForm,
|
|
2168
|
+
collectRecapItems,
|
|
2045
2169
|
buildValidationSchema,
|
|
2170
|
+
appendSearchParam,
|
|
2046
2171
|
addBrick,
|
|
2047
2172
|
UrlFileUploadService,
|
|
2048
2173
|
UnsafeEvalJsRunnerService,
|
|
@@ -2055,5 +2180,5 @@ export {
|
|
|
2055
2180
|
Base64FileUploadService
|
|
2056
2181
|
};
|
|
2057
2182
|
|
|
2058
|
-
//# debugId=
|
|
2183
|
+
//# debugId=99487FB3CC6D18E564756E2164756E21
|
|
2059
2184
|
//# sourceMappingURL=index.js.map
|