@mapomodule/form 2.0.0-beta.4 → 2.0.0-beta.6
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/README.md +2 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/module.d.ts.map +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +16 -1
- package/dist/runtime/components/MapoFocusPortal.vue +3 -1
- package/dist/runtime/components/MapoFocusPortal.vue.d.ts.map +1 -1
- package/dist/runtime/components/MapoFormTabs.vue +4 -2
- package/dist/runtime/components/MapoFormTabs.vue.d.ts.map +1 -1
- package/dist/runtime/components/fields/MapoDateField.vue +3 -1
- package/dist/runtime/components/fields/MapoDateField.vue.d.ts.map +1 -1
- package/dist/runtime/components/fields/MapoDateTimeField.vue +5 -3
- package/dist/runtime/components/fields/MapoDateTimeField.vue.d.ts.map +1 -1
- package/dist/runtime/components/fields/MapoEnhancedMediaField.vue +130 -0
- package/dist/runtime/components/fields/MapoEnhancedMediaField.vue.d.ts +16 -0
- package/dist/runtime/components/fields/MapoEnhancedMediaField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoFileField.vue +3 -1
- package/dist/runtime/components/fields/MapoFileField.vue.d.ts.map +1 -1
- package/dist/runtime/components/fields/MapoFksField.vue +6 -2
- package/dist/runtime/components/fields/MapoFksField.vue.d.ts.map +1 -1
- package/dist/runtime/components/fields/MapoMapField.vue +4 -2
- package/dist/runtime/components/fields/MapoMapField.vue.d.ts.map +1 -1
- package/dist/runtime/components/fields/MapoMediaField.vue +91 -0
- package/dist/runtime/components/fields/MapoMediaField.vue.d.ts +16 -0
- package/dist/runtime/components/fields/MapoMediaField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoMediaM2mField.vue +110 -0
- package/dist/runtime/components/fields/MapoMediaM2mField.vue.d.ts +16 -0
- package/dist/runtime/components/fields/MapoMediaM2mField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoMediaUnavailable.vue +15 -0
- package/dist/runtime/components/fields/MapoMediaUnavailable.vue.d.ts +4 -0
- package/dist/runtime/components/fields/MapoMediaUnavailable.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoRepeater.vue +32 -22
- package/dist/runtime/components/fields/MapoRepeater.vue.d.ts.map +1 -1
- package/dist/runtime/components/fields/MapoRepeaterItem.vue +13 -8
- package/dist/runtime/components/fields/MapoRepeaterItem.vue.d.ts.map +1 -1
- package/dist/runtime/components/fields/MapoSeoPreview.vue +16 -12
- package/dist/runtime/components/fields/MapoSeoPreview.vue.d.ts.map +1 -1
- package/dist/runtime/components/fields/MapoWygEditor.vue +30 -5
- package/dist/runtime/components/fields/MapoWygEditor.vue.d.ts.map +1 -1
- package/dist/runtime/composables/useMapoForm.d.ts.map +1 -1
- package/dist/runtime/composables/useMapoForm.js +4 -2
- package/dist/runtime/composables/useMediaManager.d.ts +17 -0
- package/dist/runtime/composables/useMediaManager.d.ts.map +1 -0
- package/dist/runtime/composables/useMediaManager.js +6 -0
- package/dist/runtime/composables/useUnsavedChangesGuard.d.ts.map +1 -1
- package/dist/runtime/composables/useUnsavedChangesGuard.js +3 -2
- package/dist/runtime/registry/defaults.d.ts.map +1 -1
- package/dist/runtime/registry/defaults.js +8 -2
- package/dist/runtime/types/descriptor.d.ts +18 -4
- package/dist/runtime/types/descriptor.d.ts.map +1 -1
- package/dist/runtime/types/index.d.ts +2 -1
- package/dist/runtime/types/index.d.ts.map +1 -1
- package/dist/runtime/types/media.d.ts +43 -0
- package/dist/runtime/types/media.d.ts.map +1 -0
- package/dist/runtime/types/media.js +0 -0
- package/package.json +9 -6
- package/src/__tests__/useMapoForm.test.ts +5 -0
- package/src/index.ts +5 -0
- package/src/module.ts +24 -0
- package/src/runtime/components/MapoFocusPortal.vue +3 -1
- package/src/runtime/components/MapoFormTabs.vue +4 -1
- package/src/runtime/components/fields/MapoDateField.vue +3 -1
- package/src/runtime/components/fields/MapoDateTimeField.vue +3 -1
- package/src/runtime/components/fields/MapoEnhancedMediaField.vue +153 -0
- package/src/runtime/components/fields/MapoFileField.vue +3 -1
- package/src/runtime/components/fields/MapoFksField.vue +7 -2
- package/src/runtime/components/fields/MapoMapField.vue +5 -2
- package/src/runtime/components/fields/MapoMediaField.vue +104 -0
- package/src/runtime/components/fields/MapoMediaM2mField.vue +130 -0
- package/src/runtime/components/fields/MapoMediaUnavailable.vue +19 -0
- package/src/runtime/components/fields/MapoRepeater.vue +33 -26
- package/src/runtime/components/fields/MapoRepeaterItem.vue +14 -8
- package/src/runtime/components/fields/MapoSeoPreview.vue +17 -13
- package/src/runtime/components/fields/MapoWygEditor.vue +47 -5
- package/src/runtime/composables/useMapoForm.ts +4 -2
- package/src/runtime/composables/useMediaManager.ts +21 -0
- package/src/runtime/composables/useUnsavedChangesGuard.ts +3 -3
- package/src/runtime/registry/defaults.ts +9 -1
- package/src/runtime/types/descriptor.ts +22 -7
- package/src/runtime/types/index.ts +4 -0
- package/src/runtime/types/media.ts +44 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, computed } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
4
|
+
import { useMediaManager } from "../../composables/useMediaManager.js";
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
modelValue: { type: null, required: true },
|
|
7
|
+
descriptor: { type: Object, required: true },
|
|
8
|
+
errors: { type: Array, required: false },
|
|
9
|
+
readonly: { type: Boolean, required: false },
|
|
10
|
+
disabled: { type: Boolean, required: false }
|
|
11
|
+
});
|
|
12
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
13
|
+
const { t } = useI18n();
|
|
14
|
+
const dialogOpen = ref(false);
|
|
15
|
+
const { MediaDialog, available } = useMediaManager();
|
|
16
|
+
const current = computed(() => {
|
|
17
|
+
if (!props.modelValue) return [];
|
|
18
|
+
return props.modelValue.filter((v) => typeof v === "object");
|
|
19
|
+
});
|
|
20
|
+
const mime = computed(() => props.descriptor.attrs?.mime);
|
|
21
|
+
function onConfirm(selection) {
|
|
22
|
+
emit("update:modelValue", Array.isArray(selection) ? selection : [selection]);
|
|
23
|
+
}
|
|
24
|
+
function removeItem(id) {
|
|
25
|
+
emit(
|
|
26
|
+
"update:modelValue",
|
|
27
|
+
current.value.filter((m) => m.id !== id)
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
const dragIndex = ref(null);
|
|
31
|
+
function onDragStart(index) {
|
|
32
|
+
dragIndex.value = index;
|
|
33
|
+
}
|
|
34
|
+
function onDragOverItem(index) {
|
|
35
|
+
if (dragIndex.value === null || dragIndex.value === index) return;
|
|
36
|
+
const items = [...current.value];
|
|
37
|
+
const [moved] = items.splice(dragIndex.value, 1);
|
|
38
|
+
if (!moved) return;
|
|
39
|
+
items.splice(index, 0, moved);
|
|
40
|
+
dragIndex.value = index;
|
|
41
|
+
emit("update:modelValue", items);
|
|
42
|
+
}
|
|
43
|
+
function onDragEnd() {
|
|
44
|
+
dragIndex.value = null;
|
|
45
|
+
}
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<template>
|
|
49
|
+
<div class="mapo-media-m2m-field space-y-2">
|
|
50
|
+
<MapoMediaUnavailable v-if="!available" />
|
|
51
|
+
|
|
52
|
+
<template v-else>
|
|
53
|
+
<!-- Selected items grid -->
|
|
54
|
+
<div v-if="current.length > 0" class="flex flex-wrap gap-2">
|
|
55
|
+
<div
|
|
56
|
+
v-for="(media, i) in current"
|
|
57
|
+
:key="media.id"
|
|
58
|
+
class="group relative"
|
|
59
|
+
:class="{
|
|
60
|
+
'opacity-50': dragIndex === i,
|
|
61
|
+
'cursor-grab': !readonly && !disabled
|
|
62
|
+
}"
|
|
63
|
+
:draggable="!readonly && !disabled"
|
|
64
|
+
@dragstart="onDragStart(i)"
|
|
65
|
+
@dragover.prevent="onDragOverItem(i)"
|
|
66
|
+
@dragend="onDragEnd"
|
|
67
|
+
>
|
|
68
|
+
<MapoMediaPreview :media="media" size="sm" />
|
|
69
|
+
<button
|
|
70
|
+
v-if="!readonly && !disabled"
|
|
71
|
+
class="absolute -right-1.5 -top-1.5 hidden size-4 items-center justify-center rounded-full bg-error text-inverted group-hover:flex"
|
|
72
|
+
type="button"
|
|
73
|
+
@click="removeItem(media.id)"
|
|
74
|
+
>
|
|
75
|
+
<UIcon name="i-lucide-x" class="size-2.5" />
|
|
76
|
+
</button>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<!-- Add button -->
|
|
81
|
+
<UButton
|
|
82
|
+
v-if="!readonly && !disabled"
|
|
83
|
+
icon="i-lucide-plus"
|
|
84
|
+
size="sm"
|
|
85
|
+
variant="soft"
|
|
86
|
+
color="neutral"
|
|
87
|
+
@click="dialogOpen = true"
|
|
88
|
+
>
|
|
89
|
+
{{
|
|
90
|
+
current.length > 0 ? t("mapo.mediaM2MField.addMedia") : t("mapo.mediaField.selectMedia")
|
|
91
|
+
}}
|
|
92
|
+
</UButton>
|
|
93
|
+
|
|
94
|
+
<!-- Dialog -->
|
|
95
|
+
<component
|
|
96
|
+
:is="MediaDialog"
|
|
97
|
+
v-model="dialogOpen"
|
|
98
|
+
selection-mode="multi"
|
|
99
|
+
:mime="mime"
|
|
100
|
+
:selected="current"
|
|
101
|
+
@confirm="onConfirm"
|
|
102
|
+
/>
|
|
103
|
+
</template>
|
|
104
|
+
|
|
105
|
+
<!-- Errors -->
|
|
106
|
+
<p v-for="err in errors" :key="err" class="text-xs text-error" role="alert">
|
|
107
|
+
{{ err }}
|
|
108
|
+
</p>
|
|
109
|
+
</div>
|
|
110
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MediaM2mDescriptor, MediaItem } from "../../types/index.js";
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
modelValue: MediaItem[] | number[] | null | undefined;
|
|
4
|
+
descriptor: MediaM2mDescriptor;
|
|
5
|
+
errors?: string[];
|
|
6
|
+
readonly?: boolean;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
};
|
|
9
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
10
|
+
"update:modelValue": (value: MediaItem[]) => any;
|
|
11
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
|
+
"onUpdate:modelValue"?: ((value: MediaItem[]) => any) | undefined;
|
|
13
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
14
|
+
declare const _default: typeof __VLS_export;
|
|
15
|
+
export default _default;
|
|
16
|
+
//# sourceMappingURL=MapoMediaM2mField.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MapoMediaM2mField.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoMediaM2mField.vue"],"names":[],"mappings":"AAuIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAG1E,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC;IACtD,UAAU,EAAE,kBAAkB,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AA+QF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<div
|
|
7
|
+
class="flex items-center gap-2 rounded-lg border border-dashed border-default px-3 py-2 text-xs text-muted"
|
|
8
|
+
>
|
|
9
|
+
<UIcon
|
|
10
|
+
name="i-lucide-triangle-alert"
|
|
11
|
+
class="size-4 shrink-0 text-warning"
|
|
12
|
+
/>
|
|
13
|
+
<span>Media fields require <code>@mapomodule/uikit</code>.</span>
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
4
|
+
//# sourceMappingURL=MapoMediaUnavailable.vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MapoMediaUnavailable.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoMediaUnavailable.vue"],"names":[],"mappings":"AAuEA,QAAA,MAAM,YAAY,+QAChB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, computed, watchEffect } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
3
4
|
import { VueDraggable } from "vue-draggable-plus";
|
|
4
5
|
import { useConfirmStore } from "@mapomodule/store/runtime/stores/confirm";
|
|
5
6
|
import { injectMapoForm } from "../../composables/useMapoForm.js";
|
|
@@ -13,6 +14,7 @@ const props = defineProps({
|
|
|
13
14
|
});
|
|
14
15
|
const emit = defineEmits(["update:modelValue"]);
|
|
15
16
|
const form = injectMapoForm();
|
|
17
|
+
const { t } = useI18n();
|
|
16
18
|
const attrs = computed(() => props.descriptor.attrs ?? {});
|
|
17
19
|
const items = computed({
|
|
18
20
|
get: () => Array.isArray(props.modelValue) ? props.modelValue : [],
|
|
@@ -69,7 +71,8 @@ function remainingSlots() {
|
|
|
69
71
|
function minBound() {
|
|
70
72
|
return attrs.value.minItems ?? 0;
|
|
71
73
|
}
|
|
72
|
-
async function askConfirm(message, title
|
|
74
|
+
async function askConfirm(message, title) {
|
|
75
|
+
title ??= t("mapo.confirm");
|
|
73
76
|
try {
|
|
74
77
|
return await useConfirmStore().ask({ title, message });
|
|
75
78
|
} catch {
|
|
@@ -86,8 +89,8 @@ async function deleteItem(index) {
|
|
|
86
89
|
if (!canDelete.value) return;
|
|
87
90
|
if (attrs.value.confirmDelete !== false) {
|
|
88
91
|
const confirmed = await askConfirm(
|
|
89
|
-
"
|
|
90
|
-
"
|
|
92
|
+
t("mapo.confirmDelete"),
|
|
93
|
+
t("mapo.repeater.deleteItem")
|
|
91
94
|
);
|
|
92
95
|
if (!confirmed) return;
|
|
93
96
|
}
|
|
@@ -198,14 +201,14 @@ async function bulkDelete() {
|
|
|
198
201
|
if (count === 0) return;
|
|
199
202
|
if (items.value.length - count < minBound()) {
|
|
200
203
|
await askConfirm(
|
|
201
|
-
|
|
202
|
-
"
|
|
204
|
+
t("mapo.repeater.minItemsBlock", { min: minBound() }),
|
|
205
|
+
t("mapo.repeater.actionBlocked")
|
|
203
206
|
);
|
|
204
207
|
return;
|
|
205
208
|
}
|
|
206
209
|
const confirmed = await askConfirm(
|
|
207
|
-
|
|
208
|
-
"
|
|
210
|
+
t("mapo.repeater.confirmBulkDelete", { count }),
|
|
211
|
+
t("mapo.repeater.deleteItems")
|
|
209
212
|
);
|
|
210
213
|
if (!confirmed) return;
|
|
211
214
|
pushUndo();
|
|
@@ -250,7 +253,7 @@ const errorPrefix = (index) => `${props.descriptor.key}.${index}`;
|
|
|
250
253
|
class="flex flex-wrap items-center gap-2 rounded border border-gray-200 bg-gray-50 px-3 py-2"
|
|
251
254
|
>
|
|
252
255
|
<span class="text-sm font-medium text-gray-700">
|
|
253
|
-
{{
|
|
256
|
+
{{ t("mapo.repeater.nItems", { n: items.length }, items.length) }}
|
|
254
257
|
<span v-if="attrs.maxItems" class="text-gray-400"
|
|
255
258
|
>/ {{ attrs.maxItems }}</span
|
|
256
259
|
>
|
|
@@ -258,10 +261,10 @@ const errorPrefix = (index) => `${props.descriptor.key}.${index}`;
|
|
|
258
261
|
|
|
259
262
|
<div class="flex flex-1 flex-wrap items-center justify-end gap-2">
|
|
260
263
|
<UButton size="xs" variant="ghost" color="neutral" @click="collapseAll">
|
|
261
|
-
|
|
264
|
+
{{ t("mapo.repeater.collapseAll") }}
|
|
262
265
|
</UButton>
|
|
263
266
|
<UButton size="xs" variant="ghost" color="neutral" @click="expandAll">
|
|
264
|
-
|
|
267
|
+
{{ t("mapo.repeater.expandAll") }}
|
|
265
268
|
</UButton>
|
|
266
269
|
<UButton
|
|
267
270
|
v-if="undoStack.length"
|
|
@@ -269,7 +272,7 @@ const errorPrefix = (index) => `${props.descriptor.key}.${index}`;
|
|
|
269
272
|
variant="ghost"
|
|
270
273
|
color="neutral"
|
|
271
274
|
icon="i-lucide-undo-2"
|
|
272
|
-
title="
|
|
275
|
+
:title="t('mapo.repeater.undoLastAction')"
|
|
273
276
|
@click="undo"
|
|
274
277
|
/>
|
|
275
278
|
|
|
@@ -279,8 +282,10 @@ const errorPrefix = (index) => `${props.descriptor.key}.${index}`;
|
|
|
279
282
|
:variant="selectionMode ? 'solid' : 'ghost'"
|
|
280
283
|
color="neutral"
|
|
281
284
|
icon="i-lucide-check-square"
|
|
282
|
-
title="
|
|
283
|
-
:label="
|
|
285
|
+
:title="t('mapo.repeater.bulkSelection')"
|
|
286
|
+
:label="
|
|
287
|
+
selectionMode ? t('mapo.repeater.exitSelection') : t('mapo.select')
|
|
288
|
+
"
|
|
284
289
|
@click="toggleSelectionMode"
|
|
285
290
|
/>
|
|
286
291
|
|
|
@@ -300,7 +305,7 @@ const errorPrefix = (index) => `${props.descriptor.key}.${index}`;
|
|
|
300
305
|
:disabled="!canAdd"
|
|
301
306
|
@click="addItem"
|
|
302
307
|
>
|
|
303
|
-
|
|
308
|
+
{{ t("mapo.add") }}
|
|
304
309
|
</UButton>
|
|
305
310
|
</div>
|
|
306
311
|
</div>
|
|
@@ -312,11 +317,11 @@ const errorPrefix = (index) => `${props.descriptor.key}.${index}`;
|
|
|
312
317
|
class="flex items-center gap-2 rounded border border-primary-200 bg-primary-50 px-3 py-2"
|
|
313
318
|
>
|
|
314
319
|
<span class="text-sm font-medium text-primary-700">
|
|
315
|
-
{{ selectionCount }}
|
|
320
|
+
{{ t("mapo.repeater.nSelected", { n: selectionCount }) }}
|
|
316
321
|
</span>
|
|
317
322
|
<div class="flex flex-1 flex-wrap items-center gap-2">
|
|
318
323
|
<UButton size="xs" variant="ghost" color="neutral" @click="selectAll">
|
|
319
|
-
|
|
324
|
+
{{ t("mapo.repeater.all") }}
|
|
320
325
|
</UButton>
|
|
321
326
|
<UButton
|
|
322
327
|
size="xs"
|
|
@@ -324,7 +329,7 @@ const errorPrefix = (index) => `${props.descriptor.key}.${index}`;
|
|
|
324
329
|
color="neutral"
|
|
325
330
|
@click="clearSelection"
|
|
326
331
|
>
|
|
327
|
-
|
|
332
|
+
{{ t("mapo.clear") }}
|
|
328
333
|
</UButton>
|
|
329
334
|
<UButton
|
|
330
335
|
size="xs"
|
|
@@ -333,11 +338,16 @@ const errorPrefix = (index) => `${props.descriptor.key}.${index}`;
|
|
|
333
338
|
icon="i-lucide-copy"
|
|
334
339
|
:disabled="remainingSlots() <= 0"
|
|
335
340
|
:title="
|
|
336
|
-
remainingSlots() < selectionCount ?
|
|
341
|
+
remainingSlots() < selectionCount ? t('mapo.repeater.duplicateLimited', {
|
|
342
|
+
slots: remainingSlots(),
|
|
343
|
+
count: selectionCount
|
|
344
|
+
}) : t('mapo.repeater.duplicateSelected')
|
|
337
345
|
"
|
|
338
346
|
@click="bulkDuplicate"
|
|
339
347
|
>
|
|
340
|
-
|
|
348
|
+
{{ t("mapo.duplicate") }} ({{
|
|
349
|
+
Math.min(selectionCount, remainingSlots())
|
|
350
|
+
}})
|
|
341
351
|
</UButton>
|
|
342
352
|
<UButton
|
|
343
353
|
size="xs"
|
|
@@ -346,7 +356,7 @@ const errorPrefix = (index) => `${props.descriptor.key}.${index}`;
|
|
|
346
356
|
icon="i-lucide-arrow-up"
|
|
347
357
|
@click="bulkMoveToTop"
|
|
348
358
|
>
|
|
349
|
-
|
|
359
|
+
{{ t("mapo.repeater.moveToTop") }}
|
|
350
360
|
</UButton>
|
|
351
361
|
<UButton
|
|
352
362
|
size="xs"
|
|
@@ -356,7 +366,7 @@ const errorPrefix = (index) => `${props.descriptor.key}.${index}`;
|
|
|
356
366
|
:disabled="!canDelete"
|
|
357
367
|
@click="bulkDelete"
|
|
358
368
|
>
|
|
359
|
-
|
|
369
|
+
{{ t("mapo.delete") }} ({{ selectionCount }})
|
|
360
370
|
</UButton>
|
|
361
371
|
</div>
|
|
362
372
|
</div>
|
|
@@ -439,7 +449,7 @@ const errorPrefix = (index) => `${props.descriptor.key}.${index}`;
|
|
|
439
449
|
v-if="items.length === 0"
|
|
440
450
|
class="rounded border border-dashed border-gray-300 py-8 text-center text-sm text-gray-400"
|
|
441
451
|
>
|
|
442
|
-
|
|
452
|
+
{{ t("mapo.repeater.empty") }}
|
|
443
453
|
</div>
|
|
444
454
|
</div>
|
|
445
455
|
</template>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MapoRepeater.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoRepeater.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MapoRepeater.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoRepeater.vue"],"names":[],"mappings":"AAujBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAI/D,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,kBAAkB,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAgjCF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, computed, watch, watchEffect } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
3
4
|
import { useMapoForm, injectMapoForm } from "../../composables/useMapoForm.js";
|
|
4
5
|
import { useFocusMode } from "../../composables/useFocusMode.js";
|
|
5
6
|
import MapoFormField from "../MapoFormField.vue";
|
|
7
|
+
const { t } = useI18n();
|
|
6
8
|
const props = defineProps({
|
|
7
9
|
uid: { type: String, required: false },
|
|
8
10
|
item: { type: Object, required: true },
|
|
@@ -105,7 +107,10 @@ function enterFocusMode() {
|
|
|
105
107
|
registry: props.registry,
|
|
106
108
|
languages: props.languages,
|
|
107
109
|
currentLang: props.currentLang,
|
|
108
|
-
breadcrumb: [
|
|
110
|
+
breadcrumb: [
|
|
111
|
+
props.repeaterDescriptor?.label ?? t("mapo.repeater.repeater"),
|
|
112
|
+
label.value
|
|
113
|
+
]
|
|
109
114
|
});
|
|
110
115
|
}
|
|
111
116
|
</script>
|
|
@@ -178,7 +183,7 @@ function enterFocusMode() {
|
|
|
178
183
|
color="error"
|
|
179
184
|
icon="i-lucide-trash-2"
|
|
180
185
|
:disabled="readonly"
|
|
181
|
-
title="
|
|
186
|
+
:title="t('mapo.delete')"
|
|
182
187
|
@click.stop="$emit('delete')"
|
|
183
188
|
/>
|
|
184
189
|
<UIcon name="i-lucide-chevron-down" class="h-4 w-4 text-gray-400" />
|
|
@@ -239,7 +244,7 @@ function enterFocusMode() {
|
|
|
239
244
|
:max="totalItems"
|
|
240
245
|
size="xs"
|
|
241
246
|
class="w-14 text-center"
|
|
242
|
-
title="
|
|
247
|
+
:title="t('mapo.repeater.position')"
|
|
243
248
|
@change="
|
|
244
249
|
$emit(
|
|
245
250
|
'move-to',
|
|
@@ -261,7 +266,7 @@ function enterFocusMode() {
|
|
|
261
266
|
variant="ghost"
|
|
262
267
|
color="neutral"
|
|
263
268
|
icon="i-lucide-maximize-2"
|
|
264
|
-
title="
|
|
269
|
+
:title="t('mapo.repeater.editFullScreen')"
|
|
265
270
|
@click="enterFocusMode"
|
|
266
271
|
/>
|
|
267
272
|
|
|
@@ -271,7 +276,7 @@ function enterFocusMode() {
|
|
|
271
276
|
color="neutral"
|
|
272
277
|
icon="i-lucide-chevron-up"
|
|
273
278
|
:disabled="readonly"
|
|
274
|
-
title="
|
|
279
|
+
:title="t('mapo.repeater.moveUp')"
|
|
275
280
|
@click="$emit('move-up')"
|
|
276
281
|
/>
|
|
277
282
|
<UButton
|
|
@@ -280,7 +285,7 @@ function enterFocusMode() {
|
|
|
280
285
|
color="neutral"
|
|
281
286
|
icon="i-lucide-chevron-down"
|
|
282
287
|
:disabled="readonly"
|
|
283
|
-
title="
|
|
288
|
+
:title="t('mapo.repeater.moveDown')"
|
|
284
289
|
@click="$emit('move-down')"
|
|
285
290
|
/>
|
|
286
291
|
|
|
@@ -291,7 +296,7 @@ function enterFocusMode() {
|
|
|
291
296
|
color="neutral"
|
|
292
297
|
icon="i-lucide-copy"
|
|
293
298
|
:disabled="readonly"
|
|
294
|
-
title="
|
|
299
|
+
:title="t('mapo.duplicate')"
|
|
295
300
|
@click="$emit('duplicate')"
|
|
296
301
|
/>
|
|
297
302
|
|
|
@@ -301,7 +306,7 @@ function enterFocusMode() {
|
|
|
301
306
|
color="error"
|
|
302
307
|
icon="i-lucide-trash-2"
|
|
303
308
|
:disabled="readonly"
|
|
304
|
-
title="
|
|
309
|
+
:title="t('mapo.delete')"
|
|
305
310
|
@click="$emit('delete')"
|
|
306
311
|
/>
|
|
307
312
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MapoRepeaterItem.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoRepeaterItem.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MapoRepeaterItem.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoRepeaterItem.vue"],"names":[],"mappings":"AAoaA,OAAO,KAAK,EACV,kBAAkB,EAClB,aAAa,EACb,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAO9B,KAAK,WAAW,GAAG;IACjB,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACvC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,aAAa,CAAC;IACxB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACxD,eAAe,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,wEAAwE;IACxE,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mEAAmE;IACnE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAszBF,QAAA,IAAI,QAAQ,IAAY,CAAE;AAC1B,KAAK,WAAW,GAAG,EAAE,GACnB;IAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,QAAQ,KAAK,GAAG,CAAA;CAAE,CAAC;AAKhD,QAAA,MAAM,UAAU;;;;;;;;;;;;;;;;kFAGd,CAAC;AACH,QAAA,MAAM,YAAY,EAAS,eAAe,CAAC,OAAO,UAAU,EAAE,WAAW,CAAC,CAAC;wBACtD,OAAO,YAAY;AAAxC,wBAAyC;AACzC,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
3
4
|
import { useRequestURL } from "#app";
|
|
5
|
+
const { t } = useI18n();
|
|
4
6
|
const props = defineProps({
|
|
5
7
|
modelValue: { type: null, required: true },
|
|
6
8
|
descriptor: { type: Object, required: true },
|
|
@@ -40,7 +42,9 @@ const displayUrl = computed(() => value.value.permalink || requestUrl.host);
|
|
|
40
42
|
<div class="space-y-3">
|
|
41
43
|
<div>
|
|
42
44
|
<div class="mb-1 flex items-center justify-between">
|
|
43
|
-
<label class="text-sm font-medium text-gray-700">
|
|
45
|
+
<label class="text-sm font-medium text-gray-700">{{
|
|
46
|
+
t("mapo.url")
|
|
47
|
+
}}</label>
|
|
44
48
|
</div>
|
|
45
49
|
<UInput
|
|
46
50
|
:model-value="value.permalink"
|
|
@@ -54,14 +58,16 @@ const displayUrl = computed(() => value.value.permalink || requestUrl.host);
|
|
|
54
58
|
|
|
55
59
|
<div>
|
|
56
60
|
<div class="mb-1 flex items-center justify-between">
|
|
57
|
-
<label class="text-sm font-medium text-gray-700">
|
|
61
|
+
<label class="text-sm font-medium text-gray-700">{{
|
|
62
|
+
t("mapo.seoPreview.seoTitle")
|
|
63
|
+
}}</label>
|
|
58
64
|
<span class="text-xs" :class="titleColor">{{ titleLen }} / 60</span>
|
|
59
65
|
</div>
|
|
60
66
|
<UInput
|
|
61
67
|
:model-value="value.title"
|
|
62
68
|
:readonly="readonly"
|
|
63
69
|
:disabled="disabled"
|
|
64
|
-
placeholder="
|
|
70
|
+
:placeholder="t('mapo.seoPreview.titlePlaceholder')"
|
|
65
71
|
class="w-full"
|
|
66
72
|
@update:model-value="update('title', $event)"
|
|
67
73
|
/>
|
|
@@ -69,9 +75,9 @@ const displayUrl = computed(() => value.value.permalink || requestUrl.host);
|
|
|
69
75
|
|
|
70
76
|
<div>
|
|
71
77
|
<div class="mb-1 flex items-center justify-between">
|
|
72
|
-
<label class="text-sm font-medium text-gray-700"
|
|
73
|
-
|
|
74
|
-
>
|
|
78
|
+
<label class="text-sm font-medium text-gray-700">{{
|
|
79
|
+
t("mapo.seoPreview.metaDescription")
|
|
80
|
+
}}</label>
|
|
75
81
|
<span class="text-xs" :class="descColor">{{ descLen }} / 155</span>
|
|
76
82
|
</div>
|
|
77
83
|
<UTextarea
|
|
@@ -79,7 +85,7 @@ const displayUrl = computed(() => value.value.permalink || requestUrl.host);
|
|
|
79
85
|
:rows="3"
|
|
80
86
|
:readonly="readonly"
|
|
81
87
|
:disabled="disabled"
|
|
82
|
-
placeholder="
|
|
88
|
+
:placeholder="t('mapo.seoPreview.descriptionPlaceholder')"
|
|
83
89
|
class="w-full"
|
|
84
90
|
@update:model-value="update('description', $event)"
|
|
85
91
|
/>
|
|
@@ -89,19 +95,17 @@ const displayUrl = computed(() => value.value.permalink || requestUrl.host);
|
|
|
89
95
|
<!-- SERP preview -->
|
|
90
96
|
<div class="rounded border border-gray-200 bg-gray-50 p-4">
|
|
91
97
|
<p class="mb-1 text-xs font-medium uppercase tracking-wide text-gray-400">
|
|
92
|
-
|
|
98
|
+
{{ t("mapo.seoPreview.googlePreview") }}
|
|
93
99
|
</p>
|
|
94
100
|
<div class="max-w-lg">
|
|
95
101
|
<p class="truncate text-sm text-green-700">
|
|
96
102
|
{{ displayUrl }}
|
|
97
103
|
</p>
|
|
98
104
|
<p class="truncate text-lg text-blue-700 underline">
|
|
99
|
-
{{ value.title || "
|
|
105
|
+
{{ value.title || t("mapo.seoPreview.defaultTitle") }}
|
|
100
106
|
</p>
|
|
101
107
|
<p class="mt-1 line-clamp-2 text-sm text-gray-600">
|
|
102
|
-
{{
|
|
103
|
-
value.description || "Page description that will appear in Google search results."
|
|
104
|
-
}}
|
|
108
|
+
{{ value.description || t("mapo.seoPreview.defaultDescription") }}
|
|
105
109
|
</p>
|
|
106
110
|
</div>
|
|
107
111
|
</div>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MapoSeoPreview.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoSeoPreview.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MapoSeoPreview.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoSeoPreview.vue"],"names":[],"mappings":"AAmJA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,UAAU,QAAQ;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,aAAa,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAySF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { watch, onBeforeUnmount } from "vue";
|
|
2
|
+
import { ref, watch, onBeforeUnmount, resolveComponent } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
3
4
|
import { useEditor, EditorContent } from "@tiptap/vue-3";
|
|
4
5
|
import StarterKit from "@tiptap/starter-kit";
|
|
5
6
|
import Link from "@tiptap/extension-link";
|
|
6
7
|
import Underline from "@tiptap/extension-underline";
|
|
8
|
+
import Image from "@tiptap/extension-image";
|
|
7
9
|
const props = defineProps({
|
|
8
10
|
modelValue: { type: null, required: true },
|
|
9
11
|
descriptor: { type: Object, required: true },
|
|
@@ -12,6 +14,7 @@ const props = defineProps({
|
|
|
12
14
|
disabled: { type: Boolean, required: false }
|
|
13
15
|
});
|
|
14
16
|
const emit = defineEmits(["update:modelValue"]);
|
|
17
|
+
const { t } = useI18n();
|
|
15
18
|
const extraExtensions = props.descriptor.attrs?.extensions ?? [];
|
|
16
19
|
const ALLOWED_LINK_PROTOCOLS = ["http", "https", "mailto", "tel"];
|
|
17
20
|
function isSafeUrl(url) {
|
|
@@ -61,6 +64,7 @@ const editor = useEditor({
|
|
|
61
64
|
validate: (href) => isSafeUrl(href)
|
|
62
65
|
}),
|
|
63
66
|
Underline,
|
|
67
|
+
Image.configure({ inline: false, allowBase64: false }),
|
|
64
68
|
...extraExtensions
|
|
65
69
|
],
|
|
66
70
|
onUpdate: ({ editor: e }) => {
|
|
@@ -83,6 +87,14 @@ watch(
|
|
|
83
87
|
}
|
|
84
88
|
);
|
|
85
89
|
onBeforeUnmount(() => editor.value?.destroy());
|
|
90
|
+
const imagePickerOpen = ref(false);
|
|
91
|
+
const MediaManagerDialog = resolveComponent("MapoMediaManagerDialog");
|
|
92
|
+
const hasMediaManager = typeof MediaManagerDialog !== "string";
|
|
93
|
+
function onImageConfirm(selection) {
|
|
94
|
+
const media = Array.isArray(selection) ? selection[0] : selection;
|
|
95
|
+
if (!media || !editor.value) return;
|
|
96
|
+
editor.value.chain().focus().setImage({ src: media.file, alt: media.alt_text ?? "" }).run();
|
|
97
|
+
}
|
|
86
98
|
</script>
|
|
87
99
|
|
|
88
100
|
<template>
|
|
@@ -194,9 +206,11 @@ onBeforeUnmount(() => editor.value?.destroy());
|
|
|
194
206
|
|
|
195
207
|
<USeparator orientation="vertical" class="mx-1 h-5" />
|
|
196
208
|
|
|
197
|
-
<!-- Insert Image
|
|
209
|
+
<!-- Insert Image via Media Manager (available when @mapomodule/uikit is installed) -->
|
|
198
210
|
<UTooltip
|
|
199
|
-
text="
|
|
211
|
+
:text="
|
|
212
|
+
hasMediaManager ? t('mapo.wygEditor.insertImage') : t('mapo.wygEditor.requiresUikit')
|
|
213
|
+
"
|
|
200
214
|
:delay-open="300"
|
|
201
215
|
>
|
|
202
216
|
<UButton
|
|
@@ -204,12 +218,13 @@ onBeforeUnmount(() => editor.value?.destroy());
|
|
|
204
218
|
variant="ghost"
|
|
205
219
|
color="neutral"
|
|
206
220
|
icon="i-lucide-image"
|
|
207
|
-
disabled
|
|
221
|
+
:disabled="readonly || disabled || !hasMediaManager"
|
|
222
|
+
@click="imagePickerOpen = true"
|
|
208
223
|
/>
|
|
209
224
|
</UTooltip>
|
|
210
225
|
</div>
|
|
211
226
|
|
|
212
|
-
<!--
|
|
227
|
+
<!-- Editor content -->
|
|
213
228
|
<EditorContent
|
|
214
229
|
:editor="editor"
|
|
215
230
|
class="prose prose-sm max-w-none px-3 py-2 focus-within:outline-none"
|
|
@@ -224,4 +239,14 @@ onBeforeUnmount(() => editor.value?.destroy());
|
|
|
224
239
|
{{ err }}
|
|
225
240
|
</p>
|
|
226
241
|
</div>
|
|
242
|
+
|
|
243
|
+
<!-- Image picker dialog — only rendered when MapoMediaManagerDialog is globally available -->
|
|
244
|
+
<component
|
|
245
|
+
:is="MediaManagerDialog"
|
|
246
|
+
v-if="hasMediaManager"
|
|
247
|
+
v-model="imagePickerOpen"
|
|
248
|
+
selection-mode="single"
|
|
249
|
+
mime="image/*"
|
|
250
|
+
@confirm="onImageConfirm"
|
|
251
|
+
/>
|
|
227
252
|
</template>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MapoWygEditor.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoWygEditor.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MapoWygEditor.vue.d.ts","sourceRoot":"","sources":["../../../../src/runtime/components/fields/MapoWygEditor.vue"],"names":[],"mappings":"AAqTA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,KAAK,WAAW,GAAG;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AA0pBF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMapoForm.d.ts","sourceRoot":"","sources":["../../../src/runtime/composables/useMapoForm.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,GAAG,EACR,KAAK,QAAQ,EAGd,MAAM,KAAK,CAAC;AAOb,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"useMapoForm.d.ts","sourceRoot":"","sources":["../../../src/runtime/composables/useMapoForm.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import { resolveFieldAccessor } from "../types/index.js";
|
|
17
17
|
import { useCurrentLang } from "./useCurrentLang.js";
|
|
18
18
|
import { useSnackStore } from "@mapomodule/store/runtime/stores/snack";
|
|
19
|
+
import { useMapoT } from "@mapomodule/i18n/runtime/composables/useMapoT";
|
|
19
20
|
import { useAuthStore } from "@mapomodule/store/runtime/stores/auth";
|
|
20
21
|
function safeClone(v) {
|
|
21
22
|
const raw = toRaw(v);
|
|
@@ -48,6 +49,7 @@ export function useMapoForm(options) {
|
|
|
48
49
|
} = options;
|
|
49
50
|
const currentLang = useCurrentLang(currentLangProp);
|
|
50
51
|
const snack = useSnackStore();
|
|
52
|
+
const t = useMapoT();
|
|
51
53
|
const authStore = useAuthStore();
|
|
52
54
|
const backup = ref(safeClone(model.value));
|
|
53
55
|
const clientErrors = ref({});
|
|
@@ -198,7 +200,7 @@ export function useMapoForm(options) {
|
|
|
198
200
|
function checkRequired(descriptor, val) {
|
|
199
201
|
if (!descriptor.required) return null;
|
|
200
202
|
if (val === null || val === void 0 || val === "")
|
|
201
|
-
return "
|
|
203
|
+
return t("mapo.form.required");
|
|
202
204
|
return null;
|
|
203
205
|
}
|
|
204
206
|
function getClientError(descriptor) {
|
|
@@ -236,7 +238,7 @@ export function useMapoForm(options) {
|
|
|
236
238
|
submitted.value = true;
|
|
237
239
|
const { valid } = validateClient();
|
|
238
240
|
if (!valid) {
|
|
239
|
-
snack.show("
|
|
241
|
+
snack.show(t("mapo.fixErrors"), "error");
|
|
240
242
|
return;
|
|
241
243
|
}
|
|
242
244
|
isLoading.value = true;
|