apostrophe 3.52.0 → 3.53.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 (52) hide show
  1. package/CHANGELOG.md +60 -2
  2. package/defaults.js +1 -0
  3. package/index.js +3 -2
  4. package/lib/check-if-conditions.js +44 -0
  5. package/lib/moog-require.js +23 -1
  6. package/modules/@apostrophecms/admin-bar/index.js +30 -1
  7. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +4 -1
  8. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextTitle.vue +14 -8
  9. package/modules/@apostrophecms/area/ui/apos/components/AposAreaExpandedMenu.vue +8 -2
  10. package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +4 -0
  11. package/modules/@apostrophecms/command-menu/ui/apos/components/AposCommandMenuShortcut.vue +1 -0
  12. package/modules/@apostrophecms/doc/index.js +13 -7
  13. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +36 -22
  14. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +35 -27
  15. package/modules/@apostrophecms/i18n/i18n/en.json +8 -0
  16. package/modules/@apostrophecms/i18n/index.js +49 -2
  17. package/modules/@apostrophecms/image/ui/apos/components/AposMediaUploader.vue +16 -1
  18. package/modules/@apostrophecms/login/index.js +5 -1
  19. package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +2 -0
  20. package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +37 -40
  21. package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +1 -2
  22. package/modules/@apostrophecms/modal/ui/apos/components/AposModalShareDraft.vue +3 -2
  23. package/modules/@apostrophecms/modal/ui/apos/components/AposModalTabs.vue +4 -5
  24. package/modules/@apostrophecms/modal/ui/apos/mixins/AposFocusMixin.js +91 -0
  25. package/modules/@apostrophecms/modal/ui/apos/mixins/AposModalTabsMixin.js +16 -4
  26. package/modules/@apostrophecms/page/ui/apos/components/AposPagesManager.vue +9 -3
  27. package/modules/@apostrophecms/piece-type/index.js +1 -1
  28. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +2 -0
  29. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +1 -1
  30. package/modules/@apostrophecms/schema/index.js +13 -0
  31. package/modules/@apostrophecms/schema/lib/addFieldTypes.js +3 -10
  32. package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +0 -1
  33. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +1 -15
  34. package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +20 -13
  35. package/modules/@apostrophecms/schema/ui/apos/components/AposSubform.vue +164 -0
  36. package/modules/@apostrophecms/schema/ui/apos/logic/AposSubform.js +141 -0
  37. package/modules/@apostrophecms/settings/index.js +627 -0
  38. package/modules/@apostrophecms/settings/ui/apos/apps/TheAposSettings.js +8 -0
  39. package/modules/@apostrophecms/settings/ui/apos/components/AposSettingsManager.vue +162 -0
  40. package/modules/@apostrophecms/settings/ui/apos/logic/AposSettingsManager.js +169 -0
  41. package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +10 -0
  42. package/modules/@apostrophecms/ui/ui/apos/components/AposButtonSplit.vue +23 -6
  43. package/modules/@apostrophecms/ui/ui/apos/components/AposCellLabels.vue +1 -7
  44. package/modules/@apostrophecms/ui/ui/apos/components/AposSubformPreview.vue +136 -0
  45. package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +6 -6
  46. package/modules/@apostrophecms/ui/ui/apos/mixins/AposCellMixin.js +9 -0
  47. package/modules/@apostrophecms/ui/ui/apos/scss/global/_admin.scss +9 -0
  48. package/modules/@apostrophecms/ui/ui/apos/scss/global/_widgets.scss +5 -1
  49. package/modules/@apostrophecms/user/index.js +30 -3
  50. package/package.json +1 -1
  51. package/test/i18n.js +168 -0
  52. package/test/settings.js +544 -0
package/CHANGELOG.md CHANGED
@@ -1,11 +1,68 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.53.0 (2023-08-03)
4
+
5
+ ### Adds
6
+
7
+ * Accessibility improved for navigation inside modals and various UI elements.
8
+ Pages/Docs Manager and Doc Editor modal now have better keyboard accessibility.
9
+ They keep the focus on elements inside modals and give it back to their parent modal when closed.
10
+ This implementation is evolving and will likely switch to use the `dialog` HTML element soon.
11
+ * Adds support for a new `if` property in `addContextOperation` in order to show or not a context operation based on the current document properties.
12
+ * Add `update-doc-fields` event to call `AposDocEditor.updateDocFields` method
13
+ * Add schema field `hidden` property to always hide a field
14
+ * Hide empty schema tabs in `AposDocEditor` when all fields are hidden due to `if` conditions
15
+ * The front end UI now respects the `_aposEditorModal` and `_aposAutopublish`
16
+ properties of a document if present, and otherwise falls back to module
17
+ configuration. This is a powerful addition to custom editor components
18
+ for piece and page types, allowing "virtual piece types" on the back end that
19
+ deal with many content types to give better hints to the UI.
20
+ * Respect the `_aposAutopublish` property of a document if present, otherwise
21
+ fall back to module configuration.
22
+ * For convenience in custom editor components, pass the new prop `type`, the original type of the document being copied or edited.
23
+ * For better results in custom editor components, pass the prop `copyOfId`, which implies
24
+ the custom editor should fetch the original itself by its means of choice.
25
+ For backwards compatibility `copyOf` is still passed, but it may be an
26
+ incomplete projection and should not be used in new code.
27
+ * Custom context operations now receive a `docId` prop, which should
28
+ be used in preference to `doc` because `doc` may be an incomplete
29
+ projection.
30
+ * Those creating custom context operations for documents can now
31
+ specify both a `props` object for additional properties to be passed to
32
+ their modal and a `docProps` object to map properties from the document
33
+ to props of their choosing.
34
+ * Adds support to add context labels in admin bar.
35
+ * Adds support for admin UI language configuration in the `@apostrophecms/i18n` module. The new options allow control over the default admin UI language and configures the list of languages, that any individual logged in user can choose from. See the [documentation](https://v3.docs.apostrophecms.org/reference/modules/i18n.html) for more details.
36
+ * Adds `adminLocale` User field to allow users to set their preferred admin UI language, but only when the `@apostrophecms/i18n` is configured accordingly (see above).
37
+ * Adds `@apostrophecms/settings` module and a "Personal Settings" feature. See the [documentation](https://v3.docs.apostrophecms.org/reference/modules/settings.html) for more details.
38
+ * Adds `$and` operator on `addContextOperation` `if` property in order to check multiple fields before showing or hiding a context operation.
39
+
40
+ ### Fixes
41
+
42
+ * `AposDocEditor` `onSave` method signature. We now always expect an object when a parameter is passed to the function to check
43
+ the value of `navigate` flag.
44
+ * Fixes a problem in the rich text editor where the slash would not be deleted after item selectin from the insert menu.
45
+ * Modules that have a `public` or `i18n` subdirectory no longer generate a
46
+ warning if they export no code.
47
+ * Clean up focus parent event handlers when components are destroyed. Prevents a slow degradation of performance while editing.
48
+ Thanks to [Joshua N. Miller](https://github.com/jmiller-rise8).
49
+ * Fixes a visual discrepancy in the rich text editor where empty paragraphs would appear smaller in preview mode compared to edit mode.
50
+
51
+ ### Changes
52
+
53
+ * To make life easier for module developers, modules that are `npm link`ed to
54
+ the project no longer have to be listed in `package.json` as
55
+ dependencies. To prevent surprises this is still a requirement for modules
56
+ that are not symlinked.
57
+
3
58
  ## 3.52.0 (2023-07-06)
4
59
 
5
60
  ### Changes
61
+
6
62
  * Foreign widget UI no longer uses inverted theme styles.
7
63
 
8
64
  ### Adds
65
+
9
66
  * Allows users to double-click a nested widget's breadcrumb entry and open its editor.
10
67
  * Adds support for a new `conditions` property in `addContextOperation` and validation of `addContextOperation` configuration.
11
68
 
@@ -17,6 +74,7 @@ by an `if` condition fails to satisfy a condition such as `min` or `max`
17
74
  or is otherwise invalid. Instead the invalid value is discarded for safety.
18
75
  Note that `required` has always been ignored when an `if` condition is not
19
76
  satisfied.
77
+ * Errors thrown in `@apostrophecms/login:afterSessionLogin` event handlers are now properly passed back to Passport as such, avoiding a process restart.
20
78
 
21
79
  ## 3.51.1 (2023-06-23)
22
80
 
@@ -89,8 +147,8 @@ are made before the last `apostrophe:modulesRegistered` handler has fired.
89
147
  If you need to call Apostrophe's `find()` methods at startup,
90
148
  it is best to wait for the `@apostrophecms/doc:beforeReplicate` event.
91
149
  * Allow `@` when a piece is a template and `/@` for page templates (doc-template-library module).
92
- * Adds a `prefix` option to the http frontend util module.
93
- If explicitly set to `false`, prevents the prefix from being automatically added to the URL,
150
+ * Adds a `prefix` option to the http frontend util module.
151
+ If explicitly set to `false`, prevents the prefix from being automatically added to the URL,
94
152
  when making calls with already-prefixed URLs for instance.
95
153
  * Adds the `redirectToFirstLocale` option to the `i18n` module to prevent users from reaching a version of their site that would not match any locale when requesting the site without a locale prefix in the URL.
96
154
  * If just one instance of a piece type should always exist (per locale if localized), the
package/defaults.js CHANGED
@@ -46,6 +46,7 @@ module.exports = {
46
46
  '@apostrophecms/video-widget': {},
47
47
  '@apostrophecms/ui': {},
48
48
  '@apostrophecms/user': {},
49
+ '@apostrophecms/settings': {},
49
50
  '@apostrophecms/image': {},
50
51
  '@apostrophecms/image-tag': {},
51
52
  '@apostrophecms/file': {},
package/index.js CHANGED
@@ -728,8 +728,9 @@ async function apostrophe(options, telemetry, rootSpan) {
728
728
  if (code) {
729
729
  return true;
730
730
  }
731
- if (d.__meta.dirname && (fs.existsSync(`${d.__meta.dirname}/ui/apos`) || fs.existsSync(`${d.__meta.dirname}/ui/src`) || fs.existsSync(`${d.__meta.dirname}/ui/public`))) {
732
- // Assets that will be bundled, instead of server code
731
+ const subdirs = [ 'ui/apos', 'ui/src', 'ui/public', 'public', 'i18n' ];
732
+ if (d.__meta.dirname && subdirs.find(dir => fs.existsSync(`${d.__meta.dirname}/${dir}`))) {
733
+ // Assets that will be bundled, or localizations, instead of server code
733
734
  return true;
734
735
  }
735
736
  return false;
@@ -0,0 +1,44 @@
1
+
2
+ export default function checkIfConditions(doc, conditions) {
3
+ return Object.entries(conditions).every(([ key, value ]) => {
4
+ if (key === '$or') {
5
+ return checkOrConditions(doc, value);
6
+ }
7
+
8
+ if (key === '$and') {
9
+ return checkAndConditions(doc, value);
10
+ }
11
+
12
+ const isNotEqualCondition = typeof value === 'object' &&
13
+ !Array.isArray(value) &&
14
+ value !== null &&
15
+ Object.hasOwn(value, '$ne');
16
+
17
+ if (isNotEqualCondition) {
18
+ return getNestedPropValue(doc, key) !== value.$ne;
19
+ }
20
+
21
+ return getNestedPropValue(doc, key) === value;
22
+ });
23
+ }
24
+
25
+ function checkOrConditions(doc, conditions) {
26
+ return conditions.some((condition) => {
27
+ return checkIfConditions(doc, condition);
28
+ });
29
+ }
30
+
31
+ function checkAndConditions(doc, conditions) {
32
+ return conditions.every((condition) => {
33
+ return checkIfConditions(doc, condition);
34
+ });
35
+ }
36
+
37
+ function getNestedPropValue(doc, key) {
38
+ if (key.includes('.')) {
39
+ const keys = key.split('.');
40
+ return keys.reduce((acc, cur) => acc[cur], doc);
41
+ }
42
+
43
+ return doc[key];
44
+ }
@@ -198,8 +198,9 @@ module.exports = function(options) {
198
198
  }
199
199
  }
200
200
  }
201
+ self.npmRoot = folder;
201
202
  }
202
- if (!self.validPackages.has(type)) {
203
+ if (!self.validPackages.has(type) && !symlinked(type)) {
203
204
  return null;
204
205
  }
205
206
  try {
@@ -211,6 +212,27 @@ module.exports = function(options) {
211
212
  }
212
213
  }
213
214
 
215
+ function symlinked(type) {
216
+ self.symlinksCache ||= new Map();
217
+ if (self.symlinksCache.has(type)) {
218
+ return self.symlinksCache.get(type);
219
+ }
220
+ const symlink = `${self.npmRoot}/node_modules/${type}`;
221
+ let link;
222
+ try {
223
+ link = fs.lstatSync(symlink).isSymbolicLink();
224
+ } catch (e) {
225
+ if (e.code === 'ENOENT') {
226
+ // Just doesn't exist
227
+ link = false;
228
+ } else {
229
+ throw e;
230
+ }
231
+ }
232
+ self.symlinksCache.set(type, link);
233
+ return link;
234
+ };
235
+
214
236
  self.isImprovement = function(name) {
215
237
  return _.has(self.improvements, name);
216
238
  };
@@ -92,6 +92,7 @@ module.exports = {
92
92
  self.groups = [];
93
93
  self.groupLabels = {};
94
94
  self.bars = [];
95
+ self.contextLabels = [];
95
96
  self.enableBrowserData();
96
97
  },
97
98
  handlers(self) {
@@ -355,7 +356,8 @@ module.exports = {
355
356
  tabId: cuid(),
356
357
  contextEditorName,
357
358
  pageTree: self.options.pageTree && self.apos.permission.can(req, 'edit', '@apostrophecms/any-page-type', 'draft'),
358
- bars: self.bars
359
+ bars: self.bars,
360
+ contextLabels: self.contextLabels
359
361
  };
360
362
  },
361
363
 
@@ -383,6 +385,33 @@ module.exports = {
383
385
  }
384
386
  return b.last === true ? -1 : 1;
385
387
  });
388
+ },
389
+
390
+ // Add custom context labels and place the ones
391
+ // that have `last: true` at the end
392
+ // of the list so that they will be
393
+ // displayed after the others.
394
+ //
395
+ // Example:
396
+ //
397
+ // ```js
398
+ // self.addContextLabel({
399
+ // id: 'myLabel'
400
+ // label: 'apos:myLabel',
401
+ // tooltip: 'apos:myTooltip',
402
+ // last: true,
403
+ // modifiers: ['apos-is-warning', 'apos-is-filled']
404
+ // });
405
+ // ```
406
+ addContextLabel(label) {
407
+ self.contextLabels.push(label);
408
+
409
+ self.contextLabels.sort((a, b) => {
410
+ if (a.last === true && b.last === true) {
411
+ return 0;
412
+ }
413
+ return b.last === true ? -1 : 1;
414
+ });
386
415
  }
387
416
  };
388
417
  }
@@ -129,6 +129,9 @@ export default {
129
129
  },
130
130
  canRedo() {
131
131
  return this.undone.length > 0;
132
+ },
133
+ autopublish() {
134
+ return this.context.autopublish ?? this.moduleOptions.autopublish;
132
135
  }
133
136
  },
134
137
  watch: {
@@ -715,7 +718,7 @@ export default {
715
718
  },
716
719
  async getPublished() {
717
720
  const moduleOptions = window.apos.modules[this.context.type];
718
- const manuallyPublished = moduleOptions.localized && !moduleOptions.autopublish;
721
+ const manuallyPublished = moduleOptions.localized && !this.autopublish;
719
722
  if (manuallyPublished && this.context.lastPublishedAt) {
720
723
  const action = window.apos.modules[this.context.type].action;
721
724
  const doc = await apos.http.get(`${action}/${this.context._id}`, {
@@ -33,10 +33,18 @@
33
33
  />
34
34
  <AposLabel
35
35
  v-else
36
- :label="unpublishedLabel"
37
- :tooltip="unpublishedTooltip"
36
+ :label="'apostrophe:draft'"
37
+ :tooltip="'apostrophe:notYetPublished'"
38
38
  :modifiers="['apos-is-warning', 'apos-is-filled']"
39
39
  />
40
+ <AposLabel
41
+ class="apos-admin-bar__title-context-label"
42
+ v-for="{id, label, tooltip = '', modifiers = []} in moduleOptions.contextLabels"
43
+ :key="id"
44
+ :label="label"
45
+ :tooltip="tooltip"
46
+ :modifiers="modifiers"
47
+ />
40
48
  </span>
41
49
  </transition-group>
42
50
  </template>
@@ -109,12 +117,6 @@ export default {
109
117
  },
110
118
  moduleOptions() {
111
119
  return window.apos.adminBar;
112
- },
113
- unpublishedLabel() {
114
- return this.moduleOptions.unpublishedLabel || 'apostrophe:draft';
115
- },
116
- unpublishedTooltip() {
117
- return this.moduleOptions.unpublishedTooltip || 'apostrophe:notYetPublished';
118
120
  }
119
121
  },
120
122
  mounted() {
@@ -167,6 +169,10 @@ export default {
167
169
  }
168
170
  }
169
171
 
172
+ .apos-admin-bar__title-context-label {
173
+ margin-left: 5px;
174
+ }
175
+
170
176
  .apos-admin-bar__title__indicator {
171
177
  margin-right: 5px;
172
178
  color: var(--a-text-primary);
@@ -24,7 +24,7 @@
24
24
  }`
25
25
  ]"
26
26
  >
27
- <div
27
+ <button
28
28
  v-for="(item, itemIndex) in group.widgets"
29
29
  :key="itemIndex"
30
30
  class="apos-widget"
@@ -54,7 +54,7 @@
54
54
  <p v-if="item.description" class="apos-widget__help">
55
55
  {{ $t(item.description) }}
56
56
  </p>
57
- </div>
57
+ </button>
58
58
  </div>
59
59
  </div>
60
60
  </template>
@@ -239,6 +239,12 @@ export default {
239
239
  }
240
240
 
241
241
  .apos-widget {
242
+ @include type-base;
243
+ padding: 0;
244
+ border: none;
245
+ background: none;
246
+ text-align: inherit;
247
+
242
248
  .apos-widget__preview {
243
249
  transition: opacity 250ms ease-in-out;
244
250
  .apos-icon--add {
@@ -387,6 +387,10 @@ export default {
387
387
  apos.bus.$emit('widget-focus', this.widget._id);
388
388
  }
389
389
  },
390
+ destroyed() {
391
+ // Remove the focus parent listener when unmounted
392
+ apos.bus.$off('widget-focus-parent', this.focusParent);
393
+ },
390
394
  methods: {
391
395
  // Focus parent, useful for obtrusive UI
392
396
  focusParent() {
@@ -62,6 +62,7 @@ export default {
62
62
  modal: {
63
63
  busy: false,
64
64
  active: false,
65
+ trapFocus: false,
65
66
  type: 'overlay',
66
67
  showModal: false,
67
68
  disableHeader: true
@@ -1184,7 +1184,7 @@ module.exports = {
1184
1184
  ];
1185
1185
 
1186
1186
  function validate ({
1187
- action, context, label, modal, conditions
1187
+ action, context, label, modal, conditions, if: ifProps
1188
1188
  }) {
1189
1189
  const allowedConditions = [
1190
1190
  'canPublish',
@@ -1199,19 +1199,25 @@ module.exports = {
1199
1199
  ];
1200
1200
 
1201
1201
  if (!action || !context || !label || !modal) {
1202
- throw self.apos.error('invalid', 'addContextOperation requires action, context, label and modal properties');
1202
+ throw self.apos.error('invalid', 'addContextOperation requires action, context, label and modal properties.');
1203
1203
  }
1204
1204
 
1205
- if (!conditions) {
1206
- return;
1205
+ if (
1206
+ conditions &&
1207
+ (!Array.isArray(conditions) ||
1208
+ conditions.some((perm) => !allowedConditions.includes(perm)))
1209
+ ) {
1210
+ throw self.apos.error(
1211
+ 'invalid', `The conditions property in addContextOperation must be an array containing one or multiple of these values:\n\t${allowedConditions.join('\n\t')}.`
1212
+ );
1207
1213
  }
1208
1214
 
1209
1215
  if (
1210
- !Array.isArray(conditions) ||
1211
- conditions.some((perm) => !allowedConditions.includes(perm))
1216
+ ifProps &&
1217
+ (typeof ifProps !== 'object' || Array.isArray(ifProps))
1212
1218
  ) {
1213
1219
  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')}.`
1220
+ 'invalid', 'The if property in addContextOperation must be an object containing properties and values that will be checked against the current document in order to show or not the context operation.'
1215
1221
  );
1216
1222
  }
1217
1223
  }
@@ -23,6 +23,7 @@ import { detectDocChange } from 'Modules/@apostrophecms/schema/lib/detectChange'
23
23
  import AposPublishMixin from 'Modules/@apostrophecms/ui/mixins/AposPublishMixin';
24
24
  import AposArchiveMixin from 'Modules/@apostrophecms/ui/mixins/AposArchiveMixin';
25
25
  import AposModifiedMixin from 'Modules/@apostrophecms/ui/mixins/AposModifiedMixin';
26
+ import checkIfConditions from 'apostrophe/lib/check-if-conditions';
26
27
 
27
28
  export default {
28
29
  name: 'AposDocContextMenu',
@@ -196,7 +197,7 @@ export default {
196
197
  },
197
198
  customOperationsByContext() {
198
199
  return this.customOperations.filter(({
199
- manuallyPublished, hasUrl, conditions, context
200
+ manuallyPublished, hasUrl, conditions, context, if: ifProps
200
201
  }) => {
201
202
  if (typeof manuallyPublished === 'boolean' && manuallyPublished !== this.manuallyPublished) {
202
203
  return false;
@@ -214,6 +215,14 @@ export default {
214
215
  }
215
216
  }
216
217
 
218
+ if (ifProps) {
219
+ const canSeeOperation = checkIfConditions(this.doc, ifProps);
220
+
221
+ if (!canSeeOperation) {
222
+ return false;
223
+ }
224
+ }
225
+
217
226
  return context === 'update' && this.isUpdateOperation;
218
227
  });
219
228
  },
@@ -306,7 +315,10 @@ export default {
306
315
  );
307
316
  },
308
317
  manuallyPublished() {
309
- return this.moduleOptions.localized && !this.moduleOptions.autopublish;
318
+ return this.moduleOptions.localized && !this.autopublish;
319
+ },
320
+ autopublish() {
321
+ return this.context._aposAutopublish ?? this.moduleOptions.autopublish;
310
322
  },
311
323
  isModified() {
312
324
  if (!this.current) {
@@ -383,9 +395,10 @@ export default {
383
395
  this[action](this.context);
384
396
  },
385
397
  async edit(doc) {
386
- await apos.modal.execute(this.moduleOptions.components.editorModal, {
398
+ await apos.modal.execute(doc._aposEditorModal || this.moduleOptions.components.editorModal, {
387
399
  moduleName: this.moduleName,
388
- docId: doc._id
400
+ docId: doc._id,
401
+ type: doc.type
389
402
  });
390
403
  },
391
404
  preview(doc) {
@@ -401,28 +414,32 @@ export default {
401
414
  this.$emit('close', doc);
402
415
  }
403
416
  }
404
- // Because the page or piece manager might give us just a projected,
405
- // minimum number of properties otherwise
406
- const complete = await apos.http.get(`${this.moduleOptions.action}/${doc._id}`, {
407
- busy: true
408
- });
409
- Object.assign(doc, complete);
410
417
 
411
- apos.bus.$emit('admin-menu-click', {
412
- itemName: `${this.moduleName}:editor`,
413
- props: {
414
- copyOf: {
415
- ...this.current || doc,
416
- _id: doc._id
417
- }
418
- }
418
+ await apos.modal.execute(doc._aposEditorModal || this.moduleOptions.components.editorModal, {
419
+ moduleName: this.moduleName,
420
+ copyOfId: doc._id,
421
+ // Passed for bc
422
+ copyOf: {
423
+ ...this.current || doc,
424
+ _id: doc._id
425
+ },
426
+ type: doc.type
419
427
  });
428
+
420
429
  },
421
430
  async customAction(doc, operation) {
422
431
  await apos.modal.execute(operation.modal, {
423
432
  moduleName: operation.moduleName,
424
- doc
433
+ // For backwards compatibility
434
+ doc,
435
+ ...docProps(doc),
436
+ ...operation.props
425
437
  });
438
+ function docProps(doc) {
439
+ return Object.fromEntries(Object.entries(operation.docProps || {}).map(([ key, value ]) => {
440
+ return [ key, doc[value] ];
441
+ }));
442
+ }
426
443
  },
427
444
  async localize(doc) {
428
445
  // If there are changes warn the user before discarding them before
@@ -447,6 +464,3 @@ export default {
447
464
  }
448
465
  };
449
466
  </script>
450
-
451
- <style lang="scss" scoped>
452
- </style>
@@ -67,11 +67,12 @@
67
67
  :conditional-fields="conditionalFields('other')"
68
68
  :doc-id="docId"
69
69
  :value="docFields"
70
- @input="updateDocFields"
71
- @validate="triggerValidate"
72
70
  :server-errors="serverErrors"
73
71
  :ref="tab.name"
74
72
  :generation="generation"
73
+ @input="updateDocFields"
74
+ @validate="triggerValidate"
75
+ @update-doc-data="onUpdateDocFields"
75
76
  />
76
77
  </div>
77
78
  </template>
@@ -140,8 +141,12 @@ export default {
140
141
  type: String,
141
142
  default: null
142
143
  },
143
- copyOf: {
144
- type: Object,
144
+ type: {
145
+ type: String,
146
+ default: null
147
+ },
148
+ copyOfId: {
149
+ type: String,
145
150
  default: null
146
151
  }
147
152
  },
@@ -153,6 +158,7 @@ export default {
153
158
  fieldErrors: {},
154
159
  modal: {
155
160
  active: false,
161
+ triggerFocusRefresh: 0,
156
162
  type: 'overlay',
157
163
  showModal: false
158
164
  },
@@ -265,22 +271,12 @@ export default {
265
271
  };
266
272
  }
267
273
  },
268
- currentFields() {
269
- if (this.currentTab) {
270
- const tabFields = this.tabs.find((item) => {
271
- return item.name === this.currentTab;
272
- });
273
- return this.filterOutParkedFields(tabFields.fields);
274
- } else {
275
- return [];
276
- }
277
- },
278
274
  saveLabel() {
279
275
  if (this.restoreOnly) {
280
276
  return 'apostrophe:restore';
281
277
  } else if (this.manuallyPublished) {
282
278
  if (this.canPublish) {
283
- if (this.copyOf) {
279
+ if (this.copyOfId) {
284
280
  return 'apostrophe:publish';
285
281
  } else if (this.original && this.original.lastPublishedAt) {
286
282
  return 'apostrophe:update';
@@ -288,7 +284,7 @@ export default {
288
284
  return 'apostrophe:publish';
289
285
  }
290
286
  } else {
291
- if (this.copyOf) {
287
+ if (this.copyOfId) {
292
288
  return 'apostrophe:submit';
293
289
  } else if (this.original && this.original.lastPublishedAt) {
294
290
  return 'apostrophe:submitUpdate';
@@ -374,19 +370,25 @@ export default {
374
370
  });
375
371
  }
376
372
  }
377
- } else if (this.copyOf) {
378
- const newInstance = klona(this.copyOf);
373
+ this.modal.triggerFocusRefresh++;
374
+ } else if (this.copyOfId) {
375
+ // Because the page or piece manager might give us just a projected,
376
+ // minimum number of properties otherwise, and because we need to
377
+ // make sure we use our preferred module to fetch the content
378
+ const newInstance = await apos.http.get(`${this.moduleOptions.action}/${this.copyOfId}`, {
379
+ busy: true
380
+ });
379
381
  delete newInstance.parked;
380
- newInstance.title = `Copy of ${this.copyOf.title}`;
381
- if (this.copyOf.slug.startsWith('/')) {
382
- const matches = this.copyOf.slug.match(/\/([^/]+)$/);
382
+ newInstance.title = `Copy of ${newInstance.title}`;
383
+ if (newInstance.slug.startsWith('/')) {
384
+ const matches = newInstance.slug.match(/\/([^/]+)$/);
383
385
  if (matches) {
384
386
  newInstance.slug = `${apos.page.page.slug}/copy-of-${matches[1]}`;
385
387
  } else {
386
388
  newInstance.slug = '/copy-of-home-page';
387
389
  }
388
390
  } else {
389
- newInstance.slug = this.copyOf.slug.replace(/([^/]+)$/, 'copy-of-$1');
391
+ newInstance.slug = newInstance.slug.replace(/([^/]+)$/, 'copy-of-$1');
390
392
  }
391
393
  delete newInstance._id;
392
394
  delete newInstance._url;
@@ -399,9 +401,11 @@ export default {
399
401
  this.docFields.data = newInstance;
400
402
  this.prepErrors();
401
403
  this.docReady = true;
404
+ this.modal.triggerFocusRefresh++;
402
405
  } else {
403
- this.$nextTick(() => {
404
- this.loadNewInstance();
406
+ this.$nextTick(async () => {
407
+ await this.loadNewInstance();
408
+ this.modal.triggerFocusRefresh++;
405
409
  });
406
410
  }
407
411
  apos.bus.$on('content-changed', this.onContentChanged);
@@ -505,7 +509,7 @@ export default {
505
509
  await this.restore(this.original);
506
510
  await this.loadDoc();
507
511
  },
508
- async onSave(navigate = false) {
512
+ async onSave({ navigate = false } = {}) {
509
513
  if (this.canPublish || !this.manuallyPublished) {
510
514
  await this.save({
511
515
  andPublish: this.manuallyPublished,
@@ -566,8 +570,8 @@ export default {
566
570
  body._targetId = apos.page.page._id.replace(':published', ':draft');
567
571
  body._position = 'lastChild';
568
572
  }
569
- if (this.copyOf) {
570
- body._copyingId = this.copyOf._id;
573
+ if (this.copyOfId) {
574
+ body._copyingId = this.copyOfId;
571
575
  }
572
576
  }
573
577
  let doc;
@@ -665,6 +669,10 @@ export default {
665
669
  itemName: `${this.moduleName}:editor`
666
670
  });
667
671
  },
672
+ onUpdateDocFields(value) {
673
+ this.updateDocFields(value);
674
+ this.generation++;
675
+ },
668
676
  updateDocFields(value) {
669
677
  this.updateFieldErrors(value.fieldState);
670
678
  this.docFields.data = {