apostrophe 3.51.0 → 3.52.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/.eslintrc +1 -0
- package/CHANGELOG.md +24 -0
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +8 -16
- package/modules/@apostrophecms/area/ui/apos/components/AposWidgetControls.vue +1 -10
- package/modules/@apostrophecms/doc/index.js +42 -2
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +15 -4
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +2 -1
- package/modules/@apostrophecms/page/index.js +9 -5
- package/modules/@apostrophecms/schema/index.js +18 -4
- package/package.json +1 -1
package/.eslintrc
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.52.0 (2023-07-06)
|
|
4
|
+
|
|
5
|
+
### Changes
|
|
6
|
+
* Foreign widget UI no longer uses inverted theme styles.
|
|
7
|
+
|
|
8
|
+
### Adds
|
|
9
|
+
* Allows users to double-click a nested widget's breadcrumb entry and open its editor.
|
|
10
|
+
* Adds support for a new `conditions` property in `addContextOperation` and validation of `addContextOperation` configuration.
|
|
11
|
+
|
|
12
|
+
### Fixes
|
|
13
|
+
|
|
14
|
+
* The API now allows the user to create a page without defining the page target ID. By default it takes the Home page.
|
|
15
|
+
* Users are no longer blocked from saving documents when a field is hidden
|
|
16
|
+
by an `if` condition fails to satisfy a condition such as `min` or `max`
|
|
17
|
+
or is otherwise invalid. Instead the invalid value is discarded for safety.
|
|
18
|
+
Note that `required` has always been ignored when an `if` condition is not
|
|
19
|
+
satisfied.
|
|
20
|
+
|
|
21
|
+
## 3.51.1 (2023-06-23)
|
|
22
|
+
|
|
23
|
+
## Fixes
|
|
24
|
+
|
|
25
|
+
* Fix a regression introduced in 3.51.0 - conditional fields work again in the array editor dialog box.
|
|
26
|
+
|
|
3
27
|
## 3.51.0 (2023-06-21)
|
|
4
28
|
|
|
5
29
|
### Adds
|
|
@@ -41,13 +41,13 @@
|
|
|
41
41
|
<li class="apos-area-widget__breadcrumb" data-apos-widget-breadcrumb="0">
|
|
42
42
|
<AposButton
|
|
43
43
|
type="quiet"
|
|
44
|
-
@click="foreign ? $emit('edit', i) :
|
|
44
|
+
@click="foreign ? $emit('edit', i) : null"
|
|
45
|
+
@dblclick.native="(!foreign && !isContextual) ? $emit('edit', i) : null"
|
|
45
46
|
:label="foreign ? {
|
|
46
47
|
key: 'apostrophe:editWidgetType',
|
|
47
48
|
label: $t(widgetLabel)
|
|
48
49
|
} : widgetLabel"
|
|
49
|
-
tooltip="apostrophe:editWidgetForeignTooltip"
|
|
50
|
-
:icon="foreign ? 'earth-icon' : null"
|
|
50
|
+
:tooltip="!isContextual && 'apostrophe:editWidgetForeignTooltip'"
|
|
51
51
|
:icon-size="11"
|
|
52
52
|
:modifiers="['no-motion']"
|
|
53
53
|
/>
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
:class="ui.controls"
|
|
77
77
|
>
|
|
78
78
|
<AposWidgetControls
|
|
79
|
+
v-if="!foreign"
|
|
79
80
|
:first="i === 0"
|
|
80
81
|
:last="i === next.length - 1"
|
|
81
82
|
:options="{ contextual: isContextual }"
|
|
@@ -272,7 +273,8 @@ export default {
|
|
|
272
273
|
};
|
|
273
274
|
},
|
|
274
275
|
widgetIcon() {
|
|
275
|
-
|
|
276
|
+
const natural = this.contextMenuOptions.menu.filter(item => item.name === this.widget.type)[0]?.icon || 'shape-icon';
|
|
277
|
+
return this.foreign ? 'earth-icon' : natural;
|
|
276
278
|
},
|
|
277
279
|
widgetLabel() {
|
|
278
280
|
const moduleName = `${this.widget.type}-widget`;
|
|
@@ -694,7 +696,7 @@ export default {
|
|
|
694
696
|
|
|
695
697
|
.apos-area-widget__label {
|
|
696
698
|
position: absolute;
|
|
697
|
-
top:
|
|
699
|
+
top: 0;
|
|
698
700
|
right: 0;
|
|
699
701
|
display: flex;
|
|
700
702
|
transform: translateY(-100%);
|
|
@@ -709,20 +711,13 @@ export default {
|
|
|
709
711
|
@include apos-list-reset();
|
|
710
712
|
display: flex;
|
|
711
713
|
align-items: center;
|
|
712
|
-
margin: 0;
|
|
714
|
+
margin: 0 0 8px;
|
|
713
715
|
padding: 4px 6px;
|
|
714
716
|
background-color: var(--a-background-primary);
|
|
715
717
|
border: 1px solid var(--a-primary-transparent-50);
|
|
716
718
|
border-radius: 8px;
|
|
717
719
|
}
|
|
718
720
|
|
|
719
|
-
.apos-area-widget-wrapper--foreign .apos-area-widget-inner .apos-area-widget__breadcrumbs {
|
|
720
|
-
background-color: var(--a-background-inverted);
|
|
721
|
-
& ::v-deep .apos-button__content {
|
|
722
|
-
color: var(--a-text-inverted);
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
|
|
726
721
|
.apos-area-widget__breadcrumb,
|
|
727
722
|
.apos-area-widget__breadcrumb ::v-deep .apos-button__content {
|
|
728
723
|
@include type-help;
|
|
@@ -730,9 +725,6 @@ export default {
|
|
|
730
725
|
white-space: nowrap;
|
|
731
726
|
color: var(--a-base-1);
|
|
732
727
|
transition: background-color 0.3s var(--a-transition-timing-bounce);
|
|
733
|
-
&:hover {
|
|
734
|
-
cursor: pointer;
|
|
735
|
-
}
|
|
736
728
|
}
|
|
737
729
|
|
|
738
730
|
.apos-area-widget__breadcrumbs:hover .apos-area-widget__breadcrumb,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="apos-area-modify-controls">
|
|
3
3
|
<AposButtonGroup
|
|
4
|
-
:modifiers="
|
|
4
|
+
:modifiers="[ 'vertical' ]"
|
|
5
5
|
>
|
|
6
6
|
<AposButton
|
|
7
7
|
v-if="!foreign"
|
|
@@ -124,15 +124,6 @@ export default {
|
|
|
124
124
|
};
|
|
125
125
|
},
|
|
126
126
|
computed: {
|
|
127
|
-
groupModifiers() {
|
|
128
|
-
const mods = [ 'vertical' ];
|
|
129
|
-
|
|
130
|
-
if (this.foreign) {
|
|
131
|
-
mods.push('invert');
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return mods;
|
|
135
|
-
},
|
|
136
127
|
upButton() {
|
|
137
128
|
return {
|
|
138
129
|
...this.buttonDefaults,
|
|
@@ -1153,9 +1153,10 @@ module.exports = {
|
|
|
1153
1153
|
// context: 'update',
|
|
1154
1154
|
// action: 'someAction',
|
|
1155
1155
|
// modal: 'ModalComponent',
|
|
1156
|
-
// label: 'Context Menu Label'
|
|
1156
|
+
// label: 'Context Menu Label',
|
|
1157
|
+
// conditions: ['canEdit']
|
|
1157
1158
|
// }
|
|
1158
|
-
// All properties are required.
|
|
1159
|
+
// All properties are required except conditions.
|
|
1159
1160
|
// The only supported `context` for now is `update`.
|
|
1160
1161
|
// `action` is the operation identifier and should be globally unique.
|
|
1161
1162
|
// Overriding existing custom actions is possible (the last wins).
|
|
@@ -1166,7 +1167,13 @@ module.exports = {
|
|
|
1166
1167
|
// An optional `manuallyPublished` boolean property is supported - if true
|
|
1167
1168
|
// the menu will be shown only for docs which have `autopublish: false` and
|
|
1168
1169
|
// `localized: true` options.
|
|
1170
|
+
// `conditions` defines the needed permission actions to be run on the current doc
|
|
1171
|
+
// in order to display the operation.
|
|
1172
|
+
// It must be an array containing one or multiple of these available values:
|
|
1173
|
+
// 'canPublish', 'canEdit', 'canDismissSubmission', 'canDiscardDraft',
|
|
1174
|
+
// 'canLocalize', 'canArchive', 'canUnpublish', 'canCopy', 'canRestore'.
|
|
1169
1175
|
addContextOperation(moduleName, operation) {
|
|
1176
|
+
validate(operation);
|
|
1170
1177
|
self.contextOperations = [
|
|
1171
1178
|
...self.contextOperations
|
|
1172
1179
|
.filter(op => op.action !== operation.action),
|
|
@@ -1175,6 +1182,39 @@ module.exports = {
|
|
|
1175
1182
|
moduleName
|
|
1176
1183
|
}
|
|
1177
1184
|
];
|
|
1185
|
+
|
|
1186
|
+
function validate ({
|
|
1187
|
+
action, context, label, modal, conditions
|
|
1188
|
+
}) {
|
|
1189
|
+
const allowedConditions = [
|
|
1190
|
+
'canPublish',
|
|
1191
|
+
'canEdit',
|
|
1192
|
+
'canDismissSubmission',
|
|
1193
|
+
'canDiscardDraft',
|
|
1194
|
+
'canLocalize',
|
|
1195
|
+
'canArchive',
|
|
1196
|
+
'canUnpublish',
|
|
1197
|
+
'canCopy',
|
|
1198
|
+
'canRestore'
|
|
1199
|
+
];
|
|
1200
|
+
|
|
1201
|
+
if (!action || !context || !label || !modal) {
|
|
1202
|
+
throw self.apos.error('invalid', 'addContextOperation requires action, context, label and modal properties');
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
if (!conditions) {
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
if (
|
|
1210
|
+
!Array.isArray(conditions) ||
|
|
1211
|
+
conditions.some((perm) => !allowedConditions.includes(perm))
|
|
1212
|
+
) {
|
|
1213
|
+
throw self.apos.error(
|
|
1214
|
+
'invalid', `The conditions property in addContextOperation must be an array containing one or multiple of these values:\n\t${allowedConditions.join('\n\t')}.`
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1178
1218
|
},
|
|
1179
1219
|
getBrowserData(req) {
|
|
1180
1220
|
return {
|
|
@@ -195,15 +195,26 @@ export default {
|
|
|
195
195
|
return menus;
|
|
196
196
|
},
|
|
197
197
|
customOperationsByContext() {
|
|
198
|
-
return this.customOperations.filter(
|
|
199
|
-
|
|
198
|
+
return this.customOperations.filter(({
|
|
199
|
+
manuallyPublished, hasUrl, conditions, context
|
|
200
|
+
}) => {
|
|
201
|
+
if (typeof manuallyPublished === 'boolean' && manuallyPublished !== this.manuallyPublished) {
|
|
200
202
|
return false;
|
|
201
203
|
}
|
|
202
|
-
|
|
204
|
+
|
|
205
|
+
if (typeof hasUrl === 'boolean' && hasUrl !== this.hasUrl) {
|
|
203
206
|
return false;
|
|
204
207
|
}
|
|
205
208
|
|
|
206
|
-
|
|
209
|
+
if (conditions) {
|
|
210
|
+
const notAllowed = conditions.some((action) => !this[action]);
|
|
211
|
+
|
|
212
|
+
if (notAllowed) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return context === 'update' && this.isUpdateOperation;
|
|
207
218
|
});
|
|
208
219
|
},
|
|
209
220
|
moduleName() {
|
|
@@ -133,7 +133,8 @@ export default {
|
|
|
133
133
|
return conditionalFields(
|
|
134
134
|
this.schema,
|
|
135
135
|
this.getFieldsByCategory(followedByCategory),
|
|
136
|
-
|
|
136
|
+
// currentDoc for arrays, docFields for all other editors
|
|
137
|
+
this.currentDoc ? this.currentDoc.data : this.docFields.data,
|
|
137
138
|
this.externalConditionsResults
|
|
138
139
|
);
|
|
139
140
|
},
|
|
@@ -254,14 +254,13 @@ module.exports = {
|
|
|
254
254
|
// This call is atomic with respect to other REST write operations on pages.
|
|
255
255
|
async post(req) {
|
|
256
256
|
self.publicApiCheck(req);
|
|
257
|
-
req.body._position = req.body._position || 'lastChild';
|
|
258
257
|
let targetId = self.apos.launder.string(req.body._targetId);
|
|
259
|
-
let position = self.apos.launder.string(req.body._position);
|
|
258
|
+
let position = self.apos.launder.string(req.body._position || 'lastChild');
|
|
260
259
|
// Here we have to normalize before calling insert because we
|
|
261
260
|
// need the parent page to call newChild(). insert calls again but
|
|
262
261
|
// sees there's no work to be done, so no performance hit
|
|
263
262
|
const normalized = await self.getTargetIdAndPosition(req, null, targetId, position);
|
|
264
|
-
targetId = normalized.targetId;
|
|
263
|
+
targetId = normalized.targetId || '_home';
|
|
265
264
|
position = normalized.position;
|
|
266
265
|
const copyingId = self.apos.launder.id(req.body._copyingId);
|
|
267
266
|
const input = _.omit(req.body, '_targetId', '_position', '_copyingId');
|
|
@@ -273,7 +272,7 @@ module.exports = {
|
|
|
273
272
|
if (req.body._newInstance) {
|
|
274
273
|
// If we're looking for a fresh page instance and aren't saving yet,
|
|
275
274
|
// simply get a new page doc and return it
|
|
276
|
-
const parentPage = await self.findForEditing(req,
|
|
275
|
+
const parentPage = await self.findForEditing(req, self.getIdCriteria(targetId))
|
|
277
276
|
.permission('edit', '@apostrophecms/any-page-type').toObject();
|
|
278
277
|
const newChild = self.newChild(parentPage);
|
|
279
278
|
newChild._previewable = true;
|
|
@@ -281,7 +280,12 @@ module.exports = {
|
|
|
281
280
|
}
|
|
282
281
|
|
|
283
282
|
return self.withLock(req, async () => {
|
|
284
|
-
const targetPage = await self
|
|
283
|
+
const targetPage = await self
|
|
284
|
+
.findForEditing(req, self.getIdCriteria(targetId))
|
|
285
|
+
.ancestors(true)
|
|
286
|
+
.permission('edit')
|
|
287
|
+
.toObject();
|
|
288
|
+
|
|
285
289
|
if (!targetPage) {
|
|
286
290
|
throw self.apos.error('notfound');
|
|
287
291
|
}
|
|
@@ -523,15 +523,29 @@ module.exports = {
|
|
|
523
523
|
const errorsList = [];
|
|
524
524
|
|
|
525
525
|
for (const error of errors) {
|
|
526
|
-
if (
|
|
526
|
+
if (error.path) {
|
|
527
527
|
// `self.isVisible` will only throw for required fields that have
|
|
528
528
|
// an external condition containing an unknown module or method:
|
|
529
529
|
const isVisible = await self.isVisible(req, schema, destination, error.path);
|
|
530
530
|
|
|
531
531
|
if (!isVisible) {
|
|
532
|
-
// It is not reasonable to enforce required
|
|
533
|
-
//
|
|
534
|
-
|
|
532
|
+
// It is not reasonable to enforce required,
|
|
533
|
+
// min, max or anything else for fields
|
|
534
|
+
// hidden via "if" as the user cannot correct it
|
|
535
|
+
// and it will not be used. If the user changes
|
|
536
|
+
// the conditional field later then they won't
|
|
537
|
+
// be able to save until the erroneous field
|
|
538
|
+
// is corrected
|
|
539
|
+
const name = error.path;
|
|
540
|
+
const field = schema.find(field => field.name === name);
|
|
541
|
+
if (field) {
|
|
542
|
+
// To protect against security issues, an invalid value
|
|
543
|
+
// for a field that is not visible should be quietly discarded.
|
|
544
|
+
// We only worry about this if the value is not valid, as otherwise
|
|
545
|
+
// it's a kindness to save the work so the user can toggle back to it
|
|
546
|
+
destination[field.name] = klona((field.def !== undefined) ? field.def : self.fieldTypes[field.type]?.def);
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
535
549
|
}
|
|
536
550
|
}
|
|
537
551
|
|