apostrophe 4.6.1 → 4.7.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 (124) hide show
  1. package/.github/workflows/main.yml +1 -1
  2. package/CHANGELOG.md +39 -1
  3. package/lib/big-upload-client.js +100 -0
  4. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBar.vue +5 -3
  5. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarLocale.vue +6 -3
  6. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarUser.vue +4 -1
  7. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +24 -16
  8. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextTitle.vue +1 -0
  9. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposSavingIndicator.vue +7 -5
  10. package/modules/@apostrophecms/area/index.js +5 -2
  11. package/modules/@apostrophecms/area/ui/apos/components/AposAreaContextualMenu.vue +20 -12
  12. package/modules/@apostrophecms/area/ui/apos/components/AposAreaExpandedMenu.vue +11 -7
  13. package/modules/@apostrophecms/area/ui/apos/components/AposAreaMenu.vue +20 -12
  14. package/modules/@apostrophecms/area/ui/apos/components/AposAreaMenuItem.vue +3 -1
  15. package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +15 -11
  16. package/modules/@apostrophecms/attachment/index.js +4 -2
  17. package/modules/@apostrophecms/command-menu/ui/apos/components/AposCommandMenuKey.vue +28 -24
  18. package/modules/@apostrophecms/command-menu/ui/apos/components/AposCommandMenuShortcut.vue +17 -11
  19. package/modules/@apostrophecms/doc/index.js +22 -19
  20. package/modules/@apostrophecms/doc-type/index.js +6 -2
  21. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +9 -5
  22. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocLocalePicker.vue +10 -5
  23. package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +12 -0
  24. package/modules/@apostrophecms/http/index.js +19 -3
  25. package/modules/@apostrophecms/http/lib/big-upload-middleware.js +251 -0
  26. package/modules/@apostrophecms/i18n/i18n/de.json +1 -1
  27. package/modules/@apostrophecms/i18n/i18n/en.json +9 -1
  28. package/modules/@apostrophecms/i18n/i18n/es.json +1 -1
  29. package/modules/@apostrophecms/i18n/i18n/fr.json +1 -1
  30. package/modules/@apostrophecms/i18n/i18n/it.json +1 -1
  31. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +1 -1
  32. package/modules/@apostrophecms/i18n/i18n/sk.json +1 -1
  33. package/modules/@apostrophecms/i18n/index.js +3 -0
  34. package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +30 -16
  35. package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalizeErrors.vue +7 -5
  36. package/modules/@apostrophecms/image/ui/apos/components/AposImageCropper.vue +5 -1
  37. package/modules/@apostrophecms/image/ui/apos/components/AposImageRelationshipEditor.vue +10 -6
  38. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +40 -18
  39. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerDisplay.vue +35 -25
  40. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +11 -5
  41. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerSelections.vue +15 -9
  42. package/modules/@apostrophecms/image/ui/apos/components/AposMediaUploader.vue +39 -31
  43. package/modules/@apostrophecms/job/index.js +1 -1
  44. package/modules/@apostrophecms/login/ui/apos/components/AposLoginForm.vue +9 -7
  45. package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +17 -13
  46. package/modules/@apostrophecms/login/ui/apos/components/TheAposLoginHeader.vue +30 -20
  47. package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +5 -0
  48. package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +4 -1
  49. package/modules/@apostrophecms/modal/ui/apos/components/AposModalBreadcrumbs.vue +8 -4
  50. package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +14 -8
  51. package/modules/@apostrophecms/modal/ui/apos/components/AposModalShareDraft.vue +32 -22
  52. package/modules/@apostrophecms/modal/ui/apos/components/AposModalTabs.vue +16 -14
  53. package/modules/@apostrophecms/modal/ui/apos/components/AposWidgetModalTabs.vue +16 -14
  54. package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +93 -91
  55. package/modules/@apostrophecms/page/index.js +482 -13
  56. package/modules/@apostrophecms/page/ui/apos/components/AposPagesManager.vue +43 -23
  57. package/modules/@apostrophecms/page/ui/apos/logic/AposPagesManager.js +248 -156
  58. package/modules/@apostrophecms/permission/ui/apos/components/AposPermissionGrid.vue +9 -5
  59. package/modules/@apostrophecms/piece-type/index.js +7 -7
  60. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +92 -36
  61. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +30 -24
  62. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapDivider.vue +4 -2
  63. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapLink.vue +2 -1
  64. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapMarks.vue +5 -3
  65. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapStyles.vue +5 -3
  66. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapTable.vue +5 -2
  67. package/modules/@apostrophecms/schema/index.js +26 -5
  68. package/modules/@apostrophecms/schema/lib/addFieldTypes.js +42 -9
  69. package/modules/@apostrophecms/schema/ui/apos/components/AposInputColor.vue +4 -2
  70. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRange.vue +8 -4
  71. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +6 -4
  72. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +5 -3
  73. package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +19 -13
  74. package/modules/@apostrophecms/schema/ui/apos/components/AposSearchList.vue +6 -2
  75. package/modules/@apostrophecms/schema/ui/apos/components/AposSubform.vue +6 -4
  76. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputSlug.js +28 -25
  77. package/modules/@apostrophecms/schema/ui/apos/scss/AposInputArray.scss +13 -7
  78. package/modules/@apostrophecms/settings/ui/apos/components/AposSettingsManager.vue +11 -6
  79. package/modules/@apostrophecms/translation/ui/apos/components/AposTranslationIndicator.vue +5 -3
  80. package/modules/@apostrophecms/ui/ui/apos/components/AposAvatar.vue +14 -12
  81. package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +14 -11
  82. package/modules/@apostrophecms/ui/ui/apos/components/AposButtonSplit.vue +7 -3
  83. package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +4 -2
  84. package/modules/@apostrophecms/ui/ui/apos/components/AposCombo.vue +23 -17
  85. package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue +25 -10
  86. package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuDialog.vue +7 -5
  87. package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuItem.vue +10 -8
  88. package/modules/@apostrophecms/ui/ui/apos/components/AposEmptyState.vue +9 -5
  89. package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +9 -6
  90. package/modules/@apostrophecms/ui/ui/apos/components/AposIndicator.vue +5 -0
  91. package/modules/@apostrophecms/ui/ui/apos/components/AposLoadingBlock.vue +3 -1
  92. package/modules/@apostrophecms/ui/ui/apos/components/AposLocale.vue +3 -1
  93. package/modules/@apostrophecms/ui/ui/apos/components/AposLocalePicker.vue +11 -9
  94. package/modules/@apostrophecms/ui/ui/apos/components/AposMinMaxCount.vue +5 -3
  95. package/modules/@apostrophecms/ui/ui/apos/components/AposPager.vue +4 -2
  96. package/modules/@apostrophecms/ui/ui/apos/components/AposPagerDots.vue +8 -6
  97. package/modules/@apostrophecms/ui/ui/apos/components/AposSlat.vue +25 -17
  98. package/modules/@apostrophecms/ui/ui/apos/components/AposSlatList.vue +5 -9
  99. package/modules/@apostrophecms/ui/ui/apos/components/AposSubformPreview.vue +10 -6
  100. package/modules/@apostrophecms/ui/ui/apos/components/AposTag.vue +9 -7
  101. package/modules/@apostrophecms/ui/ui/apos/components/AposTagApply.vue +8 -4
  102. package/modules/@apostrophecms/ui/ui/apos/components/AposTagList.vue +4 -2
  103. package/modules/@apostrophecms/ui/ui/apos/components/AposTagListItem.vue +7 -5
  104. package/modules/@apostrophecms/ui/ui/apos/components/AposToggle.vue +16 -0
  105. package/modules/@apostrophecms/ui/ui/apos/components/AposTree.vue +3 -1
  106. package/modules/@apostrophecms/ui/ui/apos/components/AposTreeRows.vue +11 -9
  107. package/modules/@apostrophecms/ui/ui/apos/mixins/AposArchiveMixin.js +2 -2
  108. package/modules/@apostrophecms/ui/ui/apos/mixins/AposPublishMixin.js +6 -6
  109. package/modules/@apostrophecms/ui/ui/apos/scss/global/_inputs.scss +30 -22
  110. package/modules/@apostrophecms/ui/ui/apos/scss/global/_theme.scss +22 -18
  111. package/modules/@apostrophecms/ui/ui/apos/scss/global/_tooltips.scss +18 -15
  112. package/modules/@apostrophecms/ui/ui/apos/scss/mixins/_input_mixins.scss +8 -6
  113. package/modules/@apostrophecms/ui/ui/apos/scss/mixins/_mixins.scss +3 -1
  114. package/modules/@apostrophecms/ui/ui/apos/scss/mixins/_theme_mixins.scss +34 -19
  115. package/modules/@apostrophecms/ui/ui/apos/scss/mixins/_type_mixins.scss +3 -1
  116. package/modules/@apostrophecms/ui/ui/apos/utils/index.js +140 -51
  117. package/modules/@apostrophecms/widget-type/index.js +3 -2
  118. package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +5 -1
  119. package/package.json +5 -6
  120. package/test/big-upload.js +111 -0
  121. package/test/change-doc-ids.js +60 -1
  122. package/test/pages.js +488 -0
  123. package/test/schemas.js +327 -0
  124. package/test/utils.js +266 -5
@@ -84,7 +84,7 @@
84
84
  disableUnchecked: maxReached()
85
85
  }"
86
86
  @select-click="selectAll"
87
- @search="onSearch"
87
+ @search="onSearchDebounced"
88
88
  @page-change="updatePage"
89
89
  @filter="filter"
90
90
  @batch="handleBatchAction"
@@ -129,7 +129,7 @@ import AposDocsManagerMixin from 'Modules/@apostrophecms/modal/mixins/AposDocsMa
129
129
  import AposModifiedMixin from 'Modules/@apostrophecms/ui/mixins/AposModifiedMixin';
130
130
  import AposPublishMixin from 'Modules/@apostrophecms/ui/mixins/AposPublishMixin';
131
131
  import { useModalStore } from 'Modules/@apostrophecms/ui/stores/modal';
132
- import { debounce } from 'Modules/@apostrophecms/ui/utils';
132
+ import { debounceAsync } from 'Modules/@apostrophecms/ui/utils';
133
133
 
134
134
  export default {
135
135
  name: 'AposDocsManager',
@@ -229,7 +229,9 @@ export default {
229
229
  },
230
230
  created() {
231
231
  const DEBOUNCE_TIMEOUT = 500;
232
- this.onSearch = debounce(this.search, DEBOUNCE_TIMEOUT);
232
+ this.onSearchDebounced = debounceAsync(this.onSearch, DEBOUNCE_TIMEOUT, {
233
+ onSuccess: this.search
234
+ });
233
235
 
234
236
  this.moduleOptions.filters.forEach(filter => {
235
237
  this.filterValues[filter.name] = filter.def;
@@ -244,14 +246,17 @@ export default {
244
246
  this.headers = this.computeHeaders();
245
247
  // Get the data. This will be more complex in actuality.
246
248
  this.modal.active = true;
247
- await this.getPieces();
248
- await this.getAllPiecesTotal();
249
+ await this.managePieces();
250
+ await this.manageAllPiecesTotal();
249
251
  this.modal.triggerFocusRefresh++;
250
252
 
251
253
  apos.bus.$on('content-changed', this.onContentChanged);
252
254
  apos.bus.$on('command-menu-manager-create-new', this.create);
253
255
  apos.bus.$on('command-menu-manager-close', this.confirmAndCancel);
254
256
  },
257
+ onBeforeUnmount() {
258
+ this.onSearchDebounced.cancel();
259
+ },
255
260
  unmounted() {
256
261
  this.destroyShortcuts();
257
262
  apos.bus.$off('content-changed', this.onContentChanged);
@@ -262,7 +267,6 @@ export default {
262
267
  async create() {
263
268
  await this.edit(null);
264
269
  },
265
-
266
270
  // If pieceOrId is null, a new piece is created
267
271
  async edit(pieceOrId) {
268
272
  let piece;
@@ -294,9 +298,9 @@ export default {
294
298
  });
295
299
  },
296
300
  async finishSaved() {
297
- await this.getPieces();
301
+ await this.managePieces();
298
302
  },
299
- async request (mergeOptions) {
303
+ async request(mergeOptions) {
300
304
  const options = {
301
305
  ...this.filterValues,
302
306
  ...this.queryExtras,
@@ -325,31 +329,65 @@ export default {
325
329
  draft: true
326
330
  });
327
331
  },
328
- async getPieces () {
329
- if (this.holdQueries) {
330
- return;
331
- }
332
-
333
- this.holdQueries = true;
334
-
332
+ async requestPieces(page = 1, mergeOptions = {}) {
335
333
  const {
336
334
  currentPage, pages, results, choices
337
335
  } = await this.request({
336
+ ...mergeOptions,
338
337
  ...(
339
338
  this.moduleOptions.managerApiProjection &&
340
339
  { project: this.moduleOptions.managerApiProjection }
341
340
  ),
342
- page: this.currentPage
341
+ page
343
342
  });
344
343
 
345
- this.currentPage = currentPage;
346
- this.totalPages = pages;
347
- this.items = results;
348
- this.filterChoices = choices;
349
- this.holdQueries = false;
344
+ return {
345
+ currentPage,
346
+ pages,
347
+ results,
348
+ choices
349
+ };
350
350
  },
351
- async getAllPiecesTotal () {
351
+ async requestAllPiecesTotal() {
352
352
  const { count: total } = await this.request({ count: 1 });
353
+ return total;
354
+ },
355
+ async requestData(page = 1, mergeOptions = {}) {
356
+ const pieces = await this.requestPieces(page, mergeOptions);
357
+ const total = await this.requestAllPiecesTotal();
358
+
359
+ return {
360
+ ...pieces,
361
+ total
362
+ };
363
+ },
364
+ setPieces(data) {
365
+ this.currentPage = data.currentPage;
366
+ this.totalPages = data.pages;
367
+ this.items = data.results;
368
+ this.filterChoices = data.choices;
369
+ },
370
+ async managePieces () {
371
+ if (this.holdQueries) {
372
+ return;
373
+ }
374
+
375
+ this.holdQueries = true;
376
+
377
+ const {
378
+ currentPage, pages, results, choices
379
+ } = await this.requestPieces(this.currentPage);
380
+
381
+ this.setPieces({
382
+ currentPage,
383
+ pages,
384
+ results,
385
+ choices
386
+ });
387
+ this.holdQueries = false;
388
+ },
389
+ async manageAllPiecesTotal () {
390
+ const total = await this.requestAllPiecesTotal();
353
391
 
354
392
  this.setAllPiecesSelection({
355
393
  isSelected: false,
@@ -372,25 +410,44 @@ export default {
372
410
  docs
373
411
  });
374
412
  },
375
- updatePage(num) {
413
+ async updatePage(num) {
376
414
  if (num) {
377
415
  this.currentPage = num;
378
- this.getPieces();
416
+ await this.managePieces();
379
417
  }
380
418
  },
381
- async search(query) {
419
+ // A stateless search handler, only requesting the data.
420
+ // It's meant to be debounced and used in conjunction with the search
421
+ // method that actually updates the state.
422
+ async onSearch(query) {
423
+ const queryExtras = { ...this.queryExtras };
382
424
  if (query) {
383
- this.queryExtras.autocomplete = query;
425
+ queryExtras.autocomplete = query;
384
426
  } else if ('autocomplete' in this.queryExtras) {
385
- delete this.queryExtras.autocomplete;
427
+ queryExtras.autocomplete = undefined;
386
428
  } else {
429
+ return {};
430
+ }
431
+ const { total, ...pieces } = await this.requestData(1, queryExtras);
432
+
433
+ return {
434
+ pieces,
435
+ total
436
+ };
437
+ },
438
+ async search({ pieces, total }) {
439
+ // Most probably due to empty/invalid query.
440
+ if (!pieces) {
387
441
  return;
388
442
  }
389
443
 
390
444
  this.currentPage = 1;
391
445
 
392
- await this.getPieces();
393
- await this.getAllPiecesTotal();
446
+ this.setPieces(pieces);
447
+ this.setAllPiecesSelection({
448
+ isSelected: false,
449
+ total
450
+ });
394
451
  },
395
452
  async filter(filter, value) {
396
453
  if (this.filterValues[filter] === value) {
@@ -400,8 +457,8 @@ export default {
400
457
  this.filterValues[filter] = value;
401
458
  this.currentPage = 1;
402
459
 
403
- await this.getPieces();
404
- await this.getAllPiecesTotal();
460
+ await this.managePieces();
461
+ await this.manageAllPiecesTotal();
405
462
  this.headers = this.computeHeaders();
406
463
 
407
464
  this.setCheckedDocs([]);
@@ -477,8 +534,8 @@ export default {
477
534
  }
478
535
  });
479
536
  if (action === 'archive') {
480
- await this.getPieces();
481
- this.getAllPiecesTotal();
537
+ await this.managePieces();
538
+ await this.manageAllPiecesTotal();
482
539
  this.checked = [];
483
540
  }
484
541
  } catch (error) {
@@ -496,15 +553,14 @@ export default {
496
553
  return item._id;
497
554
  });
498
555
  },
499
-
500
556
  async onContentChanged({ doc, action }) {
501
557
  if (
502
558
  !doc ||
503
559
  !doc.aposLocale ||
504
560
  doc.aposLocale.split(':')[0] === this.modalData.locale
505
561
  ) {
506
- await this.getPieces();
507
- this.getAllPiecesTotal();
562
+ await this.managePieces();
563
+ await this.manageAllPiecesTotal();
508
564
  if (action === 'archive') {
509
565
  this.checked = this.checked.filter(checkedId => doc._id !== checkedId);
510
566
  }
@@ -852,9 +852,11 @@ function traverseNextNode(node) {
852
852
  .apos-rich-text-editor__editor {
853
853
  @include apos-transition();
854
854
 
855
- position: relative;
856
- border-radius: var(--a-border-radius);
857
- background-color: transparent;
855
+ & {
856
+ position: relative;
857
+ border-radius: var(--a-border-radius);
858
+ background-color: transparent;
859
+ }
858
860
  }
859
861
 
860
862
  .apos-rich-text-editor__editor :deep([data-tippy-root]) {
@@ -874,20 +876,22 @@ function traverseNextNode(node) {
874
876
  .apos-rich-text-editor__editor_after {
875
877
  @include type-small;
876
878
 
877
- position: absolute;
878
- inset: 0;
879
- display: block;
880
- width: 200px;
881
- height: 10px;
882
- margin: auto;
883
- color: var(--a-primary-transparent-50);
884
- opacity: 0;
885
- visibility: hidden;
886
- pointer-events: none;
887
- font-weight: 700;
888
- text-transform: uppercase;
889
- letter-spacing: 1px;
890
- text-align: center;
879
+ & {
880
+ position: absolute;
881
+ inset: 0;
882
+ display: block;
883
+ width: 200px;
884
+ height: 10px;
885
+ margin: auto;
886
+ color: var(--a-primary-transparent-50);
887
+ opacity: 0;
888
+ visibility: hidden;
889
+ pointer-events: none;
890
+ font-weight: 700;
891
+ text-transform: uppercase;
892
+ letter-spacing: 1px;
893
+ text-align: center;
894
+ }
891
895
 
892
896
  &.apos-is-visually-empty {
893
897
  opacity: 1;
@@ -953,13 +957,15 @@ function traverseNextNode(node) {
953
957
  .apos-rich-text-insert-menu-item {
954
958
  @include apos-transition();
955
959
 
956
- all: unset;
957
- display: flex;
958
- flex-direction: row;
959
- align-items: center;
960
- gap: 12px;
961
- padding: 14px 16px;
962
- border-bottom: 1px solid var(--a-base-9);
960
+ & {
961
+ all: unset;
962
+ display: flex;
963
+ flex-direction: row;
964
+ align-items: center;
965
+ gap: 12px;
966
+ padding: 14px 16px;
967
+ border-bottom: 1px solid var(--a-base-9);
968
+ }
963
969
 
964
970
  &:last-of-type {
965
971
  border-bottom: none;
@@ -21,7 +21,9 @@ export default {
21
21
  .apos-rich-text-editor__control--divider {
22
22
  @include type-help;
23
23
 
24
- padding: 10px;
25
- opacity: 0.3;
24
+ & {
25
+ padding: 10px;
26
+ opacity: 0.3;
27
+ }
26
28
  }
27
29
  </style>
@@ -251,7 +251,8 @@ export default {
251
251
  this.docFields.data.href = '';
252
252
  try {
253
253
  const doc = await apos.http.get(`/api/v1/@apostrophecms/doc/${matches[1]}`, {
254
- busy: true
254
+ busy: true,
255
+ draft: true
255
256
  });
256
257
  this.docFields.data.linkTo = doc.slug.startsWith('/') ? '@apostrophecms/any-page-type' : doc.type;
257
258
  this.docFields.data[`_${this.docFields.data.linkTo}`] = [ doc ];
@@ -219,9 +219,11 @@ export default {
219
219
  .apos-marks-control__button {
220
220
  @include apos-button-reset();
221
221
 
222
- display: block;
223
- width: 100%;
224
- padding: $spacing-base;
222
+ & {
223
+ display: block;
224
+ width: 100%;
225
+ padding: $spacing-base;
226
+ }
225
227
  }
226
228
  }
227
229
 
@@ -157,9 +157,11 @@ export default {
157
157
  @include apos-button-reset();
158
158
  @include apos-transition();
159
159
 
160
- height: 100%;
161
- padding: 0 $spacing-half;
162
- font-size: var(--a-type-smaller);
160
+ & {
161
+ height: 100%;
162
+ padding: 0 $spacing-half;
163
+ font-size: var(--a-type-smaller);
164
+ }
163
165
 
164
166
  &:focus, &:active {
165
167
  outline: none;
@@ -143,12 +143,15 @@ export default {
143
143
  <style lang="scss" scoped>
144
144
  // "If another select el is needed for the rich-text toolbar these styles should be made global."
145
145
  // ... And here we are, but first let's see if we decide to rebuild this UI without the menu. -Tom
146
+
146
147
  .apos-tiptap-control--select {
147
148
  @include apos-button-reset();
148
149
  @include type-small;
149
150
 
150
- height: 100%;
151
- padding: 0 15px 0 10px;
151
+ & {
152
+ height: 100%;
153
+ padding: 0 15px 0 10px;
154
+ }
152
155
 
153
156
  &:focus, &:active {
154
157
  background-color: var(--a-base-9);
@@ -599,7 +599,8 @@ module.exports = {
599
599
  // set error class names, etc. If the error is not a string, it is a
600
600
  // database error etc. and should not be displayed in the browser directly.
601
601
 
602
- async convert(req, schema, data, destination) {
602
+ async convert(req, schema, data, destination, { fetchRelationships = true } = {}) {
603
+ const options = { fetchRelationships };
603
604
  if (Array.isArray(req)) {
604
605
  throw new Error('convert invoked without a req, do you have one in your context?');
605
606
  }
@@ -629,7 +630,8 @@ module.exports = {
629
630
  required: isRequired
630
631
  },
631
632
  data,
632
- destination
633
+ destination,
634
+ options
633
635
  );
634
636
  } catch (error) {
635
637
  if (Array.isArray(error)) {
@@ -1039,7 +1041,7 @@ module.exports = {
1039
1041
 
1040
1042
  const handlers = {
1041
1043
  arrayItem: (field, object) => {
1042
- if (!can(field)) {
1044
+ if (!object || !can(field)) {
1043
1045
  return;
1044
1046
  }
1045
1047
 
@@ -1048,7 +1050,7 @@ module.exports = {
1048
1050
  object.scopedArrayName = field.scopedArrayName;
1049
1051
  },
1050
1052
  object: (field, object) => {
1051
- if (!can(field)) {
1053
+ if (!object || !can(field)) {
1052
1054
  return;
1053
1055
  }
1054
1056
 
@@ -1056,7 +1058,7 @@ module.exports = {
1056
1058
  object.scopedObjectName = field.scopedObjectName;
1057
1059
  },
1058
1060
  relationship: (field, doc) => {
1059
- if (!can(field)) {
1061
+ if (!Array.isArray(doc[field.name]) || !can(field)) {
1060
1062
  return;
1061
1063
  }
1062
1064
 
@@ -1076,6 +1078,25 @@ module.exports = {
1076
1078
  self.apos.doc.walkByMetaType(doc, handlers);
1077
1079
  },
1078
1080
 
1081
+ simulateRelationshipsFromStorage(req, doc) {
1082
+ const handlers = {
1083
+ relationship: (field, object) => {
1084
+ const manager = self.apos.doc.getManager(field.withType);
1085
+ const setId = (id) => manager.options.localized !== false
1086
+ ? `${id}:${doc.aposLocale}`
1087
+ : id;
1088
+
1089
+ const itemIds = object[field.idsStorage] || [];
1090
+ object[field.name] = itemIds.map(id => ({
1091
+ _id: setId(id),
1092
+ _fields: object[field.fieldsStorage]?.[id] || {}
1093
+ }));
1094
+ }
1095
+ };
1096
+
1097
+ self.apos.doc.walkByMetaType(doc, handlers);
1098
+ },
1099
+
1079
1100
  // Add a new field type. The `type` object may contain the following properties:
1080
1101
  //
1081
1102
  // ### `name`
@@ -10,7 +10,8 @@ const dateRegex = /^\d{4}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$/;
10
10
  module.exports = (self) => {
11
11
  self.addFieldType({
12
12
  name: 'area',
13
- async convert(req, field, data, destination) {
13
+ async convert(req, field, data, destination, { fetchRelationships = true } = {}) {
14
+ const options = { fetchRelationships };
14
15
  const _id = self.apos.launder.id(data[field.name] && data[field.name]._id) || self.apos.util.generateId();
15
16
  if (typeof data[field.name] === 'string') {
16
17
  destination[field.name] = self.apos.area.fromPlaintext(data[field.name]);
@@ -34,7 +35,7 @@ module.exports = (self) => {
34
35
  // Always recover graciously and import something reasonable, like an empty area
35
36
  items = [];
36
37
  }
37
- items = await self.apos.area.sanitizeItems(req, items, field.options || {});
38
+ items = await self.apos.area.sanitizeItems(req, items, field.options, options);
38
39
  destination[field.name] = {
39
40
  _id,
40
41
  items,
@@ -775,7 +776,8 @@ module.exports = (self) => {
775
776
 
776
777
  self.addFieldType({
777
778
  name: 'array',
778
- async convert(req, field, data, destination) {
779
+ async convert(req, field, data, destination, { fetchRelationships = true } = {}) {
780
+ const options = { fetchRelationships };
779
781
  const schema = field.schema;
780
782
  data = data[field.name];
781
783
  if (!Array.isArray(data)) {
@@ -796,7 +798,7 @@ module.exports = (self) => {
796
798
  result.metaType = 'arrayItem';
797
799
  result.scopedArrayName = field.scopedArrayName;
798
800
  try {
799
- await self.convert(req, schema, datum, result);
801
+ await self.convert(req, schema, datum, result, options);
800
802
  } catch (e) {
801
803
  if (Array.isArray(e)) {
802
804
  for (const error of e) {
@@ -876,7 +878,8 @@ module.exports = (self) => {
876
878
 
877
879
  self.addFieldType({
878
880
  name: 'object',
879
- async convert(req, field, data, destination) {
881
+ async convert(req, field, data, destination, { fetchRelationships = true } = {}) {
882
+ const options = { fetchRelationships };
880
883
  data = data[field.name];
881
884
  const schema = field.schema;
882
885
  const errors = [];
@@ -888,7 +891,7 @@ module.exports = (self) => {
888
891
  data = {};
889
892
  }
890
893
  try {
891
- await self.convert(req, schema, data, result);
894
+ await self.convert(req, schema, data, result, options);
892
895
  } catch (e) {
893
896
  if (Array.isArray(e)) {
894
897
  for (const error of e) {
@@ -972,7 +975,8 @@ module.exports = (self) => {
972
975
  // properties is handled at a lower level in a beforeSave
973
976
  // handler of the doc-type module.
974
977
 
975
- async convert(req, field, data, destination) {
978
+ async convert(req, field, data, destination, { fetchRelationships = true } = {}) {
979
+ const options = { fetchRelationships };
976
980
  const manager = self.apos.doc.getManager(field.withType);
977
981
  if (!manager) {
978
982
  throw Error('relationship with type ' + field.withType + ' unrecognized');
@@ -991,6 +995,31 @@ module.exports = (self) => {
991
995
  if (field.max && field.max < input.length) {
992
996
  throw self.apos.error('max', `Maximum ${field.withType} required reached.`);
993
997
  }
998
+ if (fetchRelationships === false) {
999
+ destination[field.name] = [];
1000
+
1001
+ for (const relation of input) {
1002
+ if (typeof relation === 'string') {
1003
+ destination[field.name].push({
1004
+ _id: self.apos.launder.id(relation),
1005
+ _fields: {}
1006
+ });
1007
+ continue;
1008
+ }
1009
+
1010
+ const _fields = {};
1011
+ if (field.schema?.length) {
1012
+ await self.convert(req, field.schema, relation._fields || {}, _fields, options);
1013
+ }
1014
+
1015
+ destination[field.name].push({
1016
+ _id: self.apos.launder.id(relation._id),
1017
+ _fields
1018
+ });
1019
+ }
1020
+ return;
1021
+ }
1022
+
994
1023
  const ids = [];
995
1024
  const titlesOrIds = [];
996
1025
  for (const item of input) {
@@ -1034,9 +1063,13 @@ module.exports = (self) => {
1034
1063
  const result = results.find(doc => (doc._id === item._id));
1035
1064
  if (result) {
1036
1065
  if (field.schema) {
1037
- result._fields = { ...(destination[field.name]?.find?.(doc => doc._id === item._id)?._fields || {}) };
1066
+ result._fields = {
1067
+ ...(destination[field.name]
1068
+ ?.find?.(doc => doc._id === item._id)
1069
+ ?._fields || {})
1070
+ };
1038
1071
  if (item && ((typeof item._fields === 'object'))) {
1039
- await self.convert(req, field.schema, item._fields || {}, result._fields);
1072
+ await self.convert(req, field.schema, item._fields || {}, result._fields, options);
1040
1073
  }
1041
1074
  }
1042
1075
  actualDocs.push(result);
@@ -61,8 +61,10 @@ export default {
61
61
  .apos-color__info {
62
62
  @include type-base;
63
63
 
64
- margin-left: 15px;
65
- color: var(--a-text-primary);
64
+ & {
65
+ margin-left: 15px;
66
+ color: var(--a-text-primary);
67
+ }
66
68
  }
67
69
  </style>
68
70
 
@@ -65,8 +65,10 @@ export default {
65
65
  .apos-input-wrapper {
66
66
  @include type-base;
67
67
 
68
- display: flex;
69
- place-content: flex-start space-between;
68
+ & {
69
+ display: flex;
70
+ place-content: flex-start space-between;
71
+ }
70
72
  }
71
73
 
72
74
  .apos-range__value {
@@ -97,8 +99,10 @@ export default {
97
99
  .apos-range__scale {
98
100
  @include type-small;
99
101
 
100
- color: var(--a-base-4);
101
- transition: color 500ms ease;
102
+ & {
103
+ color: var(--a-base-4);
104
+ transition: color 500ms ease;
105
+ }
102
106
  }
103
107
 
104
108
  // adapted from http://danielstern.ca/range.css/#/
@@ -126,9 +126,11 @@ export default {
126
126
  .apos-field__min-size {
127
127
  @include type-help;
128
128
 
129
- display: flex;
130
- flex-grow: 1;
131
- margin-bottom: $spacing-base;
132
- font-weight: var(--a-weight-bold);
129
+ & {
130
+ display: flex;
131
+ flex-grow: 1;
132
+ margin-bottom: $spacing-base;
133
+ font-weight: var(--a-weight-bold);
134
+ }
133
135
  }
134
136
  </style>
@@ -54,9 +54,11 @@ export default {
54
54
  .apos-input-wrapper--with-prefix {
55
55
  @include apos-input();
56
56
 
57
- display: flex;
58
- align-items: center;
59
- color: var(--a-base-4);
57
+ & {
58
+ display: flex;
59
+ align-items: center;
60
+ color: var(--a-base-4);
61
+ }
60
62
 
61
63
  .apos-input {
62
64
  padding-left: 0;