apostrophe 3.58.0 → 3.59.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/CHANGELOG.md +48 -0
- package/DEVELOPMENT.md +0 -12
- package/modules/@apostrophecms/area/index.js +7 -0
- package/modules/@apostrophecms/asset/index.js +0 -7
- package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.config.js +4 -8
- package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.vue.js +1 -1
- package/modules/@apostrophecms/asset/lib/webpack/src/webpack.config.js +1 -7
- package/modules/@apostrophecms/doc-type/index.js +3 -1
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +11 -4
- package/modules/@apostrophecms/express/index.js +28 -0
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocErrorsMixin.js +5 -4
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +11 -8
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposModalTabsMixin.js +4 -3
- package/modules/@apostrophecms/module/index.js +6 -0
- package/modules/@apostrophecms/piece-page-type/index.js +9 -3
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposImageControlDialog.vue +10 -3
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapAnchor.vue +6 -1
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapLink.vue +12 -3
- package/modules/@apostrophecms/schema/index.js +105 -51
- package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +3 -3
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +10 -6
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputObject.vue +5 -3
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRadio.vue +16 -11
- package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +2 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposSubform.vue +3 -3
- package/modules/@apostrophecms/schema/ui/apos/lib/conditionalFields.js +76 -71
- package/modules/@apostrophecms/schema/ui/apos/logic/AposArrayEditor.js +9 -1
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js +48 -20
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputDateAndTime.js +10 -1
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputObject.js +8 -3
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRadio.js +3 -2
- package/modules/@apostrophecms/schema/ui/apos/logic/AposSchema.js +45 -25
- package/modules/@apostrophecms/schema/ui/apos/logic/AposSubform.js +4 -1
- package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputConditionalFieldsMixin.js +11 -4
- package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputFollowingMixin.js +1 -1
- package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputMixin.js +4 -6
- package/modules/@apostrophecms/template/index.js +99 -31
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +5 -2
- package/package.json +3 -4
- package/test/pieces.js +15 -0
- package/test/schemas.js +1700 -0
|
@@ -374,6 +374,25 @@ module.exports = {
|
|
|
374
374
|
// All fields should have an initial value in the database
|
|
375
375
|
instance[field.name] = null;
|
|
376
376
|
}
|
|
377
|
+
// A workaround specifically for areas. They must have a
|
|
378
|
+
// unique `_id` which makes `klona` a poor way to establish
|
|
379
|
+
// a default, and we don't pass functions in schema
|
|
380
|
+
// definitions, but top-level areas should always exist
|
|
381
|
+
// for reasonable results if the output of `newInstance`
|
|
382
|
+
// is saved without further editing on the front end
|
|
383
|
+
if ((field.type === 'area') && (!instance[field.name])) {
|
|
384
|
+
instance[field.name] = {
|
|
385
|
+
metaType: 'area',
|
|
386
|
+
items: [],
|
|
387
|
+
_id: self.apos.util.generateId()
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
// A workaround specifically for objects. These too need
|
|
391
|
+
// to have reasonable values in parked pages and any other
|
|
392
|
+
// situation where the data never passes through the UI
|
|
393
|
+
if ((field.type === 'object') && ((!instance[field.name]) || _.isEmpty(instance[field.name]))) {
|
|
394
|
+
instance[field.name] = self.newInstance(field.schema);
|
|
395
|
+
}
|
|
377
396
|
}
|
|
378
397
|
return instance;
|
|
379
398
|
},
|
|
@@ -465,6 +484,78 @@ module.exports = {
|
|
|
465
484
|
});
|
|
466
485
|
},
|
|
467
486
|
|
|
487
|
+
async evaluateCondition(req, field, clause, destination, conditionalFields) {
|
|
488
|
+
for (const [ key, val ] of Object.entries(clause)) {
|
|
489
|
+
const destinationKey = _.get(destination, key);
|
|
490
|
+
|
|
491
|
+
if (key === '$or') {
|
|
492
|
+
const results = await Promise.all(val.map(clause => self.evaluateCondition(req, field, clause, destination, conditionalFields)));
|
|
493
|
+
const testResults = _.isPlainObject(results?.[0])
|
|
494
|
+
? results.some(({ value }) => value)
|
|
495
|
+
: results.some((value) => value);
|
|
496
|
+
if (!testResults) {
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
continue;
|
|
500
|
+
} else if (val.$ne) {
|
|
501
|
+
// eslint-disable-next-line eqeqeq
|
|
502
|
+
if (val.$ne == destinationKey) {
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Handle external conditions:
|
|
508
|
+
// - `if: { 'methodName()': true }`
|
|
509
|
+
// - `if: { 'moduleName:methodName()': 'expected value' }`
|
|
510
|
+
// Checking if key ends with a closing parenthesis here to throw later if any argument is passed.
|
|
511
|
+
if (key.endsWith(')')) {
|
|
512
|
+
let externalConditionResult;
|
|
513
|
+
|
|
514
|
+
try {
|
|
515
|
+
externalConditionResult = await self.evaluateMethod(req, key, field.name, field.moduleName, destination._id);
|
|
516
|
+
} catch (error) {
|
|
517
|
+
throw self.apos.error('invalid', error.message);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (externalConditionResult !== val) {
|
|
521
|
+
return false;
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
// Stop there, this is an external condition thus
|
|
525
|
+
// does not need to be checked against doc fields.
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (val.min && destinationKey < val.min) {
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
if (val.max && destinationKey > val.max) {
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (conditionalFields?.[key] === false) {
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (typeof val === 'boolean' && !destinationKey) {
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// eslint-disable-next-line eqeqeq
|
|
545
|
+
if ((typeof val === 'string' || typeof val === 'number') && destinationKey != val) {
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return true;
|
|
551
|
+
},
|
|
552
|
+
|
|
553
|
+
async isFieldRequired(req, field, destination) {
|
|
554
|
+
return field.requiredIf
|
|
555
|
+
? await self.evaluateCondition(req, field, field.requiredIf, destination)
|
|
556
|
+
: field.required;
|
|
557
|
+
},
|
|
558
|
+
|
|
468
559
|
// Convert submitted `data` object according to `schema`, sanitizing it
|
|
469
560
|
// and populating the appropriate properties of `destination` with it.
|
|
470
561
|
//
|
|
@@ -504,7 +595,16 @@ module.exports = {
|
|
|
504
595
|
|
|
505
596
|
if (convert) {
|
|
506
597
|
try {
|
|
507
|
-
await
|
|
598
|
+
const isRequired = await self.isFieldRequired(req, field, destination);
|
|
599
|
+
await convert(
|
|
600
|
+
req,
|
|
601
|
+
{
|
|
602
|
+
...field,
|
|
603
|
+
required: isRequired
|
|
604
|
+
},
|
|
605
|
+
data,
|
|
606
|
+
destination
|
|
607
|
+
);
|
|
508
608
|
} catch (error) {
|
|
509
609
|
if (Array.isArray(error)) {
|
|
510
610
|
const invalid = self.apos.error('invalid', {
|
|
@@ -572,7 +672,7 @@ module.exports = {
|
|
|
572
672
|
for (const field of schema) {
|
|
573
673
|
if (field.if) {
|
|
574
674
|
try {
|
|
575
|
-
const result = await
|
|
675
|
+
const result = await self.evaluateCondition(req, field, field.if, object, conditionalFields);
|
|
576
676
|
const previous = conditionalFields[field.name];
|
|
577
677
|
if (previous !== result) {
|
|
578
678
|
change = true;
|
|
@@ -598,55 +698,6 @@ module.exports = {
|
|
|
598
698
|
} else {
|
|
599
699
|
return true;
|
|
600
700
|
}
|
|
601
|
-
async function evaluate(clause, fieldName, fieldModuleName) {
|
|
602
|
-
let result = true;
|
|
603
|
-
for (const [ key, val ] of Object.entries(clause)) {
|
|
604
|
-
if (key === '$or') {
|
|
605
|
-
const results = await Promise.all(val.map(clause => evaluate(clause, fieldName, fieldModuleName)));
|
|
606
|
-
|
|
607
|
-
if (!results.some(({ value }) => value)) {
|
|
608
|
-
result = false;
|
|
609
|
-
break;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
// No need to go further here, the key is an "$or" condition...
|
|
613
|
-
continue;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// Handle external conditions:
|
|
617
|
-
// - `if: { 'methodName()': true }`
|
|
618
|
-
// - `if: { 'moduleName:methodName()': 'expected value' }`
|
|
619
|
-
// Checking if key ends with a closing parenthesis here to throw later if any argument is passed.
|
|
620
|
-
if (key.endsWith(')')) {
|
|
621
|
-
let externalConditionResult;
|
|
622
|
-
|
|
623
|
-
try {
|
|
624
|
-
externalConditionResult = await self.evaluateMethod(req, key, fieldName, fieldModuleName, object._id);
|
|
625
|
-
} catch (error) {
|
|
626
|
-
throw self.apos.error('invalid', error.message);
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
if (externalConditionResult !== val) {
|
|
630
|
-
result = false;
|
|
631
|
-
break;
|
|
632
|
-
};
|
|
633
|
-
|
|
634
|
-
// Stop there, this is an external condition thus
|
|
635
|
-
// does not need to be checked against doc fields.
|
|
636
|
-
continue;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
if (conditionalFields[key] === false) {
|
|
640
|
-
result = false;
|
|
641
|
-
break;
|
|
642
|
-
}
|
|
643
|
-
if (val !== object[key]) {
|
|
644
|
-
result = false;
|
|
645
|
-
break;
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
return result;
|
|
649
|
-
}
|
|
650
701
|
},
|
|
651
702
|
|
|
652
703
|
async evaluateMethod(req, methodKey, fieldName, fieldModuleName, docId = null, optionalParenthesis = false) {
|
|
@@ -1246,6 +1297,9 @@ module.exports = {
|
|
|
1246
1297
|
if (field.if && field.if.$or && !Array.isArray(field.if.$or)) {
|
|
1247
1298
|
fail(`$or conditional must be an array of conditions. Current $or configuration: ${JSON.stringify(field.if.$or)}`);
|
|
1248
1299
|
}
|
|
1300
|
+
if (field.requiredIf && field.requiredIf.$or && !Array.isArray(field.requiredIf.$or)) {
|
|
1301
|
+
fail(`$or conditional must be an array of conditions. Current $or configuration: ${JSON.stringify(field.requiredIf.$or)}`);
|
|
1302
|
+
}
|
|
1249
1303
|
if (!field.editPermission && field.permission) {
|
|
1250
1304
|
field.editPermission = field.permission;
|
|
1251
1305
|
}
|
|
@@ -65,13 +65,13 @@
|
|
|
65
65
|
:schema="schema"
|
|
66
66
|
:trigger-validation="triggerValidation"
|
|
67
67
|
:following-values="followingValues()"
|
|
68
|
-
:conditional-fields="conditionalFields
|
|
68
|
+
:conditional-fields="conditionalFields"
|
|
69
69
|
:value="currentDoc"
|
|
70
|
-
@input="currentDocUpdate"
|
|
71
|
-
@validate="triggerValidate"
|
|
72
70
|
:server-errors="currentDocServerErrors"
|
|
73
71
|
ref="schema"
|
|
74
72
|
:doc-id="docId"
|
|
73
|
+
@input="currentDocUpdate"
|
|
74
|
+
@validate="triggerValidate"
|
|
75
75
|
/>
|
|
76
76
|
</div>
|
|
77
77
|
</div>
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<AposInputWrapper
|
|
3
|
-
:field="field"
|
|
4
|
-
:
|
|
3
|
+
:field="field"
|
|
4
|
+
:error="effectiveError"
|
|
5
|
+
:uid="uid"
|
|
6
|
+
:items="next"
|
|
5
7
|
:display-options="displayOptions"
|
|
6
8
|
>
|
|
7
9
|
<template #additional>
|
|
@@ -54,19 +56,21 @@
|
|
|
54
56
|
:id="listId"
|
|
55
57
|
>
|
|
56
58
|
<AposSchema
|
|
59
|
+
v-model="item.schemaInput"
|
|
57
60
|
v-for="(item, index) in items"
|
|
58
|
-
:key="item._id"
|
|
59
|
-
:schema="schema"
|
|
60
61
|
class="apos-input-array-inline-item"
|
|
61
62
|
:class="item.open && !alwaysExpand ? 'apos-input-array-inline-item--active' : null"
|
|
62
|
-
|
|
63
|
+
:key="item._id"
|
|
64
|
+
:schema="schema"
|
|
63
65
|
:trigger-validation="triggerValidation"
|
|
64
66
|
:generation="generation"
|
|
65
67
|
:modifiers="['small', 'inverted']"
|
|
66
68
|
:doc-id="docId"
|
|
67
69
|
:following-values="getFollowingValues(item)"
|
|
68
|
-
:conditional-fields="
|
|
70
|
+
:conditional-fields="itemsConditionalFields[item._id]"
|
|
69
71
|
:field-style="field.style"
|
|
72
|
+
@input="setItemsConditionalFields(item._id)"
|
|
73
|
+
@validate="emitValidate()"
|
|
70
74
|
>
|
|
71
75
|
<template #before>
|
|
72
76
|
<component
|
|
@@ -11,14 +11,16 @@
|
|
|
11
11
|
<div class="apos-input-object">
|
|
12
12
|
<div class="apos-input-wrapper">
|
|
13
13
|
<AposSchema
|
|
14
|
+
v-model="schemaInput"
|
|
15
|
+
ref="schema"
|
|
14
16
|
:schema="schema"
|
|
15
17
|
:trigger-validation="triggerValidation"
|
|
16
18
|
:generation="generation"
|
|
17
19
|
:doc-id="docId"
|
|
18
|
-
|
|
19
|
-
:conditional-fields="conditionalFields(values)"
|
|
20
|
+
:conditional-fields="conditionalFields"
|
|
20
21
|
:following-values="followingValuesWithParent"
|
|
21
|
-
|
|
22
|
+
@input="evaluateConditions(values)"
|
|
23
|
+
@validate="emitValidate()"
|
|
22
24
|
/>
|
|
23
25
|
</div>
|
|
24
26
|
</div>
|
|
@@ -7,31 +7,36 @@
|
|
|
7
7
|
>
|
|
8
8
|
<template #body>
|
|
9
9
|
<label
|
|
10
|
-
class="apos-choice-label"
|
|
11
|
-
v-for="
|
|
10
|
+
class="apos-choice-label"
|
|
11
|
+
v-for="{label, value, tooltip} in choices"
|
|
12
|
+
:key="value"
|
|
13
|
+
:for="getChoiceId(uid, value)"
|
|
12
14
|
:class="{'apos-choice-label--disabled': field.readOnly}"
|
|
13
15
|
>
|
|
14
16
|
<input
|
|
15
|
-
type="radio"
|
|
16
|
-
|
|
17
|
-
:id="getChoiceId(uid,
|
|
18
|
-
:
|
|
17
|
+
type="radio"
|
|
18
|
+
class="apos-sr-only apos-input--choice apos-input--radio"
|
|
19
|
+
:id="getChoiceId(uid, value)"
|
|
20
|
+
:value="JSON.stringify(value)"
|
|
21
|
+
:name="field.name"
|
|
22
|
+
:checked="next === value"
|
|
19
23
|
tabindex="1"
|
|
20
24
|
:disabled="field.readOnly"
|
|
21
25
|
@change="change($event.target.value)"
|
|
22
26
|
>
|
|
23
27
|
<span class="apos-input-indicator" aria-hidden="true">
|
|
24
28
|
<component
|
|
25
|
-
|
|
26
|
-
:
|
|
29
|
+
v-if="next === value"
|
|
30
|
+
:is="`${next === value ? 'check-bold-icon' : 'span'}`"
|
|
31
|
+
:size="8"
|
|
27
32
|
/>
|
|
28
33
|
</span>
|
|
29
34
|
<span class="apos-choice-label-text">
|
|
30
|
-
{{ $t(
|
|
35
|
+
{{ $t(label) }}
|
|
31
36
|
<AposIndicator
|
|
32
|
-
v-if="
|
|
37
|
+
v-if="tooltip"
|
|
33
38
|
class="apos-choice-label-info"
|
|
34
|
-
:tooltip="
|
|
39
|
+
:tooltip="tooltip"
|
|
35
40
|
:icon-size="14"
|
|
36
41
|
icon="information-icon"
|
|
37
42
|
/>
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
v-model="fieldState[field.name]"
|
|
40
40
|
:is="fieldComponentMap[field.type]"
|
|
41
41
|
:following-values="followingValues[field.name]"
|
|
42
|
-
:condition-met="conditionalFields[field.name]"
|
|
42
|
+
:condition-met="conditionalFields?.if[field.name]"
|
|
43
43
|
:field="fields[field.name].field"
|
|
44
44
|
:modifiers="fields[field.name].modifiers"
|
|
45
45
|
:display-options="getDisplayOptions(field.name)"
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
:ref="field.name"
|
|
50
50
|
:generation="generation"
|
|
51
51
|
@update-doc-data="onUpdateDocData"
|
|
52
|
+
@validate="emitValidate()"
|
|
52
53
|
/>
|
|
53
54
|
</component>
|
|
54
55
|
<slot name="after" />
|
|
@@ -7,15 +7,15 @@
|
|
|
7
7
|
{{ $t(subform.label) }}
|
|
8
8
|
</span>
|
|
9
9
|
<AposSchema
|
|
10
|
-
|
|
10
|
+
ref="schema"
|
|
11
11
|
data-apos-test="subformSchema"
|
|
12
|
+
:class="{ 'apos-subform__disabled': busy }"
|
|
12
13
|
:data-apos-test-name="subform.name"
|
|
13
|
-
ref="schema"
|
|
14
14
|
:trigger-validation="triggerValidation"
|
|
15
15
|
:schema="schema"
|
|
16
16
|
:value="docFields"
|
|
17
17
|
:following-values="followingValues()"
|
|
18
|
-
:conditional-fields="conditionalFields
|
|
18
|
+
:conditional-fields="conditionalFields"
|
|
19
19
|
:server-errors="serverErrors"
|
|
20
20
|
:modifiers="['small']"
|
|
21
21
|
@input="updateDocFields"
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// Supported field conditional types,
|
|
2
|
+
// you can add a condition type to this array to make it available to the frontend
|
|
3
|
+
const conditionTypes = [ 'if', 'requiredIf' ];
|
|
4
|
+
export const getConditionTypesObject = () => Object
|
|
5
|
+
.fromEntries(conditionTypes.map((key) => ([ key, {} ])));
|
|
6
|
+
|
|
1
7
|
// Evaluate the external conditions found in each field
|
|
2
8
|
// via API calls - made in parallel for performance-
|
|
3
9
|
// and store their result for reusability.
|
|
@@ -5,50 +11,51 @@
|
|
|
5
11
|
// `docId` - the current docId (from prop or context)
|
|
6
12
|
// `$t` - the i18n function (usually `this.$t`)
|
|
7
13
|
export async function evaluateExternalConditions(schema, docId, $t) {
|
|
8
|
-
|
|
14
|
+
const externalConditionsResults = getConditionTypesObject();
|
|
9
15
|
|
|
10
16
|
for (const field of schema) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const uniqExternalConditionKeys = [ ...new Set(externalConditionKeys) ];
|
|
18
|
-
|
|
19
|
-
let results = [];
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
const promises = uniqExternalConditionKeys
|
|
23
|
-
.map(key => externalConditionsResults[key] !== undefined
|
|
24
|
-
? null
|
|
25
|
-
: evaluateExternalCondition(key, field._id, docId)
|
|
26
|
-
)
|
|
17
|
+
for (const conditionType of conditionTypes) {
|
|
18
|
+
if (field[conditionType]) {
|
|
19
|
+
const externalConditionKeys = Object
|
|
20
|
+
.entries(field[conditionType])
|
|
21
|
+
.flatMap((entry) => getExternalConditionKeys(entry, conditionType))
|
|
27
22
|
.filter(Boolean);
|
|
28
23
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
24
|
+
const uniqExternalConditionKeys = [ ...new Set(externalConditionKeys) ];
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const promises = uniqExternalConditionKeys
|
|
28
|
+
.map(key => (externalConditionsResults[conditionType][key] !== undefined
|
|
29
|
+
? null
|
|
30
|
+
: evaluateExternalCondition(key, field._id, docId))
|
|
31
|
+
)
|
|
32
|
+
.filter(Boolean);
|
|
33
|
+
|
|
34
|
+
const results = await Promise.all(promises);
|
|
35
|
+
|
|
36
|
+
externalConditionsResults[conditionType] = {
|
|
37
|
+
...externalConditionsResults[conditionType],
|
|
38
|
+
...Object.fromEntries(results)
|
|
39
|
+
};
|
|
40
|
+
} catch (error) {
|
|
41
|
+
await apos.notify($t('apostrophe:errorEvaluatingExternalCondition', { name: field.name }), {
|
|
42
|
+
type: 'danger',
|
|
43
|
+
icon: 'alert-circle-icon',
|
|
44
|
+
dismiss: true,
|
|
45
|
+
localize: false
|
|
46
|
+
});
|
|
47
|
+
}
|
|
42
48
|
}
|
|
43
49
|
}
|
|
44
50
|
}
|
|
45
51
|
return externalConditionsResults;
|
|
46
52
|
|
|
47
|
-
function getExternalConditionKeys([ key, val ]) {
|
|
53
|
+
function getExternalConditionKeys([ key, val ], conditionType) {
|
|
48
54
|
if (key === '$or') {
|
|
49
|
-
return val.flatMap(nested => Object.entries(nested)
|
|
55
|
+
return val.flatMap(nested => Object.entries(nested)
|
|
56
|
+
.map((entry) => getExternalConditionKeys(entry, conditionType)));
|
|
50
57
|
}
|
|
51
|
-
if (isExternalCondition(key)) {
|
|
58
|
+
if (isExternalCondition(key, conditionType)) {
|
|
52
59
|
return key;
|
|
53
60
|
}
|
|
54
61
|
return null;
|
|
@@ -71,7 +78,7 @@ export async function evaluateExternalCondition(conditionKey, fieldId, docId) {
|
|
|
71
78
|
return [ conditionKey, result ];
|
|
72
79
|
}
|
|
73
80
|
// Checking if key ends with a closing parenthesis here to throw later if any argument is passed.
|
|
74
|
-
export function isExternalCondition(conditionKey) {
|
|
81
|
+
export function isExternalCondition(conditionKey, conditionType) {
|
|
75
82
|
if (!conditionKey.endsWith(')')) {
|
|
76
83
|
return false;
|
|
77
84
|
}
|
|
@@ -79,7 +86,7 @@ export function isExternalCondition(conditionKey) {
|
|
|
79
86
|
const [ methodDefinition ] = conditionKey.split('(');
|
|
80
87
|
|
|
81
88
|
if (!conditionKey.endsWith('()')) {
|
|
82
|
-
console.warn(`Warning in \`
|
|
89
|
+
console.warn(`Warning in \`${conditionType}\` definition: "${methodDefinition}()" should not be passed any argument.`);
|
|
83
90
|
}
|
|
84
91
|
|
|
85
92
|
return true;
|
|
@@ -103,56 +110,49 @@ export function isExternalCondition(conditionKey) {
|
|
|
103
110
|
// `values` - the schema (all) values
|
|
104
111
|
// `externalConditionsResults` - the results of the external conditions,
|
|
105
112
|
// as returned by `evaluateExternalConditions`
|
|
106
|
-
export function
|
|
113
|
+
export function getConditionalFields(
|
|
107
114
|
schema,
|
|
108
115
|
fields,
|
|
109
116
|
values,
|
|
110
117
|
externalConditionsResults
|
|
111
118
|
) {
|
|
112
|
-
const conditionalFields =
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const previous = conditionalFields[field.name];
|
|
120
|
-
if (previous !== result) {
|
|
121
|
-
change = true;
|
|
122
|
-
}
|
|
123
|
-
conditionalFields[field.name] = result;
|
|
119
|
+
const conditionalFields = getConditionTypesObject();
|
|
120
|
+
|
|
121
|
+
for (const field of schema) {
|
|
122
|
+
for (const conditionType of conditionTypes) {
|
|
123
|
+
if (field[conditionType]) {
|
|
124
|
+
const result = evaluate(field[conditionType], conditionType);
|
|
125
|
+
conditionalFields[conditionType][field.name] = result;
|
|
124
126
|
}
|
|
125
127
|
}
|
|
126
|
-
if (!change) {
|
|
127
|
-
break;
|
|
128
|
-
}
|
|
129
128
|
}
|
|
130
129
|
|
|
131
|
-
const result =
|
|
130
|
+
const result = getConditionTypesObject();
|
|
131
|
+
|
|
132
132
|
for (const field of fields) {
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
for (const conditionType of conditionTypes) {
|
|
134
|
+
if (field[conditionType]) {
|
|
135
|
+
result[conditionType][field.name] = conditionalFields[conditionType][field.name];
|
|
136
|
+
}
|
|
135
137
|
}
|
|
136
138
|
}
|
|
139
|
+
|
|
137
140
|
return result;
|
|
138
141
|
|
|
139
|
-
function evaluate(clause) {
|
|
140
|
-
let result = true;
|
|
142
|
+
function evaluate(clause, conditionType) {
|
|
141
143
|
for (const [ key, val ] of Object.entries(clause)) {
|
|
142
144
|
if (key === '$or') {
|
|
143
|
-
if (!val.some(clause => evaluate(clause))) {
|
|
144
|
-
|
|
145
|
-
break;
|
|
145
|
+
if (!val.some(clause => evaluate(clause, conditionType))) {
|
|
146
|
+
return false;
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
// No need to go further here, the key is an "$or" condition...
|
|
149
150
|
continue;
|
|
150
151
|
}
|
|
151
152
|
|
|
152
|
-
if (isExternalCondition(key)) {
|
|
153
|
-
if (externalConditionsResults[key] !== val) {
|
|
154
|
-
|
|
155
|
-
break;
|
|
153
|
+
if (isExternalCondition(key, conditionType)) {
|
|
154
|
+
if (externalConditionsResults[conditionType][key] !== val) {
|
|
155
|
+
return false;
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
// Stop there, this is an external condition thus
|
|
@@ -160,22 +160,27 @@ export function conditionalFields(
|
|
|
160
160
|
continue;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
if (conditionalFields[key] === false) {
|
|
164
|
-
|
|
165
|
-
break;
|
|
163
|
+
if (conditionalFields[conditionType][key] === false) {
|
|
164
|
+
return false;
|
|
166
165
|
}
|
|
167
166
|
|
|
168
167
|
const fieldValue = values[key];
|
|
169
168
|
|
|
170
169
|
if (Array.isArray(fieldValue)) {
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
return fieldValue.includes(val);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (val.min && fieldValue < val.min) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
if (val.max && fieldValue > val.max) {
|
|
177
|
+
return false;
|
|
173
178
|
}
|
|
179
|
+
|
|
174
180
|
if (val !== fieldValue) {
|
|
175
|
-
|
|
176
|
-
break;
|
|
181
|
+
return false;
|
|
177
182
|
}
|
|
178
183
|
}
|
|
179
|
-
return
|
|
184
|
+
return true;
|
|
180
185
|
}
|
|
181
186
|
}
|
|
@@ -20,6 +20,10 @@ export default {
|
|
|
20
20
|
required: true,
|
|
21
21
|
type: Object
|
|
22
22
|
},
|
|
23
|
+
inputSchema: {
|
|
24
|
+
type: Array,
|
|
25
|
+
required: true
|
|
26
|
+
},
|
|
23
27
|
serverError: {
|
|
24
28
|
type: Object,
|
|
25
29
|
default: null
|
|
@@ -80,7 +84,8 @@ export default {
|
|
|
80
84
|
},
|
|
81
85
|
schema() {
|
|
82
86
|
// For AposDocEditorMixin
|
|
83
|
-
return (this.
|
|
87
|
+
return (this.inputSchema || this.field.schema || [])
|
|
88
|
+
.filter(field => apos.schema.components.fields[field.type]);
|
|
84
89
|
},
|
|
85
90
|
countLabel() {
|
|
86
91
|
return this.$t('apostrophe:numberAdded', {
|
|
@@ -132,6 +137,8 @@ export default {
|
|
|
132
137
|
},
|
|
133
138
|
async mounted() {
|
|
134
139
|
this.modal.active = true;
|
|
140
|
+
await this.evaluateExternalConditions();
|
|
141
|
+
this.evaluateConditions();
|
|
135
142
|
if (this.next.length) {
|
|
136
143
|
this.select(this.next[0]._id);
|
|
137
144
|
}
|
|
@@ -179,6 +186,7 @@ export default {
|
|
|
179
186
|
},
|
|
180
187
|
currentDocUpdate(currentDoc) {
|
|
181
188
|
this.currentDoc = currentDoc;
|
|
189
|
+
this.evaluateConditions();
|
|
182
190
|
},
|
|
183
191
|
async add() {
|
|
184
192
|
if (await this.validate(true, false)) {
|