apostrophe 3.41.1 → 3.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -1
- package/modules/@apostrophecms/admin-bar/index.js +1 -0
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +1 -1
- package/modules/@apostrophecms/asset/lib/globalIcons.js +3 -0
- package/modules/@apostrophecms/doc/ui/apos/apps/AposDoc.js +42 -0
- package/modules/@apostrophecms/doc-type/index.js +82 -51
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +10 -3
- package/modules/@apostrophecms/file/index.js +2 -1
- package/modules/@apostrophecms/file-tag/index.js +2 -1
- package/modules/@apostrophecms/i18n/i18n/en.json +115 -109
- package/modules/@apostrophecms/i18n/i18n/es.json +83 -78
- package/modules/@apostrophecms/i18n/i18n/fr.json +89 -84
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +81 -76
- package/modules/@apostrophecms/i18n/i18n/sk.json +91 -86
- package/modules/@apostrophecms/image/index.js +5 -1
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +6 -1
- package/modules/@apostrophecms/image-tag/index.js +2 -1
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +35 -2
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +2 -2
- package/modules/@apostrophecms/page/index.js +8 -4
- package/modules/@apostrophecms/piece-type/index.js +24 -3
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +3 -63
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +9 -2
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposUtilityOperations.vue +126 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +10 -10
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Default.js +44 -48
- package/modules/@apostrophecms/schema/index.js +105 -35
- package/modules/@apostrophecms/schema/lib/addFieldTypes.js +21 -2
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +339 -112
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +72 -20
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +6 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +7 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +18 -4
- package/modules/@apostrophecms/schema/ui/apos/components/AposSearchList.vue +111 -30
- package/modules/@apostrophecms/submitted-draft/index.js +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlatList.vue +5 -1
- package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +1 -0
- package/modules/@apostrophecms/user/index.js +2 -1
- package/modules/@apostrophecms/widget-type/index.js +6 -3
- package/package.json +15 -15
- package/test/pieces.js +726 -13
- package/test/schemas.js +392 -22
|
@@ -505,19 +505,16 @@ module.exports = {
|
|
|
505
505
|
if (convert) {
|
|
506
506
|
try {
|
|
507
507
|
await convert(req, field, data, destination);
|
|
508
|
-
} catch (
|
|
509
|
-
if (Array.isArray(
|
|
508
|
+
} catch (error) {
|
|
509
|
+
if (Array.isArray(error)) {
|
|
510
510
|
const invalid = self.apos.error('invalid', {
|
|
511
|
-
errors:
|
|
511
|
+
errors: error
|
|
512
512
|
});
|
|
513
513
|
invalid.path = field.name;
|
|
514
514
|
errors.push(invalid);
|
|
515
515
|
} else {
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
e.path = field.name;
|
|
520
|
-
errors.push(e);
|
|
516
|
+
error.path = field.name;
|
|
517
|
+
errors.push(error);
|
|
521
518
|
}
|
|
522
519
|
}
|
|
523
520
|
}
|
|
@@ -526,19 +523,26 @@ module.exports = {
|
|
|
526
523
|
const errorsList = [];
|
|
527
524
|
|
|
528
525
|
for (const error of errors) {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
526
|
+
if ((error.name === 'required' || error.name === 'mandatory')) {
|
|
527
|
+
// `self.isVisible` will only throw for required fields that have
|
|
528
|
+
// an external condition containing an unknown module or method:
|
|
529
|
+
const isVisible = await self.isVisible(req, schema, destination, error.path);
|
|
530
|
+
|
|
531
|
+
if (!isVisible) {
|
|
532
|
+
// It is not reasonable to enforce required for
|
|
533
|
+
// fields hidden via conditional fields
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
535
536
|
}
|
|
536
537
|
|
|
538
|
+
if (!Array.isArray(error) && typeof error !== 'string') {
|
|
539
|
+
self.apos.util.error(error + '\n\n' + error.stack);
|
|
540
|
+
}
|
|
537
541
|
errorsList.push(error);
|
|
538
542
|
}
|
|
539
543
|
|
|
540
544
|
if (errorsList.length) {
|
|
541
|
-
throw
|
|
545
|
+
throw errorsList;
|
|
542
546
|
}
|
|
543
547
|
},
|
|
544
548
|
|
|
@@ -547,18 +551,30 @@ module.exports = {
|
|
|
547
551
|
|
|
548
552
|
async isVisible(req, schema, object, name) {
|
|
549
553
|
const conditionalFields = {};
|
|
554
|
+
const errors = {};
|
|
555
|
+
|
|
550
556
|
while (true) {
|
|
551
557
|
let change = false;
|
|
552
558
|
for (const field of schema) {
|
|
553
559
|
if (field.if) {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
560
|
+
try {
|
|
561
|
+
const result = await evaluate(field.if, field.name, field.moduleName);
|
|
562
|
+
const previous = conditionalFields[field.name];
|
|
563
|
+
if (previous !== result) {
|
|
564
|
+
change = true;
|
|
565
|
+
}
|
|
566
|
+
conditionalFields[field.name] = result;
|
|
567
|
+
} catch (error) {
|
|
568
|
+
errors[field.name] = error;
|
|
558
569
|
}
|
|
559
|
-
conditionalFields[field.name] = result;
|
|
560
570
|
}
|
|
561
571
|
}
|
|
572
|
+
|
|
573
|
+
// send the error related to the given field via the `name` param
|
|
574
|
+
if (errors[name]) {
|
|
575
|
+
throw errors[name];
|
|
576
|
+
}
|
|
577
|
+
|
|
562
578
|
if (!change) {
|
|
563
579
|
break;
|
|
564
580
|
}
|
|
@@ -588,7 +604,13 @@ module.exports = {
|
|
|
588
604
|
// - `if: { 'moduleName:methodName()': 'expected value' }`
|
|
589
605
|
// Checking if key ends with a closing parenthesis here to throw later if any argument is passed.
|
|
590
606
|
if (key.endsWith(')')) {
|
|
591
|
-
|
|
607
|
+
let externalConditionResult;
|
|
608
|
+
|
|
609
|
+
try {
|
|
610
|
+
externalConditionResult = await self.evaluateMethod(req, key, fieldName, fieldModuleName, object._id);
|
|
611
|
+
} catch (error) {
|
|
612
|
+
throw self.apos.error('invalid', error.message);
|
|
613
|
+
}
|
|
592
614
|
|
|
593
615
|
if (externalConditionResult !== val) {
|
|
594
616
|
result = false;
|
|
@@ -613,11 +635,15 @@ module.exports = {
|
|
|
613
635
|
}
|
|
614
636
|
},
|
|
615
637
|
|
|
616
|
-
async
|
|
617
|
-
const [ methodDefinition ] =
|
|
638
|
+
async evaluateMethod(req, methodKey, fieldName, fieldModuleName, docId = null, optionalParenthesis = false) {
|
|
639
|
+
const [ methodDefinition, rest ] = methodKey.split('(');
|
|
640
|
+
const hasParenthesis = rest !== undefined;
|
|
618
641
|
|
|
619
|
-
if (!
|
|
620
|
-
|
|
642
|
+
if (!hasParenthesis && !optionalParenthesis) {
|
|
643
|
+
throw new Error(`The method "${methodDefinition}" defined in the "${fieldName}" field should be written with parenthesis: "${methodDefinition}()".`);
|
|
644
|
+
}
|
|
645
|
+
if (hasParenthesis && !methodKey.endsWith('()')) {
|
|
646
|
+
self.apos.util.warn(`The method "${methodDefinition}" defined in the "${fieldName}" field should be written without argument: "${methodDefinition}()".`);
|
|
621
647
|
}
|
|
622
648
|
|
|
623
649
|
const [ methodName, moduleName = fieldModuleName ] = methodDefinition
|
|
@@ -627,9 +653,9 @@ module.exports = {
|
|
|
627
653
|
const module = self.apos.modules[moduleName];
|
|
628
654
|
|
|
629
655
|
if (!module) {
|
|
630
|
-
throw new Error(`
|
|
656
|
+
throw new Error(`The "${moduleName}" module defined in the "${fieldName}" field does not exist.`);
|
|
631
657
|
} else if (!module[methodName]) {
|
|
632
|
-
throw new Error(`
|
|
658
|
+
throw new Error(`The "${methodName}" method from "${moduleName}" module defined in the "${fieldName}" field does not exist.`);
|
|
633
659
|
}
|
|
634
660
|
|
|
635
661
|
return module[methodName](req, { docId });
|
|
@@ -910,17 +936,37 @@ module.exports = {
|
|
|
910
936
|
// Currently `req` does not impact this, but that may change.
|
|
911
937
|
|
|
912
938
|
prepareForStorage(req, doc) {
|
|
939
|
+
const can = (field) => {
|
|
940
|
+
return (!field.withType && !field.editPermission && !field.viewPermission) ||
|
|
941
|
+
(field.withType && self.apos.permission.can(req, 'view', field.withType)) ||
|
|
942
|
+
(field.editPermission && self.apos.permission.can(req, field.editPermission.action, field.editPermission.type)) ||
|
|
943
|
+
(field.viewPermission && self.apos.permission.can(req, field.viewPermission.action, field.viewPermission.type)) ||
|
|
944
|
+
false;
|
|
945
|
+
};
|
|
946
|
+
|
|
913
947
|
const handlers = {
|
|
914
948
|
arrayItem: (field, object) => {
|
|
949
|
+
if (!can(field)) {
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
|
|
915
953
|
object._id = object._id || self.apos.util.generateId();
|
|
916
954
|
object.metaType = 'arrayItem';
|
|
917
955
|
object.scopedArrayName = field.scopedArrayName;
|
|
918
956
|
},
|
|
919
957
|
object: (field, object) => {
|
|
958
|
+
if (!can(field)) {
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
|
|
920
962
|
object.metaType = 'object';
|
|
921
963
|
object.scopedObjectName = field.scopedObjectName;
|
|
922
964
|
},
|
|
923
965
|
relationship: (field, doc) => {
|
|
966
|
+
if (!can(field)) {
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
|
|
924
970
|
doc[field.idsStorage] = doc[field.name].map(relatedDoc => self.apos.doc.toAposDocId(relatedDoc));
|
|
925
971
|
if (field.fieldsStorage) {
|
|
926
972
|
const fieldsById = doc[field.fieldsStorage] || {};
|
|
@@ -1169,7 +1215,7 @@ module.exports = {
|
|
|
1169
1215
|
},
|
|
1170
1216
|
|
|
1171
1217
|
// Validates a single schema field. See `validate`.
|
|
1172
|
-
validateField(field, options) {
|
|
1218
|
+
validateField(field, options, parent = null) {
|
|
1173
1219
|
const fieldType = self.fieldTypes[field.type];
|
|
1174
1220
|
if (!fieldType) {
|
|
1175
1221
|
fail('Unknown schema field type.');
|
|
@@ -1183,6 +1229,15 @@ module.exports = {
|
|
|
1183
1229
|
if (field.if && field.if.$or && !Array.isArray(field.if.$or)) {
|
|
1184
1230
|
fail(`$or conditional must be an array of conditions. Current $or configuration: ${JSON.stringify(field.if.$or)}`);
|
|
1185
1231
|
}
|
|
1232
|
+
if (!field.editPermission && field.permission) {
|
|
1233
|
+
field.editPermission = field.permission;
|
|
1234
|
+
}
|
|
1235
|
+
if (options.type !== 'doc type' && (field.editPermission || field.viewPermission)) {
|
|
1236
|
+
warn(`editPermission or viewPermission must be defined on doc-type schemas only, "${options.type}" provided`);
|
|
1237
|
+
}
|
|
1238
|
+
if (options.type === 'doc type' && (field.editPermission || field.viewPermission) && parent) {
|
|
1239
|
+
warn(`editPermission or viewPermission must be defined on root fields only, provided on "${parent.name}.${field.name}"`);
|
|
1240
|
+
}
|
|
1186
1241
|
if (fieldType.validate) {
|
|
1187
1242
|
fieldType.validate(field, options, warn, fail);
|
|
1188
1243
|
}
|
|
@@ -1193,8 +1248,12 @@ module.exports = {
|
|
|
1193
1248
|
self.apos.util.error(format(s));
|
|
1194
1249
|
}
|
|
1195
1250
|
function format(s) {
|
|
1251
|
+
const fieldName = parent && parent.name
|
|
1252
|
+
? `${parent.name}.${field.name}`
|
|
1253
|
+
: field.name;
|
|
1254
|
+
|
|
1196
1255
|
return stripIndents`
|
|
1197
|
-
${options.type} ${options.subtype}, ${field.type} field "${
|
|
1256
|
+
${options.type} ${options.subtype}, ${field.type} field "${fieldName}":
|
|
1198
1257
|
|
|
1199
1258
|
${s}
|
|
1200
1259
|
|
|
@@ -1520,9 +1579,16 @@ module.exports = {
|
|
|
1520
1579
|
},
|
|
1521
1580
|
|
|
1522
1581
|
async getChoices(req, field) {
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1582
|
+
if (typeof field.choices !== 'string') {
|
|
1583
|
+
return field.choices;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
try {
|
|
1587
|
+
const result = await self.evaluateMethod(req, field.choices, field.name, field.moduleName, null, true);
|
|
1588
|
+
return result;
|
|
1589
|
+
} catch (error) {
|
|
1590
|
+
throw self.apos.error('invalid', error.message);
|
|
1591
|
+
}
|
|
1526
1592
|
}
|
|
1527
1593
|
|
|
1528
1594
|
};
|
|
@@ -1542,7 +1608,11 @@ module.exports = {
|
|
|
1542
1608
|
) {
|
|
1543
1609
|
throw self.apos.error('invalid');
|
|
1544
1610
|
}
|
|
1545
|
-
|
|
1611
|
+
try {
|
|
1612
|
+
choices = await self.evaluateMethod(req, field.choices, field.name, field.moduleName, docId, true);
|
|
1613
|
+
} catch (error) {
|
|
1614
|
+
throw self.apos.error('invalid', error.message);
|
|
1615
|
+
}
|
|
1546
1616
|
if (Array.isArray(choices)) {
|
|
1547
1617
|
return {
|
|
1548
1618
|
choices
|
|
@@ -1559,8 +1629,8 @@ module.exports = {
|
|
|
1559
1629
|
const field = self.getFieldById(fieldId);
|
|
1560
1630
|
|
|
1561
1631
|
try {
|
|
1562
|
-
const result = await self.
|
|
1563
|
-
return result;
|
|
1632
|
+
const result = await self.evaluateMethod(req, conditionKey, field.name, field.moduleName, docId);
|
|
1633
|
+
return { result };
|
|
1564
1634
|
} catch (error) {
|
|
1565
1635
|
throw self.apos.error('invalid', error.message);
|
|
1566
1636
|
}
|
|
@@ -752,6 +752,19 @@ module.exports = (self) => {
|
|
|
752
752
|
if ((field.max !== undefined) && (results.length > field.max)) {
|
|
753
753
|
throw self.apos.error('max');
|
|
754
754
|
}
|
|
755
|
+
if (data.length && field.schema && field.schema.length) {
|
|
756
|
+
const { name: uniqueFieldName, label: uniqueFieldLabel } = field.schema.find(subfield => subfield.unique) || [];
|
|
757
|
+
if (uniqueFieldName) {
|
|
758
|
+
const duplicates = data
|
|
759
|
+
.map(item => Array.isArray(item[uniqueFieldName])
|
|
760
|
+
? item[uniqueFieldName][0]._id
|
|
761
|
+
: item[uniqueFieldName])
|
|
762
|
+
.filter((item, index, array) => array.indexOf(item) !== index);
|
|
763
|
+
if (duplicates.length) {
|
|
764
|
+
throw self.apos.error('duplicate', `${req.t(uniqueFieldLabel)} in ${req.t(field.label)} must be unique`);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
755
768
|
if (errors.length) {
|
|
756
769
|
throw errors;
|
|
757
770
|
}
|
|
@@ -766,7 +779,7 @@ module.exports = (self) => {
|
|
|
766
779
|
},
|
|
767
780
|
validate: function (field, options, warn, fail) {
|
|
768
781
|
for (const subField of field.schema || field.fields.add) {
|
|
769
|
-
self.validateField(subField, options);
|
|
782
|
+
self.validateField(subField, options, field);
|
|
770
783
|
}
|
|
771
784
|
},
|
|
772
785
|
register: function (metaType, type, field) {
|
|
@@ -833,7 +846,7 @@ module.exports = (self) => {
|
|
|
833
846
|
},
|
|
834
847
|
validate: function (field, options, warn, fail) {
|
|
835
848
|
for (const subField of field.schema || field.fields.add) {
|
|
836
|
-
self.validateField(subField, options);
|
|
849
|
+
self.validateField(subField, options, field);
|
|
837
850
|
}
|
|
838
851
|
},
|
|
839
852
|
isEqual(req, field, one, two) {
|
|
@@ -1042,6 +1055,12 @@ module.exports = (self) => {
|
|
|
1042
1055
|
field.postprocessor = field.postprocessor || withTypeManager.options.relationshipPostprocessor;
|
|
1043
1056
|
field.editorLabel = field.editorLabel || withTypeManager.options.relationshipEditorLabel;
|
|
1044
1057
|
field.editorIcon = field.editorIcon || withTypeManager.options.relationshipEditorIcon;
|
|
1058
|
+
field.suggestionLabel = field.suggestionLabel || withTypeManager.options.relationshipSuggestionLabel;
|
|
1059
|
+
field.suggestionHelp = field.suggestionHelp || withTypeManager.options.relationshipSuggestionHelp;
|
|
1060
|
+
field.suggestionLimit = field.suggestionLimit || withTypeManager.options.relationshipSuggestionLimit;
|
|
1061
|
+
field.suggestionSort = field.suggestionSort || withTypeManager.options.relationshipSuggestionSort;
|
|
1062
|
+
field.suggestionIcon = field.suggestionIcon || withTypeManager.options.relationshipSuggestionIcon;
|
|
1063
|
+
field.suggestionFields = field.suggestionFields || withTypeManager.options.relationshipSuggestionFields;
|
|
1045
1064
|
|
|
1046
1065
|
if (!field.schema && !Array.isArray(field.withType)) {
|
|
1047
1066
|
const fieldsOption = withTypeManager.options.relationshipFields;
|