apostrophe 4.3.1 → 4.3.3

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 CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.3.3 (2024-06-04)
4
+
5
+ * Removes `$nextTick` use to re render schema in `AposArrayEditor` because it was triggering weird vue error in production.
6
+ Instead, makes the AposSchema for loop keys more unique using `modelValue.data._id`,
7
+ if document changes it re-renders schema fields.
8
+
9
+ ## 4.3.2 (2024-05-18)
10
+
11
+ ### Fixes
12
+
13
+ * Corrects a regression introduced in version 4.3.0 that broke the validation of widget modals, resulting in a confusing
14
+ error on the page. A "required" field in a widget, for instance, once again blocks the save operation properly.
15
+
16
+ ### Changes
17
+
18
+ * Improves widget tab UI for the hidden entries, improves UX when validation errors are present in non-focused tabs.
19
+
3
20
  ## 4.3.1 (2024-05-17)
4
21
 
5
22
  ### Fixes
@@ -18,21 +18,22 @@
18
18
  v-if="tabErrors[tab.name] && tabErrors[tab.name].length"
19
19
  class="apos-modal-tabs__label apos-modal-tabs__label--error"
20
20
  >
21
- {{ tabErrors[tab.name].length }} {{ generateErrorLabel(tabErrors[tab.name].length) }}
21
+ {{ tabErrors[tab.name].length }} {{ generateErrorLabel(tabErrors[tab.name].length) }}
22
22
  </span>
23
23
  </button>
24
24
  </li>
25
25
  <li
26
- v-if="hiddenTabs.length"
26
+ v-if="menuTabs.length"
27
27
  key="placeholder-for-hidden-tabs"
28
28
  class="apos-modal-tabs__tab apos-modal-tabs__tab--small"
29
29
  />
30
30
  </ul>
31
31
  <AposContextMenu
32
- v-if="hiddenTabs.length"
33
- :menu="hiddenTabs"
32
+ v-if="menuTabs.length"
33
+ :menu="menuTabs"
34
34
  menu-placement="bottom-end"
35
35
  :button="moreMenuButton"
36
+ data-apos-test="context-menu-tabs"
36
37
  @item-clicked="moreMenuHandler($event)"
37
38
  />
38
39
  </div>
@@ -64,7 +65,6 @@ export default {
64
65
  emits: [ 'select-tab' ],
65
66
  data() {
66
67
  const visibleTabs = [];
67
- const hiddenTabs = [];
68
68
 
69
69
  for (let i = 0; i < this.tabs.length; i++) {
70
70
  // Shallow clone is sufficient to make mutating
@@ -73,14 +73,11 @@ export default {
73
73
  tab.action = tab.name;
74
74
  if (i < 5) {
75
75
  visibleTabs.push(tab);
76
- } else {
77
- hiddenTabs.push(tab);
78
76
  }
79
77
  }
80
78
 
81
79
  return {
82
80
  visibleTabs,
83
- hiddenTabs,
84
81
  moreMenuButton: {
85
82
  icon: 'dots-vertical-icon',
86
83
  iconOnly: true,
@@ -100,6 +97,25 @@ export default {
100
97
  }
101
98
  }
102
99
  return errors;
100
+ },
101
+ menuTabs() {
102
+ return this.tabs.map((tab) => {
103
+ const modifiers = [];
104
+ if (tab.name === this.current) {
105
+ modifiers.push('selected');
106
+ if (!this.tabErrors[tab.name] || !this.tabErrors[tab.name].length) {
107
+ modifiers.push('primary');
108
+ }
109
+ }
110
+ if (this.tabErrors[tab.name] && this.tabErrors[tab.name].length) {
111
+ modifiers.push('danger');
112
+ }
113
+ return {
114
+ ...tab,
115
+ action: tab.name,
116
+ modifiers
117
+ };
118
+ });
103
119
  }
104
120
  },
105
121
  methods: {
@@ -116,12 +132,6 @@ export default {
116
132
  this.$emit('select-tab', id);
117
133
  },
118
134
  moreMenuHandler(item) {
119
- const lastVisibleTab = this.visibleTabs[this.visibleTabs.length - 1];
120
- const selectedItem = this.hiddenTabs.find((tab) => tab.name === item);
121
-
122
- this.hiddenTabs.splice(this.hiddenTabs.indexOf(selectedItem), 1, lastVisibleTab);
123
- this.visibleTabs.splice(this.visibleTabs.length - 1, 1, selectedItem);
124
-
125
135
  this.$emit('select-tab', item);
126
136
  }
127
137
  }
@@ -219,6 +229,7 @@ export default {
219
229
 
220
230
  .apos-modal-tabs__label--error {
221
231
  border: 1px solid var(--a-danger);
232
+ margin-left: 5px;
222
233
  }
223
234
 
224
235
  .apos-modal-tabs__btn {
@@ -32,7 +32,7 @@
32
32
  <component
33
33
  :is="fieldStyle === 'table' ? 'td' : 'div'"
34
34
  v-for="field in schema"
35
- :key="field.name.concat(field._id ?? '')"
35
+ :key="generateItemUniqueKey(field)"
36
36
  :data-apos-field="field.name"
37
37
  :style="(fieldStyle === 'table' && field.columnStyle) || {}"
38
38
  :class="{'apos-field--hidden': !displayComponent(field)}"
@@ -149,7 +149,7 @@ export default {
149
149
  const [ _id, name ] = first.path.split('.');
150
150
  await this.select(_id);
151
151
  const aposSchema = this.$refs.schema;
152
- await this.nextTick();
152
+ await this.$nextTick();
153
153
  aposSchema.scrollFieldIntoView(name);
154
154
  }
155
155
  this.titleFieldChoices = await this.getTitleFieldChoices();
@@ -160,11 +160,7 @@ export default {
160
160
  return;
161
161
  }
162
162
  if (await this.validate(true, false)) {
163
- // Force the array editor to totally reset to avoid in-schema
164
- // animations when switching (e.g., the relationship input).
165
163
  this.currentDocToCurrentItem();
166
- this.currentId = null;
167
- await this.nextTick();
168
164
  this.currentId = _id;
169
165
  this.currentDoc = {
170
166
  hasErrors: false,
@@ -261,7 +257,7 @@ export default {
261
257
  if (validateItem) {
262
258
  this.triggerValidation = true;
263
259
  }
264
- await this.nextTick();
260
+ await this.$nextTick();
265
261
  if (validateLength) {
266
262
  this.updateMinMax();
267
263
  }
@@ -279,14 +275,6 @@ export default {
279
275
  return true;
280
276
  }
281
277
  },
282
- // Awaitable nextTick
283
- nextTick() {
284
- return new Promise((resolve, reject) => {
285
- this.$nextTick(() => {
286
- return resolve();
287
- });
288
- });
289
- },
290
278
  newInstance() {
291
279
  const instance = {};
292
280
  for (const field of this.schema) {
@@ -181,7 +181,7 @@ export default {
181
181
  this.populateDocData();
182
182
  }
183
183
  },
184
- generation() {
184
+ generation(generated) {
185
185
  // repopulate the schema.
186
186
  this.populateDocData();
187
187
  },
@@ -331,6 +331,10 @@ export default {
331
331
  },
332
332
  highlight(fieldName) {
333
333
  return this.meta[fieldName]?.['@apostrophecms/schema:highlight'];
334
+ },
335
+ generateItemUniqueKey(field) {
336
+ return `${field.name}:${field._id ?? ''}:${this.modelValue?.data?._id ?? ''}`;
337
+
334
338
  }
335
339
  }
336
340
  };
@@ -4,6 +4,7 @@
4
4
  class="apos-context-menu__button"
5
5
  :class="modifiers"
6
6
  :tabindex="tabindex"
7
+ data-apos-test="context-menu-item"
7
8
  @click="click"
8
9
  >
9
10
  {{ $t(label) }}
@@ -91,6 +92,15 @@ export default {
91
92
  }
92
93
  }
93
94
 
95
+ &--primary {
96
+ color: var(--a-primary);
97
+ &:hover,
98
+ &:focus,
99
+ &:active {
100
+ color: var(--a-primary);
101
+ }
102
+ }
103
+
94
104
  &--disabled {
95
105
  color: var(--a-base-5);
96
106
  &:hover,
@@ -52,7 +52,7 @@
52
52
  <AposButton
53
53
  type="primary"
54
54
  :label="saveLabel"
55
- :disabled="docFields.hasErrors"
55
+ :disabled="errorCount > 0"
56
56
  @click="save"
57
57
  />
58
58
  </template>
@@ -62,6 +62,7 @@
62
62
  <script>
63
63
  import AposModifiedMixin from 'Modules/@apostrophecms/ui/mixins/AposModifiedMixin';
64
64
  import AposEditorMixin from 'Modules/@apostrophecms/modal/mixins/AposEditorMixin';
65
+ import AposDocErrorsMixin from 'Modules/@apostrophecms/modal/mixins/AposDocErrorsMixin';
65
66
  import AposModalTabsMixin from 'Modules/@apostrophecms/modal/mixins/AposModalTabsMixin';
66
67
  import { detectDocChange } from 'Modules/@apostrophecms/schema/lib/detectChange';
67
68
  import cuid from 'cuid';
@@ -69,7 +70,7 @@ import { klona } from 'klona';
69
70
 
70
71
  export default {
71
72
  name: 'AposWidgetEditor',
72
- mixins: [ AposModifiedMixin, AposEditorMixin, AposModalTabsMixin ],
73
+ mixins: [ AposModifiedMixin, AposEditorMixin, AposDocErrorsMixin, AposModalTabsMixin ],
73
74
  props: {
74
75
  type: {
75
76
  required: true,
@@ -193,6 +194,7 @@ export default {
193
194
  },
194
195
  methods: {
195
196
  updateDocFields(value) {
197
+ this.updateFieldErrors(value.fieldState);
196
198
  this.docFields.data = {
197
199
  ...this.docFields.data,
198
200
  ...value.data
@@ -203,8 +205,14 @@ export default {
203
205
  this.triggerValidation = true;
204
206
  this.$nextTick(async () => {
205
207
  const widget = klona(this.docFields.data);
206
- if (this.docFields.hasErrors) {
208
+ if (this.errorCount > 0) {
207
209
  this.triggerValidation = false;
210
+ await apos.notify('apostrophe:resolveErrorsBeforeSaving', {
211
+ type: 'warning',
212
+ icon: 'alert-circle-icon',
213
+ dismiss: true
214
+ });
215
+ this.focusNextError();
208
216
  return;
209
217
  }
210
218
  try {
@@ -239,6 +247,9 @@ export default {
239
247
  : null;
240
248
  });
241
249
  return widget;
250
+ },
251
+ getAposSchema(field) {
252
+ return this.$refs[field.group.name][0];
242
253
  }
243
254
  }
244
255
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apostrophe",
3
- "version": "4.3.1",
3
+ "version": "4.3.3",
4
4
  "description": "The Apostrophe Content Management System.",
5
5
  "main": "index.js",
6
6
  "scripts": {