apostrophe 4.5.3 → 4.6.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 (55) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/lib/mongodb-connect.js +9 -2
  3. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBar.vue +6 -1
  4. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarLocale.vue +18 -180
  5. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarMenu.vue +6 -2
  6. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +1 -1
  7. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextModeAndSettings.vue +11 -0
  8. package/modules/@apostrophecms/any-page-type/index.js +6 -1
  9. package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +2 -0
  10. package/modules/@apostrophecms/area/ui/apos/components/AposAreaMenu.vue +6 -1
  11. package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +63 -11
  12. package/modules/@apostrophecms/area/ui/apos/components/AposWidgetControls.vue +12 -15
  13. package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.config.js +10 -1
  14. package/modules/@apostrophecms/db/index.js +2 -3
  15. package/modules/@apostrophecms/doc-type/index.js +7 -1
  16. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +183 -109
  17. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocLocalePicker.vue +177 -0
  18. package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +4 -0
  19. package/modules/@apostrophecms/i18n/i18n/de.json +1 -0
  20. package/modules/@apostrophecms/i18n/i18n/en.json +7 -1
  21. package/modules/@apostrophecms/i18n/i18n/es.json +1 -0
  22. package/modules/@apostrophecms/i18n/i18n/fr.json +1 -0
  23. package/modules/@apostrophecms/i18n/i18n/it.json +1 -0
  24. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +1 -0
  25. package/modules/@apostrophecms/i18n/i18n/sk.json +1 -0
  26. package/modules/@apostrophecms/i18n/index.js +22 -0
  27. package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +1 -0
  28. package/modules/@apostrophecms/i18n/ui/src/index.js +3 -0
  29. package/modules/@apostrophecms/modal/ui/apos/apps/AposModals.js +1 -0
  30. package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +22 -30
  31. package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +24 -1
  32. package/modules/@apostrophecms/modal/ui/apos/components/TheAposModals.vue +48 -38
  33. package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +7 -0
  34. package/modules/@apostrophecms/page/index.js +5 -3
  35. package/modules/@apostrophecms/page/ui/apos/components/AposPagesManager.vue +1 -0
  36. package/modules/@apostrophecms/page/ui/apos/logic/AposPagesManager.js +17 -3
  37. package/modules/@apostrophecms/piece-type/index.js +5 -3
  38. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +25 -4
  39. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerSelectBox.vue +1 -1
  40. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapLink.vue +4 -3
  41. package/modules/@apostrophecms/search/index.js +36 -2
  42. package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +17 -10
  43. package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue +36 -23
  44. package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuDialog.vue +1 -1
  45. package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuItem.vue +1 -0
  46. package/modules/@apostrophecms/ui/ui/apos/components/AposLocale.vue +36 -0
  47. package/modules/@apostrophecms/ui/ui/apos/components/AposLocalePicker.vue +283 -0
  48. package/modules/@apostrophecms/ui/ui/apos/components/AposSlat.vue +56 -18
  49. package/modules/@apostrophecms/ui/ui/apos/lib/tooltip.js +2 -1
  50. package/modules/@apostrophecms/ui/ui/apos/mixins/AposArchiveMixin.js +4 -2
  51. package/modules/@apostrophecms/ui/ui/apos/mixins/AposPublishMixin.js +12 -6
  52. package/modules/@apostrophecms/ui/ui/apos/stores/modal.js +10 -1
  53. package/modules/@apostrophecms/util/ui/src/http.js +12 -5
  54. package/package.json +4 -4
  55. package/test/search.js +64 -0
@@ -11,7 +11,6 @@
11
11
  content: (!disabled && !first) ? 'apostrophe:nudgeUp' : null,
12
12
  placement: 'left'
13
13
  }"
14
- :modifiers="[ 'inline' ]"
15
14
  @click="$emit('up')"
16
15
  />
17
16
  <AposButton
@@ -22,7 +21,6 @@
22
21
  content: 'apostrophe:editWidget',
23
22
  placement: 'left'
24
23
  }"
25
- :modifiers="[ 'inline' ]"
26
24
  @click="$emit('edit')"
27
25
  />
28
26
  <AposButton
@@ -32,7 +30,6 @@
32
30
  content: 'apostrophe:cut',
33
31
  placement: 'left'
34
32
  }"
35
- :modifiers="[ 'inline' ]"
36
33
  @click="$emit('cut')"
37
34
  />
38
35
  <AposButton
@@ -52,7 +49,6 @@
52
49
  content: 'apostrophe:duplicate',
53
50
  placement: 'left'
54
51
  }"
55
- :modifiers="[ 'inline' ]"
56
52
  @click="$emit('clone')"
57
53
  />
58
54
  <AposButton
@@ -63,7 +59,6 @@
63
59
  content: 'apostrophe:delete',
64
60
  placement: 'left'
65
61
  }"
66
- :modifiers="[ 'inline' ]"
67
62
  @click="$emit('remove')"
68
63
  />
69
64
  <AposButton
@@ -74,7 +69,6 @@
74
69
  content: (!disabled && !last) ? 'apostrophe:nudgeDown' : null,
75
70
  placement: 'left'
76
71
  }"
77
- :modifiers="[ 'inline' ]"
78
72
  @click="$emit('down')"
79
73
  />
80
74
  </AposButtonGroup>
@@ -110,23 +104,26 @@ export default {
110
104
  maxReached: {
111
105
  type: Boolean,
112
106
  default: false
107
+ },
108
+ tabbable: {
109
+ type: Boolean,
110
+ default: false
113
111
  }
114
112
  },
115
113
  emits: [ 'remove', 'edit', 'cut', 'copy', 'clone', 'up', 'down' ],
116
- data() {
117
- return {
118
- buttonDefaults: {
114
+ computed: {
115
+ buttonDefaults() {
116
+ return {
119
117
  iconOnly: true,
120
118
  icon: 'plus-icon',
121
119
  type: 'group',
122
- modifiers: [ 'small' ],
120
+ modifiers: [ 'small', 'inline' ],
123
121
  role: 'menuitem',
124
122
  class: 'apos-area-modify-controls__button',
125
- iconSize: 16
126
- }
127
- };
128
- },
129
- computed: {
123
+ iconSize: 16,
124
+ disableFocus: !this.tabbable
125
+ };
126
+ },
130
127
  upButton() {
131
128
  return {
132
129
  ...this.buttonDefaults,
@@ -25,6 +25,15 @@ module.exports = ({
25
25
 
26
26
  const mode = process.env.NODE_ENV || 'development';
27
27
  const pnpmModulePath = apos.isPnpm ? [ path.join(apos.selfDir, '../') ] : [];
28
+ const dev = (mode === 'development');
29
+ // The default behavior of the security-headers module
30
+ // blocks eval, and only eval-source-map currently associates
31
+ // errors with the right file. With regular source-map the
32
+ // results are worse than no source map. A developer who wishes
33
+ // to experiment can set the devSourceMap option of the asset module
34
+ const csp = apos.modules['@apostrophecms/security-headers'];
35
+ const fallbackSourceMap = csp ? false : 'eval-source-map';
36
+ const devtool = dev ? (apos.asset.options.devSourceMap || fallbackSourceMap) : false;
28
37
  const config = {
29
38
  performance: {
30
39
  hints: false
@@ -36,7 +45,7 @@ module.exports = ({
36
45
  optimization: {
37
46
  minimize: process.env.NODE_ENV === 'production'
38
47
  },
39
- devtool: 'source-map',
48
+ devtool,
40
49
  output: {
41
50
  path: outputPath,
42
51
  filename: outputFilename
@@ -119,9 +119,8 @@ module.exports = {
119
119
 
120
120
  self.apos.dbClient = await mongodbConnect(uri, self.options.connect);
121
121
  self.uri = uri;
122
- const parsed = new URL(uri);
123
- self.apos.db = self.apos.dbClient.db(parsed.pathname.substring(1));
124
-
122
+ // Automatically uses the db name in the connection string
123
+ self.apos.db = self.apos.dbClient.db();
125
124
  },
126
125
  async versionCheck() {
127
126
  if (!self.options.versionCheck) {
@@ -759,7 +759,9 @@ module.exports = {
759
759
 
760
760
  async convert(req, input, doc, options = {
761
761
  presentFieldsOnly: false,
762
- copyingId: false
762
+ type: null,
763
+ copyingId: null,
764
+ createId: null
763
765
  }) {
764
766
  const fullSchema = self.apos.doc.getManager(options.type || self.name)
765
767
  .allowedSchema(req, doc);
@@ -784,6 +786,10 @@ module.exports = {
784
786
 
785
787
  await self.apos.schema.convert(req, schema, input, doc);
786
788
 
789
+ if (options.createId) {
790
+ doc.aposDocId = options.createId;
791
+ }
792
+
787
793
  if (copyOf) {
788
794
  if (copyOf._id) {
789
795
  doc.copyOfId = copyOf._id;
@@ -3,6 +3,7 @@
3
3
  class="apos-doc-editor"
4
4
  :modal="modal"
5
5
  :modal-title="modalTitle"
6
+ :modal-data="modalData"
6
7
  @inactive="modal.active = false"
7
8
  @show-modal="modal.showModal = true"
8
9
  @esc="confirmAndCancel"
@@ -14,6 +15,15 @@
14
15
  @click="confirmAndCancel"
15
16
  />
16
17
  </template>
18
+ <template v-if="showLocalePicker" #localeDisplay>
19
+ <AposDocLocalePicker
20
+ :locale="modalData.locale"
21
+ :doc-id="referenceDocId"
22
+ :module-options="moduleOptions"
23
+ :is-modified="isModified"
24
+ @switch-locale="switchLocale"
25
+ />
26
+ </template>
17
27
  <template #primaryControls>
18
28
  <AposDocContextMenu
19
29
  v-if="original"
@@ -23,6 +33,7 @@
23
33
  :published="published"
24
34
  :show-edit="false"
25
35
  :can-delete-draft="moduleOptions.canDeleteDraft"
36
+ :locale-switched="localeSwitched"
26
37
  @close="close"
27
38
  />
28
39
  <AposButton
@@ -71,7 +82,7 @@
71
82
  :utility-rail="false"
72
83
  :following-values="followingValues('other')"
73
84
  :conditional-fields="conditionalFields"
74
- :doc-id="docId"
85
+ :doc-id="currentId"
75
86
  :model-value="docFields"
76
87
  :meta="docMeta"
77
88
  :server-errors="serverErrors"
@@ -97,7 +108,7 @@
97
108
  :utility-rail="true"
98
109
  :following-values="followingUtils"
99
110
  :conditional-fields="conditionalFields"
100
- :doc-id="docId"
111
+ :doc-id="currentId"
101
112
  :model-value="docFields"
102
113
  :meta="docMeta"
103
114
  :modifiers="['small', 'inverted']"
@@ -114,6 +125,7 @@
114
125
 
115
126
  <script>
116
127
  import { klona } from 'klona';
128
+ import { mapActions } from 'pinia';
117
129
  import AposModifiedMixin from 'Modules/@apostrophecms/ui/mixins/AposModifiedMixin';
118
130
  import AposModalTabsMixin from 'Modules/@apostrophecms/modal/mixins/AposModalTabsMixin';
119
131
  import AposEditorMixin from 'Modules/@apostrophecms/modal/mixins/AposEditorMixin';
@@ -122,6 +134,7 @@ import AposArchiveMixin from 'Modules/@apostrophecms/ui/mixins/AposArchiveMixin'
122
134
  import AposAdvisoryLockMixin from 'Modules/@apostrophecms/ui/mixins/AposAdvisoryLockMixin';
123
135
  import AposDocErrorsMixin from 'Modules/@apostrophecms/modal/mixins/AposDocErrorsMixin';
124
136
  import { detectDocChange } from 'Modules/@apostrophecms/schema/lib/detectChange';
137
+ import { useModalStore } from 'Modules/@apostrophecms/ui/stores/modal';
125
138
 
126
139
  export default {
127
140
  name: 'AposDocEditor',
@@ -156,6 +169,10 @@ export default {
156
169
  copyOfId: {
157
170
  type: String,
158
171
  default: null
172
+ },
173
+ modalData: {
174
+ type: Object,
175
+ required: true
159
176
  }
160
177
  },
161
178
  emits: [ 'modal-result' ],
@@ -168,7 +185,8 @@ export default {
168
185
  active: false,
169
186
  triggerFocusRefresh: 0,
170
187
  type: 'overlay',
171
- showModal: false
188
+ showModal: false,
189
+ componentType: 'editorModal'
172
190
  },
173
191
  triggerValidation: false,
174
192
  original: null,
@@ -179,12 +197,15 @@ export default {
179
197
  readOnly: false,
180
198
  restoreOnly: false,
181
199
  saveMenu: null,
182
- generation: 0
200
+ generation: 0,
201
+ localeSwitched: this.modalData.hasContextLocale,
202
+ referenceDocId: this.docId,
203
+ currentId: this.docId
183
204
  };
184
205
  },
185
206
  computed: {
186
207
  getOnePath() {
187
- return `${this.moduleAction}/${this.docId}`;
208
+ return `${this.moduleAction}/${this.currentId}`;
188
209
  },
189
210
  followingUtils() {
190
211
  return this.followingValues('utility');
@@ -220,7 +241,7 @@ export default {
220
241
  // Always block save if there are errors in the modal
221
242
  return true;
222
243
  }
223
- if (!this.docId) {
244
+ if (!this.currentId) {
224
245
  // If it is new you can always save it, even just to insert it with
225
246
  // defaults is sometimes useful
226
247
  return false;
@@ -272,17 +293,10 @@ export default {
272
293
  return this.filterOutParkedFields(fields);
273
294
  },
274
295
  modalTitle() {
275
- if (this.docId) {
276
- return {
277
- key: 'apostrophe:editType',
278
- type: this.$t(this.moduleOptions.label)
279
- };
280
- } else {
281
- return {
282
- key: 'apostrophe:newDocType',
283
- type: this.$t(this.moduleOptions.label)
284
- };
285
- }
296
+ return {
297
+ key: this.currentId ? 'apostrophe:editType' : 'apostrophe:newDocType',
298
+ type: this.$t(this.moduleOptions.label)
299
+ };
286
300
  },
287
301
  saveLabel() {
288
302
  if (this.restoreOnly) {
@@ -330,6 +344,11 @@ export default {
330
344
  pref = null;
331
345
  }
332
346
  return pref;
347
+ },
348
+ showLocalePicker() {
349
+ return Object.keys(window.apos.i18n.locales).length > 1 &&
350
+ this.moduleOptions.localized !== false &&
351
+ !this.modalData.hasContextLocale;
333
352
  }
334
353
  },
335
354
  watch: {
@@ -363,6 +382,21 @@ export default {
363
382
  type: this.$t(this.moduleOptions.label)
364
383
  };
365
384
  if (this.docId) {
385
+ await this.instantiateExistingDoc();
386
+ } else if (this.copyOfId) {
387
+ this.instantiateCopiedDoc();
388
+ } else {
389
+ await this.$nextTick();
390
+ await this.instantiateNewDoc();
391
+ }
392
+ apos.bus.$on('content-changed', this.onContentChanged);
393
+ },
394
+ unmounted() {
395
+ apos.bus.$off('content-changed', this.onContentChanged);
396
+ },
397
+ methods: {
398
+ ...mapActions(useModalStore, [ 'updateModalData' ]),
399
+ async instantiateExistingDoc() {
366
400
  await this.loadDoc();
367
401
  this.evaluateConditions();
368
402
  try {
@@ -386,7 +420,8 @@ export default {
386
420
  }
387
421
  }
388
422
  this.modal.triggerFocusRefresh++;
389
- } else if (this.copyOfId) {
423
+ },
424
+ async instantiateCopiedDoc() {
390
425
  this.evaluateConditions();
391
426
 
392
427
  // Because the page or piece manager might give us just a projected,
@@ -419,37 +454,44 @@ export default {
419
454
  this.prepErrors();
420
455
  this.docReady = true;
421
456
  this.modal.triggerFocusRefresh++;
422
- } else {
423
- this.$nextTick(async () => {
424
- await this.loadNewInstance();
425
- this.evaluateConditions();
426
- this.modal.triggerFocusRefresh++;
427
- });
428
- }
429
- apos.bus.$on('content-changed', this.onContentChanged);
430
- },
431
- unmounted() {
432
- apos.bus.$off('content-changed', this.onContentChanged);
433
- },
434
- methods: {
435
- async saveHandler(action) {
457
+ },
458
+ async instantiateNewDoc () {
459
+ this.docReady = false;
460
+ const newInstance = await this.getNewInstance();
461
+ this.original = newInstance;
462
+ if (newInstance && newInstance.type !== this.docType) {
463
+ this.docType = newInstance.type;
464
+ }
465
+ this.docFields.data = newInstance;
466
+ const slugField = this.schema.find(field => field.name === 'slug');
467
+ if (slugField) {
468
+ // As a matter of UI implementation, we know our slug input field will
469
+ // automatically change the empty string to the prefix, so to
470
+ // prevent a false positive for this being considered a change,
471
+ // do it earlier when creating a new doc.
472
+ this.original.slug = this.original.slug || slugField.def || slugField.prefix || '';
473
+ }
474
+ this.prepErrors();
475
+ this.docReady = true;
476
+ this.evaluateConditions();
477
+ },
478
+ async saveHandler(action, saveOpts = {}) {
436
479
  this.triggerValidation = true;
437
- this.$nextTick(async () => {
438
- if (this.savePreference !== action) {
439
- this.setSavePreference(action);
440
- }
441
- if (!this.errorCount) {
442
- this[action]();
443
- } else {
444
- this.triggerValidation = false;
445
- await apos.notify('apostrophe:resolveErrorsBeforeSaving', {
446
- type: 'warning',
447
- icon: 'alert-circle-icon',
448
- dismiss: true
449
- });
450
- this.focusNextError();
451
- }
452
- });
480
+ await this.$nextTick();
481
+ if (this.savePreference !== action) {
482
+ this.setSavePreference(action);
483
+ }
484
+ if (!this.errorCount) {
485
+ return this[action](saveOpts);
486
+ } else {
487
+ this.triggerValidation = false;
488
+ await apos.notify('apostrophe:resolveErrorsBeforeSaving', {
489
+ type: 'warning',
490
+ icon: 'alert-circle-icon',
491
+ dismiss: true
492
+ });
493
+ this.focusNextError();
494
+ }
453
495
  },
454
496
  async loadDoc() {
455
497
  let docData;
@@ -469,7 +511,7 @@ export default {
469
511
  }
470
512
  const canEdit = docData._edit || this.moduleOptions.canEdit;
471
513
  this.readOnly = canEdit === false;
472
- if (canEdit && !await this.lock(this.getOnePath, this.docId)) {
514
+ if (canEdit && !await this.lock(this.getOnePath, this.currentId)) {
473
515
  this.lockNotAvailable();
474
516
  return;
475
517
  }
@@ -490,8 +532,14 @@ export default {
490
532
  ...this.getDefault(),
491
533
  ...docData
492
534
  };
535
+ // TODO: Is this block even useful since published is fetched after loadDoc?
493
536
  if (this.published) {
494
- this.changed = detectDocChange(this.schema, this.original, this.published, { differences: true });
537
+ this.changed = detectDocChange(
538
+ this.schema,
539
+ this.original,
540
+ this.published,
541
+ { differences: true }
542
+ );
495
543
  }
496
544
  this.docReady = true;
497
545
  this.prepErrors();
@@ -527,17 +575,21 @@ export default {
527
575
  await this.restore(this.original);
528
576
  await this.loadDoc();
529
577
  },
530
- async onSave({ navigate = false } = {}) {
578
+ async onSave({
579
+ navigate = false, keepOpen = false, andPublish = null
580
+ } = {}) {
531
581
  if (this.canPublish || !this.manuallyPublished) {
532
- await this.save({
533
- andPublish: this.manuallyPublished,
534
- navigate
582
+ return this.save({
583
+ andPublish: andPublish ?? this.manuallyPublished,
584
+ navigate,
585
+ keepOpen
535
586
  });
536
587
  } else {
537
- await this.save({
588
+ return this.save({
538
589
  andPublish: false,
539
590
  andSubmit: true,
540
- navigate
591
+ navigate,
592
+ keepOpen
541
593
  });
542
594
  }
543
595
  },
@@ -555,7 +607,7 @@ export default {
555
607
  async onSaveDraftAndView() {
556
608
  await this.onSaveDraft({ navigate: true });
557
609
  },
558
- async onSaveDraft(navigate = false) {
610
+ async onSaveDraft({ navigate = false } = {}) {
559
611
  await this.save({
560
612
  andPublish: false,
561
613
  navigate
@@ -566,32 +618,22 @@ export default {
566
618
  icon: 'file-document-icon'
567
619
  });
568
620
  },
569
- // If andPublish is true, publish after saving.
570
621
  async save({
571
622
  andPublish = false,
572
623
  navigate = false,
573
- andSubmit = false
624
+ andSubmit = false,
625
+ keepOpen = false
574
626
  }) {
575
- const body = this.docFields.data;
576
- let route;
577
- let requestMethod;
578
- if (this.docId) {
579
- route = `${this.moduleAction}/${this.docId}`;
580
- requestMethod = apos.http.put;
581
- this.addLockToRequest(body);
582
- } else {
583
- route = this.moduleAction;
584
- requestMethod = apos.http.post;
627
+ const body = this.getRequestBody({ update: Boolean(this.currentId) });
628
+ const route = this.currentId
629
+ ? `${this.moduleAction}/${this.currentId}`
630
+ : this.moduleAction;
631
+ const requestMethod = this.currentId ? apos.http.put : apos.http.post;
585
632
 
586
- if (this.moduleName === '@apostrophecms/page') {
587
- // New pages are always born as drafts
588
- body._targetId = apos.page.page._id.replace(':published', ':draft');
589
- body._position = 'lastChild';
590
- }
591
- if (this.copyOfId) {
592
- body._copyingId = this.copyOfId;
593
- }
633
+ if (this.currentId) {
634
+ this.addLockToRequest(body);
594
635
  }
636
+
595
637
  let doc;
596
638
  try {
597
639
  await this.postprocess();
@@ -607,7 +649,8 @@ export default {
607
649
  }
608
650
  apos.bus.$emit('content-changed', {
609
651
  doc,
610
- action: (requestMethod === apos.http.put) ? 'update' : 'insert'
652
+ action: (requestMethod === apos.http.put) ? 'update' : 'insert',
653
+ localeSwitched: this.localeSwitched
611
654
  });
612
655
  } catch (e) {
613
656
  if (this.isLockedError(e)) {
@@ -621,8 +664,10 @@ export default {
621
664
  return;
622
665
  }
623
666
  }
624
- this.$emit('modal-result', doc);
625
- this.modal.showModal = false;
667
+ if (!keepOpen) {
668
+ this.$emit('modal-result', doc);
669
+ this.modal.showModal = false;
670
+ }
626
671
  if (navigate) {
627
672
  if (doc._url) {
628
673
  window.location = doc._url;
@@ -633,18 +678,11 @@ export default {
633
678
  });
634
679
  }
635
680
  }
681
+ return doc;
636
682
  },
637
683
  async getNewInstance() {
638
684
  try {
639
- const body = {
640
- _newInstance: true
641
- };
642
-
643
- if (this.moduleName === '@apostrophecms/page') {
644
- // New pages are always born as drafts
645
- body._targetId = apos.page.page._id.replace(':published', ':draft');
646
- body._position = 'lastChild';
647
- }
685
+ const body = this.getRequestBody({ newInstance: true });
648
686
  const newDoc = await apos.http.post(this.moduleAction, {
649
687
  body,
650
688
  draft: true
@@ -662,25 +700,6 @@ export default {
662
700
  this.modal.showModal = false;
663
701
  }
664
702
  },
665
- async loadNewInstance () {
666
- this.docReady = false;
667
- const newInstance = await this.getNewInstance();
668
- this.original = newInstance;
669
- if (newInstance && newInstance.type !== this.docType) {
670
- this.docType = newInstance.type;
671
- }
672
- this.docFields.data = newInstance;
673
- const slugField = this.schema.find(field => field.name === 'slug');
674
- if (slugField) {
675
- // As a matter of UI implementation, we know our slug input field will
676
- // automatically change the empty string to the prefix, so to
677
- // prevent a false positive for this being considered a change,
678
- // do it earlier when creating a new doc.
679
- this.original.slug = this.original.slug || slugField.def || slugField.prefix || '';
680
- }
681
- this.prepErrors();
682
- this.docReady = true;
683
- },
684
703
  startNew() {
685
704
  this.modal.showModal = false;
686
705
  apos.bus.$emit('admin-menu-click', {
@@ -719,7 +738,7 @@ export default {
719
738
  const typeLabel = this.$t(this.moduleOptions
720
739
  ? this.moduleOptions.label
721
740
  : 'document');
722
- const isNew = !this.docId;
741
+ const isNew = !this.currentId;
723
742
  // this.original takes a moment to populate, don't crash
724
743
  const canPreview = this.original && (this.original._id ? this.original._url : this.original._previewable);
725
744
  const canNew = this.moduleOptions.showCreate;
@@ -823,6 +842,61 @@ export default {
823
842
  },
824
843
  close() {
825
844
  this.modal.showModal = false;
845
+ },
846
+ async switchLocale({
847
+ locale, localized, save
848
+ }) {
849
+ if (save) {
850
+ const saved = await this.saveHandler('onSave', {
851
+ keepOpen: true,
852
+ andPublish: false
853
+ });
854
+ if (this.errorCount > 0) {
855
+ return;
856
+ }
857
+ if (!this.referenceDocId && saved) {
858
+ this.referenceDocId = saved._id;
859
+ }
860
+ }
861
+ this.updateModalData(this.modalData.id, { locale });
862
+ this.localeSwitched = locale !== apos.i18n.locale;
863
+ this.published = null;
864
+ if (localized) {
865
+ this.currentId = localized._id;
866
+ await this.instantiateExistingDoc();
867
+ } else {
868
+ this.currentId = '';
869
+ this.docType = this.moduleName;
870
+ await this.instantiateNewDoc();
871
+ }
872
+ },
873
+ getRequestBody({ newInstance = false, update = false }) {
874
+ const body = newInstance
875
+ ? { _newInstance: true }
876
+ : this.docFields.data;
877
+
878
+ if (update) {
879
+ return body;
880
+ }
881
+
882
+ if (this.moduleName === '@apostrophecms/page') {
883
+ // New pages are always born as drafts
884
+ // When in another locale we don't know if the current page exist
885
+ body._targetId = this.localeSwitched
886
+ ? '_home'
887
+ : apos.page.page._id.replace(':published', ':draft');
888
+ body._position = 'lastChild';
889
+ }
890
+
891
+ if (!newInstance) {
892
+ if (this.copyOfId) {
893
+ body._copyingId = this.copyOfId;
894
+ } else if (this.localeSwitched && this.referenceDocId) {
895
+ body._createId = this.referenceDocId.split(':')[0];
896
+ }
897
+ }
898
+
899
+ return body;
826
900
  }
827
901
  }
828
902
  };