apostrophe 3.4.1 → 3.8.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 (111) hide show
  1. package/.eslintrc +4 -0
  2. package/.scratch.md +2 -0
  3. package/CHANGELOG.md +114 -2
  4. package/README.md +1 -1
  5. package/deploy-test-count +1 -1
  6. package/index.js +125 -5
  7. package/lib/moog-require.js +41 -3
  8. package/lib/moog.js +20 -8
  9. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarLocale.vue +42 -23
  10. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +30 -14
  11. package/modules/@apostrophecms/area/index.js +9 -0
  12. package/modules/@apostrophecms/area/lib/custom-tags/area.js +1 -1
  13. package/modules/@apostrophecms/area/lib/custom-tags/widget.js +1 -1
  14. package/modules/@apostrophecms/area/ui/apos/apps/AposAreas.js +3 -0
  15. package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +6 -6
  16. package/modules/@apostrophecms/asset/index.js +85 -21
  17. package/modules/@apostrophecms/asset/lib/globalIcons.js +2 -0
  18. package/modules/@apostrophecms/asset/lib/webpack/src/webpack.scss.js +5 -2
  19. package/modules/@apostrophecms/attachment/index.js +1 -0
  20. package/modules/@apostrophecms/db/index.js +5 -6
  21. package/modules/@apostrophecms/doc/index.js +13 -3
  22. package/modules/@apostrophecms/doc-type/index.js +24 -4
  23. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +13 -1
  24. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +3 -0
  25. package/modules/@apostrophecms/i18n/i18n/en.json +26 -6
  26. package/modules/@apostrophecms/i18n/i18n/es.json +382 -0
  27. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +379 -0
  28. package/modules/@apostrophecms/i18n/i18n/sk.json +380 -0
  29. package/modules/@apostrophecms/i18n/index.js +10 -1
  30. package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +153 -121
  31. package/modules/@apostrophecms/image/index.js +2 -1
  32. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +6 -3
  33. package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +24 -13
  34. package/modules/@apostrophecms/image-widget/index.js +2 -1
  35. package/modules/@apostrophecms/image-widget/views/widget.html +12 -2
  36. package/modules/@apostrophecms/job/index.js +164 -212
  37. package/modules/@apostrophecms/login/index.js +36 -17
  38. package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +8 -0
  39. package/modules/@apostrophecms/migration/index.js +1 -1
  40. package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +151 -61
  41. package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +6 -2
  42. package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +9 -7
  43. package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +12 -15
  44. package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +6 -0
  45. package/modules/@apostrophecms/module/index.js +1 -1
  46. package/modules/@apostrophecms/notification/index.js +116 -8
  47. package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +89 -11
  48. package/modules/@apostrophecms/notification/ui/apos/components/TheAposNotifications.vue +1 -1
  49. package/modules/@apostrophecms/page/index.js +37 -30
  50. package/modules/@apostrophecms/permission/index.js +1 -1
  51. package/modules/@apostrophecms/permission/ui/apos/components/AposInputRole.vue +4 -2
  52. package/modules/@apostrophecms/piece-type/index.js +178 -61
  53. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +179 -47
  54. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +1 -3
  55. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerSelectBox.vue +138 -0
  56. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +42 -10
  57. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapStyles.vue +3 -0
  58. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Classes.js +6 -10
  59. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Default.js +64 -0
  60. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Document.js +15 -0
  61. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Heading.js +23 -0
  62. package/modules/@apostrophecms/schema/index.js +97 -20
  63. package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +1 -0
  64. package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +4 -1
  65. package/modules/@apostrophecms/schema/ui/apos/components/AposInputAttachment.vue +11 -160
  66. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRadio.vue +8 -5
  67. package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +24 -2
  68. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +24 -6
  69. package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +0 -4
  70. package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +0 -7
  71. package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +25 -3
  72. package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputMixin.js +10 -2
  73. package/modules/@apostrophecms/task/index.js +2 -2
  74. package/modules/@apostrophecms/template/index.js +63 -36
  75. package/modules/@apostrophecms/template/lib/custom-tags/component.js +1 -1
  76. package/modules/@apostrophecms/template/lib/custom-tags/render.js +6 -2
  77. package/modules/@apostrophecms/ui/index.js +6 -2
  78. package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +21 -3
  79. package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +1 -1
  80. package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +205 -0
  81. package/modules/@apostrophecms/ui/ui/apos/components/AposIndicator.vue +5 -0
  82. package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +16 -2
  83. package/modules/@apostrophecms/ui/ui/apos/scss/global/_tables.scss +4 -3
  84. package/modules/@apostrophecms/ui/ui/apos/scss/global/_theme.scss +3 -0
  85. package/modules/@apostrophecms/ui/ui/apos/scss/global/_widgets.scss +3 -0
  86. package/modules/@apostrophecms/ui/ui/apos/scss/global/import-all.scss +2 -1
  87. package/modules/@apostrophecms/user/index.js +21 -0
  88. package/modules/@apostrophecms/util/index.js +2 -2
  89. package/modules/@apostrophecms/util/ui/src/http.js +12 -8
  90. package/modules/@apostrophecms/util/ui/src/util.js +15 -0
  91. package/modules/@apostrophecms/widget-type/index.js +1 -1
  92. package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +1 -0
  93. package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +15 -7
  94. package/package.json +4 -4
  95. package/test/extra_node_modules/improve-global/index.js +7 -0
  96. package/test/extra_node_modules/improve-piece-type/index.js +7 -0
  97. package/test/improve-overrides.js +30 -0
  98. package/test/job.js +224 -0
  99. package/test/login.js +183 -0
  100. package/test/modules/@apostrophecms/global/index.js +8 -0
  101. package/test/modules/fragment-all/views/aux-test.html +7 -0
  102. package/test/modules/fragment-all/views/fragment.html +5 -0
  103. package/test/moog.js +47 -0
  104. package/test/package.json +5 -4
  105. package/test/pieces.js +17 -0
  106. package/test/reverse-relationship.js +170 -0
  107. package/test/subdir-project/app.js +3 -0
  108. package/test/subdir-project.js +26 -0
  109. package/test/templates.js +7 -1
  110. package/test-lib/test.js +23 -12
  111. package/test-lib/util.js +33 -0
@@ -19,10 +19,10 @@
19
19
  </template>
20
20
  <template #primaryControls>
21
21
  <AposContextMenu
22
- v-if="moreMenu.menu.length"
23
- :button="moreMenu.button"
24
- :menu="moreMenu.menu"
25
- @item-clicked="moreMenuHandler"
22
+ v-if="utilityOperations.menu.length"
23
+ :button="utilityOperations.button"
24
+ :menu="utilityOperations.menu"
25
+ @item-clicked="utilityOperationsHandler"
26
26
  />
27
27
  <AposButton
28
28
  v-if="relationshipField"
@@ -66,19 +66,33 @@
66
66
  :selected-state="selectAllState"
67
67
  :total-pages="totalPages"
68
68
  :current-page="currentPage"
69
- :filters="moduleOptions.filters"
70
69
  :filter-choices="filterChoices"
71
70
  :filter-values="filterValues"
71
+ :filters="moduleOptions.filters"
72
72
  :labels="moduleLabels"
73
+ :displayed-items="items.length"
74
+ :is-relationship="!!relationshipField"
75
+ :checked-count="checked.length"
76
+ :batch-operations="moduleOptions.batchOperations"
73
77
  @select-click="selectAll"
74
78
  @search="search"
75
79
  @page-change="updatePage"
76
80
  @filter="filter"
81
+ @batch="handleBatchAction"
77
82
  :options="{
78
- disableUnchecked: maxReached(),
79
- hideSelectAll: !relationshipField
83
+ disableUnchecked: maxReached()
80
84
  }"
81
85
  />
86
+ <AposDocsManagerSelectBox
87
+ :selected-state="selectAllState"
88
+ :module-labels="moduleLabels"
89
+ :filter-values="filterValues"
90
+ :checked-ids="checked"
91
+ :all-pieces-selection="allPiecesSelection"
92
+ :displayed-items="items.length"
93
+ @select-all="selectAllPieces"
94
+ @set-all-pieces-selection="setAllPiecesSelection"
95
+ />
82
96
  </template>
83
97
  <template #bodyMain>
84
98
  <AposDocsManagerDisplay
@@ -90,7 +104,6 @@
90
104
  :options="{
91
105
  ...moduleOptions,
92
106
  disableUnchecked: maxReached(),
93
- hideCheckboxes: !relationshipField,
94
107
  disableUnpublished: disableUnpublished,
95
108
  manuallyPublished: manuallyPublished
96
109
  }"
@@ -136,7 +149,7 @@ export default {
136
149
  filterValues: {},
137
150
  queryExtras: {},
138
151
  holdQueries: false,
139
- moreMenu: {
152
+ utilityOperations: {
140
153
  button: {
141
154
  label: 'apostrophe:moreOperations',
142
155
  iconOnly: true,
@@ -145,7 +158,11 @@ export default {
145
158
  },
146
159
  menu: []
147
160
  },
148
- filterChoices: {}
161
+ filterChoices: {},
162
+ allPiecesSelection: {
163
+ isSelected: false,
164
+ total: 0
165
+ }
149
166
  };
150
167
  },
151
168
  computed: {
@@ -190,6 +207,16 @@ export default {
190
207
  },
191
208
  disableUnpublished() {
192
209
  return this.relationshipField && apos.modules[this.relationshipField.withType].localized;
210
+ },
211
+ selectAllChoice() {
212
+ const checkCount = this.checked.length;
213
+ const pageNotFullyChecked = this.items
214
+ .some((item) => !this.checked.includes(item._id));
215
+
216
+ return {
217
+ value: 'checked',
218
+ indeterminate: checkCount && pageNotFullyChecked
219
+ };
193
220
  }
194
221
  },
195
222
  created() {
@@ -206,14 +233,10 @@ export default {
206
233
  this.headers = this.computeHeaders();
207
234
  // Get the data. This will be more complex in actuality.
208
235
  this.modal.active = true;
209
- this.getPieces();
210
- if (this.relationshipField && this.moduleOptions.canEdit) {
211
- // Add computed singular label to context menu
212
- this.moreMenu.menu.unshift({
213
- action: 'new',
214
- label: `New ${this.moduleLabels.singular}`
215
- });
216
- }
236
+ this.setUtilityOperations();
237
+ await this.getPieces();
238
+ await this.getAllPiecesTotal();
239
+
217
240
  apos.bus.$on('content-changed', this.getPieces);
218
241
  },
219
242
  destroyed() {
@@ -221,10 +244,13 @@ export default {
221
244
  apos.bus.$off('content-changed', this.getPieces);
222
245
  },
223
246
  methods: {
224
- moreMenuHandler(action) {
247
+ utilityOperationsHandler(action) {
225
248
  if (action === 'new') {
226
249
  this.create();
250
+ return;
227
251
  }
252
+
253
+ this.handleUtilityOperation(action);
228
254
  },
229
255
  setCheckedDocs(checked) {
230
256
  this.checkedDocs = checked;
@@ -235,6 +261,29 @@ export default {
235
261
  async create() {
236
262
  await this.edit(null);
237
263
  },
264
+ async handleUtilityOperation(action) {
265
+ const operation = this.utilityOperations.menu
266
+ .find((op) => op.action === action);
267
+
268
+ if (!operation) {
269
+ return;
270
+ }
271
+
272
+ const {
273
+ modal, ...modalOptions
274
+ } = operation.modalOptions || {};
275
+
276
+ if (modal) {
277
+ await apos.modal.execute(modal, {
278
+ moduleAction: this.moduleOptions.action,
279
+ action,
280
+ labels: this.moduleLabels,
281
+ messages: operation.messages,
282
+ ...modalOptions
283
+ });
284
+ }
285
+ },
286
+
238
287
  // If pieceOrId is null, a new piece is created
239
288
  async edit(pieceOrId) {
240
289
  let piece;
@@ -268,42 +317,68 @@ export default {
268
317
  async finishSaved() {
269
318
  await this.getPieces();
270
319
  },
271
- async getPieces () {
272
- if (this.holdQueries) {
273
- return;
274
- }
275
-
276
- this.holdQueries = true;
277
-
278
- const qs = {
320
+ async request (mergeOptions) {
321
+ const options = {
279
322
  ...this.filterValues,
280
- page: this.currentPage,
281
323
  ...this.queryExtras,
282
- // Also fetch published docs as _publishedDoc subproperties
324
+ ...mergeOptions,
283
325
  withPublished: 1
284
326
  };
285
327
 
286
328
  // Avoid undefined properties.
287
- for (const prop in qs) {
288
- if (qs[prop] === undefined) {
289
- delete qs[prop];
290
- };
329
+ const qs = Object.entries(options)
330
+ .reduce((acc, [ key, val ]) => ({
331
+ ...acc,
332
+ ...val !== undefined && { [key]: val }
333
+ }), {});
334
+
335
+ return apos.http.get(this.moduleOptions.action, {
336
+ qs,
337
+ busy: true,
338
+ draft: true
339
+ });
340
+ },
341
+ async getPieces () {
342
+ if (this.holdQueries) {
343
+ return;
291
344
  }
292
345
 
293
- const getResponse = await apos.http.get(
294
- this.moduleOptions.action, {
295
- busy: true,
296
- qs,
297
- draft: true
298
- }
299
- );
346
+ this.holdQueries = true;
347
+
348
+ const {
349
+ currentPage, pages, results, choices
350
+ } = await this.request({
351
+ page: this.currentPage
352
+ });
300
353
 
301
- this.currentPage = getResponse.currentPage;
302
- this.totalPages = getResponse.pages;
303
- this.items = getResponse.results;
304
- this.filterChoices = getResponse.choices;
354
+ this.currentPage = currentPage;
355
+ this.totalPages = pages;
356
+ this.items = results;
357
+ this.filterChoices = choices;
305
358
  this.holdQueries = false;
306
359
  },
360
+ async getAllPiecesTotal () {
361
+ const { count: total } = await this.request({ count: 1 });
362
+
363
+ this.setAllPiecesSelection({
364
+ isSelected: false,
365
+ total
366
+ });
367
+ },
368
+ async selectAllPieces () {
369
+ const { results: docs } = await this.request({
370
+ project: {
371
+ _id: 1
372
+ },
373
+ attachments: false,
374
+ perPage: this.allPiecesSelection.total
375
+ });
376
+
377
+ this.setAllPiecesSelection({
378
+ isSelected: true,
379
+ docs
380
+ });
381
+ },
307
382
  updatePage(num) {
308
383
  if (num) {
309
384
  this.currentPage = num;
@@ -322,6 +397,7 @@ export default {
322
397
  this.currentPage = 1;
323
398
 
324
399
  await this.getPieces();
400
+ await this.getAllPiecesTotal();
325
401
  },
326
402
  async filter(filter, value) {
327
403
  if (this.filterValues[filter] === value) {
@@ -331,10 +407,12 @@ export default {
331
407
  this.filterValues[filter] = value;
332
408
  this.currentPage = 1;
333
409
 
334
- this.getPieces();
410
+ await this.getPieces();
411
+ await this.getAllPiecesTotal();
335
412
  this.headers = this.computeHeaders();
336
- },
337
413
 
414
+ this.setCheckedDocs([]);
415
+ },
338
416
  shortcutNew(event) {
339
417
  const interesting = (event.keyCode === 78 || event.keyCode === 67); // C(reate) or N(ew)
340
418
  const topModal = apos.modal.stack[apos.modal.stack.length - 1] ? apos.modal.stack[apos.modal.stack.length - 1].id : null;
@@ -346,7 +424,6 @@ export default {
346
424
  this.create();
347
425
  }
348
426
  },
349
-
350
427
  bindShortcuts() {
351
428
  window.addEventListener('keydown', this.shortcutNew);
352
429
  },
@@ -373,6 +450,61 @@ export default {
373
450
  _fields: result
374
451
  });
375
452
  }
453
+ },
454
+ setAllPiecesSelection ({
455
+ isSelected, total, docs
456
+ }) {
457
+ if (typeof isSelected === 'boolean') {
458
+ this.allPiecesSelection.isSelected = isSelected;
459
+ }
460
+
461
+ if (typeof total === 'number') {
462
+ this.allPiecesSelection.total = total;
463
+ }
464
+
465
+ if (docs) {
466
+ this.setCheckedDocs(docs);
467
+ }
468
+ },
469
+ async handleBatchAction({
470
+ label, action, requestOptions = {}, messages
471
+ }) {
472
+ if (action) {
473
+ try {
474
+ await apos.http.post(`${this.moduleOptions.action}/${action}`, {
475
+ body: {
476
+ ...requestOptions,
477
+ _ids: this.checked,
478
+ messages: messages,
479
+ type: this.checked.length === 1 ? this.moduleLabels.singular
480
+ : this.moduleLabels.plural
481
+ }
482
+ });
483
+ } catch (error) {
484
+ apos.notify('Batch operation {{ operation }} failed.', {
485
+ interpolate: { operation: label },
486
+ type: 'danger'
487
+ });
488
+ }
489
+ }
490
+ },
491
+ setUtilityOperations () {
492
+ const { utilityOperations } = this.moduleOptions;
493
+
494
+ const newPiece = {
495
+ action: 'new',
496
+ label: {
497
+ key: 'apostrophe:newDocType',
498
+ type: this.$t(this.moduleLabels.singular)
499
+ }
500
+ };
501
+
502
+ this.utilityOperations.menu = [
503
+ ...this.relationshipField && this.moduleOptions.canEdit
504
+ ? [ newPiece ] : [],
505
+ ...this.utilityOperations.menu,
506
+ ...(!this.relationshipField && Array.isArray(utilityOperations) && utilityOperations) || []
507
+ ];
376
508
  }
377
509
  }
378
510
  };
@@ -4,7 +4,6 @@
4
4
  <tr>
5
5
  <th
6
6
  class="apos-table__header"
7
- v-if="!options.hideCheckboxes"
8
7
  />
9
8
  <th
10
9
  v-for="header in headers" scope="col"
@@ -26,7 +25,7 @@
26
25
  <th class="apos-table__header" key="contextMenu">
27
26
  <component
28
27
  :is="getEl({})"
29
- class="apos-table__header-label is-hidden"
28
+ class="apos-table__header-label apos-is-hidden"
30
29
  >
31
30
  {{ $t('apostrophe:moreOperations') }}
32
31
  </component>
@@ -41,7 +40,6 @@
41
40
  >
42
41
  <td
43
42
  class="apos-table__cell"
44
- v-if="!options.hideCheckboxes"
45
43
  >
46
44
  <AposCheckbox
47
45
  v-if="item._id"
@@ -0,0 +1,138 @@
1
+ <template>
2
+ <transition
3
+ name="collapse"
4
+ :duration="300"
5
+ >
6
+ <div
7
+ v-if="selectedState === 'checked' || allPiecesSelection.isSelected"
8
+ class="apos-select-box"
9
+ >
10
+ <div class="apos-select-box__content">
11
+ <p class="apos-select-box__text">
12
+ {{ selectBoxMessage }}
13
+ <button
14
+ v-if="!allPiecesSelection.isSelected"
15
+ class="apos-select-box__select-all"
16
+ @click="$emit('select-all')"
17
+ >
18
+ {{ selectBoxMessageButton }}
19
+ </button>
20
+ <button
21
+ v-else
22
+ class="apos-select-box__select-all"
23
+ @click="clearSelection"
24
+ >
25
+ {{ $t('apostrophe:clearSelection') }}.
26
+ </button>
27
+ </p>
28
+ </div>
29
+ </div>
30
+ </transition>
31
+ </template>
32
+
33
+ <script>
34
+ export default {
35
+ props: {
36
+ selectedState: {
37
+ type: String,
38
+ required: true
39
+ },
40
+ moduleLabels: {
41
+ type: Object,
42
+ required: true
43
+ },
44
+ checkedIds: {
45
+ type: Array,
46
+ required: true
47
+ },
48
+ allPiecesSelection: {
49
+ type: Object,
50
+ required: true
51
+ },
52
+ displayedItems: {
53
+ type: Number,
54
+ required: true
55
+ }
56
+ },
57
+ emits: [ 'select-all', 'clear-select', 'set-all-pieces-selection' ],
58
+ computed: {
59
+ selectBoxMessage () {
60
+ const checkedCount = this.checkedIds.length;
61
+ const showAllWord = (checkedCount === this.allPiecesSelection.total) &&
62
+ checkedCount !== 1;
63
+
64
+ const translationKey = this.allPiecesSelection.isSelected
65
+ ? showAllWord
66
+ ? 'apostrophe:selectBoxMessageAllSelected'
67
+ : 'apostrophe:selectBoxMessageSelected'
68
+ : checkedCount > this.displayedItems
69
+ ? 'apostrophe:selectBoxMessage'
70
+ : 'apostrophe:selectBoxMessagePage';
71
+
72
+ return this.$t(translationKey, {
73
+ num: this.checkedIds.length,
74
+ label: this.getLabel(this.checkedIds.length)
75
+ });
76
+ },
77
+ selectBoxMessageButton () {
78
+ const translationKey = this.allPiecesSelection.total === 1
79
+ ? 'apostrophe:selectBoxMessageButton'
80
+ : 'apostrophe:selectBoxMessageAllButton';
81
+
82
+ return this.$t(translationKey, {
83
+ num: this.allPiecesSelection.total,
84
+ label: this.getLabel(this.allPiecesSelection.total)
85
+ });
86
+ }
87
+ },
88
+ methods: {
89
+ getLabel(number) {
90
+ return number === 1
91
+ ? this.$t(this.moduleLabels.singular).toLowerCase()
92
+ : this.$t(this.moduleLabels.plural).toLowerCase();
93
+ },
94
+ clearSelection () {
95
+ this.$emit('set-all-pieces-selection', {
96
+ isSelected: false,
97
+ docs: []
98
+ });
99
+ }
100
+ }
101
+ };
102
+ </script>
103
+ <style lang='scss' scoped>
104
+ .apos-select-box {
105
+ box-sizing: border-box;
106
+ overflow: hidden;
107
+ height: 5rem;
108
+ transition: all 0.3s linear;
109
+
110
+ &.collapse-enter, &.collapse-leave-to {
111
+ height: 0;
112
+ }
113
+
114
+ &__content {
115
+ display: flex;
116
+ align-items: center;
117
+ justify-content: center;
118
+ background-color: var(--a-base-9);
119
+ margin-top: 1rem;
120
+ color: var(--a-text-primary);
121
+ }
122
+
123
+ &__text {
124
+ @include type-large;
125
+ }
126
+
127
+ &__select-all {
128
+ color: var(--a-primary);
129
+ cursor: pointer;
130
+ margin-left: 0.4rem;
131
+ border: none;
132
+
133
+ &:hover {
134
+ text-decoration: underline;
135
+ }
136
+ }
137
+ }
138
+ </style>
@@ -26,9 +26,8 @@
26
26
  </AposContextMenuDialog>
27
27
  </bubble-menu>
28
28
  <div class="apos-rich-text-editor__editor" :class="editorModifiers">
29
- <editor-content :editor="editor" :class="moduleOptions.className" />
29
+ <editor-content :editor="editor" :class="editorOptions.className" />
30
30
  </div>
31
- <!-- Using actual DOM element rather than :after to ease localization -->
32
31
  <div class="apos-rich-text-editor__editor_after" :class="editorModifiers">
33
32
  {{ $t('apostrophe:emptyRichTextWidget') }}
34
33
  </div>
@@ -103,11 +102,27 @@ export default {
103
102
 
104
103
  activeOptions.styles = this.enhanceStyles(activeOptions.styles || this.defaultOptions.styles);
105
104
 
105
+ activeOptions.className = (activeOptions.className !== undefined)
106
+ ? activeOptions.className : this.moduleOptions.className;
107
+
106
108
  return activeOptions;
107
109
  },
108
-
110
+ autofocus() {
111
+ // Only true for a new rich text widget
112
+ return !this.stripPlaceholderBrs(this.value.content).length;
113
+ },
109
114
  initialContent() {
110
- return this.stripPlaceholderBrs(this.value.content);
115
+ const content = this.stripPlaceholderBrs(this.value.content);
116
+ if (!content.length) {
117
+ // If we don't supply a valid instance of the first style, then
118
+ // the text align control will not work until the user manually
119
+ // applies a style or refreshes the page
120
+ const defaultStyle = this.editorOptions.styles.find(style => style.def);
121
+ const _class = defaultStyle.class ? ` class="${defaultStyle.class}"` : '';
122
+ return `<${defaultStyle.tag}${_class}></${defaultStyle.tag}>`;
123
+ } else {
124
+ return content;
125
+ }
111
126
  },
112
127
  toolbar() {
113
128
  return this.editorOptions.toolbar;
@@ -136,7 +151,7 @@ export default {
136
151
  aposTiptapExtensions() {
137
152
  return (apos.tiptapExtensions || [])
138
153
  .map(extension => extension({
139
- styles: this.editorOptions.styles,
154
+ styles: this.editorOptions.styles.map(this.localizeStyle),
140
155
  types: this.tiptapTypes
141
156
  }));
142
157
  }
@@ -153,7 +168,7 @@ export default {
153
168
  mounted() {
154
169
  this.editor = new Editor({
155
170
  content: this.initialContent,
156
- autofocus: !this.initialContent,
171
+ autofocus: this.autofocus,
157
172
  onUpdate: this.editorUpdate,
158
173
  extensions: [
159
174
  StarterKit,
@@ -217,7 +232,6 @@ export default {
217
232
  // commands and parameters used internally.
218
233
  enhanceStyles(styles) {
219
234
  const self = this;
220
- const enhanced = [];
221
235
  (styles || []).forEach(style => {
222
236
  style.options = {};
223
237
  for (const key in self.tiptapTextCommands) {
@@ -242,9 +256,7 @@ export default {
242
256
  style.options.class = style.class;
243
257
  }
244
258
 
245
- if (style.type) {
246
- enhanced.push(style);
247
- } else {
259
+ if (!style.type) {
248
260
  apos.notify('apostrophe:richTextStyleConfigWarning', {
249
261
  type: 'warning',
250
262
  dismiss: true,
@@ -256,7 +268,27 @@ export default {
256
268
  });
257
269
  }
258
270
  });
271
+
272
+ // ensure a default so we can rely on it throughout
273
+ const hasDefault = !!styles.find(style => style.def);
274
+ if (!hasDefault && styles.length) {
275
+ // If no dev set default, use the first paragraph we can find
276
+ if (styles.filter(style => style.type === 'paragraph').length) {
277
+ styles.filter(style => style.type === 'paragraph')[0].def = true;
278
+ } else {
279
+ // Otherwise, set the first style
280
+ styles[0].def = true;
281
+ }
282
+ }
259
283
  return styles;
284
+ },
285
+ localizeStyle(style) {
286
+ style.label = this.$t(style.label);
287
+
288
+ return {
289
+ ...style,
290
+ label: this.$t(style.label)
291
+ };
260
292
  }
261
293
  }
262
294
  };
@@ -52,6 +52,9 @@ export default {
52
52
  const style = styles[i];
53
53
  if (this.editor.isActive(style.type, (style.options || {}))) {
54
54
  return i;
55
+ } else if (this.editor.state.selection.$head.parent.type.name === 'defaultNode' && style.def) {
56
+ // Look deeper to see if custom defaultNode is active
57
+ return i;
55
58
  }
56
59
  }
57
60
  return 0;
@@ -25,9 +25,7 @@ export default (options) => {
25
25
  const tag = element.tagName.toLowerCase();
26
26
  // This tag is not configured
27
27
  if (!allow[tag]) {
28
- return {
29
- class: null
30
- };
28
+ return null;
31
29
  }
32
30
  const classes = (element.getAttribute('class') || '')
33
31
  .split(' ')
@@ -36,13 +34,11 @@ export default (options) => {
36
34
  // If no valid classes for this parse, default to the
37
35
  // the first setting for this tag (including null for tags defined without classes).
38
36
  // else, remove classes.
39
- return {
40
- class: classes.length
41
- ? classes.join(' ')
42
- : (
43
- allow[tag].length ? allow[tag][0] : null
44
- )
45
- };
37
+ return classes.length
38
+ ? classes.join(' ')
39
+ : (
40
+ allow[tag].length ? allow[tag][0] : null
41
+ );
46
42
  }
47
43
  }
48
44
  }