@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.
Files changed (83) hide show
  1. package/README.md +2 -1
  2. package/dist/index.d.mts +1 -1
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/module.d.ts.map +1 -1
  6. package/dist/module.json +1 -1
  7. package/dist/module.mjs +16 -1
  8. package/dist/runtime/components/MapoFocusPortal.vue +3 -1
  9. package/dist/runtime/components/MapoFocusPortal.vue.d.ts.map +1 -1
  10. package/dist/runtime/components/MapoFormTabs.vue +4 -2
  11. package/dist/runtime/components/MapoFormTabs.vue.d.ts.map +1 -1
  12. package/dist/runtime/components/fields/MapoDateField.vue +3 -1
  13. package/dist/runtime/components/fields/MapoDateField.vue.d.ts.map +1 -1
  14. package/dist/runtime/components/fields/MapoDateTimeField.vue +5 -3
  15. package/dist/runtime/components/fields/MapoDateTimeField.vue.d.ts.map +1 -1
  16. package/dist/runtime/components/fields/MapoEnhancedMediaField.vue +130 -0
  17. package/dist/runtime/components/fields/MapoEnhancedMediaField.vue.d.ts +16 -0
  18. package/dist/runtime/components/fields/MapoEnhancedMediaField.vue.d.ts.map +1 -0
  19. package/dist/runtime/components/fields/MapoFileField.vue +3 -1
  20. package/dist/runtime/components/fields/MapoFileField.vue.d.ts.map +1 -1
  21. package/dist/runtime/components/fields/MapoFksField.vue +6 -2
  22. package/dist/runtime/components/fields/MapoFksField.vue.d.ts.map +1 -1
  23. package/dist/runtime/components/fields/MapoMapField.vue +4 -2
  24. package/dist/runtime/components/fields/MapoMapField.vue.d.ts.map +1 -1
  25. package/dist/runtime/components/fields/MapoMediaField.vue +91 -0
  26. package/dist/runtime/components/fields/MapoMediaField.vue.d.ts +16 -0
  27. package/dist/runtime/components/fields/MapoMediaField.vue.d.ts.map +1 -0
  28. package/dist/runtime/components/fields/MapoMediaM2mField.vue +110 -0
  29. package/dist/runtime/components/fields/MapoMediaM2mField.vue.d.ts +16 -0
  30. package/dist/runtime/components/fields/MapoMediaM2mField.vue.d.ts.map +1 -0
  31. package/dist/runtime/components/fields/MapoMediaUnavailable.vue +15 -0
  32. package/dist/runtime/components/fields/MapoMediaUnavailable.vue.d.ts +4 -0
  33. package/dist/runtime/components/fields/MapoMediaUnavailable.vue.d.ts.map +1 -0
  34. package/dist/runtime/components/fields/MapoRepeater.vue +32 -22
  35. package/dist/runtime/components/fields/MapoRepeater.vue.d.ts.map +1 -1
  36. package/dist/runtime/components/fields/MapoRepeaterItem.vue +13 -8
  37. package/dist/runtime/components/fields/MapoRepeaterItem.vue.d.ts.map +1 -1
  38. package/dist/runtime/components/fields/MapoSeoPreview.vue +16 -12
  39. package/dist/runtime/components/fields/MapoSeoPreview.vue.d.ts.map +1 -1
  40. package/dist/runtime/components/fields/MapoWygEditor.vue +30 -5
  41. package/dist/runtime/components/fields/MapoWygEditor.vue.d.ts.map +1 -1
  42. package/dist/runtime/composables/useMapoForm.d.ts.map +1 -1
  43. package/dist/runtime/composables/useMapoForm.js +4 -2
  44. package/dist/runtime/composables/useMediaManager.d.ts +17 -0
  45. package/dist/runtime/composables/useMediaManager.d.ts.map +1 -0
  46. package/dist/runtime/composables/useMediaManager.js +6 -0
  47. package/dist/runtime/composables/useUnsavedChangesGuard.d.ts.map +1 -1
  48. package/dist/runtime/composables/useUnsavedChangesGuard.js +3 -2
  49. package/dist/runtime/registry/defaults.d.ts.map +1 -1
  50. package/dist/runtime/registry/defaults.js +8 -2
  51. package/dist/runtime/types/descriptor.d.ts +18 -4
  52. package/dist/runtime/types/descriptor.d.ts.map +1 -1
  53. package/dist/runtime/types/index.d.ts +2 -1
  54. package/dist/runtime/types/index.d.ts.map +1 -1
  55. package/dist/runtime/types/media.d.ts +43 -0
  56. package/dist/runtime/types/media.d.ts.map +1 -0
  57. package/dist/runtime/types/media.js +0 -0
  58. package/package.json +9 -6
  59. package/src/__tests__/useMapoForm.test.ts +5 -0
  60. package/src/index.ts +5 -0
  61. package/src/module.ts +24 -0
  62. package/src/runtime/components/MapoFocusPortal.vue +3 -1
  63. package/src/runtime/components/MapoFormTabs.vue +4 -1
  64. package/src/runtime/components/fields/MapoDateField.vue +3 -1
  65. package/src/runtime/components/fields/MapoDateTimeField.vue +3 -1
  66. package/src/runtime/components/fields/MapoEnhancedMediaField.vue +153 -0
  67. package/src/runtime/components/fields/MapoFileField.vue +3 -1
  68. package/src/runtime/components/fields/MapoFksField.vue +7 -2
  69. package/src/runtime/components/fields/MapoMapField.vue +5 -2
  70. package/src/runtime/components/fields/MapoMediaField.vue +104 -0
  71. package/src/runtime/components/fields/MapoMediaM2mField.vue +130 -0
  72. package/src/runtime/components/fields/MapoMediaUnavailable.vue +19 -0
  73. package/src/runtime/components/fields/MapoRepeater.vue +33 -26
  74. package/src/runtime/components/fields/MapoRepeaterItem.vue +14 -8
  75. package/src/runtime/components/fields/MapoSeoPreview.vue +17 -13
  76. package/src/runtime/components/fields/MapoWygEditor.vue +47 -5
  77. package/src/runtime/composables/useMapoForm.ts +4 -2
  78. package/src/runtime/composables/useMediaManager.ts +21 -0
  79. package/src/runtime/composables/useUnsavedChangesGuard.ts +3 -3
  80. package/src/runtime/registry/defaults.ts +9 -1
  81. package/src/runtime/types/descriptor.ts +22 -7
  82. package/src/runtime/types/index.ts +4 -0
  83. 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 = "Confirm") {
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
- "Are you sure you want to delete this item?",
90
- "Delete item"
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
- `Cannot delete: at least ${minBound()} items must remain.`,
202
- "Action blocked"
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
- `Delete ${count} selected items?`,
208
- "Delete items"
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
- {{ items.length }} {{ items.length === 1 ? "item" : "items" }}
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
- Collapse all
264
+ {{ t("mapo.repeater.collapseAll") }}
262
265
  </UButton>
263
266
  <UButton size="xs" variant="ghost" color="neutral" @click="expandAll">
264
- Expand all
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="Undo last action"
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="Bulk selection"
283
- :label="selectionMode ? 'Exit selection' : 'Select'"
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
- Add
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 }} selected
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
- All
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
- Clear
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 ? `Only ${remainingSlots()} of ${selectionCount} can be duplicated (maxItems reached)` : 'Duplicate selected'
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
- Duplicate ({{ Math.min(selectionCount, remainingSlots()) }})
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
- Move to top
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
- Delete ({{ selectionCount }})
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
- No items. Click "Add" to start.
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":"AA+iBA,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;AA4hCF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
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: [props.repeaterDescriptor?.label ?? "Repeater", label.value]
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="Delete"
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="Position"
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="Edit full screen"
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="Move up"
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="Move down"
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="Duplicate"
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="Delete"
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":"AA6ZA,OAAO,KAAK,EACV,kBAAkB,EAClB,aAAa,EACb,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAK9B,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;AAkzBF,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
+ {"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">URL</label>
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">SEO title</label>
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="Page title..."
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
- >Meta description</label
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="Page description..."
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
- Google preview
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 || "Page 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":"AA8IA,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;AAED,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;AAiSF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
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 — enabled in Phase 6 when Media Manager is available -->
209
+ <!-- Insert Image via Media Manager (available when @mapomodule/uikit is installed) -->
198
210
  <UTooltip
199
- text="Insert image (requires Media Manager — Phase 6)"
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
- <!-- Contenuto editor -->
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":"AAyQA,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;AAilBF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
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":"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"}
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 "This field is required.";
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("Please fix the errors before saving.", "error");
241
+ snack.show(t("mapo.fixErrors"), "error");
240
242
  return;
241
243
  }
242
244
  isLoading.value = true;