apostrophe 4.4.2 → 4.5.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 (85) hide show
  1. package/CHANGELOG.md +62 -3
  2. package/index.js +2 -3
  3. package/lib/glob.js +12 -0
  4. package/lib/moog-require.js +3 -3
  5. package/modules/@apostrophecms/admin-bar/index.js +9 -2
  6. package/modules/@apostrophecms/admin-bar/ui/apos/apps/AposAdminBar.js +1 -1
  7. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +2 -2
  8. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextTitle.vue +0 -1
  9. package/modules/@apostrophecms/area/ui/apos/components/AposAreaContextualMenu.vue +2 -2
  10. package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +6 -6
  11. package/modules/@apostrophecms/area/ui/apos/components/AposAreaExpandedMenu.vue +7 -1
  12. package/modules/@apostrophecms/asset/index.js +5 -6
  13. package/modules/@apostrophecms/command-menu/index.js +7 -2
  14. package/modules/@apostrophecms/doc/index.js +2 -2
  15. package/modules/@apostrophecms/doc-type/index.js +1 -1
  16. package/modules/@apostrophecms/i18n/i18n/en.json +37 -1
  17. package/modules/@apostrophecms/i18n/index.js +2 -2
  18. package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +5 -5
  19. package/modules/@apostrophecms/image/index.js +1 -1
  20. package/modules/@apostrophecms/image/ui/apos/components/AposImageRelationshipEditor.vue +4 -3
  21. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +221 -76
  22. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerDisplay.vue +91 -24
  23. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +48 -51
  24. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerSelections.vue +1 -0
  25. package/modules/@apostrophecms/login/index.js +3 -3
  26. package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +3 -11
  27. package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +4 -12
  28. package/modules/@apostrophecms/modal/ui/apos/components/AposModalBody.vue +20 -6
  29. package/modules/@apostrophecms/modal/ui/apos/components/AposModalBreadcrumbs.vue +1 -1
  30. package/modules/@apostrophecms/modal/ui/apos/components/AposModalShareDraft.vue +1 -1
  31. package/modules/@apostrophecms/modal/ui/apos/components/AposModalTabs.vue +3 -5
  32. package/modules/@apostrophecms/modal/ui/apos/components/AposWidgetModalTabs.vue +3 -5
  33. package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocErrorsMixin.js +2 -2
  34. package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +44 -39
  35. package/modules/@apostrophecms/modal/ui/apos/mixins/AposModalTabsMixin.js +2 -2
  36. package/modules/@apostrophecms/oembed/index.js +2 -2
  37. package/modules/@apostrophecms/oembed-field/ui/apos/components/AposInputOembed.vue +2 -2
  38. package/modules/@apostrophecms/page/index.js +5 -2
  39. package/modules/@apostrophecms/page/ui/apos/components/AposPagesManager.vue +1 -2
  40. package/modules/@apostrophecms/page/ui/apos/logic/AposPagesManager.js +14 -7
  41. package/modules/@apostrophecms/piece-type/index.js +5 -2
  42. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +23 -11
  43. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +4 -1
  44. package/modules/@apostrophecms/rich-text-widget/index.js +31 -3
  45. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapColor.vue +285 -0
  46. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Color.js +5 -0
  47. package/modules/@apostrophecms/schema/lib/addFieldTypes.js +38 -25
  48. package/modules/@apostrophecms/schema/ui/apos/components/AposInputBoolean.vue +2 -2
  49. package/modules/@apostrophecms/schema/ui/apos/components/AposInputColor.vue +0 -1
  50. package/modules/@apostrophecms/schema/ui/apos/components/AposInputObject.vue +1 -0
  51. package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +13 -2
  52. package/modules/@apostrophecms/schema/ui/apos/logic/AposArrayEditor.js +11 -9
  53. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArea.js +4 -4
  54. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js +7 -7
  55. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputObject.js +11 -0
  56. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputSlug.js +45 -51
  57. package/modules/@apostrophecms/schema/ui/apos/logic/AposSchema.js +3 -1
  58. package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputMixin.js +1 -1
  59. package/modules/@apostrophecms/submitted-draft/index.js +2 -2
  60. package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +7 -3
  61. package/modules/@apostrophecms/ui/ui/apos/components/AposButtonSplit.vue +0 -1
  62. package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue +12 -4
  63. package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +4 -4
  64. package/modules/@apostrophecms/ui/ui/apos/components/AposFilterMenu.vue +2 -1
  65. package/modules/@apostrophecms/ui/ui/apos/components/AposLoading.vue +1 -1
  66. package/modules/@apostrophecms/ui/ui/apos/components/AposLoadingBlock.vue +50 -0
  67. package/modules/@apostrophecms/ui/ui/apos/components/AposSlat.vue +0 -1
  68. package/modules/@apostrophecms/ui/ui/apos/components/AposSlatList.vue +6 -4
  69. package/modules/@apostrophecms/ui/ui/apos/components/AposTagApply.vue +17 -11
  70. package/modules/@apostrophecms/ui/ui/apos/components/AposTree.vue +2 -2
  71. package/modules/@apostrophecms/ui/ui/apos/components/AposTreeRows.vue +2 -2
  72. package/modules/@apostrophecms/ui/ui/apos/lib/tooltip.js +2 -2
  73. package/modules/@apostrophecms/ui/ui/apos/stores/modal.js +3 -3
  74. package/modules/@apostrophecms/user/index.js +1 -0
  75. package/modules/@apostrophecms/util/index.js +12 -4
  76. package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +2 -2
  77. package/package.json +8 -7
  78. package/scripts/lint-i18n.js +8 -3
  79. package/test/command-menu.js +76 -12
  80. package/test/pages-rest.js +102 -0
  81. package/test/pieces.js +133 -10
  82. package/test/recursionGuard.js +5 -5
  83. package/test/schemas.js +1 -1
  84. package/test/utils/commands.js +26 -0
  85. package/test-lib/util.js +2 -2
@@ -3,7 +3,6 @@
3
3
  <div class="apos-media-manager-display__grid">
4
4
  <AposMediaUploader
5
5
  v-if="moduleOptions.canCreate"
6
- :disabled="maxReached"
7
6
  :action="moduleOptions.action"
8
7
  :accept="accept"
9
8
  @upload-started="$emit('upload-started')"
@@ -25,9 +24,12 @@
25
24
  :field="{
26
25
  name: item._id,
27
26
  hideLabel: true,
28
- label: `Toggle selection of ${item.title}`,
27
+ label: $t({
28
+ key: 'apostrophe:toggleSelectionOf',
29
+ title: item.title
30
+ }),
29
31
  disableFocus: true,
30
- readOnly: options.disableUnchecked && !checked.includes(item._id)
32
+ readOnly: canSelect(item._id) === false
31
33
  }"
32
34
  :choice="{ value: item._id }"
33
35
  />
@@ -35,20 +37,19 @@
35
37
  <button
36
38
  :id="`btn-${item._id}`"
37
39
  :disabled="
38
- item._id === 'placeholder' ||
39
- (options.disableUnchecked && !checked.includes(item._id))
40
+ item._id === 'placeholder' || canSelect(item._id) === false
40
41
  "
41
42
  class="apos-media-manager-display__select"
42
43
  @click.exact="$emit('select', item._id)"
43
44
  @click.shift="$emit('select-series', item._id)"
44
45
  @click.meta="$emit('select-another', item._id)"
46
+ @click.ctrl="$emit('select-another', item._id)"
45
47
  >
46
48
  <div
47
49
  v-if="item.dimensions"
48
50
  class="apos-media-manager-display__placeholder"
49
51
  :style="getPlaceholderStyles(item)"
50
52
  />
51
- <!-- TODO: make sure using TITLE is the correct alt tag application here. -->
52
53
  <img
53
54
  v-else
54
55
  class="apos-media-manager-display__media"
@@ -57,8 +58,6 @@
57
58
  >
58
59
  </button>
59
60
  </div>
60
- <!-- We need a placeholder display cell to generate the first image
61
- placeholder. -->
62
61
  <div
63
62
  v-if="items.length === 0"
64
63
  class="apos-media-manager-display__cell apos-is-hidden"
@@ -70,11 +69,25 @@
70
69
  />
71
70
  </div>
72
71
  </div>
72
+ <div
73
+ v-if="!isLastPage"
74
+ ref="scrollLoad"
75
+ class="apos-media-manager-display__scroll-load"
76
+ :class="{ 'apos-media-manager-display__scroll-load--loading': isScrollLoading }"
77
+ >
78
+ <AposLoading
79
+ v-if="isScrollLoading"
80
+ class="apos-loading"
81
+ />
82
+ </div>
83
+ <div v-else class="apos-media-manager-display__end-reached">
84
+ <p>{{ $t('apostrophe:mediaLibraryEndReached') }}</p>
85
+ </div>
73
86
  </div>
74
87
  </template>
75
88
 
76
89
  <script>
77
- import cuid from 'cuid';
90
+ import { createId } from '@paralleldrive/cuid2';
78
91
 
79
92
  export default {
80
93
  // Custom model to handle the v-model connection on the parent.
@@ -111,6 +124,18 @@ export default {
111
124
  largePreview: {
112
125
  type: Boolean,
113
126
  default: false
127
+ },
128
+ relationshipField: {
129
+ type: [ Object, Boolean ],
130
+ default: false
131
+ },
132
+ isLastPage: {
133
+ type: Boolean,
134
+ default: false
135
+ },
136
+ isScrollLoading: {
137
+ type: Boolean,
138
+ default: false
114
139
  }
115
140
  },
116
141
  emits: [
@@ -120,7 +145,8 @@ export default {
120
145
  'select-another',
121
146
  'upload-started',
122
147
  'upload-complete',
123
- 'create-placeholder'
148
+ 'create-placeholder',
149
+ 'set-load-ref'
124
150
  ],
125
151
  computed: {
126
152
  // Handle the local check state within this component.
@@ -129,10 +155,24 @@ export default {
129
155
  return this.checked;
130
156
  },
131
157
  set(val) {
132
- this.$emit('update:checked', val);
158
+ this.$emit(
159
+ 'update:checked',
160
+ this.relationshipField?.max === 1
161
+ ? [].concat(val.at(-1) || [])
162
+ : val
163
+ );
133
164
  }
134
165
  }
135
166
  },
167
+ watch: {
168
+ async isLastPage(val) {
169
+ await this.$nextTick();
170
+ this.$emit('set-load-ref', this.$refs.scrollLoad);
171
+ }
172
+ },
173
+ mounted() {
174
+ this.$emit('set-load-ref', this.$refs.scrollLoad);
175
+ },
136
176
  methods: {
137
177
  getPlaceholderStyles(item) {
138
178
  // Account for whether the refs have been set by the v-for or if on the
@@ -178,7 +218,12 @@ export default {
178
218
  event.target.classList.remove('apos-is-hovering');
179
219
  },
180
220
  idFor(item) {
181
- return `${item._id}-${cuid()}`;
221
+ return `${item._id}-${createId()}`;
222
+ },
223
+ canSelect(id) {
224
+ return this.checked.includes(id) ||
225
+ this.relationshipField?.max === 1 ||
226
+ (this.relationshipField?.max && !this.maxReached);
182
227
  }
183
228
  }
184
229
  };
@@ -188,12 +233,13 @@ export default {
188
233
  .apos-media-manager-display__grid {
189
234
  display: grid;
190
235
  grid-auto-rows: 140px;
191
- grid-template-columns: repeat(5, 17.1%);
192
- gap: 2.4% 2.4%;
236
+ grid-template-columns: repeat(5, 1fr);
237
+ gap: 15px;
238
+ padding: 20px 0;
193
239
 
194
240
  @include media-up(lap) {
195
- grid-template-columns: repeat(7, 12.22%);
196
- gap: 2.4% 2.4%;
241
+ grid-template-columns: repeat(7, 1fr);
242
+ gap: 20px;
197
243
  }
198
244
  }
199
245
 
@@ -209,14 +255,6 @@ export default {
209
255
 
210
256
  &.apos-is-hidden { visibility: hidden; }
211
257
 
212
- &::before {
213
- content: '';
214
- display: inline-block;
215
- width: 1px;
216
- height: 0;
217
- padding-bottom: calc(100% / (1/1));
218
- }
219
-
220
258
  &:hover,
221
259
  &.apos-is-selected,
222
260
  &:focus {
@@ -259,6 +297,7 @@ export default {
259
297
  @include apos-transition();
260
298
 
261
299
  display: flex;
300
+ box-sizing: border-box;
262
301
  align-items: center;
263
302
  justify-content: center;
264
303
  width: 100%;
@@ -291,4 +330,32 @@ export default {
291
330
  box-shadow: none;
292
331
  }
293
332
  }
333
+
334
+ .apos-media-manager-display__scroll-load {
335
+ display: flex;
336
+ align-items: center;
337
+ justify-content: center;
338
+
339
+ &--loading {
340
+ height: 80px;
341
+ margin-top: 15px;
342
+ background-color: var(--a-base-10);
343
+ margin-bottom: 10px;
344
+ border: 1px solid var(--a-base-8);
345
+ border-radius: 8px;
346
+ }
347
+
348
+ .apos-loading {
349
+ flex-grow: 1;
350
+ }
351
+ }
352
+
353
+ .apos-media-manager-display__end-reached {
354
+ @include type-label;
355
+
356
+ display: flex;
357
+ align-items: center;
358
+ justify-content: center;
359
+ height: 40px;
360
+ }
294
361
  </style>
@@ -112,7 +112,7 @@ import { klona } from 'klona';
112
112
  import dayjs from 'dayjs';
113
113
  import { isEqual } from 'lodash';
114
114
  import advancedFormat from 'dayjs/plugin/advancedFormat';
115
- import cuid from 'cuid';
115
+ import { createId } from '@paralleldrive/cuid2';
116
116
 
117
117
  dayjs.extend(advancedFormat);
118
118
 
@@ -125,12 +125,6 @@ export default {
125
125
  return {};
126
126
  }
127
127
  },
128
- selected: {
129
- type: Array,
130
- default() {
131
- return [];
132
- }
133
- },
134
128
  isModified: {
135
129
  type: Boolean,
136
130
  default: false
@@ -145,7 +139,7 @@ export default {
145
139
  }
146
140
  }
147
141
  },
148
- emits: [ 'saved', 'back', 'modified' ],
142
+ emits: [ 'back', 'modified' ],
149
143
  data() {
150
144
  return {
151
145
  // Primarily use `activeMedia` to support hot-swapping image docs.
@@ -294,60 +288,63 @@ export default {
294
288
  });
295
289
  await this.cancel();
296
290
  },
297
- save() {
291
+ async save() {
298
292
  this.triggerValidation = true;
299
293
  const route = `${this.moduleOptions.action}/${this.activeMedia._id}`;
300
294
  // Repopulate `attachment` since it was removed from the schema.
301
295
  this.docFields.data.attachment = this.activeMedia.attachment;
302
296
 
303
- this.$nextTick(async () => {
304
- if (this.docFields.hasErrors) {
305
- this.triggerValidation = false;
306
- await apos.notify('apostrophe:resolveErrorsBeforeSaving', {
307
- type: 'warning',
308
- icon: 'alert-circle-icon',
309
- dismiss: true
310
- });
311
- return;
297
+ await this.$nextTick();
298
+
299
+ if (this.docFields.hasErrors) {
300
+ this.triggerValidation = false;
301
+ await apos.notify('apostrophe:resolveErrorsBeforeSaving', {
302
+ type: 'warning',
303
+ icon: 'alert-circle-icon',
304
+ dismiss: true
305
+ });
306
+ return;
307
+ }
308
+
309
+ let body = this.docFields.data;
310
+ this.addLockToRequest(body);
311
+ try {
312
+ const requestMethod = this.restoreOnly ? apos.http.patch : apos.http.put;
313
+ if (this.restoreOnly) {
314
+ body = {
315
+ archived: false
316
+ };
312
317
  }
318
+ const doc = await requestMethod(route, {
319
+ busy: true,
320
+ body,
321
+ draft: true
322
+ });
323
+ apos.bus.$emit('content-changed', {
324
+ doc,
325
+ action: 'update'
326
+ });
327
+ this.original = klona(this.docFields.data);
328
+ } catch (e) {
329
+ if (this.isLockedError(e)) {
330
+ await this.showLockedError(e);
331
+ this.lockNotAvailable();
332
+ } else {
333
+ const errorMessage = this.restoreOnly
334
+ ? this.$t('apostrophe:mediaManagerErrorRestoring')
335
+ : this.$t('apostrophe:mediaManagerErrorSaving');
313
336
 
314
- let body = this.docFields.data;
315
- this.addLockToRequest(body);
316
- try {
317
- const requestMethod = this.restoreOnly ? apos.http.patch : apos.http.put;
318
- if (this.restoreOnly) {
319
- body = {
320
- archived: false
321
- };
322
- }
323
- const doc = await requestMethod(route, {
324
- busy: true,
325
- body,
326
- draft: true
327
- });
328
- apos.bus.$emit('content-changed', {
329
- doc,
330
- action: 'update'
337
+ await this.handleSaveError(e, {
338
+ fallback: `${errorMessage} ${this.moduleLabels.label}`
331
339
  });
332
- this.original = klona(this.docFields.data);
333
- this.$emit('modified', false);
334
- this.$emit('saved');
335
- } catch (e) {
336
- if (this.isLockedError(e)) {
337
- await this.showLockedError(e);
338
- this.lockNotAvailable();
339
- } else {
340
- await this.handleSaveError(e, {
341
- fallback: `Error ${this.restoreOnly ? 'Restoring' : 'Saving'} ${this.moduleLabels.label}`
342
- });
343
- }
344
- } finally {
345
- this.showReplace = false;
346
340
  }
347
- });
341
+ } finally {
342
+ this.showReplace = false;
343
+
344
+ }
348
345
  },
349
346
  generateLipKey() {
350
- this.lipKey = cuid();
347
+ this.lipKey = createId();
351
348
  },
352
349
  cancel() {
353
350
  this.showReplace = false;
@@ -84,6 +84,7 @@ export default {
84
84
  .apos-media-manager-selections {
85
85
  @include type-base;
86
86
 
87
+ box-sizing: border-box;
87
88
  height: 100%;
88
89
  padding: 20px;
89
90
  }
@@ -41,7 +41,7 @@
41
41
  const Passport = require('passport').Passport;
42
42
  const LocalStrategy = require('passport-local');
43
43
  const Promise = require('bluebird');
44
- const cuid = require('cuid');
44
+ const { createId } = require('@paralleldrive/cuid2');
45
45
  const expressSession = require('express-session');
46
46
 
47
47
  const loginAttemptsNamespace = '@apostrophecms/loginAttempt';
@@ -774,7 +774,7 @@ module.exports = {
774
774
 
775
775
  const requirementsToVerify = Object.keys(lateRequirements);
776
776
  if (requirementsToVerify.length) {
777
- const token = cuid();
777
+ const token = createId();
778
778
 
779
779
  await self.bearerTokens.insertOne({
780
780
  _id: token,
@@ -802,7 +802,7 @@ module.exports = {
802
802
  });
803
803
  return {};
804
804
  } else {
805
- const token = cuid();
805
+ const token = createId();
806
806
  await self.bearerTokens.insertOne({
807
807
  _id: token,
808
808
  userId: user._id,
@@ -69,8 +69,9 @@
69
69
  :status="searchField.status"
70
70
  :model-value="searchField.value"
71
71
  :modifiers="['small']"
72
+ :no-blur-emit="true"
72
73
  @update:model-value="search"
73
- @return="search($event, true)"
74
+ @return="search($event)"
74
75
  />
75
76
  </template>
76
77
  </AposModalToolbar>
@@ -257,16 +258,7 @@ export default {
257
258
  this.computeActiveOperations();
258
259
  }
259
260
  },
260
- search(value, force) {
261
- if ((force && !value) || value.data === '') {
262
- value = {
263
- data: '',
264
- error: false
265
- };
266
- } else if (!value || value.error || (!force && value.data.length < 3)) {
267
- return;
268
- }
269
-
261
+ search(value) {
270
262
  this.$emit('search', value.data);
271
263
  },
272
264
  registerPageChange(pageNum) {
@@ -291,9 +291,9 @@ function close() {
291
291
  z-index: $z-index-modal;
292
292
  position: fixed;
293
293
  inset: $spacing-base $spacing-base $spacing-base $spacing-base;
294
- display: grid;
295
- grid-template-rows: auto 1fr auto;
296
- height: calc(100vh - #{$spacing-base * 2});
294
+ display: flex;
295
+ flex-direction: column;
296
+ height: calc(100vh - $spacing-base * 2);
297
297
  border-radius: var(--a-border-radius);
298
298
  background-color: var(--a-background-primary);
299
299
  border: 1px solid var(--a-base-9);
@@ -373,13 +373,9 @@ function close() {
373
373
  height: 100%;
374
374
  }
375
375
 
376
- .apos-modal__header {
377
- grid-row: 1 / 2;
378
- }
379
-
380
376
  .apos-modal__main {
381
377
  display: grid;
382
- grid-row: 2 / 3;
378
+ flex-grow: 1;
383
379
  overflow-y: auto;
384
380
  }
385
381
 
@@ -432,10 +428,6 @@ function close() {
432
428
  border-bottom: 1px solid var(--a-base-9);
433
429
  }
434
430
 
435
- .apos-modal__footer {
436
- grid-row: 3 / 4;
437
- }
438
-
439
431
  .apos-modal__footer__inner {
440
432
  z-index: $z-index-default;
441
433
  position: relative;
@@ -1,10 +1,17 @@
1
1
  <template>
2
2
  <div class="apos-modal__body" :class="{ 'apos-modal__body--flex': hasSlot('footer') }">
3
3
  <div class="apos-modal__body-inner">
4
- <div v-if="hasSlot('bodyHeader')" class="apos-modal__body-header">
4
+ <div
5
+ v-if="hasSlot('bodyHeader')"
6
+ ref="bodyHeader"
7
+ class="apos-modal__body-header"
8
+ >
5
9
  <slot name="bodyHeader" />
6
10
  </div>
7
- <div class="apos-modal__body-main">
11
+ <div
12
+ class="apos-modal__body-main"
13
+ :style="{ height: `calc(100% - ${headerHeight}px)`}"
14
+ >
8
15
  <slot name="bodyMain" />
9
16
  </div>
10
17
  </div>
@@ -17,6 +24,17 @@
17
24
  <script>
18
25
  export default {
19
26
  name: 'AposModalBody',
27
+ data() {
28
+ return {
29
+ headerHeight: 0
30
+ };
31
+ },
32
+ async mounted() {
33
+ if (this.$refs.bodyHeader) {
34
+ await this.$nextTick();
35
+ this.headerHeight = this.$refs.bodyHeader.offsetHeight;
36
+ }
37
+ },
20
38
  methods: {
21
39
  hasSlot(name) {
22
40
  return !!this.$slots[name];
@@ -52,10 +70,6 @@ export default {
52
70
  padding: 20px;
53
71
  }
54
72
 
55
- .apos-modal__body-header {
56
- margin-bottom: 20px;
57
- }
58
-
59
73
  .apos-modal__body-footer {
60
74
  display: flex;
61
75
  justify-content: space-between;
@@ -38,7 +38,7 @@ export default {
38
38
  },
39
39
  props: {
40
40
  label: {
41
- default: 'Set a label',
41
+ default: 'apostrophe:modalBreadcrumbsDefaultLabel',
42
42
  type: String
43
43
  },
44
44
  modifier: {
@@ -322,6 +322,6 @@ export default {
322
322
  margin-top: $spacing-double;
323
323
  color: var(--a-primary);
324
324
  text-decoration: none;
325
- font-weight: var(--a-weight-bold);;
325
+ font-weight: var(--a-weight-bold);
326
326
  }
327
327
  </style>
@@ -59,11 +59,9 @@ export default {
59
59
  },
60
60
  methods: {
61
61
  generateErrorLabel(errorCount) {
62
- let label = 'Error';
63
- if (errorCount > 1) {
64
- label += 's';
65
- }
66
- return label;
62
+ return errorCount > 1
63
+ ? this.$t('apostrophe:modalTabsErrors')
64
+ : this.$t('apostrophe:modalTabsError');
67
65
  },
68
66
  selectTab: function (e) {
69
67
  const tab = e.target;
@@ -123,11 +123,9 @@ export default {
123
123
  },
124
124
  methods: {
125
125
  generateErrorLabel(errorCount) {
126
- let label = 'Error';
127
- if (errorCount > 1) {
128
- label += 's';
129
- }
130
- return label;
126
+ return errorCount > 1
127
+ ? this.$t('apostrophe:modalTabsErrors')
128
+ : this.$t('apostrophe:modalTabsError');
131
129
  },
132
130
  selectTab: function (e) {
133
131
  const tab = e.target;
@@ -1,4 +1,4 @@
1
- import cuid from 'cuid';
1
+ import { createId } from '@paralleldrive/cuid2';
2
2
 
3
3
  export default {
4
4
  data: () => ({
@@ -19,7 +19,7 @@ export default {
19
19
  },
20
20
  methods: {
21
21
  updateFieldErrors(fieldState) {
22
- this.tabKey = cuid();
22
+ this.tabKey = createId();
23
23
  for (const key in this.groups) {
24
24
  this.groups[key].fields.forEach(field => {
25
25
  if (fieldState[field]) {