apostrophe 4.7.0 → 4.8.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 +38 -0
- package/index.js +1 -1
- package/lib/moog.js +1 -1
- package/modules/@apostrophecms/admin-bar/index.js +61 -2
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBreakpointPreviewMode.vue +166 -0
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextTitle.vue +25 -0
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +3 -12
- package/modules/@apostrophecms/asset/index.js +41 -1
- package/modules/@apostrophecms/asset/lib/globalIcons.js +6 -0
- package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.scss.js +16 -16
- package/modules/@apostrophecms/asset/lib/webpack/media-to-container-queries-loader.js +94 -0
- package/modules/@apostrophecms/asset/lib/webpack/src/webpack.scss.js +12 -0
- package/modules/@apostrophecms/attachment/index.js +8 -1
- package/modules/@apostrophecms/command-menu/ui/apos/components/AposCommandMenuKey.vue +1 -1
- package/modules/@apostrophecms/command-menu/ui/apos/components/AposCommandMenuShortcut.vue +5 -2
- package/modules/@apostrophecms/doc-type/index.js +0 -1
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +1 -19
- package/modules/@apostrophecms/i18n/i18n/en.json +12 -1
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaUploader.vue +9 -18
- package/modules/@apostrophecms/migration/index.js +20 -13
- package/modules/@apostrophecms/migration/lib/addMissingSchemaFields.js +182 -0
- package/modules/@apostrophecms/page/index.js +19 -0
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposRelationshipEditor.vue +2 -7
- package/modules/@apostrophecms/rich-text-widget/index.js +66 -0
- package/modules/@apostrophecms/schema/index.js +20 -29
- package/modules/@apostrophecms/schema/lib/addFieldTypes.js +2 -27
- package/modules/@apostrophecms/schema/lib/newInstance.js +36 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +238 -80
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +34 -24
- package/modules/@apostrophecms/schema/ui/apos/logic/AposArrayEditor.js +2 -7
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js +207 -44
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRelationship.js +3 -14
- package/modules/@apostrophecms/schema/ui/apos/scss/AposInputArray.scss +288 -105
- package/modules/@apostrophecms/template/views/outerLayoutBase.html +1 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +2 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue +9 -6
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuDialog.vue +8 -6
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuItem.vue +52 -51
- package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +16 -6
- package/modules/@apostrophecms/ui/ui/apos/components/AposIndicator.vue +1 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposLocalePicker.vue +6 -4
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlatList.vue +6 -2
- package/modules/@apostrophecms/ui/ui/apos/components/AposTagApply.vue +8 -6
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_breakpoint_preview.scss +38 -0
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_tables.scss +3 -1
- package/modules/@apostrophecms/ui/ui/apos/scss/global/import-all.scss +1 -0
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +2 -13
- package/package.json +3 -2
- package/test/add-missing-schema-fields.js +323 -0
- package/test/content-i18n.js +1 -1
- package/test/events2.js +4 -2
- package/test/pages.js +10 -0
- package/test/parked-pages.js +75 -0
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
<div>
|
|
3
3
|
<label
|
|
4
4
|
class="apos-input-wrapper apos-file-dropzone"
|
|
5
|
+
tabindex="0"
|
|
5
6
|
:class="{
|
|
6
7
|
'apos-file-dropzone--dragover': dragging,
|
|
7
8
|
'apos-is-disabled': disabled || fileOrAttachment
|
|
8
9
|
}"
|
|
10
|
+
@keydown="handleKeydown"
|
|
9
11
|
@drop.prevent="uploadFile"
|
|
10
12
|
@dragover="dragHandler"
|
|
11
13
|
@dragleave="dragging = false"
|
|
@@ -29,6 +31,7 @@
|
|
|
29
31
|
class="apos-sr-only"
|
|
30
32
|
:disabled="disabled || fileOrAttachment"
|
|
31
33
|
:accept="allowedExtensions"
|
|
34
|
+
tabindex="-1"
|
|
32
35
|
@input="uploadFile"
|
|
33
36
|
>
|
|
34
37
|
</label>
|
|
@@ -101,6 +104,15 @@ export default {
|
|
|
101
104
|
}
|
|
102
105
|
},
|
|
103
106
|
methods: {
|
|
107
|
+
handleKeydown(event) {
|
|
108
|
+
switch (event.key) {
|
|
109
|
+
case ' ':
|
|
110
|
+
case 'Enter':
|
|
111
|
+
event.preventDefault();
|
|
112
|
+
this.$refs.uploadField.click();
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
},
|
|
104
116
|
async uploadFile ({ target, dataTransfer }) {
|
|
105
117
|
this.dragging = false;
|
|
106
118
|
const [ file ] = target.files ? target.files : (dataTransfer.files || []);
|
|
@@ -174,14 +186,12 @@ export default {
|
|
|
174
186
|
transition: all 200ms ease;
|
|
175
187
|
}
|
|
176
188
|
|
|
177
|
-
&:hover
|
|
178
|
-
border-color: var(--a-primary);
|
|
179
|
-
background-color: var(--a-base-10);
|
|
180
|
-
}
|
|
181
|
-
|
|
189
|
+
&:hover,
|
|
182
190
|
&:active,
|
|
183
191
|
&:focus {
|
|
184
|
-
border:
|
|
192
|
+
border-color: var(--a-primary);
|
|
193
|
+
background-color: var(--a-base-10);
|
|
194
|
+
outline: none;
|
|
185
195
|
}
|
|
186
196
|
|
|
187
197
|
&.apos-is-disabled {
|
|
@@ -151,10 +151,12 @@ function getForbiddenTooltip(locale) {
|
|
|
151
151
|
.apos-locale-picker__locale-display {
|
|
152
152
|
@include apos-button-reset();
|
|
153
153
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
154
|
+
& {
|
|
155
|
+
display: block;
|
|
156
|
+
box-sizing: border-box;
|
|
157
|
+
width: 100%;
|
|
158
|
+
padding: 12px 35px;
|
|
159
|
+
}
|
|
158
160
|
|
|
159
161
|
&:focus, &:active {
|
|
160
162
|
outline: none;
|
|
@@ -132,6 +132,8 @@ export default {
|
|
|
132
132
|
oldIndex, newIndex
|
|
133
133
|
}) {
|
|
134
134
|
this.next.splice(newIndex, 0, this.next.splice(oldIndex, 1)[0]);
|
|
135
|
+
// FIX: swapping the items does not trigger the watcher
|
|
136
|
+
this.next = this.next.slice();
|
|
135
137
|
},
|
|
136
138
|
engage(id) {
|
|
137
139
|
this.engaged = id;
|
|
@@ -199,8 +201,10 @@ export default {
|
|
|
199
201
|
.apos-slat-list {
|
|
200
202
|
@include apos-list-reset();
|
|
201
203
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
+
& {
|
|
205
|
+
min-height: 20px;
|
|
206
|
+
max-width: $input-max-width;
|
|
207
|
+
}
|
|
204
208
|
}
|
|
205
209
|
|
|
206
210
|
.apos-slat-status {
|
|
@@ -317,12 +317,14 @@ export default {
|
|
|
317
317
|
.apos-apply-tag-menu__tags {
|
|
318
318
|
@include apos-list-reset();
|
|
319
319
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
320
|
+
& {
|
|
321
|
+
max-height: 160px;
|
|
322
|
+
overflow-y: auto;
|
|
323
|
+
margin-top: 15px;
|
|
324
|
+
// Negative margin/padding below is for the checkbox focus state.
|
|
325
|
+
margin-left: -10px;
|
|
326
|
+
padding-left: 10px;
|
|
327
|
+
}
|
|
326
328
|
}
|
|
327
329
|
|
|
328
330
|
.apos-apply-tag-menu__tag {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[data-apos-context-label] {
|
|
2
|
+
@include type-help;
|
|
3
|
+
|
|
4
|
+
& {
|
|
5
|
+
position: relative;
|
|
6
|
+
top: $spacing-base * 5;
|
|
7
|
+
display: none;
|
|
8
|
+
text-align: center;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
body[data-breakpoint-preview-mode] {
|
|
13
|
+
background: var(--a-base-10);
|
|
14
|
+
|
|
15
|
+
[data-apos-context-label] {
|
|
16
|
+
display: block;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
[data-apos-refreshable] {
|
|
20
|
+
container-type: size;
|
|
21
|
+
overflow: clip scroll;
|
|
22
|
+
flex-grow: unset;
|
|
23
|
+
margin: $spacing-base * 6 auto auto;
|
|
24
|
+
border: 1px solid var(--a-base-6);
|
|
25
|
+
box-shadow: 0 0 12px 0 var(--a-base-7);
|
|
26
|
+
background-color: var(--a-background-primary);
|
|
27
|
+
|
|
28
|
+
&[data-resizable="false"] {
|
|
29
|
+
transition: width 400ms ease, height 400ms ease;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&[data-resizable="true"] {
|
|
33
|
+
resize: both;
|
|
34
|
+
overflow: scroll;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
}
|
|
@@ -67,6 +67,7 @@ import AposModalTabsMixin from 'Modules/@apostrophecms/modal/mixins/AposModalTab
|
|
|
67
67
|
import { detectDocChange } from 'Modules/@apostrophecms/schema/lib/detectChange';
|
|
68
68
|
import { createId } from '@paralleldrive/cuid2';
|
|
69
69
|
import { klona } from 'klona';
|
|
70
|
+
import newInstance from 'apostrophe/modules/@apostrophecms/schema/lib/newInstance.js';
|
|
70
71
|
|
|
71
72
|
export default {
|
|
72
73
|
name: 'AposWidgetEditor',
|
|
@@ -234,19 +235,7 @@ export default {
|
|
|
234
235
|
});
|
|
235
236
|
},
|
|
236
237
|
getDefault() {
|
|
237
|
-
|
|
238
|
-
this.schema.forEach(field => {
|
|
239
|
-
if (field.name.startsWith('_')) {
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
// Using `hasOwn` here, not simply checking if `field.def` is truthy
|
|
243
|
-
// so that `false`, `null`, `''` or `0` are taken into account:
|
|
244
|
-
const hasDefaultValue = Object.hasOwn(field, 'def');
|
|
245
|
-
widget[field.name] = hasDefaultValue
|
|
246
|
-
? klona(field.def)
|
|
247
|
-
: null;
|
|
248
|
-
});
|
|
249
|
-
return widget;
|
|
238
|
+
return newInstance(this.schema);
|
|
250
239
|
},
|
|
251
240
|
getAposSchema(field) {
|
|
252
241
|
return this.$refs[field.group.name][0];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apostrophe",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.8.0",
|
|
4
4
|
"description": "The Apostrophe Content Management System.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -100,6 +100,7 @@
|
|
|
100
100
|
"path-to-regexp": "^1.8.0",
|
|
101
101
|
"performance-now": "^2.1.0",
|
|
102
102
|
"pinia": "^2.1.7",
|
|
103
|
+
"postcss": "^8.4.47",
|
|
103
104
|
"postcss-html": "^1.3.0",
|
|
104
105
|
"postcss-loader": "^5.0.0",
|
|
105
106
|
"postcss-scss": "^4.0.3",
|
|
@@ -140,7 +141,7 @@
|
|
|
140
141
|
"nyc": "^15.1.0",
|
|
141
142
|
"replace-in-file": "^6.1.0",
|
|
142
143
|
"stylelint": "^16.5.0",
|
|
143
|
-
"stylelint-config-apostrophe": "4.
|
|
144
|
+
"stylelint-config-apostrophe": "^4.2.0",
|
|
144
145
|
"vue-eslint-parser": "^7.1.1",
|
|
145
146
|
"vue-template-compiler": "^2.7.14"
|
|
146
147
|
},
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
const t = require('../test-lib/test.js');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
|
|
5
|
+
const cleanup = [];
|
|
6
|
+
|
|
7
|
+
after(async () => {
|
|
8
|
+
for (const apos of cleanup) {
|
|
9
|
+
await t.destroy(apos);
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('add missing schema fields', function() {
|
|
14
|
+
|
|
15
|
+
this.timeout(t.timeout);
|
|
16
|
+
|
|
17
|
+
it('first generation & sanity checks', async function() {
|
|
18
|
+
const apos = await t.create({
|
|
19
|
+
root: module,
|
|
20
|
+
shortName: 'test-amsf',
|
|
21
|
+
modules: {
|
|
22
|
+
product: {
|
|
23
|
+
extend: '@apostrophecms/piece-type',
|
|
24
|
+
options: {
|
|
25
|
+
alias: 'product'
|
|
26
|
+
},
|
|
27
|
+
fields: {
|
|
28
|
+
add: {
|
|
29
|
+
price: {
|
|
30
|
+
type: 'integer',
|
|
31
|
+
required: true,
|
|
32
|
+
def: 10
|
|
33
|
+
},
|
|
34
|
+
body: {
|
|
35
|
+
type: 'area',
|
|
36
|
+
widgets: {
|
|
37
|
+
hero: {},
|
|
38
|
+
'@apostrophecms/rich-text': {}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
'hero-widget': {
|
|
45
|
+
extend: '@apostrophecms/widget-type',
|
|
46
|
+
fields: {
|
|
47
|
+
add: {
|
|
48
|
+
label: {
|
|
49
|
+
type: 'string'
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
cleanup.push(apos);
|
|
57
|
+
const product = apos.product.newInstance();
|
|
58
|
+
product.title = 'Product 1';
|
|
59
|
+
// Intentionally not equal to the default
|
|
60
|
+
product.price = 15;
|
|
61
|
+
product.body = {
|
|
62
|
+
metaType: 'area',
|
|
63
|
+
items: [
|
|
64
|
+
{
|
|
65
|
+
type: 'hero',
|
|
66
|
+
label: 'Sample Label'
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
};
|
|
70
|
+
const req = apos.task.getReq();
|
|
71
|
+
await apos.product.insert(req, product);
|
|
72
|
+
const products = await apos.doc.db.find({
|
|
73
|
+
type: 'product'
|
|
74
|
+
}).sort({
|
|
75
|
+
aposMode: 1
|
|
76
|
+
}).toArray();
|
|
77
|
+
assert.strictEqual(products.length, 2);
|
|
78
|
+
for (const product of products) {
|
|
79
|
+
// Not a thing yet
|
|
80
|
+
assert.strictEqual(product.code, undefined);
|
|
81
|
+
assert.strictEqual(product.title, 'Product 1');
|
|
82
|
+
assert.strictEqual(product.price, 15);
|
|
83
|
+
assert.strictEqual(product.body.items[0].label, 'Sample Label');
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('second generation schema (objects, arrays, new widget fields)', async function() {
|
|
88
|
+
const apos = await t.create({
|
|
89
|
+
root: module,
|
|
90
|
+
// Same on purpose so we reuse the database
|
|
91
|
+
shortName: 'test-amsf',
|
|
92
|
+
modules: {
|
|
93
|
+
product: {
|
|
94
|
+
extend: '@apostrophecms/piece-type',
|
|
95
|
+
options: {
|
|
96
|
+
alias: 'product'
|
|
97
|
+
},
|
|
98
|
+
fields: {
|
|
99
|
+
add: {
|
|
100
|
+
price: {
|
|
101
|
+
type: 'float',
|
|
102
|
+
required: true
|
|
103
|
+
},
|
|
104
|
+
body: {
|
|
105
|
+
type: 'area',
|
|
106
|
+
widgets: {
|
|
107
|
+
hero: {},
|
|
108
|
+
'@apostrophecms/rich-text': {}
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
code: {
|
|
112
|
+
type: 'integer',
|
|
113
|
+
required: true,
|
|
114
|
+
def: 20
|
|
115
|
+
},
|
|
116
|
+
attitude: {
|
|
117
|
+
// Test the fallback default of the type
|
|
118
|
+
type: 'string'
|
|
119
|
+
},
|
|
120
|
+
addresses: {
|
|
121
|
+
type: 'array',
|
|
122
|
+
fields: {
|
|
123
|
+
add: {
|
|
124
|
+
street: {
|
|
125
|
+
type: 'string'
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
flavors: {
|
|
131
|
+
type: 'object',
|
|
132
|
+
fields: {
|
|
133
|
+
add: {
|
|
134
|
+
chocolate: {
|
|
135
|
+
type: 'boolean',
|
|
136
|
+
def: true
|
|
137
|
+
},
|
|
138
|
+
vanilla: {
|
|
139
|
+
type: 'boolean',
|
|
140
|
+
def: false
|
|
141
|
+
},
|
|
142
|
+
strawberry: {
|
|
143
|
+
type: 'boolean'
|
|
144
|
+
// should get fallback default
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
'hero-widget': {
|
|
153
|
+
extend: '@apostrophecms/widget-type',
|
|
154
|
+
fields: {
|
|
155
|
+
add: {
|
|
156
|
+
label: {
|
|
157
|
+
type: 'string'
|
|
158
|
+
},
|
|
159
|
+
// New properties, with defaults
|
|
160
|
+
sublabel: {
|
|
161
|
+
type: 'string',
|
|
162
|
+
def: 'Default Sublabel'
|
|
163
|
+
},
|
|
164
|
+
specialized: {
|
|
165
|
+
type: 'boolean',
|
|
166
|
+
def: false
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
cleanup.push(apos);
|
|
174
|
+
const products = await apos.doc.db.find({
|
|
175
|
+
type: 'product'
|
|
176
|
+
}).sort({
|
|
177
|
+
aposMode: 1
|
|
178
|
+
}).toArray();
|
|
179
|
+
assert.strictEqual(products.length, 2);
|
|
180
|
+
for (const product of products) {
|
|
181
|
+
assert.strictEqual(product.code, 20);
|
|
182
|
+
assert.strictEqual(product.title, 'Product 1');
|
|
183
|
+
// Still unchanged from existing non-default value
|
|
184
|
+
assert.strictEqual(product.price, 15);
|
|
185
|
+
assert.strictEqual(product.attitude, '');
|
|
186
|
+
assert(_.isEqual(product.addresses, []));
|
|
187
|
+
assert.strictEqual(product.flavors.chocolate, true);
|
|
188
|
+
assert.strictEqual(product.flavors.vanilla, false);
|
|
189
|
+
// Type has no fallback default (update test if this changes)
|
|
190
|
+
assert.strictEqual(product.flavors.strawberry, undefined);
|
|
191
|
+
assert.strictEqual(product.body.items[0].label, 'Sample Label');
|
|
192
|
+
assert.strictEqual(product.body.items[0].sublabel, 'Default Sublabel');
|
|
193
|
+
assert.strictEqual(product.body.items[0].specialized, false);
|
|
194
|
+
}
|
|
195
|
+
const product = products.find(product => product.aposMode === 'draft');
|
|
196
|
+
const req = apos.task.getReq({
|
|
197
|
+
mode: 'draft'
|
|
198
|
+
});
|
|
199
|
+
product.addresses.push({
|
|
200
|
+
street: '1168 E Passyunk Ave'
|
|
201
|
+
});
|
|
202
|
+
const result = await apos.product.update(req, product);
|
|
203
|
+
await apos.product.publish(req, result);
|
|
204
|
+
assert.strictEqual(result.addresses.length, 1);
|
|
205
|
+
assert.strictEqual(result.addresses[0].street, '1168 E Passyunk Ave');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('third generation schema (new array fields & verify new defaults do not crush existing values)', async function() {
|
|
209
|
+
const apos = await t.create({
|
|
210
|
+
root: module,
|
|
211
|
+
// Same on purpose so we reuse the database
|
|
212
|
+
shortName: 'test-amsf',
|
|
213
|
+
modules: {
|
|
214
|
+
product: {
|
|
215
|
+
extend: '@apostrophecms/piece-type',
|
|
216
|
+
options: {
|
|
217
|
+
alias: 'product'
|
|
218
|
+
},
|
|
219
|
+
fields: {
|
|
220
|
+
add: {
|
|
221
|
+
price: {
|
|
222
|
+
type: 'float',
|
|
223
|
+
required: true
|
|
224
|
+
},
|
|
225
|
+
body: {
|
|
226
|
+
type: 'area',
|
|
227
|
+
widgets: {
|
|
228
|
+
hero: {},
|
|
229
|
+
'@apostrophecms/rich-text': {}
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
code: {
|
|
233
|
+
type: 'integer',
|
|
234
|
+
required: true,
|
|
235
|
+
def: 20
|
|
236
|
+
},
|
|
237
|
+
addresses: {
|
|
238
|
+
type: 'array',
|
|
239
|
+
fields: {
|
|
240
|
+
add: {
|
|
241
|
+
street: {
|
|
242
|
+
type: 'string'
|
|
243
|
+
},
|
|
244
|
+
city: {
|
|
245
|
+
type: 'string',
|
|
246
|
+
def: 'Philadelphia'
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
flavors: {
|
|
252
|
+
type: 'object',
|
|
253
|
+
fields: {
|
|
254
|
+
add: {
|
|
255
|
+
chocolate: {
|
|
256
|
+
type: 'boolean',
|
|
257
|
+
def: true
|
|
258
|
+
},
|
|
259
|
+
vanilla: {
|
|
260
|
+
type: 'boolean',
|
|
261
|
+
// Verify change of default does NOT mess up an existing property
|
|
262
|
+
def: true
|
|
263
|
+
},
|
|
264
|
+
strawberry: {
|
|
265
|
+
type: 'boolean'
|
|
266
|
+
// should get fallback default
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
'hero-widget': {
|
|
275
|
+
extend: '@apostrophecms/widget-type',
|
|
276
|
+
fields: {
|
|
277
|
+
add: {
|
|
278
|
+
label: {
|
|
279
|
+
type: 'string'
|
|
280
|
+
},
|
|
281
|
+
// New properties, with defaults
|
|
282
|
+
sublabel: {
|
|
283
|
+
type: 'string',
|
|
284
|
+
def: 'Default Sublabel'
|
|
285
|
+
},
|
|
286
|
+
specialized: {
|
|
287
|
+
type: 'boolean',
|
|
288
|
+
def: false
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
cleanup.push(apos);
|
|
296
|
+
const products = await apos.doc.db.find({
|
|
297
|
+
type: 'product',
|
|
298
|
+
aposMode: {
|
|
299
|
+
$in: [ 'draft', 'published' ]
|
|
300
|
+
}
|
|
301
|
+
}).sort({
|
|
302
|
+
aposMode: 1
|
|
303
|
+
}).toArray();
|
|
304
|
+
assert.strictEqual(products.length, 2);
|
|
305
|
+
for (const product of products) {
|
|
306
|
+
assert.strictEqual(product.code, 20);
|
|
307
|
+
assert.strictEqual(product.title, 'Product 1');
|
|
308
|
+
assert.strictEqual(product.addresses.length, 1);
|
|
309
|
+
// Should NOT change because it already has a value
|
|
310
|
+
assert.strictEqual(product.flavors.vanilla, false);
|
|
311
|
+
assert.strictEqual(product.addresses[0].street, '1168 E Passyunk Ave');
|
|
312
|
+
assert.strictEqual(product.addresses[0].city, 'Philadelphia');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Direct invocation to make sure an additional invocation with the same schemas
|
|
316
|
+
// does no new work
|
|
317
|
+
|
|
318
|
+
const { scans, updates } = await apos.migration.addMissingSchemaFields();
|
|
319
|
+
assert.strictEqual(scans, 0);
|
|
320
|
+
assert.strictEqual(updates, 0);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
});
|
package/test/content-i18n.js
CHANGED
package/test/events2.js
CHANGED
|
@@ -45,10 +45,12 @@ describe('Promisified Events: @apostrophecms/doc-type:beforeInsert', function()
|
|
|
45
45
|
{
|
|
46
46
|
type: 'default-page',
|
|
47
47
|
findMeAgain: true,
|
|
48
|
-
title: 'Test',
|
|
49
48
|
slug: '/test',
|
|
50
49
|
visibility: 'public',
|
|
51
|
-
parkedId: 'test'
|
|
50
|
+
parkedId: 'test',
|
|
51
|
+
_defaults: {
|
|
52
|
+
title: 'Test'
|
|
53
|
+
}
|
|
52
54
|
}
|
|
53
55
|
]
|
|
54
56
|
}
|
package/test/pages.js
CHANGED
|
@@ -421,6 +421,16 @@ describe('Pages', function() {
|
|
|
421
421
|
assert.strictEqual(page.rank, 1);
|
|
422
422
|
});
|
|
423
423
|
|
|
424
|
+
it('is not able to move a page under itself', async function() {
|
|
425
|
+
await assert.rejects(
|
|
426
|
+
apos.page.move(apos.task.getReq(), 'cousin:en:published', 'cousin:en:published', 'lastChild'),
|
|
427
|
+
{
|
|
428
|
+
name: 'forbidden',
|
|
429
|
+
message: 'Cannot move a page under itself'
|
|
430
|
+
}
|
|
431
|
+
);
|
|
432
|
+
});
|
|
433
|
+
|
|
424
434
|
it('is able to move root/cousin before root/parent/child', async function() {
|
|
425
435
|
// 'Cousin' _id === 4312
|
|
426
436
|
// 'Child' _id === 2341
|
package/test/parked-pages.js
CHANGED
|
@@ -271,6 +271,81 @@ describe('Parked Pages', function() {
|
|
|
271
271
|
});
|
|
272
272
|
await validate(apos6, [ '/', '/archive', '/default1', '/default2', '/default3', '/default3/child1' ]);
|
|
273
273
|
});
|
|
274
|
+
|
|
275
|
+
it('field override on save is possible only when it is configured as default', async function () {
|
|
276
|
+
this.timeout(20000);
|
|
277
|
+
await t.destroy(apos6);
|
|
278
|
+
apos6 = await t.create({
|
|
279
|
+
root: module,
|
|
280
|
+
modules: {
|
|
281
|
+
'@apostrophecms/page': {
|
|
282
|
+
options: {
|
|
283
|
+
park: [
|
|
284
|
+
...park2,
|
|
285
|
+
{
|
|
286
|
+
parkedId: 'default3',
|
|
287
|
+
type: 'default-page',
|
|
288
|
+
title: 'Default 3',
|
|
289
|
+
slug: '/default3'
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
parkedId: 'default4',
|
|
293
|
+
type: 'default-page',
|
|
294
|
+
title: 'Default 4',
|
|
295
|
+
_defaults: {
|
|
296
|
+
slug: '/default4'
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
]
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
'default-page': {}
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
const manager = apos6.doc.getManager('default-page');
|
|
306
|
+
const req = apos.task.getReq({ mode: 'draft' });
|
|
307
|
+
|
|
308
|
+
// slug override not possible, slug is NOT configured in defaults
|
|
309
|
+
{
|
|
310
|
+
const page = await manager.find(req, {
|
|
311
|
+
parkedId: 'default3',
|
|
312
|
+
aposMode: 'draft'
|
|
313
|
+
}).toObject();
|
|
314
|
+
assert.strictEqual(page.slug, '/default3');
|
|
315
|
+
assert.deepStrictEqual(page.parked, [ 'parkedId', 'type', 'title', 'slug' ]);
|
|
316
|
+
|
|
317
|
+
page.slug = '/default3-overridden';
|
|
318
|
+
await manager.update(req, page);
|
|
319
|
+
const updated = await manager.find(req, {
|
|
320
|
+
parkedId: 'default3',
|
|
321
|
+
aposMode: 'draft'
|
|
322
|
+
}).toObject();
|
|
323
|
+
|
|
324
|
+
assert.strictEqual(updated.slug, '/default3');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// slug override is possible because slug is configured in defaults
|
|
328
|
+
{
|
|
329
|
+
const page = await manager.find(req, {
|
|
330
|
+
parkedId: 'default4',
|
|
331
|
+
aposMode: 'draft'
|
|
332
|
+
}).toObject();
|
|
333
|
+
assert.strictEqual(page.slug, '/default4');
|
|
334
|
+
assert.deepStrictEqual(page.parked, [ 'parkedId', 'type', 'title' ]);
|
|
335
|
+
|
|
336
|
+
page.slug = '/default4-overridden';
|
|
337
|
+
await manager.update(req, page);
|
|
338
|
+
const updated = await manager.find(req, {
|
|
339
|
+
parkedId: 'default4',
|
|
340
|
+
aposMode: 'draft'
|
|
341
|
+
}).toObject();
|
|
342
|
+
|
|
343
|
+
assert.strictEqual(updated.slug, '/default4-overridden');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
await t.destroy(apos6);
|
|
347
|
+
apos6 = null;
|
|
348
|
+
});
|
|
274
349
|
});
|
|
275
350
|
|
|
276
351
|
async function validate(apos, expected) {
|