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.
- package/CHANGELOG.md +42 -1
- package/lib/mongodb-connect.js +1 -1
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarMenu.vue +1 -0
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +22 -16
- package/modules/@apostrophecms/area/index.js +1 -1
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +6 -1
- package/modules/@apostrophecms/db/index.js +0 -6
- package/modules/@apostrophecms/doc/index.js +19 -31
- package/modules/@apostrophecms/doc/lib/migrations.js +59 -0
- package/modules/@apostrophecms/doc-type/index.js +5 -2
- package/modules/@apostrophecms/express/index.js +3 -2
- package/modules/@apostrophecms/i18n/i18n/de.json +5 -1
- package/modules/@apostrophecms/i18n/i18n/en.json +5 -1
- package/modules/@apostrophecms/i18n/i18n/es.json +5 -1
- package/modules/@apostrophecms/i18n/i18n/fr.json +5 -1
- package/modules/@apostrophecms/i18n/i18n/it.json +5 -1
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +5 -1
- package/modules/@apostrophecms/i18n/i18n/sk.json +5 -1
- package/modules/@apostrophecms/i18n/index.js +1 -1
- package/modules/@apostrophecms/migration/index.js +5 -0
- package/modules/@apostrophecms/modal/ui/apos/apps/AposModals.js +2 -2
- package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +188 -187
- package/modules/@apostrophecms/modal/ui/apos/composables/AposFocus.js +2 -2
- package/modules/@apostrophecms/notification/index.js +2 -0
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +2 -2
- package/modules/@apostrophecms/rich-text-widget/index.js +19 -8
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +49 -16
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapMarks.vue +226 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapStyles.vue +90 -18
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Classes.js +70 -6
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Default.js +2 -1
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Document.js +1 -1
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Heading.js +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputColor.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputColor.js +4 -0
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputSlug.js +3 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposButtonSplit.vue +2 -2
- package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +4 -8
- package/modules/@apostrophecms/user/index.js +40 -14
- package/modules/@apostrophecms/user/lib/password-hash.js +122 -0
- package/modules/@apostrophecms/util/ui/src/http.js +7 -0
- package/package.json +3 -5
- package/test/docs.js +151 -0
- package/test/password-hash.js +56 -0
- package/test/users.js +19 -3
- package/.github/workflows/outdated-dependencies.yml +0 -43
- 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="
|
|
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 ||
|
|
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="
|
|
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="
|
|
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
|
|
71
|
+
<slot name="leftRail" />
|
|
72
72
|
<slot name="main" />
|
|
73
73
|
<slot name="rightRail" />
|
|
74
74
|
</div>
|
|
75
|
-
<footer v-if="
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
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
|
|
59
|
+
function focusLastModalFocusedElement() {
|
|
60
60
|
const lastModal = apos.modal.stack.at(-1);
|
|
61
61
|
|
|
62
62
|
if (!lastModal) {
|
|
@@ -405,11 +405,11 @@ export default {
|
|
|
405
405
|
},
|
|
406
406
|
shortcutNew(event) {
|
|
407
407
|
const interesting = event.keyCode === 78; // N(ew)
|
|
408
|
-
const
|
|
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 ===
|
|
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
|
-
|
|
145
|
+
nodes: {
|
|
146
146
|
component: 'AposTiptapStyles',
|
|
147
|
-
label: 'apostrophe:
|
|
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', '
|
|
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
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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,
|
package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue
CHANGED
|
@@ -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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
811
|
+
gap: 6px;
|
|
779
812
|
}
|
|
780
813
|
|
|
781
814
|
.apos-rich-text-editor__editor :deep(.ProseMirror) {
|