apostrophe 4.5.4 → 4.6.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 +22 -1
- package/lib/mongodb-connect.js +9 -2
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBar.vue +6 -1
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarLocale.vue +18 -180
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarMenu.vue +6 -2
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +1 -1
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextModeAndSettings.vue +11 -0
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +2 -0
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaMenu.vue +6 -1
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +63 -11
- package/modules/@apostrophecms/area/ui/apos/components/AposWidgetControls.vue +12 -15
- package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.config.js +10 -1
- package/modules/@apostrophecms/db/index.js +2 -3
- package/modules/@apostrophecms/doc-type/index.js +7 -1
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +183 -109
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocLocalePicker.vue +177 -0
- package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +4 -0
- package/modules/@apostrophecms/i18n/i18n/de.json +1 -0
- package/modules/@apostrophecms/i18n/i18n/en.json +7 -1
- package/modules/@apostrophecms/i18n/i18n/es.json +1 -0
- package/modules/@apostrophecms/i18n/i18n/fr.json +1 -0
- package/modules/@apostrophecms/i18n/i18n/it.json +1 -0
- package/modules/@apostrophecms/i18n/i18n/pt-BR.json +1 -0
- package/modules/@apostrophecms/i18n/i18n/sk.json +1 -0
- package/modules/@apostrophecms/i18n/index.js +22 -0
- package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +1 -0
- package/modules/@apostrophecms/i18n/ui/src/index.js +3 -0
- package/modules/@apostrophecms/modal/ui/apos/apps/AposModals.js +1 -0
- package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +22 -30
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +24 -1
- package/modules/@apostrophecms/modal/ui/apos/components/TheAposModals.vue +48 -38
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +7 -0
- package/modules/@apostrophecms/page/index.js +3 -1
- package/modules/@apostrophecms/page/ui/apos/components/AposPagesManager.vue +1 -0
- package/modules/@apostrophecms/page/ui/apos/logic/AposPagesManager.js +17 -3
- package/modules/@apostrophecms/piece-type/index.js +3 -1
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +25 -4
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerSelectBox.vue +1 -1
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapLink.vue +4 -3
- package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +17 -10
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue +35 -22
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuItem.vue +1 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposLocale.vue +36 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposLocalePicker.vue +283 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlat.vue +56 -18
- package/modules/@apostrophecms/ui/ui/apos/lib/tooltip.js +2 -1
- package/modules/@apostrophecms/ui/ui/apos/mixins/AposArchiveMixin.js +4 -2
- package/modules/@apostrophecms/ui/ui/apos/mixins/AposPublishMixin.js +12 -6
- package/modules/@apostrophecms/ui/ui/apos/stores/modal.js +10 -1
- package/modules/@apostrophecms/util/ui/src/http.js +12 -5
- package/package.json +4 -4
|
@@ -10,8 +10,13 @@ export default {
|
|
|
10
10
|
name: 'AposPagesManager',
|
|
11
11
|
mixins: [ AposModifiedMixin, AposDocsManagerMixin, AposArchiveMixin, AposPublishMixin ],
|
|
12
12
|
emits: [ 'archive', 'search', 'modal-result' ],
|
|
13
|
+
props: {
|
|
14
|
+
modalData: {
|
|
15
|
+
type: Object,
|
|
16
|
+
required: true
|
|
17
|
+
}
|
|
18
|
+
},
|
|
13
19
|
data() {
|
|
14
|
-
|
|
15
20
|
return {
|
|
16
21
|
moduleName: '@apostrophecms/page',
|
|
17
22
|
modal: {
|
|
@@ -149,12 +154,12 @@ export default {
|
|
|
149
154
|
await this.getPages();
|
|
150
155
|
this.modal.triggerFocusRefresh++;
|
|
151
156
|
|
|
152
|
-
apos.bus.$on('content-changed', this.
|
|
157
|
+
apos.bus.$on('content-changed', this.onContentChanged);
|
|
153
158
|
apos.bus.$on('command-menu-manager-create-new', this.create);
|
|
154
159
|
apos.bus.$on('command-menu-manager-close', this.confirmAndCancel);
|
|
155
160
|
},
|
|
156
161
|
unmounted() {
|
|
157
|
-
apos.bus.$off('content-changed', this.
|
|
162
|
+
apos.bus.$off('content-changed', this.onContentChanged);
|
|
158
163
|
apos.bus.$off('command-menu-manager-create-new', this.create);
|
|
159
164
|
apos.bus.$off('command-menu-manager-close', this.confirmAndCancel);
|
|
160
165
|
},
|
|
@@ -164,6 +169,15 @@ export default {
|
|
|
164
169
|
this.create();
|
|
165
170
|
}
|
|
166
171
|
},
|
|
172
|
+
onContentChanged({ doc }) {
|
|
173
|
+
if (
|
|
174
|
+
!doc ||
|
|
175
|
+
!doc.aposLocale ||
|
|
176
|
+
doc.aposLocale.split(':')[0] === this.modalData.locale
|
|
177
|
+
) {
|
|
178
|
+
this.getPages();
|
|
179
|
+
}
|
|
180
|
+
},
|
|
167
181
|
async getPages () {
|
|
168
182
|
const self = this;
|
|
169
183
|
if (this.gettingPages) {
|
|
@@ -820,8 +820,10 @@ module.exports = {
|
|
|
820
820
|
async convertInsertAndRefresh(req, input, options) {
|
|
821
821
|
const piece = self.newInstance();
|
|
822
822
|
const copyingId = self.apos.launder.id(input._copyingId);
|
|
823
|
+
const createId = self.apos.launder.id(input._createId);
|
|
823
824
|
await self.convert(req, input, piece, {
|
|
824
|
-
copyingId
|
|
825
|
+
copyingId,
|
|
826
|
+
createId
|
|
825
827
|
});
|
|
826
828
|
await self.emit('afterConvert', req, input, piece);
|
|
827
829
|
await self.insert(req, piece);
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
ref="modal"
|
|
4
4
|
:modal="modal"
|
|
5
5
|
:modal-title="modalTitle"
|
|
6
|
+
:modal-data="modalData"
|
|
6
7
|
@esc="confirmAndCancel"
|
|
7
8
|
@inactive="modal.active = false"
|
|
8
9
|
@show-modal="modal.showModal = true"
|
|
@@ -139,6 +140,10 @@ export default {
|
|
|
139
140
|
moduleName: {
|
|
140
141
|
type: String,
|
|
141
142
|
required: true
|
|
143
|
+
},
|
|
144
|
+
modalData: {
|
|
145
|
+
type: Object,
|
|
146
|
+
required: true
|
|
142
147
|
}
|
|
143
148
|
},
|
|
144
149
|
emits: [ 'archive' ],
|
|
@@ -354,7 +359,9 @@ export default {
|
|
|
354
359
|
async selectAllPieces () {
|
|
355
360
|
const { results: docs } = await this.request({
|
|
356
361
|
project: {
|
|
357
|
-
_id: 1
|
|
362
|
+
_id: 1,
|
|
363
|
+
_url: 1,
|
|
364
|
+
title: 1
|
|
358
365
|
},
|
|
359
366
|
attachments: false,
|
|
360
367
|
perPage: this.allPiecesSelection.total
|
|
@@ -469,6 +476,11 @@ export default {
|
|
|
469
476
|
: this.moduleLabels.plural
|
|
470
477
|
}
|
|
471
478
|
});
|
|
479
|
+
if (action === 'archive') {
|
|
480
|
+
await this.getPieces();
|
|
481
|
+
this.getAllPiecesTotal();
|
|
482
|
+
this.checked = [];
|
|
483
|
+
}
|
|
472
484
|
} catch (error) {
|
|
473
485
|
apos.notify('apostrophe:errorBatchOperationNoti', {
|
|
474
486
|
interpolate: { operation: label },
|
|
@@ -479,15 +491,24 @@ export default {
|
|
|
479
491
|
}
|
|
480
492
|
},
|
|
481
493
|
setCheckedDocs(checked) {
|
|
482
|
-
this.checkedDocs = checked;
|
|
494
|
+
this.checkedDocs = checked.slice(0, this.relationshipField?.max || checked.length);
|
|
483
495
|
this.checked = this.checkedDocs.map(item => {
|
|
484
496
|
return item._id;
|
|
485
497
|
});
|
|
486
498
|
},
|
|
487
499
|
|
|
488
500
|
async onContentChanged({ doc, action }) {
|
|
489
|
-
|
|
490
|
-
|
|
501
|
+
if (
|
|
502
|
+
!doc ||
|
|
503
|
+
!doc.aposLocale ||
|
|
504
|
+
doc.aposLocale.split(':')[0] === this.modalData.locale
|
|
505
|
+
) {
|
|
506
|
+
await this.getPieces();
|
|
507
|
+
this.getAllPiecesTotal();
|
|
508
|
+
if (action === 'archive') {
|
|
509
|
+
this.checked = this.checked.filter(checkedId => doc._id !== checkedId);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
491
512
|
}
|
|
492
513
|
}
|
|
493
514
|
};
|
|
@@ -63,7 +63,7 @@ export default {
|
|
|
63
63
|
showSelectAll() {
|
|
64
64
|
if (
|
|
65
65
|
this.allPiecesSelection.isSelected ||
|
|
66
|
-
(
|
|
66
|
+
([ 'checked', 'indeterminate' ].includes(this.selectedState) && this.allPiecesSelection.total > this.displayedItems)
|
|
67
67
|
) {
|
|
68
68
|
return true;
|
|
69
69
|
}
|
|
@@ -138,7 +138,7 @@ export default {
|
|
|
138
138
|
},
|
|
139
139
|
active(newVal) {
|
|
140
140
|
if (newVal) {
|
|
141
|
-
this.hasLinkOnOpen = !!(this.
|
|
141
|
+
this.hasLinkOnOpen = !!(this.attributes.href);
|
|
142
142
|
window.addEventListener('keydown', this.keyboardHandler);
|
|
143
143
|
} else {
|
|
144
144
|
window.removeEventListener('keydown', this.keyboardHandler);
|
|
@@ -207,10 +207,10 @@ export default {
|
|
|
207
207
|
});
|
|
208
208
|
},
|
|
209
209
|
keyboardHandler(e) {
|
|
210
|
-
if (e.
|
|
210
|
+
if (e.key === 'Escape') {
|
|
211
211
|
this.close();
|
|
212
212
|
}
|
|
213
|
-
if (e.
|
|
213
|
+
if (e.key === 'Enter') {
|
|
214
214
|
if (this.docFields.data.href || e.metaKey) {
|
|
215
215
|
this.save();
|
|
216
216
|
this.close();
|
|
@@ -268,6 +268,7 @@ export default {
|
|
|
268
268
|
} finally {
|
|
269
269
|
this.generation++;
|
|
270
270
|
}
|
|
271
|
+
this.evaluateConditions();
|
|
271
272
|
}
|
|
272
273
|
}
|
|
273
274
|
};
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
v-bind="attrs"
|
|
10
10
|
:is="href ? 'a' : 'button'"
|
|
11
11
|
:id="attrs.id ? attrs.id : id"
|
|
12
|
-
:
|
|
12
|
+
:target="target"
|
|
13
|
+
:href="href"
|
|
13
14
|
class="apos-button"
|
|
14
15
|
:class="modifierClass"
|
|
15
16
|
:tabindex="tabindex"
|
|
@@ -82,8 +83,8 @@ export default {
|
|
|
82
83
|
default: null
|
|
83
84
|
},
|
|
84
85
|
href: {
|
|
85
|
-
type:
|
|
86
|
-
default:
|
|
86
|
+
type: String,
|
|
87
|
+
default: null
|
|
87
88
|
},
|
|
88
89
|
iconSize: {
|
|
89
90
|
type: Number,
|
|
@@ -121,16 +122,20 @@ export default {
|
|
|
121
122
|
},
|
|
122
123
|
disableFocus: Boolean,
|
|
123
124
|
buttonType: {
|
|
124
|
-
type:
|
|
125
|
-
default:
|
|
125
|
+
type: String,
|
|
126
|
+
default: null
|
|
126
127
|
},
|
|
127
128
|
role: {
|
|
128
|
-
type:
|
|
129
|
-
default:
|
|
129
|
+
type: String,
|
|
130
|
+
default: null
|
|
130
131
|
},
|
|
131
132
|
tooltip: {
|
|
132
133
|
type: [ String, Object, Boolean ],
|
|
133
134
|
default: false
|
|
135
|
+
},
|
|
136
|
+
target: {
|
|
137
|
+
type: String,
|
|
138
|
+
default: null
|
|
134
139
|
}
|
|
135
140
|
},
|
|
136
141
|
emits: [ 'click' ],
|
|
@@ -207,7 +212,7 @@ export default {
|
|
|
207
212
|
return null;
|
|
208
213
|
},
|
|
209
214
|
isDisabled() {
|
|
210
|
-
return this.disabled || this.busy;
|
|
215
|
+
return (this.disabled || this.busy) || null;
|
|
211
216
|
},
|
|
212
217
|
actionTestLabel() {
|
|
213
218
|
return this.action
|
|
@@ -384,6 +389,10 @@ export default {
|
|
|
384
389
|
text-decoration: none;
|
|
385
390
|
background-color: var(--a-base-10);
|
|
386
391
|
}
|
|
392
|
+
|
|
393
|
+
&:focus {
|
|
394
|
+
outline: 1px solid var(--a-base-7);
|
|
395
|
+
}
|
|
387
396
|
}
|
|
388
397
|
|
|
389
398
|
.apos-button--gradient-on-hover {
|
|
@@ -661,8 +670,6 @@ export default {
|
|
|
661
670
|
&:hover:not([disabled]),
|
|
662
671
|
&:focus:not([disabled]) {
|
|
663
672
|
transform: none;
|
|
664
|
-
box-shadow: none;
|
|
665
|
-
outline: none;
|
|
666
673
|
}
|
|
667
674
|
}
|
|
668
675
|
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
v-bind="button"
|
|
10
10
|
ref="button"
|
|
11
11
|
class="apos-context-menu__btn"
|
|
12
|
-
data-apos-test="contextMenuTrigger"
|
|
13
12
|
role="button"
|
|
13
|
+
:data-apos-test="identifier"
|
|
14
14
|
:state="buttonState"
|
|
15
15
|
:disabled="disabled"
|
|
16
16
|
:tooltip="tooltip"
|
|
@@ -20,28 +20,27 @@
|
|
|
20
20
|
}"
|
|
21
21
|
@click.stop="buttonClicked($event)"
|
|
22
22
|
/>
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
<div
|
|
24
|
+
v-if="isOpen"
|
|
25
|
+
ref="dropdownContent"
|
|
26
|
+
v-click-outside-element="hide"
|
|
27
|
+
class="apos-context-menu__dropdown-content"
|
|
28
|
+
:class="popoverClass"
|
|
29
|
+
data-apos-menu
|
|
30
|
+
:style="dropdownContentStyle"
|
|
31
|
+
:aria-hidden="!isOpen"
|
|
32
|
+
>
|
|
33
|
+
<AposContextMenuDialog
|
|
34
|
+
:menu-placement="placement"
|
|
35
|
+
:class-list="classList"
|
|
36
|
+
:menu="menu"
|
|
37
|
+
:is-open="isOpen"
|
|
38
|
+
@item-clicked="menuItemClicked"
|
|
39
|
+
@set-arrow="setArrow"
|
|
33
40
|
>
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
:menu="menu"
|
|
38
|
-
@item-clicked="menuItemClicked"
|
|
39
|
-
@set-arrow="setArrow"
|
|
40
|
-
>
|
|
41
|
-
<slot />
|
|
42
|
-
</AposContextMenuDialog>
|
|
43
|
-
</div>
|
|
44
|
-
</Teleport>
|
|
41
|
+
<slot />
|
|
42
|
+
</AposContextMenuDialog>
|
|
43
|
+
</div>
|
|
45
44
|
</div>
|
|
46
45
|
</div>
|
|
47
46
|
</template>
|
|
@@ -57,6 +56,10 @@ import { useAposTheme } from 'Modules/@apostrophecms/ui/composables/AposTheme';
|
|
|
57
56
|
import { createId } from '@paralleldrive/cuid2';
|
|
58
57
|
|
|
59
58
|
const props = defineProps({
|
|
59
|
+
identifier: {
|
|
60
|
+
type: String,
|
|
61
|
+
default: 'contextMenuTrigger'
|
|
62
|
+
},
|
|
60
63
|
menu: {
|
|
61
64
|
type: Array,
|
|
62
65
|
default: null
|
|
@@ -155,10 +158,14 @@ watch(isOpen, (newVal) => {
|
|
|
155
158
|
if (newVal) {
|
|
156
159
|
window.addEventListener('resize', setDropdownPosition);
|
|
157
160
|
window.addEventListener('scroll', setDropdownPosition);
|
|
161
|
+
window.addEventListener('keydown', handleKeyboard);
|
|
158
162
|
setDropdownPosition();
|
|
163
|
+
dropdownContent.value.querySelector('[tabindex]')?.focus();
|
|
159
164
|
} else {
|
|
160
165
|
window.removeEventListener('resize', setDropdownPosition);
|
|
161
166
|
window.removeEventListener('scroll', setDropdownPosition);
|
|
167
|
+
window.removeEventListener('keydown', handleKeyboard);
|
|
168
|
+
dropdown.value.querySelector('[tabindex]').focus();
|
|
162
169
|
}
|
|
163
170
|
}, { flush: 'post' });
|
|
164
171
|
|
|
@@ -237,6 +244,12 @@ async function setDropdownPosition() {
|
|
|
237
244
|
...arrowY && { top: `${arrowY}px` }
|
|
238
245
|
});
|
|
239
246
|
}
|
|
247
|
+
|
|
248
|
+
function handleKeyboard(event) {
|
|
249
|
+
if (event.key === 'Escape') {
|
|
250
|
+
hide();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
240
253
|
</script>
|
|
241
254
|
|
|
242
255
|
<style lang="scss">
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="apos-locale">
|
|
3
|
+
<span class="apos-locale__label">
|
|
4
|
+
{{ $t('apostrophe:locale') }}:
|
|
5
|
+
</span> <span class="apos-locale__label-name">
|
|
6
|
+
{{ locale }}
|
|
7
|
+
</span>
|
|
8
|
+
</div>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script setup>
|
|
12
|
+
import { computed } from 'vue';
|
|
13
|
+
const props = defineProps({
|
|
14
|
+
locale: {
|
|
15
|
+
type: String,
|
|
16
|
+
required: true
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const locale = computed(() => {
|
|
21
|
+
const label = apos.i18n.locales[props.locale]?.label;
|
|
22
|
+
return label ? `${label} (${props.locale})` : props.locale;
|
|
23
|
+
});
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<style lang="scss" scoped>
|
|
27
|
+
.apos-locale {
|
|
28
|
+
@include type-base;
|
|
29
|
+
|
|
30
|
+
font-weight: var(--a-weight-bold);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.apos-locale__label-name {
|
|
34
|
+
color: var(--a-primary);
|
|
35
|
+
}
|
|
36
|
+
</style>
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="apos-locales-picker"
|
|
4
|
+
data-apos-test="localePicker"
|
|
5
|
+
>
|
|
6
|
+
<div class="apos-input-wrapper">
|
|
7
|
+
<input
|
|
8
|
+
v-model="search"
|
|
9
|
+
type="text"
|
|
10
|
+
class="apos-locales-picker__filter"
|
|
11
|
+
:placeholder="$t('apostrophe:searchLocalesPlaceholder')"
|
|
12
|
+
>
|
|
13
|
+
</div>
|
|
14
|
+
<ul class="apos-locales-picker__list">
|
|
15
|
+
<li
|
|
16
|
+
v-for="locale in filteredLocales"
|
|
17
|
+
:key="locale.name"
|
|
18
|
+
v-apos-tooltip="getForbiddenTooltip(locale)"
|
|
19
|
+
:class="localeClasses(locale)"
|
|
20
|
+
class="apos-locale-picker__item"
|
|
21
|
+
data-apos-test="localeItem"
|
|
22
|
+
@click="switchLocale(locale)"
|
|
23
|
+
>
|
|
24
|
+
<button :tabindex="isOpen ? '0' : '-1'" class="apos-locale-picker__locale-display">
|
|
25
|
+
<AposIndicator
|
|
26
|
+
v-if="isForbidden(locale)"
|
|
27
|
+
icon="lock-icon"
|
|
28
|
+
icon-color="var(--a-base-5)"
|
|
29
|
+
class="apos-locale-picker__check"
|
|
30
|
+
:icon-size="12"
|
|
31
|
+
:title="$t('apostrophe:forbidden')"
|
|
32
|
+
/>
|
|
33
|
+
<AposIndicator
|
|
34
|
+
v-else-if="isActive(locale)"
|
|
35
|
+
icon="check-bold-icon"
|
|
36
|
+
icon-color="var(--a-primary)"
|
|
37
|
+
class="apos-locale-picker__check"
|
|
38
|
+
:icon-size="12"
|
|
39
|
+
:title="$t('apostrophe:currentLocale')"
|
|
40
|
+
/>
|
|
41
|
+
{{ locale.label }}
|
|
42
|
+
<span class="apos-locale-picker__name">
|
|
43
|
+
({{ locale.name }})
|
|
44
|
+
</span>
|
|
45
|
+
<span
|
|
46
|
+
class="apos-locale-picker__localized"
|
|
47
|
+
:class="{ 'apos-state-is-localized': isLocalized(locale) }"
|
|
48
|
+
/>
|
|
49
|
+
</button>
|
|
50
|
+
</li>
|
|
51
|
+
</ul>
|
|
52
|
+
<div class="apos-locales-picker__available">
|
|
53
|
+
<p class="apos-locales-picker__available-desc">
|
|
54
|
+
{{ $t('apostrophe:documentExistsInLocales') }}
|
|
55
|
+
</p>
|
|
56
|
+
<AposButton
|
|
57
|
+
v-for="locale in availableLocales"
|
|
58
|
+
:key="locale.name"
|
|
59
|
+
class="apos-locales-picker__available-locale"
|
|
60
|
+
:label="locale.label"
|
|
61
|
+
type="quiet"
|
|
62
|
+
:modifiers="['no-motion']"
|
|
63
|
+
@click="switchLocale(locale)"
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</template>
|
|
68
|
+
|
|
69
|
+
<script setup>
|
|
70
|
+
import { ref, computed } from 'vue';
|
|
71
|
+
|
|
72
|
+
const emit = defineEmits([ 'switch-locale' ]);
|
|
73
|
+
const props = defineProps({
|
|
74
|
+
currentLocale: {
|
|
75
|
+
type: String,
|
|
76
|
+
required: true
|
|
77
|
+
},
|
|
78
|
+
localized: {
|
|
79
|
+
type: Object,
|
|
80
|
+
required: true
|
|
81
|
+
},
|
|
82
|
+
forbidden: {
|
|
83
|
+
type: Array,
|
|
84
|
+
default: () => ([])
|
|
85
|
+
},
|
|
86
|
+
forbiddenTooltip: {
|
|
87
|
+
type: String,
|
|
88
|
+
default: null
|
|
89
|
+
},
|
|
90
|
+
isOpen: {
|
|
91
|
+
type: Boolean,
|
|
92
|
+
default: false
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const search = ref('');
|
|
97
|
+
const locales = ref(Object.entries(window.apos.i18n.locales).map(
|
|
98
|
+
([ locale, options ]) => {
|
|
99
|
+
return {
|
|
100
|
+
name: locale,
|
|
101
|
+
label: options.label || locale
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
));
|
|
105
|
+
|
|
106
|
+
const filteredLocales = computed(() => {
|
|
107
|
+
return locales.value.filter(({ name, label }) => {
|
|
108
|
+
const matches = term =>
|
|
109
|
+
term.toLowerCase().includes(search.value.toLowerCase());
|
|
110
|
+
return matches(name) || matches(label);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const availableLocales = computed(() => {
|
|
115
|
+
return locales.value.filter(locale => isLocalized(locale));
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
function isLocalized(locale) {
|
|
119
|
+
return Boolean(props.localized[locale.name]);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isActive(locale) {
|
|
123
|
+
return props.currentLocale === locale.name;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function isForbidden(locale) {
|
|
127
|
+
return props.forbidden.includes(locale.name);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function switchLocale(locale) {
|
|
131
|
+
emit('switch-locale', locale);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function localeClasses(locale) {
|
|
135
|
+
return {
|
|
136
|
+
'apos-active': isActive(locale),
|
|
137
|
+
'apos-exists': isLocalized(locale),
|
|
138
|
+
'apos-forbidden': isForbidden(locale)
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getForbiddenTooltip(locale) {
|
|
143
|
+
return isForbidden(locale) && {
|
|
144
|
+
content: props.forbiddenTooltip,
|
|
145
|
+
placement: 'left'
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
</script>
|
|
149
|
+
|
|
150
|
+
<style lang="scss" scoped>
|
|
151
|
+
.apos-locale-picker__locale-display {
|
|
152
|
+
@include apos-button-reset();
|
|
153
|
+
|
|
154
|
+
display: block;
|
|
155
|
+
box-sizing: border-box;
|
|
156
|
+
width: 100%;
|
|
157
|
+
padding: 12px 35px;
|
|
158
|
+
|
|
159
|
+
&:focus, &:active {
|
|
160
|
+
outline: none;
|
|
161
|
+
border: none;
|
|
162
|
+
background-color: var(--a-base-10);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
&:hover {
|
|
166
|
+
background-color: var(--a-base-9);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.apos-locales-picker {
|
|
172
|
+
width: 315px;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.apos-locales-picker__filter {
|
|
176
|
+
@include type-large;
|
|
177
|
+
|
|
178
|
+
box-sizing: border-box;
|
|
179
|
+
width: 100%;
|
|
180
|
+
padding: 20px 45px 20px 20px;
|
|
181
|
+
border-top: 0;
|
|
182
|
+
border-right: 0;
|
|
183
|
+
border-bottom: 1px solid var(--a-base-9);
|
|
184
|
+
border-left: 0;
|
|
185
|
+
border-top-right-radius: var(--a-border-radius);
|
|
186
|
+
border-top-left-radius: var(--a-border-radius);
|
|
187
|
+
|
|
188
|
+
&::placeholder {
|
|
189
|
+
color: var(--a-base-4);
|
|
190
|
+
font-style: italic;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
&:focus {
|
|
194
|
+
outline: none;
|
|
195
|
+
background-color: var(--a-base-10);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.apos-locales-picker__list {
|
|
200
|
+
margin: $spacing-base 0;
|
|
201
|
+
padding-left: 0;
|
|
202
|
+
list-style-type: none;
|
|
203
|
+
max-height: 350px;
|
|
204
|
+
overflow-y: scroll;
|
|
205
|
+
font-weight: var(--a-weight-base);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.apos-locale-picker__item {
|
|
209
|
+
position: relative;
|
|
210
|
+
line-height: 1;
|
|
211
|
+
cursor: pointer;
|
|
212
|
+
|
|
213
|
+
.state {
|
|
214
|
+
opacity: 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
&:hover {
|
|
218
|
+
background-color: var(--a-base-10);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.apos-locale-picker__check {
|
|
222
|
+
position: absolute;
|
|
223
|
+
top: 50%;
|
|
224
|
+
left: 18px;
|
|
225
|
+
transform: translateY(-50%);
|
|
226
|
+
color: var(--a-primary);
|
|
227
|
+
stroke: var(--a-primary);
|
|
228
|
+
|
|
229
|
+
:deep(.material-design-icon__svg) {
|
|
230
|
+
stroke: none;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.apos-locale-picker__localized {
|
|
235
|
+
position: relative;
|
|
236
|
+
top: -1px;
|
|
237
|
+
left: 5px;
|
|
238
|
+
display: inline-block;
|
|
239
|
+
width: 3px;
|
|
240
|
+
height: 3px;
|
|
241
|
+
border: 1px solid var(--a-base-5);
|
|
242
|
+
border-radius: 50%;
|
|
243
|
+
|
|
244
|
+
&.apos-state-is-localized {
|
|
245
|
+
background-color: var(--a-success);
|
|
246
|
+
border-color: var(--a-success);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.apos-forbidden {
|
|
252
|
+
color: var(--a-base-5);
|
|
253
|
+
cursor: not-allowed;
|
|
254
|
+
|
|
255
|
+
&:hover {
|
|
256
|
+
background-color: var(--a-base-10);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.apos-locales-picker__name {
|
|
261
|
+
text-transform: uppercase;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.apos-locales-picker__available {
|
|
265
|
+
padding: $spacing-double;
|
|
266
|
+
border-top: 1px solid var(--a-base-9);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.apos-locales-picker__available-locale {
|
|
270
|
+
display: inline-block;
|
|
271
|
+
color: var(--a-primary);
|
|
272
|
+
font-size: var(--a-type-small);
|
|
273
|
+
|
|
274
|
+
&:not(:last-of-type) {
|
|
275
|
+
margin-right: 10px;
|
|
276
|
+
margin-bottom: 5px;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.apos-locales-picker__available-desc {
|
|
281
|
+
margin-top: 0;
|
|
282
|
+
}
|
|
283
|
+
</style>
|