@mapomodule/form 2.0.0-beta.1 → 2.0.0-beta.11
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 +37 -118
- package/dist/__tests__/__mocks__/nuxt-imports.d.ts +4 -0
- package/dist/__tests__/__mocks__/nuxt-imports.d.ts.map +1 -0
- package/dist/__tests__/camomillaRoundTrip.test.d.ts +2 -0
- package/dist/__tests__/camomillaRoundTrip.test.d.ts.map +1 -0
- package/dist/__tests__/registry.test.d.ts +2 -0
- package/dist/__tests__/registry.test.d.ts.map +1 -0
- package/dist/__tests__/tabVisibility.test.d.ts +2 -0
- package/dist/__tests__/tabVisibility.test.d.ts.map +1 -0
- package/dist/__tests__/useFormFromSchema.test.d.ts +2 -0
- package/dist/__tests__/useFormFromSchema.test.d.ts.map +1 -0
- package/dist/__tests__/useMapoForm.test.d.ts +2 -0
- package/dist/__tests__/useMapoForm.test.d.ts.map +1 -0
- package/dist/index.d.mts +9 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +7 -34
- package/dist/module.d.mts +20 -2
- package/dist/module.d.ts +12 -2
- package/dist/module.d.ts.map +1 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +187 -13
- package/dist/runtime/components/MapoFocusPortal.vue +115 -0
- package/dist/runtime/components/MapoFocusPortal.vue.d.ts +4 -0
- package/dist/runtime/components/MapoFocusPortal.vue.d.ts.map +1 -0
- package/dist/runtime/components/MapoForm.vue +177 -0
- package/dist/runtime/components/MapoForm.vue.d.ts +47 -0
- package/dist/runtime/components/MapoForm.vue.d.ts.map +1 -0
- package/dist/runtime/components/MapoFormField.vue +269 -0
- package/dist/runtime/components/MapoFormField.vue.d.ts +46 -0
- package/dist/runtime/components/MapoFormField.vue.d.ts.map +1 -0
- package/dist/runtime/components/MapoFormFlatSection.vue +149 -0
- package/dist/runtime/components/MapoFormFlatSection.vue.d.ts +15 -0
- package/dist/runtime/components/MapoFormFlatSection.vue.d.ts.map +1 -0
- package/dist/runtime/components/MapoFormGroup.vue +247 -0
- package/dist/runtime/components/MapoFormGroup.vue.d.ts +44 -0
- package/dist/runtime/components/MapoFormGroup.vue.d.ts.map +1 -0
- package/dist/runtime/components/MapoFormTabs.vue +122 -0
- package/dist/runtime/components/MapoFormTabs.vue.d.ts +15 -0
- package/dist/runtime/components/MapoFormTabs.vue.d.ts.map +1 -0
- package/dist/runtime/components/MapoUnknownField.vue +62 -0
- package/dist/runtime/components/MapoUnknownField.vue.d.ts +16 -0
- package/dist/runtime/components/MapoUnknownField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoDateField.vue +88 -0
- package/dist/runtime/components/fields/MapoDateField.vue.d.ts +17 -0
- package/dist/runtime/components/fields/MapoDateField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoDateTimeField.vue +159 -0
- package/dist/runtime/components/fields/MapoDateTimeField.vue.d.ts +17 -0
- package/dist/runtime/components/fields/MapoDateTimeField.vue.d.ts.map +1 -0
- 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 +99 -0
- package/dist/runtime/components/fields/MapoFileField.vue.d.ts +16 -0
- package/dist/runtime/components/fields/MapoFileField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoFksField.vue +122 -0
- package/dist/runtime/components/fields/MapoFksField.vue.d.ts +16 -0
- package/dist/runtime/components/fields/MapoFksField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoMapField.vue +99 -0
- package/dist/runtime/components/fields/MapoMapField.vue.d.ts +22 -0
- package/dist/runtime/components/fields/MapoMapField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoMapFieldClient.vue +67 -0
- package/dist/runtime/components/fields/MapoMapFieldClient.vue.d.ts +20 -0
- package/dist/runtime/components/fields/MapoMapFieldClient.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoMediaField.vue +93 -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 +111 -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 +459 -0
- package/dist/runtime/components/fields/MapoRepeater.vue.d.ts +16 -0
- package/dist/runtime/components/fields/MapoRepeater.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoRepeaterItem.vue +346 -0
- package/dist/runtime/components/fields/MapoRepeaterItem.vue.d.ts +55 -0
- package/dist/runtime/components/fields/MapoRepeaterItem.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoSeoPreview.vue +122 -0
- package/dist/runtime/components/fields/MapoSeoPreview.vue.d.ts +22 -0
- package/dist/runtime/components/fields/MapoSeoPreview.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoTimeField.vue +44 -0
- package/dist/runtime/components/fields/MapoTimeField.vue.d.ts +17 -0
- package/dist/runtime/components/fields/MapoTimeField.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/MapoWygEditor.vue +258 -0
- package/dist/runtime/components/fields/MapoWygEditor.vue.d.ts +16 -0
- package/dist/runtime/components/fields/MapoWygEditor.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/NuiCheckbox.vue +9 -0
- package/dist/runtime/components/fields/nui/NuiCheckbox.vue.d.ts +4 -0
- package/dist/runtime/components/fields/nui/NuiCheckbox.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/NuiInput.vue +9 -0
- package/dist/runtime/components/fields/nui/NuiInput.vue.d.ts +4 -0
- package/dist/runtime/components/fields/nui/NuiInput.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/NuiSelectMenu.vue +18 -0
- package/dist/runtime/components/fields/nui/NuiSelectMenu.vue.d.ts +4 -0
- package/dist/runtime/components/fields/nui/NuiSelectMenu.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/NuiSlider.vue +9 -0
- package/dist/runtime/components/fields/nui/NuiSlider.vue.d.ts +4 -0
- package/dist/runtime/components/fields/nui/NuiSlider.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/NuiSwitch.vue +9 -0
- package/dist/runtime/components/fields/nui/NuiSwitch.vue.d.ts +4 -0
- package/dist/runtime/components/fields/nui/NuiSwitch.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/NuiTextarea.vue +9 -0
- package/dist/runtime/components/fields/nui/NuiTextarea.vue.d.ts +4 -0
- package/dist/runtime/components/fields/nui/NuiTextarea.vue.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/useNuiFieldAttrs.d.ts +13 -0
- package/dist/runtime/components/fields/nui/useNuiFieldAttrs.d.ts.map +1 -0
- package/dist/runtime/components/fields/nui/useNuiFieldAttrs.js +19 -0
- package/dist/runtime/composables/defineFormField.d.ts +25 -0
- package/dist/runtime/composables/defineFormField.d.ts.map +1 -0
- package/dist/runtime/composables/defineFormField.js +27 -0
- package/dist/runtime/composables/useCurrentLang.d.ts +6 -0
- package/dist/runtime/composables/useCurrentLang.d.ts.map +1 -0
- package/dist/runtime/composables/useCurrentLang.js +9 -0
- package/dist/runtime/composables/useFocusMode.d.ts +40 -0
- package/dist/runtime/composables/useFocusMode.d.ts.map +1 -0
- package/dist/runtime/composables/useFocusMode.js +31 -0
- package/dist/runtime/composables/useFormDraft.d.ts +60 -0
- package/dist/runtime/composables/useFormDraft.d.ts.map +1 -0
- package/dist/runtime/composables/useFormDraft.js +52 -0
- package/dist/runtime/composables/useFormFromSchema.d.ts +21 -0
- package/dist/runtime/composables/useFormFromSchema.d.ts.map +1 -0
- package/dist/runtime/composables/useFormFromSchema.js +145 -0
- package/dist/runtime/composables/useMapoForm.d.ts +96 -0
- package/dist/runtime/composables/useMapoForm.d.ts.map +1 -0
- package/dist/runtime/composables/useMapoForm.js +325 -0
- 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/useProgressiveDisclosure.d.ts +44 -0
- package/dist/runtime/composables/useProgressiveDisclosure.d.ts.map +1 -0
- package/dist/runtime/composables/useProgressiveDisclosure.js +23 -0
- package/dist/runtime/composables/useUnsavedChangesGuard.d.ts +24 -0
- package/dist/runtime/composables/useUnsavedChangesGuard.d.ts.map +1 -0
- package/dist/runtime/composables/useUnsavedChangesGuard.js +28 -0
- package/dist/runtime/devtools/DevtoolsFormsPage.vue +235 -0
- package/dist/runtime/devtools/DevtoolsFormsPage.vue.d.ts +4 -0
- package/dist/runtime/devtools/DevtoolsFormsPage.vue.d.ts.map +1 -0
- package/dist/runtime/plugins/form-registry.d.ts +18 -0
- package/dist/runtime/plugins/form-registry.d.ts.map +1 -0
- package/dist/runtime/plugins/form-registry.js +19 -0
- package/dist/runtime/registry/defaults.d.ts +3 -0
- package/dist/runtime/registry/defaults.d.ts.map +1 -0
- package/dist/runtime/registry/defaults.js +93 -0
- package/dist/runtime/types/descriptor.d.ts +373 -0
- package/dist/runtime/types/descriptor.d.ts.map +1 -0
- package/dist/runtime/types/descriptor.js +26 -0
- package/dist/runtime/types/index.d.ts +12 -0
- package/dist/runtime/types/index.d.ts.map +1 -0
- package/dist/runtime/types/index.js +6 -0
- package/dist/runtime/types/layout.d.ts +21 -0
- package/dist/runtime/types/layout.d.ts.map +1 -0
- package/dist/runtime/types/layout.js +0 -0
- 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/dist/runtime/types/registry.d.ts +62 -0
- package/dist/runtime/types/registry.d.ts.map +1 -0
- package/dist/runtime/types/registry.js +19 -0
- package/dist/runtime/utils/flattenFieldGroups.d.ts +50 -0
- package/dist/runtime/utils/flattenFieldGroups.d.ts.map +1 -0
- package/dist/runtime/utils/flattenFieldGroups.js +24 -0
- package/dist/runtime/utils/tabVisibility.d.ts +18 -0
- package/dist/runtime/utils/tabVisibility.d.ts.map +1 -0
- package/dist/runtime/utils/tabVisibility.js +18 -0
- package/dist/types.d.mts +6 -10
- package/package.json +10 -8
- package/src/__tests__/article.schema.json +193 -0
- package/src/__tests__/camomillaRoundTrip.test.ts +73 -0
- package/src/__tests__/tabVisibility.test.ts +102 -0
- package/src/__tests__/useFormFromSchema.test.ts +24 -0
- package/src/__tests__/useMapoForm.test.ts +5 -0
- package/src/index.ts +5 -0
- package/src/module.ts +34 -0
- package/src/runtime/components/MapoFocusPortal.vue +3 -1
- package/src/runtime/components/MapoForm.vue +17 -4
- package/src/runtime/components/MapoFormTabs.vue +29 -6
- 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 +106 -0
- package/src/runtime/components/fields/MapoMediaM2mField.vue +131 -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 +55 -7
- package/src/runtime/composables/useFormFromSchema.ts +4 -1
- 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
- package/src/runtime/utils/tabVisibility.ts +44 -0
- package/dist/index.d.cts +0 -1
- package/dist/module.d.cts +0 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
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 type { RepeaterDescriptor } from "../../types/index.js";
|
|
@@ -17,6 +18,7 @@ const props = defineProps<{
|
|
|
17
18
|
const emit = defineEmits<{ "update:modelValue": [value: unknown[]] }>();
|
|
18
19
|
|
|
19
20
|
const form = injectMapoForm()!;
|
|
21
|
+
const { t } = useI18n();
|
|
20
22
|
|
|
21
23
|
const attrs = computed(() => props.descriptor.attrs ?? {});
|
|
22
24
|
|
|
@@ -108,10 +110,8 @@ function minBound(): number {
|
|
|
108
110
|
return attrs.value.minItems ?? 0;
|
|
109
111
|
}
|
|
110
112
|
|
|
111
|
-
async function askConfirm(
|
|
112
|
-
|
|
113
|
-
title = "Confirm",
|
|
114
|
-
): Promise<boolean> {
|
|
113
|
+
async function askConfirm(message: string, title?: string): Promise<boolean> {
|
|
114
|
+
title ??= t("mapo.confirm");
|
|
115
115
|
try {
|
|
116
116
|
// Falls back to native confirm when the Pinia store is not registered.
|
|
117
117
|
return await useConfirmStore().ask({ title, message });
|
|
@@ -135,8 +135,8 @@ async function deleteItem(index: number) {
|
|
|
135
135
|
if (!canDelete.value) return;
|
|
136
136
|
if (attrs.value.confirmDelete !== false) {
|
|
137
137
|
const confirmed = await askConfirm(
|
|
138
|
-
"
|
|
139
|
-
"
|
|
138
|
+
t("mapo.confirmDelete"),
|
|
139
|
+
t("mapo.repeater.deleteItem"),
|
|
140
140
|
);
|
|
141
141
|
if (!confirmed) return;
|
|
142
142
|
}
|
|
@@ -275,14 +275,14 @@ async function bulkDelete() {
|
|
|
275
275
|
// Enforce minItems: would deletion drop below the lower bound?
|
|
276
276
|
if (items.value.length - count < minBound()) {
|
|
277
277
|
await askConfirm(
|
|
278
|
-
|
|
279
|
-
"
|
|
278
|
+
t("mapo.repeater.minItemsBlock", { min: minBound() }),
|
|
279
|
+
t("mapo.repeater.actionBlocked"),
|
|
280
280
|
);
|
|
281
281
|
return;
|
|
282
282
|
}
|
|
283
283
|
const confirmed = await askConfirm(
|
|
284
|
-
|
|
285
|
-
"
|
|
284
|
+
t("mapo.repeater.confirmBulkDelete", { count }),
|
|
285
|
+
t("mapo.repeater.deleteItems"),
|
|
286
286
|
);
|
|
287
287
|
if (!confirmed) return;
|
|
288
288
|
pushUndo();
|
|
@@ -336,7 +336,7 @@ const errorPrefix = (index: number) =>
|
|
|
336
336
|
class="flex flex-wrap items-center gap-2 rounded border border-gray-200 bg-gray-50 px-3 py-2"
|
|
337
337
|
>
|
|
338
338
|
<span class="text-sm font-medium text-gray-700">
|
|
339
|
-
{{
|
|
339
|
+
{{ t("mapo.repeater.nItems", { n: items.length }, items.length) }}
|
|
340
340
|
<span v-if="attrs.maxItems" class="text-gray-400"
|
|
341
341
|
>/ {{ attrs.maxItems }}</span
|
|
342
342
|
>
|
|
@@ -344,10 +344,10 @@ const errorPrefix = (index: number) =>
|
|
|
344
344
|
|
|
345
345
|
<div class="flex flex-1 flex-wrap items-center justify-end gap-2">
|
|
346
346
|
<UButton size="xs" variant="ghost" color="neutral" @click="collapseAll">
|
|
347
|
-
|
|
347
|
+
{{ t("mapo.repeater.collapseAll") }}
|
|
348
348
|
</UButton>
|
|
349
349
|
<UButton size="xs" variant="ghost" color="neutral" @click="expandAll">
|
|
350
|
-
|
|
350
|
+
{{ t("mapo.repeater.expandAll") }}
|
|
351
351
|
</UButton>
|
|
352
352
|
<UButton
|
|
353
353
|
v-if="undoStack.length"
|
|
@@ -355,7 +355,7 @@ const errorPrefix = (index: number) =>
|
|
|
355
355
|
variant="ghost"
|
|
356
356
|
color="neutral"
|
|
357
357
|
icon="i-lucide-undo-2"
|
|
358
|
-
title="
|
|
358
|
+
:title="t('mapo.repeater.undoLastAction')"
|
|
359
359
|
@click="undo"
|
|
360
360
|
/>
|
|
361
361
|
|
|
@@ -365,8 +365,10 @@ const errorPrefix = (index: number) =>
|
|
|
365
365
|
:variant="selectionMode ? 'solid' : 'ghost'"
|
|
366
366
|
color="neutral"
|
|
367
367
|
icon="i-lucide-check-square"
|
|
368
|
-
title="
|
|
369
|
-
:label="
|
|
368
|
+
:title="t('mapo.repeater.bulkSelection')"
|
|
369
|
+
:label="
|
|
370
|
+
selectionMode ? t('mapo.repeater.exitSelection') : t('mapo.select')
|
|
371
|
+
"
|
|
370
372
|
@click="toggleSelectionMode"
|
|
371
373
|
/>
|
|
372
374
|
|
|
@@ -386,7 +388,7 @@ const errorPrefix = (index: number) =>
|
|
|
386
388
|
:disabled="!canAdd"
|
|
387
389
|
@click="addItem"
|
|
388
390
|
>
|
|
389
|
-
|
|
391
|
+
{{ t("mapo.add") }}
|
|
390
392
|
</UButton>
|
|
391
393
|
</div>
|
|
392
394
|
</div>
|
|
@@ -398,11 +400,11 @@ const errorPrefix = (index: number) =>
|
|
|
398
400
|
class="flex items-center gap-2 rounded border border-primary-200 bg-primary-50 px-3 py-2"
|
|
399
401
|
>
|
|
400
402
|
<span class="text-sm font-medium text-primary-700">
|
|
401
|
-
{{ selectionCount }}
|
|
403
|
+
{{ t("mapo.repeater.nSelected", { n: selectionCount }) }}
|
|
402
404
|
</span>
|
|
403
405
|
<div class="flex flex-1 flex-wrap items-center gap-2">
|
|
404
406
|
<UButton size="xs" variant="ghost" color="neutral" @click="selectAll">
|
|
405
|
-
|
|
407
|
+
{{ t("mapo.repeater.all") }}
|
|
406
408
|
</UButton>
|
|
407
409
|
<UButton
|
|
408
410
|
size="xs"
|
|
@@ -410,7 +412,7 @@ const errorPrefix = (index: number) =>
|
|
|
410
412
|
color="neutral"
|
|
411
413
|
@click="clearSelection"
|
|
412
414
|
>
|
|
413
|
-
|
|
415
|
+
{{ t("mapo.clear") }}
|
|
414
416
|
</UButton>
|
|
415
417
|
<UButton
|
|
416
418
|
size="xs"
|
|
@@ -420,12 +422,17 @@ const errorPrefix = (index: number) =>
|
|
|
420
422
|
:disabled="remainingSlots() <= 0"
|
|
421
423
|
:title="
|
|
422
424
|
remainingSlots() < selectionCount
|
|
423
|
-
?
|
|
424
|
-
|
|
425
|
+
? t('mapo.repeater.duplicateLimited', {
|
|
426
|
+
slots: remainingSlots(),
|
|
427
|
+
count: selectionCount,
|
|
428
|
+
})
|
|
429
|
+
: t('mapo.repeater.duplicateSelected')
|
|
425
430
|
"
|
|
426
431
|
@click="bulkDuplicate"
|
|
427
432
|
>
|
|
428
|
-
|
|
433
|
+
{{ t("mapo.duplicate") }} ({{
|
|
434
|
+
Math.min(selectionCount, remainingSlots())
|
|
435
|
+
}})
|
|
429
436
|
</UButton>
|
|
430
437
|
<UButton
|
|
431
438
|
size="xs"
|
|
@@ -434,7 +441,7 @@ const errorPrefix = (index: number) =>
|
|
|
434
441
|
icon="i-lucide-arrow-up"
|
|
435
442
|
@click="bulkMoveToTop"
|
|
436
443
|
>
|
|
437
|
-
|
|
444
|
+
{{ t("mapo.repeater.moveToTop") }}
|
|
438
445
|
</UButton>
|
|
439
446
|
<UButton
|
|
440
447
|
size="xs"
|
|
@@ -444,7 +451,7 @@ const errorPrefix = (index: number) =>
|
|
|
444
451
|
:disabled="!canDelete"
|
|
445
452
|
@click="bulkDelete"
|
|
446
453
|
>
|
|
447
|
-
|
|
454
|
+
{{ t("mapo.delete") }} ({{ selectionCount }})
|
|
448
455
|
</UButton>
|
|
449
456
|
</div>
|
|
450
457
|
</div>
|
|
@@ -527,7 +534,7 @@ const errorPrefix = (index: number) =>
|
|
|
527
534
|
v-if="items.length === 0"
|
|
528
535
|
class="rounded border border-dashed border-gray-300 py-8 text-center text-sm text-gray-400"
|
|
529
536
|
>
|
|
530
|
-
|
|
537
|
+
{{ t("mapo.repeater.empty") }}
|
|
531
538
|
</div>
|
|
532
539
|
</div>
|
|
533
540
|
</template>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref, computed, watch, watchEffect } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
3
4
|
import type {
|
|
4
5
|
AnyFieldDescriptor,
|
|
5
6
|
FieldRegistry,
|
|
@@ -9,6 +10,8 @@ import { useMapoForm, injectMapoForm } from "../../composables/useMapoForm.js";
|
|
|
9
10
|
import { useFocusMode } from "../../composables/useFocusMode.js";
|
|
10
11
|
import MapoFormField from "../MapoFormField.vue";
|
|
11
12
|
|
|
13
|
+
const { t } = useI18n();
|
|
14
|
+
|
|
12
15
|
const props = defineProps<{
|
|
13
16
|
/** Stable identity-based UID assigned by the parent repeater. */
|
|
14
17
|
uid?: string;
|
|
@@ -173,7 +176,10 @@ function enterFocusMode() {
|
|
|
173
176
|
registry: props.registry,
|
|
174
177
|
languages: props.languages,
|
|
175
178
|
currentLang: props.currentLang,
|
|
176
|
-
breadcrumb: [
|
|
179
|
+
breadcrumb: [
|
|
180
|
+
props.repeaterDescriptor?.label ?? t("mapo.repeater.repeater"),
|
|
181
|
+
label.value,
|
|
182
|
+
],
|
|
177
183
|
});
|
|
178
184
|
}
|
|
179
185
|
</script>
|
|
@@ -246,7 +252,7 @@ function enterFocusMode() {
|
|
|
246
252
|
color="error"
|
|
247
253
|
icon="i-lucide-trash-2"
|
|
248
254
|
:disabled="readonly"
|
|
249
|
-
title="
|
|
255
|
+
:title="t('mapo.delete')"
|
|
250
256
|
@click.stop="$emit('delete')"
|
|
251
257
|
/>
|
|
252
258
|
<UIcon name="i-lucide-chevron-down" class="h-4 w-4 text-gray-400" />
|
|
@@ -307,7 +313,7 @@ function enterFocusMode() {
|
|
|
307
313
|
:max="totalItems"
|
|
308
314
|
size="xs"
|
|
309
315
|
class="w-14 text-center"
|
|
310
|
-
title="
|
|
316
|
+
:title="t('mapo.repeater.position')"
|
|
311
317
|
@change="
|
|
312
318
|
$emit(
|
|
313
319
|
'move-to',
|
|
@@ -329,7 +335,7 @@ function enterFocusMode() {
|
|
|
329
335
|
variant="ghost"
|
|
330
336
|
color="neutral"
|
|
331
337
|
icon="i-lucide-maximize-2"
|
|
332
|
-
title="
|
|
338
|
+
:title="t('mapo.repeater.editFullScreen')"
|
|
333
339
|
@click="enterFocusMode"
|
|
334
340
|
/>
|
|
335
341
|
|
|
@@ -339,7 +345,7 @@ function enterFocusMode() {
|
|
|
339
345
|
color="neutral"
|
|
340
346
|
icon="i-lucide-chevron-up"
|
|
341
347
|
:disabled="readonly"
|
|
342
|
-
title="
|
|
348
|
+
:title="t('mapo.repeater.moveUp')"
|
|
343
349
|
@click="$emit('move-up')"
|
|
344
350
|
/>
|
|
345
351
|
<UButton
|
|
@@ -348,7 +354,7 @@ function enterFocusMode() {
|
|
|
348
354
|
color="neutral"
|
|
349
355
|
icon="i-lucide-chevron-down"
|
|
350
356
|
:disabled="readonly"
|
|
351
|
-
title="
|
|
357
|
+
:title="t('mapo.repeater.moveDown')"
|
|
352
358
|
@click="$emit('move-down')"
|
|
353
359
|
/>
|
|
354
360
|
|
|
@@ -359,7 +365,7 @@ function enterFocusMode() {
|
|
|
359
365
|
color="neutral"
|
|
360
366
|
icon="i-lucide-copy"
|
|
361
367
|
:disabled="readonly"
|
|
362
|
-
title="
|
|
368
|
+
:title="t('mapo.duplicate')"
|
|
363
369
|
@click="$emit('duplicate')"
|
|
364
370
|
/>
|
|
365
371
|
|
|
@@ -369,7 +375,7 @@ function enterFocusMode() {
|
|
|
369
375
|
color="error"
|
|
370
376
|
icon="i-lucide-trash-2"
|
|
371
377
|
:disabled="readonly"
|
|
372
|
-
title="
|
|
378
|
+
:title="t('mapo.delete')"
|
|
373
379
|
@click="$emit('delete')"
|
|
374
380
|
/>
|
|
375
381
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
3
4
|
import { useRequestURL } from "#app";
|
|
4
5
|
import type { SeoDescriptor } from "../../types/index.js";
|
|
5
6
|
|
|
@@ -10,6 +11,8 @@ interface SeoValue {
|
|
|
10
11
|
permalink?: string;
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
const { t } = useI18n();
|
|
15
|
+
|
|
13
16
|
const props = defineProps<{
|
|
14
17
|
modelValue: unknown;
|
|
15
18
|
descriptor: SeoDescriptor;
|
|
@@ -58,7 +61,9 @@ const displayUrl = computed(() => value.value.permalink || requestUrl.host);
|
|
|
58
61
|
<div class="space-y-3">
|
|
59
62
|
<div>
|
|
60
63
|
<div class="mb-1 flex items-center justify-between">
|
|
61
|
-
<label class="text-sm font-medium text-gray-700">
|
|
64
|
+
<label class="text-sm font-medium text-gray-700">{{
|
|
65
|
+
t("mapo.url")
|
|
66
|
+
}}</label>
|
|
62
67
|
</div>
|
|
63
68
|
<UInput
|
|
64
69
|
:model-value="value.permalink"
|
|
@@ -72,14 +77,16 @@ const displayUrl = computed(() => value.value.permalink || requestUrl.host);
|
|
|
72
77
|
|
|
73
78
|
<div>
|
|
74
79
|
<div class="mb-1 flex items-center justify-between">
|
|
75
|
-
<label class="text-sm font-medium text-gray-700">
|
|
80
|
+
<label class="text-sm font-medium text-gray-700">{{
|
|
81
|
+
t("mapo.seoPreview.seoTitle")
|
|
82
|
+
}}</label>
|
|
76
83
|
<span class="text-xs" :class="titleColor">{{ titleLen }} / 60</span>
|
|
77
84
|
</div>
|
|
78
85
|
<UInput
|
|
79
86
|
:model-value="value.title"
|
|
80
87
|
:readonly="readonly"
|
|
81
88
|
:disabled="disabled"
|
|
82
|
-
placeholder="
|
|
89
|
+
:placeholder="t('mapo.seoPreview.titlePlaceholder')"
|
|
83
90
|
class="w-full"
|
|
84
91
|
@update:model-value="update('title', $event as string)"
|
|
85
92
|
/>
|
|
@@ -87,9 +94,9 @@ const displayUrl = computed(() => value.value.permalink || requestUrl.host);
|
|
|
87
94
|
|
|
88
95
|
<div>
|
|
89
96
|
<div class="mb-1 flex items-center justify-between">
|
|
90
|
-
<label class="text-sm font-medium text-gray-700"
|
|
91
|
-
|
|
92
|
-
>
|
|
97
|
+
<label class="text-sm font-medium text-gray-700">{{
|
|
98
|
+
t("mapo.seoPreview.metaDescription")
|
|
99
|
+
}}</label>
|
|
93
100
|
<span class="text-xs" :class="descColor">{{ descLen }} / 155</span>
|
|
94
101
|
</div>
|
|
95
102
|
<UTextarea
|
|
@@ -97,7 +104,7 @@ const displayUrl = computed(() => value.value.permalink || requestUrl.host);
|
|
|
97
104
|
:rows="3"
|
|
98
105
|
:readonly="readonly"
|
|
99
106
|
:disabled="disabled"
|
|
100
|
-
placeholder="
|
|
107
|
+
:placeholder="t('mapo.seoPreview.descriptionPlaceholder')"
|
|
101
108
|
class="w-full"
|
|
102
109
|
@update:model-value="update('description', $event as string)"
|
|
103
110
|
/>
|
|
@@ -107,20 +114,17 @@ const displayUrl = computed(() => value.value.permalink || requestUrl.host);
|
|
|
107
114
|
<!-- SERP preview -->
|
|
108
115
|
<div class="rounded border border-gray-200 bg-gray-50 p-4">
|
|
109
116
|
<p class="mb-1 text-xs font-medium uppercase tracking-wide text-gray-400">
|
|
110
|
-
|
|
117
|
+
{{ t("mapo.seoPreview.googlePreview") }}
|
|
111
118
|
</p>
|
|
112
119
|
<div class="max-w-lg">
|
|
113
120
|
<p class="truncate text-sm text-green-700">
|
|
114
121
|
{{ displayUrl }}
|
|
115
122
|
</p>
|
|
116
123
|
<p class="truncate text-lg text-blue-700 underline">
|
|
117
|
-
{{ value.title || "
|
|
124
|
+
{{ value.title || t("mapo.seoPreview.defaultTitle") }}
|
|
118
125
|
</p>
|
|
119
126
|
<p class="mt-1 line-clamp-2 text-sm text-gray-600">
|
|
120
|
-
{{
|
|
121
|
-
value.description ||
|
|
122
|
-
"Page description that will appear in Google search results."
|
|
123
|
-
}}
|
|
127
|
+
{{ value.description || t("mapo.seoPreview.defaultDescription") }}
|
|
124
128
|
</p>
|
|
125
129
|
</div>
|
|
126
130
|
</div>
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
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 type { AnyExtension } from "@tiptap/vue-3";
|
|
5
6
|
import StarterKit from "@tiptap/starter-kit";
|
|
6
7
|
import Link from "@tiptap/extension-link";
|
|
7
8
|
import Underline from "@tiptap/extension-underline";
|
|
9
|
+
import Image from "@tiptap/extension-image";
|
|
8
10
|
import type { EditorDescriptor } from "../../types/index.js";
|
|
9
11
|
|
|
10
12
|
const props = defineProps<{
|
|
@@ -17,6 +19,8 @@ const props = defineProps<{
|
|
|
17
19
|
|
|
18
20
|
const emit = defineEmits<{ "update:modelValue": [value: string] }>();
|
|
19
21
|
|
|
22
|
+
const { t } = useI18n();
|
|
23
|
+
|
|
20
24
|
const extraExtensions =
|
|
21
25
|
(props.descriptor.attrs?.extensions as AnyExtension[] | undefined) ?? [];
|
|
22
26
|
|
|
@@ -85,6 +89,7 @@ const editor = useEditor({
|
|
|
85
89
|
validate: (href: string) => isSafeUrl(href),
|
|
86
90
|
}),
|
|
87
91
|
Underline,
|
|
92
|
+
Image.configure({ inline: false, allowBase64: false }),
|
|
88
93
|
...extraExtensions,
|
|
89
94
|
] as AnyExtension[],
|
|
90
95
|
onUpdate: ({ editor: e }) => {
|
|
@@ -112,13 +117,38 @@ watch(
|
|
|
112
117
|
);
|
|
113
118
|
|
|
114
119
|
onBeforeUnmount(() => editor.value?.destroy());
|
|
120
|
+
|
|
121
|
+
// ─── Image insertion ──────────────────────────────────────────────────────
|
|
122
|
+
// MapoMediaManagerDialog is globally registered by @mapomodule/uikit.
|
|
123
|
+
// resolveComponent() works at runtime because it is registered before this
|
|
124
|
+
// component renders. We avoid a direct import to break the form → uikit cycle.
|
|
125
|
+
const imagePickerOpen = ref(false);
|
|
126
|
+
const MediaManagerDialog = resolveComponent("MapoMediaManagerDialog");
|
|
127
|
+
const hasMediaManager = typeof MediaManagerDialog !== "string";
|
|
128
|
+
|
|
129
|
+
function onImageConfirm(
|
|
130
|
+
selection:
|
|
131
|
+
| { file: string; alt_text?: string }
|
|
132
|
+
| Array<{ file: string; alt_text?: string }>,
|
|
133
|
+
) {
|
|
134
|
+
const media = Array.isArray(selection) ? selection[0] : selection;
|
|
135
|
+
if (!media || !editor.value) return;
|
|
136
|
+
editor.value
|
|
137
|
+
.chain()
|
|
138
|
+
.focus()
|
|
139
|
+
.setImage({ src: media.file, alt: media.alt_text ?? "" })
|
|
140
|
+
.run();
|
|
141
|
+
}
|
|
115
142
|
</script>
|
|
116
143
|
|
|
117
144
|
<template>
|
|
145
|
+
<!-- An input surface of its own, like the Nuxt UI inputs next to it
|
|
146
|
+
(`bg-default`, `accented` border): without it the text area takes the
|
|
147
|
+
page background and disappears on a tinted page. -->
|
|
118
148
|
<div
|
|
119
|
-
class="mapo-wyg-editor rounded border"
|
|
149
|
+
class="mapo-wyg-editor rounded-md border bg-default"
|
|
120
150
|
:class="[
|
|
121
|
-
errors?.length ? 'border-red-300' : 'border-
|
|
151
|
+
errors?.length ? 'border-red-300' : 'border-accented',
|
|
122
152
|
readonly || disabled ? 'opacity-60' : '',
|
|
123
153
|
]"
|
|
124
154
|
>
|
|
@@ -223,9 +253,13 @@ onBeforeUnmount(() => editor.value?.destroy());
|
|
|
223
253
|
|
|
224
254
|
<USeparator orientation="vertical" class="mx-1 h-5" />
|
|
225
255
|
|
|
226
|
-
<!-- Insert Image
|
|
256
|
+
<!-- Insert Image via Media Manager (available when @mapomodule/uikit is installed) -->
|
|
227
257
|
<UTooltip
|
|
228
|
-
text="
|
|
258
|
+
:text="
|
|
259
|
+
hasMediaManager
|
|
260
|
+
? t('mapo.wygEditor.insertImage')
|
|
261
|
+
: t('mapo.wygEditor.requiresUikit')
|
|
262
|
+
"
|
|
229
263
|
:delay-open="300"
|
|
230
264
|
>
|
|
231
265
|
<UButton
|
|
@@ -233,12 +267,13 @@ onBeforeUnmount(() => editor.value?.destroy());
|
|
|
233
267
|
variant="ghost"
|
|
234
268
|
color="neutral"
|
|
235
269
|
icon="i-lucide-image"
|
|
236
|
-
disabled
|
|
270
|
+
:disabled="readonly || disabled || !hasMediaManager"
|
|
271
|
+
@click="imagePickerOpen = true"
|
|
237
272
|
/>
|
|
238
273
|
</UTooltip>
|
|
239
274
|
</div>
|
|
240
275
|
|
|
241
|
-
<!--
|
|
276
|
+
<!-- Editor content -->
|
|
242
277
|
<EditorContent
|
|
243
278
|
:editor="editor"
|
|
244
279
|
class="prose prose-sm max-w-none px-3 py-2 focus-within:outline-none"
|
|
@@ -252,5 +287,18 @@ onBeforeUnmount(() => editor.value?.destroy());
|
|
|
252
287
|
>
|
|
253
288
|
{{ err }}
|
|
254
289
|
</p>
|
|
290
|
+
|
|
291
|
+
<!-- Image picker dialog — only rendered when MapoMediaManagerDialog is
|
|
292
|
+
globally available. Inside the root on purpose: a second root would
|
|
293
|
+
drop what MapoFormField passes down (`flex-1`, `id`, `style`), and the
|
|
294
|
+
editor would shrink to its toolbar. As a modal it renders in <body>. -->
|
|
295
|
+
<component
|
|
296
|
+
:is="MediaManagerDialog"
|
|
297
|
+
v-if="hasMediaManager"
|
|
298
|
+
v-model="imagePickerOpen"
|
|
299
|
+
selection-mode="single"
|
|
300
|
+
mime="image/*"
|
|
301
|
+
@confirm="onImageConfirm"
|
|
302
|
+
/>
|
|
255
303
|
</div>
|
|
256
304
|
</template>
|
|
@@ -11,9 +11,10 @@ function mapType(schema: JSONSchema, key: string): FieldType {
|
|
|
11
11
|
const t = Array.isArray(schema.type)
|
|
12
12
|
? schema.type.find((v) => v !== "null")
|
|
13
13
|
: schema.type;
|
|
14
|
-
const fmt = schema.format
|
|
14
|
+
const fmt = schema.format;
|
|
15
15
|
|
|
16
16
|
if (t === "string") {
|
|
17
|
+
if (fmt === "textarea") return "textarea";
|
|
17
18
|
if (fmt === "date") return "date";
|
|
18
19
|
if (fmt === "date-time") return "datetime";
|
|
19
20
|
if (fmt === "time") return "time";
|
|
@@ -179,6 +180,8 @@ function propertyToDescriptor(
|
|
|
179
180
|
type,
|
|
180
181
|
label: schema.title ?? undefined,
|
|
181
182
|
required: requiredKeys.has(key),
|
|
183
|
+
...(schema.readOnly ? { readonly: true } : {}),
|
|
184
|
+
...(schema.translatable ? { translatable: true } : {}),
|
|
182
185
|
...(visible ? { visible } : {}),
|
|
183
186
|
...(Object.keys(attrs).length ? { attrs } : {}),
|
|
184
187
|
};
|
|
@@ -19,6 +19,7 @@ import type { AnyFieldDescriptor, FieldRegistry } from "../types/index.js";
|
|
|
19
19
|
import { resolveFieldAccessor } from "../types/index.js";
|
|
20
20
|
import { useCurrentLang } from "./useCurrentLang.js";
|
|
21
21
|
import { useSnackStore } from "@mapomodule/store/runtime/stores/snack";
|
|
22
|
+
import { useMapoT } from "@mapomodule/i18n/runtime/composables/useMapoT";
|
|
22
23
|
import { useAuthStore } from "@mapomodule/store/runtime/stores/auth";
|
|
23
24
|
|
|
24
25
|
/** Options accepted by `useMapoForm()`. */
|
|
@@ -121,6 +122,7 @@ export function useMapoForm<T extends object>(options: UseMapoFormOptions<T>) {
|
|
|
121
122
|
|
|
122
123
|
const currentLang = useCurrentLang(currentLangProp);
|
|
123
124
|
const snack = useSnackStore();
|
|
125
|
+
const t = useMapoT();
|
|
124
126
|
const authStore = useAuthStore();
|
|
125
127
|
const backup = ref(safeClone(model.value)) as Ref<T>;
|
|
126
128
|
const clientErrors = ref<Record<string, string>>({});
|
|
@@ -318,7 +320,7 @@ export function useMapoForm<T extends object>(options: UseMapoFormOptions<T>) {
|
|
|
318
320
|
): string | null {
|
|
319
321
|
if (!descriptor.required) return null;
|
|
320
322
|
if (val === null || val === undefined || val === "")
|
|
321
|
-
return "
|
|
323
|
+
return t("mapo.form.required");
|
|
322
324
|
return null;
|
|
323
325
|
}
|
|
324
326
|
|
|
@@ -379,7 +381,7 @@ export function useMapoForm<T extends object>(options: UseMapoFormOptions<T>) {
|
|
|
379
381
|
submitted.value = true;
|
|
380
382
|
const { valid } = validateClient();
|
|
381
383
|
if (!valid) {
|
|
382
|
-
snack.show("
|
|
384
|
+
snack.show(t("mapo.fixErrors"), "error");
|
|
383
385
|
return;
|
|
384
386
|
}
|
|
385
387
|
isLoading.value = true;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { resolveComponent } from "vue";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the Media Manager picker dialog at runtime.
|
|
5
|
+
*
|
|
6
|
+
* The media field types ship with `@mapomodule/form`, but the Media Manager
|
|
7
|
+
* itself lives in `@mapomodule/uikit`. Resolving the dialog by name instead of
|
|
8
|
+
* importing it keeps `form` free of a static dependency on `uikit` (which
|
|
9
|
+
* depends on `form`, so a static import would be a cycle) and lets `form` stay
|
|
10
|
+
* usable standalone — the fields then render an explicit notice instead of a
|
|
11
|
+
* broken picker.
|
|
12
|
+
*
|
|
13
|
+
* Must be called from `setup()`.
|
|
14
|
+
*/
|
|
15
|
+
export function useMediaManager() {
|
|
16
|
+
const MediaDialog = resolveComponent("MapoMediaManagerDialog");
|
|
17
|
+
// resolveComponent() returns the name unchanged when nothing is registered.
|
|
18
|
+
const available = typeof MediaDialog !== "string";
|
|
19
|
+
|
|
20
|
+
return { MediaDialog, available };
|
|
21
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { onBeforeUnmount, type Ref } from "vue";
|
|
2
|
-
import { onBeforeRouteLeave } from "
|
|
2
|
+
import { onBeforeRouteLeave } from "#app";
|
|
3
|
+
import { useMapoT } from "@mapomodule/i18n/runtime/composables/useMapoT";
|
|
3
4
|
|
|
4
5
|
/** Optional configuration for the unsaved-changes navigation guard. */
|
|
5
6
|
export interface UnsavedChangesGuardOptions {
|
|
@@ -27,8 +28,7 @@ export function useUnsavedChangesGuard(
|
|
|
27
28
|
isDirty: Ref<boolean>,
|
|
28
29
|
options: UnsavedChangesGuardOptions = {},
|
|
29
30
|
) {
|
|
30
|
-
const message =
|
|
31
|
-
options.message ?? "You have unsaved changes. Do you really want to leave?";
|
|
31
|
+
const message = options.message ?? useMapoT()("mapo.confirmLeaveChanges");
|
|
32
32
|
const canUseWindow = typeof window !== "undefined";
|
|
33
33
|
|
|
34
34
|
const isEnabled = (): boolean => {
|
|
@@ -31,7 +31,15 @@ export const defaultRegistry: FieldRegistry = {
|
|
|
31
31
|
editor: () => import("../components/fields/MapoWygEditor.vue"),
|
|
32
32
|
map: () => import("../components/fields/MapoMapField.vue"),
|
|
33
33
|
repeater: () => import("../components/fields/MapoRepeater.vue"),
|
|
34
|
-
|
|
34
|
+
|
|
35
|
+
// ─── Media pickers ────────────────────────────────────────────────────────
|
|
36
|
+
// The fields live here with every other field type; the Media Manager they
|
|
37
|
+
// drive ships with @mapomodule/uikit and is resolved at runtime
|
|
38
|
+
// (see useMediaManager). Without uikit they render an explicit notice.
|
|
39
|
+
media: () => import("../components/fields/MapoMediaField.vue"),
|
|
40
|
+
"media-m2m": () => import("../components/fields/MapoMediaM2mField.vue"),
|
|
41
|
+
"enhanced-media": () =>
|
|
42
|
+
import("../components/fields/MapoEnhancedMediaField.vue"),
|
|
35
43
|
},
|
|
36
44
|
|
|
37
45
|
attrs: {
|
|
@@ -344,15 +344,28 @@ export interface RepeaterDescriptor<
|
|
|
344
344
|
};
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
-
/** Descriptor for media
|
|
347
|
+
/** Descriptor for a single-media picker field (`type: 'media'`). */
|
|
348
348
|
export interface MediaDescriptor<
|
|
349
349
|
T = Record<string, unknown>,
|
|
350
350
|
> extends FieldBase<T> {
|
|
351
|
-
type:
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
351
|
+
type: `${KnownFieldType.Media}`;
|
|
352
|
+
attrs?: { mime?: string };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** Descriptor for a multi-media picker field (`type: 'media-m2m'`). */
|
|
356
|
+
export interface MediaM2mDescriptor<
|
|
357
|
+
T = Record<string, unknown>,
|
|
358
|
+
> extends FieldBase<T> {
|
|
359
|
+
type: `${KnownFieldType.MediaM2m}`;
|
|
360
|
+
attrs?: { mime?: string; maxItems?: number };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** Descriptor for a media picker with inline alt/caption metadata (`type: 'enhanced-media'`). */
|
|
364
|
+
export interface EnhancedMediaDescriptor<
|
|
365
|
+
T = Record<string, unknown>,
|
|
366
|
+
> extends FieldBase<T> {
|
|
367
|
+
type: `${KnownFieldType.EnhancedMedia}`;
|
|
368
|
+
attrs?: { mime?: string };
|
|
356
369
|
}
|
|
357
370
|
|
|
358
371
|
/**
|
|
@@ -395,7 +408,9 @@ export type FieldDescriptor<T = Record<string, unknown>> =
|
|
|
395
408
|
| SeoDescriptor<T>
|
|
396
409
|
| MapDescriptor<T>
|
|
397
410
|
| RepeaterDescriptor<T>
|
|
398
|
-
| MediaDescriptor<T
|
|
411
|
+
| MediaDescriptor<T>
|
|
412
|
+
| MediaM2mDescriptor<T>
|
|
413
|
+
| EnhancedMediaDescriptor<T>;
|
|
399
414
|
|
|
400
415
|
/**
|
|
401
416
|
* {@link FieldDescriptor} plus the {@link CustomDescriptor} escape hatch.
|
|
@@ -22,9 +22,13 @@ export type {
|
|
|
22
22
|
MapDescriptor,
|
|
23
23
|
RepeaterDescriptor,
|
|
24
24
|
MediaDescriptor,
|
|
25
|
+
MediaM2mDescriptor,
|
|
26
|
+
EnhancedMediaDescriptor,
|
|
25
27
|
CustomDescriptor,
|
|
26
28
|
} from "./descriptor.js";
|
|
27
29
|
|
|
30
|
+
export type { MediaLink, MediaItem, EnhancedMediaValue } from "./media.js";
|
|
31
|
+
|
|
28
32
|
export type {
|
|
29
33
|
FieldComponentEntry,
|
|
30
34
|
FieldRegistry,
|