apostrophe 4.1.0 → 4.2.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 (48) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/lib/mongodb-connect.js +1 -1
  3. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarMenu.vue +1 -0
  4. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +22 -16
  5. package/modules/@apostrophecms/area/index.js +1 -1
  6. package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +6 -1
  7. package/modules/@apostrophecms/db/index.js +0 -6
  8. package/modules/@apostrophecms/doc/index.js +19 -31
  9. package/modules/@apostrophecms/doc/lib/migrations.js +59 -0
  10. package/modules/@apostrophecms/doc-type/index.js +5 -2
  11. package/modules/@apostrophecms/express/index.js +3 -2
  12. package/modules/@apostrophecms/i18n/i18n/de.json +5 -1
  13. package/modules/@apostrophecms/i18n/i18n/en.json +5 -1
  14. package/modules/@apostrophecms/i18n/i18n/es.json +5 -1
  15. package/modules/@apostrophecms/i18n/i18n/fr.json +5 -1
  16. package/modules/@apostrophecms/i18n/i18n/it.json +5 -1
  17. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +5 -1
  18. package/modules/@apostrophecms/i18n/i18n/sk.json +5 -1
  19. package/modules/@apostrophecms/i18n/index.js +1 -1
  20. package/modules/@apostrophecms/migration/index.js +5 -0
  21. package/modules/@apostrophecms/modal/ui/apos/apps/AposModals.js +2 -2
  22. package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +188 -187
  23. package/modules/@apostrophecms/modal/ui/apos/composables/AposFocus.js +2 -2
  24. package/modules/@apostrophecms/notification/index.js +2 -0
  25. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +2 -2
  26. package/modules/@apostrophecms/rich-text-widget/index.js +19 -8
  27. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +49 -16
  28. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapMarks.vue +226 -0
  29. package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapStyles.vue +90 -18
  30. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Classes.js +70 -6
  31. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Default.js +2 -1
  32. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Document.js +1 -1
  33. package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Heading.js +1 -1
  34. package/modules/@apostrophecms/schema/ui/apos/components/AposInputColor.vue +1 -1
  35. package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +1 -1
  36. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputColor.js +4 -0
  37. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputSlug.js +3 -0
  38. package/modules/@apostrophecms/ui/ui/apos/components/AposButtonSplit.vue +2 -2
  39. package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +4 -8
  40. package/modules/@apostrophecms/user/index.js +40 -14
  41. package/modules/@apostrophecms/user/lib/password-hash.js +122 -0
  42. package/modules/@apostrophecms/util/ui/src/http.js +7 -0
  43. package/package.json +3 -5
  44. package/test/docs.js +151 -0
  45. package/test/password-hash.js +56 -0
  46. package/test/users.js +19 -3
  47. package/.github/workflows/outdated-dependencies.yml +0 -43
  48. package/modules/@apostrophecms/modal/ui/apos/mixins/AposFocusMixin.js +0 -91
@@ -11,10 +11,10 @@
11
11
  :class="classes"
12
12
  role="dialog"
13
13
  aria-modal="true"
14
- :aria-labelledby="id"
14
+ :aria-labelledby="state.id"
15
15
  data-apos-modal
16
- @keydown="cycleElementsToFocus"
17
16
  @focus.capture="storeFocusedElement"
17
+ @keydown="onKeydown"
18
18
  >
19
19
  <transition :name="transitionType">
20
20
  <div
@@ -41,16 +41,16 @@
41
41
  <template v-else>
42
42
  <header v-if="!modal.disableHeader" class="apos-modal__header">
43
43
  <div class="apos-modal__header__main">
44
- <div v-if="hasSecondaryControls" class="apos-modal__controls--secondary">
44
+ <div v-if="hasSlot('secondaryControls')" class="apos-modal__controls--secondary">
45
45
  <slot name="secondaryControls" />
46
46
  </div>
47
- <h2 :id="id" class="apos-modal__heading">
47
+ <h2 :id="state.id" class="apos-modal__heading">
48
48
  <span v-if="modal.a11yTitle" class="apos-sr-only">
49
49
  {{ $t(modal.a11yTitle) }}
50
50
  </span>
51
51
  {{ $t(modalTitle) }}
52
52
  </h2>
53
- <div v-if="hasBeenLocalized || hasPrimaryControls" class="apos-modal__controls--header">
53
+ <div v-if="hasBeenLocalized || hasSlot('primaryControls')" class="apos-modal__controls--header">
54
54
  <div v-if="hasBeenLocalized" class="apos-modal__locale">
55
55
  <span class="apos-modal__locale-label">
56
56
  {{ $t('apostrophe:locale') }}:
@@ -58,21 +58,21 @@
58
58
  {{ currentLocale }}
59
59
  </span>
60
60
  </div>
61
- <div v-if="hasPrimaryControls" class="apos-modal__controls--primary">
61
+ <div v-if="hasSlot('primaryControls')" class="apos-modal__controls--primary">
62
62
  <slot name="primaryControls" />
63
63
  </div>
64
64
  </div>
65
65
  </div>
66
- <div v-if="hasBreadcrumbs" class="apos-modal__breadcrumbs">
66
+ <div v-if="hasSlot('breadcrumbs')" class="apos-modal__breadcrumbs">
67
67
  <slot class="apos-modal__breadcrumbs" name="breadcrumbs" />
68
68
  </div>
69
69
  </header>
70
70
  <div class="apos-modal__main" :class="gridModifier">
71
- <slot v-if="hasLeftRail" name="leftRail" />
71
+ <slot name="leftRail" />
72
72
  <slot name="main" />
73
73
  <slot name="rightRail" />
74
74
  </div>
75
- <footer v-if="hasFooter" class="apos-modal__footer">
75
+ <footer v-if="hasSlot('footer')" class="apos-modal__footer">
76
76
  <div class="apos-modal__footer__inner">
77
77
  <slot name="footer" />
78
78
  </div>
@@ -84,7 +84,7 @@
84
84
  </transition>
85
85
  </template>
86
86
 
87
- <script>
87
+ <script setup>
88
88
  // NOTE:
89
89
  // To get the desired transition effect, modal props have two properties,
90
90
  // `active` and `showModal`, which control their visibility. Basically,
@@ -94,186 +94,187 @@
94
94
  // So as the modal exits, they should change in reverse. `showModal` becomes
95
95
  // `false`, then `active` is set to `false` once the modal has finished its
96
96
  // transition.
97
- import AposFocusMixin from 'Modules/@apostrophecms/modal/mixins/AposFocusMixin';
98
-
99
- export default {
100
- name: 'AposModal',
101
- mixins: [
102
- AposFocusMixin
103
- ],
104
- props: {
105
- modal: {
106
- type: Object,
107
- required: true
108
- },
109
- modalTitle: {
110
- type: [ String, Object ],
111
- default: ''
112
- }
113
- },
114
- emits: [ 'inactive', 'esc', 'show-modal', 'no-modal', 'ready' ],
115
- data() {
116
- return {
117
- // For aria purposes
118
- id: 'modal:' + Math.random().toString().replace('.', '')
119
- };
120
- },
121
- computed: {
122
- transitionType: function () {
123
- if (this.modal.type === 'slide') {
124
- if (this.modal.origin === 'left') {
125
- return 'slide-right';
126
- } else {
127
- return 'slide-left';
128
- }
129
- } else {
130
- return 'fade';
131
- }
132
- },
133
- shouldTrapFocus() {
134
- return this.modal.trapFocus || this.modal.trapFocus === undefined;
135
- },
136
- triggerFocusRefresh () {
137
- return this.modal.triggerFocusRefresh;
138
- },
139
- hasBeenLocalized: function() {
140
- return Object.keys(apos.i18n.locales).length > 1;
141
- },
142
- currentLocale: function() {
143
- return apos.i18n.locale;
144
- },
145
- hasPrimaryControls: function () {
146
- return !!this.$slots.primaryControls;
147
- },
148
- hasSecondaryControls: function () {
149
- return !!this.$slots.secondaryControls;
150
- },
151
- hasBreadcrumbs: function () {
152
- return !!this.$slots.breadcrumbs;
153
- },
154
- hasLeftRail: function () {
155
- return !!this.$slots.leftRail;
156
- },
157
- hasRightRail: function () {
158
- return !!this.$slots.rightRail;
159
- },
160
- hasFooter: function () {
161
- return !!this.$slots.footer;
162
- },
163
- classes() {
164
- const classes = [ 'apos-modal' ];
165
- classes.push(`apos-modal--${this.modal.type}`);
166
- if (this.modal.type === 'slide') {
167
- if (this.modal.origin) {
168
- classes.push(`apos-modal--origin-${this.modal.origin}`);
169
- } else {
170
- classes.push('apos-modal--origin-right');
171
- }
172
- classes.push('apos-modal--full-height');
173
- }
174
- if (this.modal.busy) {
175
- classes.push('apos-modal--busy');
176
- }
177
- return classes.join(' ');
178
- },
179
- innerClasses() {
180
- const classes = [];
181
- if (this.modal.width) {
182
- classes.push(`apos-modal__inner--${this.modal.width}`);
183
- };
184
- return classes;
185
- },
186
- gridModifier() {
187
- if (this.hasLeftRail && this.hasRightRail) {
188
- return 'apos-modal__main--with-rails';
189
- }
190
- if (this.hasLeftRail && !this.hasRightRail) {
191
- return 'apos-modal__main--with-left-rail';
192
- }
193
- if (!this.hasLeftRail && this.hasRightRail) {
194
- return 'apos-modal__main--with-right-rail';
195
- }
196
- return false;
197
- }
198
- },
199
- watch: {
200
- // Simple way to re-trigger focusable elements
201
- // that might have been created or removed
202
- // after an update, like an XHR call to get the
203
- // pieces list in the AposDocsManager modal, for instance.
204
- triggerFocusRefresh (newVal) {
205
- if (this.shouldTrapFocus) {
206
- this.$nextTick(this.trapFocus);
207
- }
208
- }
209
- },
210
- mounted() {
211
- if (this.shouldTrapFocus) {
212
- this.$nextTick(this.trapFocus);
213
- }
97
+
98
+ import {
99
+ ref, reactive, onMounted, computed, watch, nextTick, useSlots
100
+ } from 'vue';
101
+ import { useAposFocus } from 'Modules/@apostrophecms/modal/composables/AposFocus';
102
+ import cuid from 'cuid';
103
+
104
+ const {
105
+ cycleElementsToFocus,
106
+ elementsToFocus,
107
+ focusElement,
108
+ focusLastModalFocusedElement,
109
+ focusedElement,
110
+ isElementVisible,
111
+ storeFocusedElement
112
+ } = useAposFocus();
113
+
114
+ const props = defineProps({
115
+ modal: {
116
+ type: Object,
117
+ required: true
214
118
  },
215
- methods: {
216
- onKeydown (e) {
217
- const hasPressedEsc = e.keyCode === 27;
218
- if (hasPressedEsc) {
219
- this.close(e);
220
- }
221
- },
222
- onEnter () {
223
- this.$emit('show-modal');
224
- this.bindEventListeners();
225
- apos.modal.stack = apos.modal.stack || [];
226
- apos.modal.stack.push(this);
227
- this.$nextTick(() => {
228
- this.$emit('ready');
229
- });
230
- },
231
- onLeave () {
232
- this.removeEventListeners();
233
- this.$emit('no-modal');
234
- // pop doesn't quite suffice because of race conditions when
235
- // closing one and opening another
236
- apos.modal.stack = apos.modal.stack.filter(modal => modal !== this);
237
- this.focusLastModalFocusedElement();
238
- },
239
- bindEventListeners () {
240
- window.addEventListener('keydown', this.onKeydown);
241
- },
242
- removeEventListeners () {
243
- window.removeEventListener('keydown', this.onKeydown);
244
- },
245
- close (e) {
246
- if (apos.modal.stack[apos.modal.stack.length - 1] !== this) {
247
- return;
248
- }
249
- e.stopPropagation();
250
- this.$emit('esc');
251
- },
252
- trapFocus () {
253
- const elementSelectors = [
254
- '[tabindex]',
255
- '[href]',
256
- 'input',
257
- 'select',
258
- 'textarea',
259
- 'button'
260
- ];
261
-
262
- const selector = elementSelectors
263
- .map(addExcludingAttributes)
264
- .join(', ');
265
-
266
- this.elementsToFocus = [ ...this.$refs.modalEl.querySelectorAll(selector) ]
267
- .filter(this.isElementVisible);
268
-
269
- this.focusElement(this.focusedElement, this.elementsToFocus[0]);
270
-
271
- function addExcludingAttributes(element) {
272
- return `${element}:not([tabindex="-1"]):not([disabled]):not([type="hidden"]):not([aria-hidden])`;
273
- }
119
+ modalTitle: {
120
+ type: [ String, Object ],
121
+ default: ''
122
+ }
123
+ });
124
+
125
+ const slots = useSlots();
126
+ const emit = defineEmits([ 'inactive', 'esc', 'show-modal', 'no-modal', 'ready' ]);
127
+ const modalEl = ref(null);
128
+ const state = reactive({
129
+ id: `modal:${cuid()}`,
130
+ elementsToFocus,
131
+ focusedElement,
132
+ modalEl
133
+ });
134
+
135
+ const transitionType = computed(() => {
136
+ if (props.modal.type !== 'slide') {
137
+ return 'fade';
138
+ }
139
+
140
+ if (props.modal.origin === 'left') {
141
+ return 'slide-right';
142
+ } else {
143
+ return 'slide-left';
144
+ }
145
+ });
146
+
147
+ const shouldTrapFocus = computed(() => {
148
+ return props.modal.trapFocus || props.modal.trapFocus === undefined;
149
+ });
150
+
151
+ const triggerFocusRefresh = computed(() => {
152
+ return props.modal.triggerFocusRefresh;
153
+ });
154
+
155
+ const hasBeenLocalized = computed(() => {
156
+ return Object.keys(apos.i18n.locales).length > 1;
157
+ });
158
+
159
+ const currentLocale = computed(() => {
160
+ return apos.i18n.locale;
161
+ });
162
+
163
+ function hasSlot(slotName) {
164
+ return Boolean(slots[slotName]);
165
+ }
166
+
167
+ const classes = computed(() => {
168
+ const classes = [ 'apos-modal' ];
169
+ classes.push(`apos-modal--${props.modal.type}`);
170
+ if (props.modal.type === 'slide') {
171
+ if (props.modal.origin) {
172
+ classes.push(`apos-modal--origin-${props.modal.origin}`);
173
+ } else {
174
+ classes.push('apos-modal--origin-right');
274
175
  }
176
+ classes.push('apos-modal--full-height');
177
+ }
178
+ if (props.modal.busy) {
179
+ classes.push('apos-modal--busy');
180
+ }
181
+ return classes.join(' ');
182
+ });
183
+
184
+ const innerClasses = computed(() => {
185
+ const classes = [];
186
+ if (props.modal.width) {
187
+ classes.push(`apos-modal__inner--${props.modal.width}`);
188
+ };
189
+
190
+ return classes;
191
+ });
192
+
193
+ const gridModifier = computed(() => {
194
+ const hasLeftRail = hasSlot('leftRail');
195
+ const hasRightRail = hasSlot('rightRail');
196
+
197
+ if (hasLeftRail && hasRightRail) {
198
+ return 'apos-modal__main--with-rails';
199
+ }
200
+ if (hasLeftRail && !hasRightRail) {
201
+ return 'apos-modal__main--with-left-rail';
202
+ }
203
+ if (!hasLeftRail && hasRightRail) {
204
+ return 'apos-modal__main--with-right-rail';
205
+ }
206
+ return false;
207
+ });
208
+
209
+ watch(triggerFocusRefresh, (newVal) => {
210
+ if (shouldTrapFocus.value) {
211
+ nextTick(trapFocus);
212
+ }
213
+ });
214
+
215
+ onMounted(() => {
216
+ if (shouldTrapFocus.value) {
217
+ nextTick(trapFocus);
218
+ }
219
+ });
220
+
221
+ function onKeydown(e) {
222
+ const hasPressedEsc = e.keyCode === 27;
223
+ if (hasPressedEsc) {
224
+ close(e);
225
+ }
226
+ cycleElementsToFocus(e);
227
+ }
228
+
229
+ function onEnter() {
230
+ emit('show-modal');
231
+ apos.modal.stack = apos.modal.stack || [];
232
+
233
+ apos.modal.stack.push(state);
234
+ nextTick(() => {
235
+ emit('ready');
236
+ });
237
+ }
238
+
239
+ function onLeave() {
240
+ emit('no-modal');
241
+
242
+ apos.modal.stack = apos.modal.stack
243
+ .filter(modal => modal.id !== state.id);
244
+
245
+ focusLastModalFocusedElement();
246
+ }
247
+
248
+ function trapFocus() {
249
+ const elementSelectors = [
250
+ '[tabindex]',
251
+ '[href]',
252
+ 'input',
253
+ 'select',
254
+ 'textarea',
255
+ 'button'
256
+ ];
257
+
258
+ const selector = elementSelectors
259
+ .map(addExcludingAttributes)
260
+ .join(', ');
261
+
262
+ elementsToFocus.value = [ ...modalEl.value.querySelectorAll(selector) ]
263
+ .filter(isElementVisible);
264
+
265
+ focusElement(focusedElement.value, elementsToFocus.value[0]);
266
+
267
+ function addExcludingAttributes(element) {
268
+ return `${element}:not([tabindex="-1"]):not([disabled]):not([type="hidden"]):not([aria-hidden])`;
269
+ }
270
+ }
271
+
272
+ function close() {
273
+ if (apos.modal.stack.at(-1)?.id !== state.id) {
274
+ return;
275
275
  }
276
- };
276
+ emit('esc');
277
+ }
277
278
  </script>
278
279
 
279
280
  <style lang="scss" scoped>
@@ -8,7 +8,7 @@ export function useAposFocus() {
8
8
  elementsToFocus,
9
9
  focusedElement,
10
10
  cycleElementsToFocus,
11
- focuslastmodalfocusedelement,
11
+ focusLastModalFocusedElement,
12
12
  storeFocusedElement,
13
13
  focusElement,
14
14
  isElementVisible
@@ -56,7 +56,7 @@ export function useAposFocus() {
56
56
  // Focus the last focused element from the last modal.
57
57
  // If it is not focusable (not visible/not in the DOM),
58
58
  // fallbacks to the first focusable element from the last modal.
59
- function focuslastmodalfocusedelement() {
59
+ function focusLastModalFocusedElement() {
60
60
  const lastModal = apos.modal.stack.at(-1);
61
61
 
62
62
  if (!lastModal) {
@@ -271,6 +271,8 @@ module.exports = {
271
271
  throw self.apos.error('required');
272
272
  }
273
273
 
274
+ req.body = req.body || {};
275
+
274
276
  const notification = {
275
277
  _id: self.apos.util.generateId(),
276
278
  createdAt: new Date(),
@@ -405,11 +405,11 @@ export default {
405
405
  },
406
406
  shortcutNew(event) {
407
407
  const interesting = event.keyCode === 78; // N(ew)
408
- const topModal = apos.modal.stack[apos.modal.stack.length - 1] ? apos.modal.stack[apos.modal.stack.length - 1].id : null;
408
+ const topModalId = apos.modal.stack.at(-1)?.id;
409
409
  if (
410
410
  interesting &&
411
411
  document.activeElement.tagName !== 'INPUT' &&
412
- this.$refs.modal.id === topModal
412
+ this.$refs.modal.id === topModalId
413
413
  ) {
414
414
  this.create();
415
415
  }
@@ -142,9 +142,15 @@ module.exports = {
142
142
  widgetEditor: 'AposRichTextWidgetEditor'
143
143
  },
144
144
  editorTools: {
145
- styles: {
145
+ nodes: {
146
146
  component: 'AposTiptapStyles',
147
- label: 'apostrophe:richTextStyles'
147
+ label: 'apostrophe:richTextNodeStyles',
148
+ icon: 'format-text-icon'
149
+ },
150
+ marks: {
151
+ component: 'AposTiptapMarks',
152
+ label: 'apostrophe:richTextMarkStyles',
153
+ icon: 'palette-swatch-icon'
148
154
  },
149
155
  table: {
150
156
  component: 'AposTiptapTable',
@@ -311,9 +317,10 @@ module.exports = {
311
317
  setNode: [ 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre', 'div' ],
312
318
  toggleMark: [
313
319
  'b', 'strong', 'code', 'mark', 'em', 'i',
314
- 'a', 's', 'del', 'strike', 'span', 'u', 'anchor',
320
+ 'a', 's', 'del', 'strike', 'u', 'anchor',
315
321
  'superscript', 'subscript'
316
322
  ],
323
+ toggleClassOrToggleMark: [ 'span' ],
317
324
  wrapIn: [ 'blockquote' ]
318
325
  },
319
326
  tiptapTypes: {
@@ -347,7 +354,8 @@ module.exports = {
347
354
  icons: {
348
355
  'format-text-icon': 'FormatText',
349
356
  'format-color-highlight-icon': 'FormatColorHighlight',
350
- 'table-icon': 'Table'
357
+ 'table-icon': 'Table',
358
+ 'palette-swatch-icon': 'PaletteSwatch'
351
359
  },
352
360
  handlers(self) {
353
361
  return {
@@ -609,9 +617,13 @@ module.exports = {
609
617
  for (const style of options.styles || []) {
610
618
  const tag = style.tag;
611
619
  const classes = self.getStyleClasses(style);
612
- allowedClasses[tag] = allowedClasses[tag] || {};
613
- for (const c of classes) {
614
- allowedClasses[tag][c] = true;
620
+
621
+ // Add classes to THIS tag's allowList
622
+ if (tag) {
623
+ allowedClasses[tag] = allowedClasses[tag] || {};
624
+ for (const c of classes) {
625
+ allowedClasses[tag][c] = true;
626
+ }
615
627
  }
616
628
  }
617
629
  }
@@ -867,7 +879,6 @@ module.exports = {
867
879
  // Add on the core default options to use, if needed.
868
880
  getBrowserData(_super, req) {
869
881
  const initialData = _super(req);
870
-
871
882
  const finalData = {
872
883
  ...initialData,
873
884
  tools: self.options.editorTools,
@@ -8,7 +8,8 @@
8
8
  duration: 300,
9
9
  zIndex: 2000,
10
10
  animation: 'fade',
11
- inertia: true
11
+ inertia: true,
12
+ placement: 'bottom'
12
13
  }"
13
14
  :editor="editor"
14
15
  >
@@ -140,6 +141,8 @@ import TableHeader from '@tiptap/extension-table-header';
140
141
  import TableRow from '@tiptap/extension-table-row';
141
142
  import Placeholder from '@tiptap/extension-placeholder';
142
143
 
144
+ import { klona } from 'klona';
145
+
143
146
  export default {
144
147
  name: 'AposRichTextWidgetEditor',
145
148
  components: {
@@ -209,13 +212,20 @@ export default {
209
212
  return this.moduleOptions.defaultOptions;
210
213
  },
211
214
  editorOptions() {
212
- const activeOptions = Object.assign({}, this.options);
213
-
214
- activeOptions.styles = this.enhanceStyles(
215
- activeOptions.styles?.length
216
- ? activeOptions.styles
217
- : this.defaultOptions.styles
218
- );
215
+ // Deep clone to prevent runaway recursive rendering
216
+ // as the subproperties are mutated in several places
217
+ // by this code and its dependencies
218
+ let activeOptions = klona(this.options);
219
+
220
+ activeOptions = {
221
+ ...activeOptions,
222
+ ...this.enhanceStyles(
223
+ activeOptions.styles?.length
224
+ ? activeOptions.styles
225
+ : this.defaultOptions.styles
226
+ )
227
+ };
228
+ delete activeOptions.styles;
219
229
 
220
230
  // Allow default options to pass through if `false`
221
231
  Object.keys(this.defaultOptions).forEach((option) => {
@@ -228,6 +238,15 @@ export default {
228
238
  activeOptions.className = (activeOptions.className !== undefined)
229
239
  ? activeOptions.className : this.moduleOptions.className;
230
240
 
241
+ if (activeOptions.toolbar.includes('styles')) {
242
+ activeOptions.toolbar = activeOptions.toolbar.filter(t => t !== 'styles');
243
+ if (activeOptions.marks.length) {
244
+ activeOptions.toolbar = [ 'marks', ...activeOptions.toolbar ];
245
+ }
246
+ if (activeOptions.nodes.length) {
247
+ activeOptions.toolbar = [ 'nodes', ...activeOptions.toolbar ];
248
+ }
249
+ }
231
250
  return activeOptions;
232
251
  },
233
252
  autofocus() {
@@ -243,7 +262,12 @@ export default {
243
262
  // If we don't supply a valid instance of the first style, then
244
263
  // the text align control will not work until the user manually
245
264
  // applies a style or refreshes the page
246
- const defaultStyle = this.editorOptions.styles.find(style => style.def);
265
+ const defaultStyle =
266
+ this.editorOptions.nodes.length
267
+ ? this.editorOptions.nodes.find(style => style.def)
268
+ : this.editorOptions.marks.length
269
+ ? this.editorOptions.marks.find(style => style.def)
270
+ : null;
247
271
 
248
272
  const _class = defaultStyle.class ? ` class="${defaultStyle.class}"` : '';
249
273
  return `<${defaultStyle.tag}${_class}></${defaultStyle.tag}>`;
@@ -491,6 +515,7 @@ export default {
491
515
  },
492
516
  // Enhances the dev-defined styles list with tiptap
493
517
  // commands and parameters used internally.
518
+ // WARNING: mutates its argument
494
519
  enhanceStyles(styles) {
495
520
  const self = this;
496
521
  (styles || []).forEach(style => {
@@ -541,11 +566,15 @@ export default {
541
566
  styles[0].def = true;
542
567
  }
543
568
  }
544
- return styles;
569
+
570
+ // Split styles into node and mark selects
571
+ const result = {
572
+ nodes: styles.filter(style => style.command === 'setNode'),
573
+ marks: styles.filter(style => style.command !== 'setNode')
574
+ };
575
+ return result;
545
576
  },
546
577
  localizeStyle(style) {
547
- style.label = this.$t(style.label);
548
-
549
578
  return {
550
579
  ...style,
551
580
  label: this.$t(style.label)
@@ -555,7 +584,8 @@ export default {
555
584
  return (apos.tiptapExtensions || [])
556
585
  .map(extension => extension({
557
586
  ...this.editorOptions,
558
- styles: this.editorOptions.styles.map(this.localizeStyle),
587
+ nodes: this.editorOptions.nodes.map(this.localizeStyle),
588
+ marks: this.editorOptions.marks.map(this.localizeStyle),
559
589
  types: this.tiptapTypes
560
590
  }));
561
591
  },
@@ -713,13 +743,16 @@ function traverseNextNode(node) {
713
743
 
714
744
  .apos-button--rich-text {
715
745
  position: relative;
716
- width: 24px;
717
746
  height: 24px;
718
- padding: 0;
747
+ padding: 0 8px;
719
748
  border: none;
720
749
  border-radius: var(--a-border-radius);
721
750
  background-color: transparent;
722
751
  color: var(--a-base-1);
752
+ &.apos-button--icon-only {
753
+ width: 24px;
754
+ padding: 0;
755
+ }
723
756
  &:hover {
724
757
  background-color: transparent;
725
758
  }
@@ -775,7 +808,7 @@ function traverseNextNode(node) {
775
808
  align-items: stretch;
776
809
  max-width: 100%;
777
810
  height: auto;
778
- gap: 4px;
811
+ gap: 6px;
779
812
  }
780
813
 
781
814
  .apos-rich-text-editor__editor :deep(.ProseMirror) {