apostrophe 3.62.0 → 3.63.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.
Files changed (84) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBar.vue +1 -1
  3. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +6 -4
  4. package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +9 -1
  5. package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +8 -0
  6. package/modules/@apostrophecms/area/ui/apos/components/AposWidgetControls.vue +6 -3
  7. package/modules/@apostrophecms/doc/index.js +254 -5
  8. package/modules/@apostrophecms/doc/ui/apos/mixins/AposFieldMetaUtilsMixin.js +93 -0
  9. package/modules/@apostrophecms/doc-type/index.js +70 -10
  10. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +2 -0
  11. package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +12 -3
  12. package/modules/@apostrophecms/i18n/i18n/en.json +1 -0
  13. package/modules/@apostrophecms/login/index.js +25 -19
  14. package/modules/@apostrophecms/login/ui/apos/components/AposLoginForm.vue +11 -1
  15. package/modules/@apostrophecms/login/ui/apos/logic/AposLoginForm.js +46 -2
  16. package/modules/@apostrophecms/modal/ui/apos/components/AposModalShareDraft.vue +8 -3
  17. package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +3 -0
  18. package/modules/@apostrophecms/page/index.js +87 -20
  19. package/modules/@apostrophecms/page-type/index.js +67 -2
  20. package/modules/@apostrophecms/piece-type/index.js +1 -34
  21. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +8 -0
  22. package/modules/@apostrophecms/piece-type/ui/apos/components/AposUtilityOperations.vue +16 -1
  23. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +8 -0
  24. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapImage.vue +7 -7
  25. package/modules/@apostrophecms/schema/index.js +9 -0
  26. package/modules/@apostrophecms/schema/lib/addFieldTypes.js +3 -0
  27. package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +1 -0
  28. package/modules/@apostrophecms/schema/ui/apos/components/AposInputArea.vue +3 -0
  29. package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +4 -8
  30. package/modules/@apostrophecms/schema/ui/apos/components/AposInputObject.vue +2 -0
  31. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +1 -0
  32. package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +1 -0
  33. package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +74 -29
  34. package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +1 -0
  35. package/modules/@apostrophecms/schema/ui/apos/components/AposSearchList.vue +1 -1
  36. package/modules/@apostrophecms/schema/ui/apos/logic/AposArrayEditor.js +7 -0
  37. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArea.js +13 -1
  38. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js +5 -1
  39. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputObject.js +21 -0
  40. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputWrapper.js +35 -0
  41. package/modules/@apostrophecms/schema/ui/apos/logic/AposSchema.js +6 -0
  42. package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputMixin.js +41 -0
  43. package/modules/@apostrophecms/ui/ui/apos/mixins/AposPublishMixin.js +10 -4
  44. package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +7 -0
  45. package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +6 -0
  46. package/package.json +2 -2
  47. package/test/areas.js +1 -1
  48. package/test/assets.js +2 -2
  49. package/test/attachments.js +2 -2
  50. package/test/base-module.js +2 -1
  51. package/test/base-url-env-var.js +2 -2
  52. package/test/change-doc-ids.js +33 -31
  53. package/test/command-menu.js +2 -2
  54. package/test/content-i18n.js +47 -46
  55. package/test/db.js +2 -2
  56. package/test/docs.js +301 -126
  57. package/test/draft-published.js +2 -2
  58. package/test/email.js +2 -1
  59. package/test/express.js +3 -2
  60. package/test/external-front.js +4 -4
  61. package/test/field-meta.js +363 -0
  62. package/test/global.js +2 -1
  63. package/test/http.js +4 -2
  64. package/test/images.js +87 -88
  65. package/test/job.js +34 -34
  66. package/test/locks.js +2 -2
  67. package/test/login-requirements.js +3 -2
  68. package/test/middleware-and-route-order.js +2 -2
  69. package/test/page-type.js +2 -1
  70. package/test/pages-autocomplete.js +0 -11
  71. package/test/pages-public-api.js +2 -2
  72. package/test/pages-rest.js +4 -4
  73. package/test/pages.js +389 -57
  74. package/test/parked-pages.js +47 -47
  75. package/test/pieces-page-type.js +2 -1
  76. package/test/pieces-public-api.js +38 -38
  77. package/test/pieces.js +4 -4
  78. package/test/published-pages.js +16 -16
  79. package/test/schemaBuilders.js +4 -4
  80. package/test/schemas.js +220 -221
  81. package/test/search.js +2 -2
  82. package/test/soft-redirects.js +2 -1
  83. package/test/templates.js +2 -2
  84. package/test/users.js +2 -1
@@ -161,6 +161,14 @@ export default {
161
161
  return {};
162
162
  }
163
163
  },
164
+ // not used, but we need to keep it here to avoid
165
+ // an attribute [object Object]
166
+ meta: {
167
+ type: Object,
168
+ default() {
169
+ return {};
170
+ }
171
+ },
164
172
  docId: {
165
173
  type: String,
166
174
  required: false,
@@ -42,13 +42,6 @@ export default {
42
42
  active: false
43
43
  };
44
44
  },
45
- watch: {
46
- hasSelection(newVal, oldVal) {
47
- if (!newVal) {
48
- this.close();
49
- }
50
- }
51
- },
52
45
  computed: {
53
46
  attributes() {
54
47
  return this.editor.getAttributes('image');
@@ -71,6 +64,13 @@ export default {
71
64
  return text !== '' || type?.name === 'image';
72
65
  }
73
66
  },
67
+ watch: {
68
+ hasSelection(newVal, oldVal) {
69
+ if (!newVal) {
70
+ this.close();
71
+ }
72
+ }
73
+ },
74
74
  methods: {
75
75
  click() {
76
76
  if (this.hasSelection) {
@@ -28,6 +28,7 @@ module.exports = {
28
28
  self.fieldsById = {};
29
29
  self.arrayManagers = {};
30
30
  self.objectManagers = {};
31
+ self.fieldMetadataComponents = [];
31
32
 
32
33
  self.enableBrowserData();
33
34
 
@@ -1127,6 +1128,13 @@ module.exports = {
1127
1128
  return self.fieldTypes[typeName];
1128
1129
  },
1129
1130
 
1131
+ addFieldMetadataComponent(namespace, component) {
1132
+ self.fieldMetadataComponents.push({
1133
+ name: component,
1134
+ namespace
1135
+ });
1136
+ },
1137
+
1130
1138
  // Given a schema and a query, add query builders to the query
1131
1139
  // for each of the fields in the schema, based on their field type,
1132
1140
  // if supported by the field type. If the field already has a
@@ -1762,6 +1770,7 @@ module.exports = {
1762
1770
  }
1763
1771
  browserOptions.action = self.action;
1764
1772
  browserOptions.components = { fields: fields };
1773
+ browserOptions.fieldMetadataComponents = self.fieldMetadataComponents;
1765
1774
  return browserOptions;
1766
1775
  }
1767
1776
  };
@@ -192,6 +192,9 @@ module.exports = (self) => {
192
192
  query.and(criteria);
193
193
  }
194
194
  },
195
+ launder: function (s) {
196
+ return self.apos.launder.string(s);
197
+ },
195
198
  choices: async function () {
196
199
  return self.sortedDistinct(field.name, query);
197
200
  }
@@ -70,6 +70,7 @@
70
70
  :following-values="followingValues()"
71
71
  :conditional-fields="conditionalFields"
72
72
  :value="currentDoc"
73
+ :meta="currentDocMeta"
73
74
  :server-errors="currentDocServerErrors"
74
75
  ref="schema"
75
76
  :doc-id="docId"
@@ -4,6 +4,8 @@
4
4
  :error="effectiveError" :uid="uid"
5
5
  :display-options="displayOptions"
6
6
  :modifiers="[...modifiers, 'full-width']"
7
+ :items="next.items"
8
+ :meta="areaMeta"
7
9
  >
8
10
  <template #body>
9
11
  <!-- data-apos-schema-area lets all the child areas know that this area is in a schema (which is in a modal)
@@ -18,6 +20,7 @@
18
20
  :is="editorComponent"
19
21
  :options="field.options"
20
22
  :items="next.items"
23
+ :meta="areaMeta"
21
24
  :choices="choices"
22
25
  :id="next._id"
23
26
  :field-id="field._id"
@@ -8,6 +8,7 @@
8
8
  :modifiers="[
9
9
  ...field.style === 'table' ? ['full-width'] : []
10
10
  ]"
11
+ :meta="arrayMeta"
11
12
  >
12
13
  <template #additional>
13
14
  <AposMinMaxCount
@@ -61,6 +62,7 @@
61
62
  >
62
63
  <AposSchema
63
64
  v-model="item.schemaInput"
65
+ :meta="arrayMeta[item._id]?.aposMeta"
64
66
  v-for="(item, index) in items"
65
67
  class="apos-input-array-inline-item"
66
68
  :class="item.open && !alwaysExpand ? 'apos-input-array-inline-item--active' : null"
@@ -114,9 +116,9 @@
114
116
  >
115
117
  <AposButton
116
118
  label="apostrophe:removeItem"
117
- icon="trash-can-outline-icon"
119
+ icon="close-icon"
118
120
  type="subtle"
119
- :modifiers="['inline', 'danger', 'no-motion']"
121
+ :modifiers="['inline','no-motion']"
120
122
  :icon-only="true"
121
123
  @click="remove(item._id)"
122
124
  />
@@ -171,12 +173,6 @@ export default {
171
173
  }
172
174
  }
173
175
  ::v-deep .apos-input-relationship {
174
- .apos-button__wrapper {
175
- display: none;
176
- }
177
- .apos-input {
178
- width: auto;
179
- }
180
176
  .apos-slat__main {
181
177
  min-width: 130px;
182
178
  }
@@ -6,12 +6,14 @@
6
6
  :error="null"
7
7
  :uid="uid"
8
8
  :display-options="displayOptions"
9
+ :meta="objectMeta"
9
10
  >
10
11
  <template #body>
11
12
  <div class="apos-input-object">
12
13
  <div class="apos-input-wrapper">
13
14
  <AposSchema
14
15
  v-model="schemaInput"
16
+ :meta="currentDocMeta"
15
17
  ref="schema"
16
18
  :schema="schema"
17
19
  :trigger-validation="triggerValidation"
@@ -3,6 +3,7 @@
3
3
  :modifiers="modifiers" :field="field"
4
4
  :error="effectiveError" :uid="uid"
5
5
  :display-options="displayOptions"
6
+ :meta="fieldMeta"
6
7
  >
7
8
  <template #body>
8
9
  <div :class="wrapperClasses">
@@ -3,6 +3,7 @@
3
3
  :modifiers="modifiers" :field="field"
4
4
  :error="effectiveError" :uid="uid"
5
5
  :display-options="displayOptions"
6
+ :meta="fieldMeta"
6
7
  >
7
8
  <template #body>
8
9
  <div class="apos-input-wrapper">
@@ -9,37 +9,64 @@
9
9
  <component :is="wrapEl" :class="classList">
10
10
  <div class="apos-field__info">
11
11
  <component
12
- v-if="field.label" :class="{'apos-sr-only': field.hideLabel }"
12
+ v-if="field.label"
13
13
  class="apos-field__label"
14
- :is="labelEl" :for="uid"
14
+ :class="{'apos-sr-only': field.hideLabel }"
15
+ :is="labelEl"
16
+ :for="uid"
17
+ :data-apos-test-name="field.name"
18
+ :data-apos-test-label="field.label"
19
+ data-apos-test="field-label"
15
20
  >
16
- {{ $t(label) }}
17
- <span v-if="field.required" class="apos-field__required">
18
- *
19
- </span>
20
- <AposLabel
21
- class="apos-field__tag"
22
- v-if="field.tag"
23
- :label="field.tag.value || field.tag"
24
- :modifiers="[ `apos-is-${field.tag.type || 'success'}`, 'apos-is-filled' ]"
25
- />
26
- <span
27
- v-if="(field.help || field.htmlHelp) && displayOptions.helpTooltip"
28
- class="apos-field__help-tooltip"
29
- >
30
- <AposIndicator
31
- icon="help-circle-icon"
32
- class="apos-field__help-tooltip__icon"
33
- :tooltip="field.help || field.htmlHelp"
34
- :icon-size="11"
35
- icon-color="var(--a-base-4)"
21
+ <span class="apos-field_label-info">
22
+ {{ $t(label) }}
23
+ <span v-if="field.required" class="apos-field__required">
24
+ *
25
+ </span>
26
+ <AposLabel
27
+ class="apos-field__tag"
28
+ v-if="field.tag"
29
+ :label="field.tag.value || field.tag"
30
+ :modifiers="[ `apos-is-${field.tag.type || 'success'}`, 'apos-is-filled' ]"
31
+ data-apos-test="field-tag"
36
32
  />
33
+ <span
34
+ v-if="(field.help || field.htmlHelp) && displayOptions.helpTooltip"
35
+ data-apos-test="field-help-tooltip"
36
+ class="apos-field__help-tooltip"
37
+ >
38
+ <AposIndicator
39
+ icon="help-circle-icon"
40
+ class="apos-field__help-tooltip__icon"
41
+ :tooltip="field.help || field.htmlHelp"
42
+ :icon-size="11"
43
+ icon-color="var(--a-base-4)"
44
+ />
45
+ </span>
46
+ <span
47
+ v-if="displayOptions.changed" class="apos-field__changed"
48
+ data-apos-test="field-changed"
49
+ >
50
+ <AposLabel
51
+ label="apostrophe:changed" class="apos-field__changed__label"
52
+ :modifiers="[ 'apos-is-warning', 'apos-is-filled' ]"
53
+ tooltip="apostrophe:fieldHasUnpublishedChanges"
54
+ />
55
+ </span>
37
56
  </span>
38
- <span v-if="displayOptions.changed" class="apos-field__changed">
39
- <AposLabel
40
- label="apostrophe:changed" class="apos-field__changed__label"
41
- :modifiers="[ 'apos-is-warning', 'apos-is-filled' ]"
42
- tooltip="apostrophe:fieldHasUnpublishedChanges"
57
+ <span data-apos-test="field-meta-wrapper" class="apos-field__label-meta">
58
+ <component
59
+ v-for="component in metaComponents"
60
+ :key="component.name"
61
+ :is="component.name"
62
+ :field="field"
63
+ :items="items"
64
+ :namespace="component.namespace"
65
+ :meta="component.data"
66
+ :meta-raw="meta"
67
+ :data-apos-test-component="component.name"
68
+ :data-apos-test-namespace="component.namespace"
69
+ data-apos-test="field-meta"
43
70
  />
44
71
  </span>
45
72
  </component>
@@ -53,7 +80,10 @@
53
80
  <slot name="additional" />
54
81
  </div>
55
82
  <slot name="body" />
56
- <div v-if="errorMessage" class="apos-field__error">
83
+ <div
84
+ v-if="errorMessage" class="apos-field__error"
85
+ data-apos-test="field-error"
86
+ >
57
87
  {{ $t(errorMessage) }}
58
88
  </div>
59
89
  </component>
@@ -93,10 +123,25 @@ export default {
93
123
 
94
124
  .apos-field__label {
95
125
  @include type-label;
96
- display: block;
126
+ display: flex;
127
+ align-items: center;
128
+ justify-content: space-between;
129
+ flex-wrap: nowrap;
130
+ gap: $spacing-double;
97
131
  margin: 0 0 $spacing-base;
98
132
  padding: 0;
99
133
  color: var(--a-text-primary);
134
+
135
+ &-info {
136
+ display: block;
137
+ }
138
+
139
+ &-meta {
140
+ display: inline-flex;
141
+ align-items: center;
142
+ gap: $spacing-half;
143
+ justify-content: flex-end;
144
+ }
100
145
  }
101
146
 
102
147
  .apos-field__help {
@@ -42,6 +42,7 @@
42
42
  :following-values="followingValues[field.name]"
43
43
  :condition-met="conditionalFields?.if[field.name]"
44
44
  :field="fields[field.name].field"
45
+ :meta="meta"
45
46
  :modifiers="fields[field.name].modifiers"
46
47
  :display-options="getDisplayOptions(field.name)"
47
48
  :trigger-validation="triggerValidation"
@@ -50,7 +50,7 @@ export default {
50
50
 
51
51
  <style lang="scss" scoped>
52
52
  .apos-search {
53
- z-index: $z-index-default;
53
+ z-index: calc(#{$z-index-widget-focused-controls} + 1);
54
54
  position: absolute;
55
55
  overflow: auto;
56
56
  width: 100%;
@@ -16,6 +16,10 @@ export default {
16
16
  required: true,
17
17
  type: Array
18
18
  },
19
+ meta: {
20
+ type: Object,
21
+ default: () => ({})
22
+ },
19
23
  field: {
20
24
  required: true,
21
25
  type: Object
@@ -133,6 +137,9 @@ export default {
133
137
  }
134
138
  });
135
139
  return serverErrors;
140
+ },
141
+ currentDocMeta() {
142
+ return this.meta[this.currentId]?.aposMeta || {};
136
143
  }
137
144
  },
138
145
  async mounted() {
@@ -43,10 +43,22 @@ export default {
43
43
  for (const [ name, options ] of Object.entries(widgets)) {
44
44
  result.push({
45
45
  name,
46
- label: options.addLabel || apos.modules[`${name}-widget`].label
46
+ label: options.addLabel || apos.modules[`${name}-widget`].label,
47
+ icon: apos.modules[`${name}-widget`].icon
47
48
  });
48
49
  }
49
50
  return result;
51
+ },
52
+ areaMeta() {
53
+ const meta = this.convertMetaToItems(this.next.items);
54
+ // Get meta for the area itself
55
+ if (this.meta?.[`@${this.next._id}`]) {
56
+ return {
57
+ ...meta,
58
+ ...this.meta[`@${this.next._id}`]
59
+ };
60
+ }
61
+ return meta;
50
62
  }
51
63
  },
52
64
  methods: {
@@ -78,6 +78,9 @@ export default {
78
78
  return false;
79
79
  }
80
80
  return error;
81
+ },
82
+ arrayMeta() {
83
+ return this.convertMetaToItems(this.items);
81
84
  }
82
85
  },
83
86
  watch: {
@@ -187,7 +190,8 @@ export default {
187
190
  items: this.next,
188
191
  serverError: this.serverError,
189
192
  docId: this.docId,
190
- parentFollowingValues: this.followingValues
193
+ parentFollowingValues: this.followingValues,
194
+ meta: this.arrayMeta
191
195
  });
192
196
  if (result) {
193
197
  this.next = result;
@@ -1,4 +1,5 @@
1
1
 
2
+ import { klona } from 'klona';
2
3
  import AposInputMixin from 'Modules/@apostrophecms/schema/mixins/AposInputMixin.js';
3
4
  import AposInputFollowingMixin from 'Modules/@apostrophecms/schema/mixins/AposInputFollowingMixin.js';
4
5
  import AposInputConditionalFieldsMixin from 'Modules/@apostrophecms/schema/mixins/AposInputConditionalFieldsMixin.js';
@@ -46,6 +47,26 @@ export default {
46
47
  },
47
48
  values() {
48
49
  return this.schemaInput.data || {};
50
+ },
51
+ objectMeta() {
52
+ const meta = klona(this.fieldMeta);
53
+ const shared = {};
54
+
55
+ for (const fieldName of Object.keys(this.meta)) {
56
+ if (fieldName.startsWith('@')) {
57
+ shared[fieldName] = this.meta[fieldName];
58
+ }
59
+ }
60
+
61
+ meta.aposMeta = {
62
+ ...(this.fieldMeta.aposMeta || {}),
63
+ ...shared
64
+ };
65
+
66
+ return meta;
67
+ },
68
+ currentDocMeta() {
69
+ return this.objectMeta.aposMeta || {};
49
70
  }
50
71
  },
51
72
  watch: {
@@ -14,6 +14,12 @@ export default {
14
14
  type: Object,
15
15
  required: true
16
16
  },
17
+ meta: {
18
+ type: Object,
19
+ default() {
20
+ return {};
21
+ }
22
+ },
17
23
  error: {
18
24
  type: [ String, Boolean, Object ],
19
25
  default: null
@@ -107,6 +113,35 @@ export default {
107
113
  } else {
108
114
  return false;
109
115
  }
116
+ },
117
+ // Meta components receive the original data (key `_original`) and
118
+ // the "pure" keys (no namespace prefix) and values from their namespace.
119
+ // The `_original` key is useful for analyzing e.g. `area`, `array`, etc fields
120
+ // inside the metadata components.
121
+ // All registered metadata components will be rendered. It's the external
122
+ // component responsibility to not render itself when no matching conditions
123
+ // from its namespace are met.
124
+ metaComponents() {
125
+ const meta = {};
126
+ for (const metaKey of Object.keys(this.meta)) {
127
+ const [ ns, key ] = metaKey.split(':', 2);
128
+ if (!key) {
129
+ continue;
130
+ }
131
+ if (!meta[ns]) {
132
+ meta[ns] = {};
133
+ }
134
+ meta[ns][key] = this.meta[metaKey];
135
+ }
136
+
137
+ return apos.schema.fieldMetadataComponents
138
+ .map(component => {
139
+ return {
140
+ name: component.name,
141
+ namespace: component.namespace,
142
+ data: meta[component.namespace] || {}
143
+ };
144
+ });
110
145
  }
111
146
  },
112
147
  mounted: function () {
@@ -8,6 +8,12 @@ export default {
8
8
  type: Object,
9
9
  required: true
10
10
  },
11
+ meta: {
12
+ type: Object,
13
+ default() {
14
+ return {};
15
+ }
16
+ },
11
17
  generation: {
12
18
  type: Number,
13
19
  required: false,
@@ -1,3 +1,5 @@
1
+ import { klona } from 'klona';
2
+
1
3
  export default {
2
4
  // Implements v-model pattern
3
5
  emits: [ 'input' ],
@@ -12,6 +14,12 @@ export default {
12
14
  type: Object,
13
15
  required: true
14
16
  },
17
+ meta: {
18
+ type: Object,
19
+ default() {
20
+ return {};
21
+ }
22
+ },
15
23
  modifiers: {
16
24
  default: function () {
17
25
  return [];
@@ -88,6 +96,9 @@ export default {
88
96
  },
89
97
  effectiveError () {
90
98
  return this.error || this.serverError;
99
+ },
100
+ fieldMeta() {
101
+ return this.meta?.[this.field.name] || {};
91
102
  }
92
103
  },
93
104
  watch: {
@@ -152,6 +163,36 @@ export default {
152
163
  // experience.
153
164
  convert() {
154
165
  return this.next;
166
+ },
167
+ // Accepts an array of object values and convertts the current meta to items
168
+ // so that it contains only the item _id's as keys (without leading `@`) and
169
+ // meta keys for the current field if any.
170
+ // This util is meant to be used in array and widget wrappers or
171
+ // custom fields that manage array of object values having unique `_id`.
172
+ convertMetaToItems(valueItems = []) {
173
+ const fieldMeta = klona(this.fieldMeta);
174
+ const meta = this.meta || {};
175
+
176
+ const shared = {};
177
+ for (const fieldName of Object.keys(meta)) {
178
+ if (fieldName.startsWith('@') && !valueItems.some(item => meta[`@${item._id}`])) {
179
+ shared[fieldName] = meta[fieldName];
180
+ continue;
181
+ }
182
+ }
183
+ for (const item of valueItems) {
184
+ const itemMeta = meta[`@${item._id}`] || {};
185
+ const subMeta = itemMeta.aposMeta;
186
+ fieldMeta[item._id] = {
187
+ ...itemMeta,
188
+ aposMeta: {
189
+ ...(subMeta || {}),
190
+ ...shared
191
+ }
192
+ };
193
+ }
194
+
195
+ return fieldMeta;
155
196
  }
156
197
  }
157
198
  };
@@ -65,9 +65,10 @@ export default {
65
65
  // Retry now that ancestors are published
66
66
  return this.publish(doc);
67
67
  } catch (e) {
68
+ const errorMessage = e.name === 'forbidden' ? 'apostrophe:errorWhilePublishingParentPageForbidden' : e.message;
68
69
  await apos.alert({
69
70
  heading: this.$t('apostrophe:errorWhilePublishing'),
70
- description: e.message || this.$t('apostrophe:errorWhilePublishingParentPage'),
71
+ description: errorMessage || this.$t('apostrophe:errorWhilePublishingParentPage'),
71
72
  localize: false
72
73
  });
73
74
  }
@@ -118,13 +119,18 @@ export default {
118
119
  body: {},
119
120
  draft: true
120
121
  });
122
+ const newDoc = {
123
+ ...doc,
124
+ submitted
125
+ };
121
126
  apos.notify('apostrophe:submittedForReview', {
122
127
  type: 'success',
123
128
  icon: 'list-status-icon',
124
129
  dismiss: true
125
130
  });
131
+
126
132
  apos.bus.$emit('content-changed', {
127
- doc: submitted,
133
+ doc: newDoc,
128
134
  action: 'submit'
129
135
  });
130
136
  return submitted;
@@ -151,12 +157,12 @@ export default {
151
157
  dismiss: true,
152
158
  icon: 'close-circle-icon'
153
159
  });
154
- doc = {
160
+ const newDoc = {
155
161
  ...doc,
156
162
  submitted: null
157
163
  };
158
164
  apos.bus.$emit('content-changed', {
159
- doc,
165
+ doc: newDoc,
160
166
  action: 'dismiss-submission'
161
167
  });
162
168
  } catch (e) {
@@ -18,6 +18,7 @@
18
18
  :trigger-validation="triggerValidation"
19
19
  :schema="schema"
20
20
  :value="docFields"
21
+ :meta="meta"
21
22
  @input="updateDocFields"
22
23
  @validate="triggerValidate"
23
24
  :following-values="followingValues()"
@@ -74,6 +75,12 @@ export default {
74
75
  return {};
75
76
  }
76
77
  },
78
+ meta: {
79
+ type: Object,
80
+ default() {
81
+ return {};
82
+ }
83
+ },
77
84
  focused: {
78
85
  type: Boolean,
79
86
  default: false
@@ -7,6 +7,12 @@ export default {
7
7
  type: String,
8
8
  areaFieldId: String,
9
9
  value: Object,
10
+ meta: {
11
+ type: Object,
12
+ default() {
13
+ return {};
14
+ }
15
+ },
10
16
  // Ignored for server side rendering
11
17
  areaField: Object,
12
18
  followingValues: Object,
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "apostrophe",
3
- "version": "3.62.0",
3
+ "version": "3.63.0",
4
4
  "description": "The Apostrophe Content Management System.",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "pretest": "npm run lint",
8
- "test": "nyc --reporter=html mocha -t 10000",
8
+ "test": "nyc --reporter=html mocha -t 10000 --ignore=test/assets.js && nyc --reporter=html mocha -t 10000 test/assets.js",
9
9
  "eslint": "eslint --ext .js,.vue .",
10
10
  "eslint-fix": "npm run eslint -- --fix",
11
11
  "i18n": "node scripts/lint-i18n",
package/test/areas.js CHANGED
@@ -1,8 +1,8 @@
1
1
  const t = require('../test-lib/test.js');
2
2
  const assert = require('assert');
3
- let apos;
4
3
 
5
4
  describe('Areas', function() {
5
+ let apos;
6
6
 
7
7
  this.timeout(t.timeout);
8
8