@xleddyl/nuxt-cms 0.1.23 → 0.1.25
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/dist/module.json +1 -1
- package/dist/module.mjs +16 -3
- package/dist/runtime/app/components/cms/BlocksField.vue +2 -9
- package/dist/runtime/app/components/cms/Button.vue +4 -2
- package/dist/runtime/app/components/cms/ConfirmModal.vue +5 -14
- package/dist/runtime/app/components/cms/Drawer.d.vue.ts +30 -0
- package/dist/runtime/app/components/cms/Drawer.vue +65 -0
- package/dist/runtime/app/components/cms/Drawer.vue.d.ts +30 -0
- package/dist/runtime/app/components/cms/DropdownMenu.d.vue.ts +0 -3
- package/dist/runtime/app/components/cms/DropdownMenu.vue +2 -7
- package/dist/runtime/app/components/cms/DropdownMenu.vue.d.ts +0 -3
- package/dist/runtime/app/components/cms/EmptyState.d.vue.ts +1 -0
- package/dist/runtime/app/components/cms/EmptyState.vue +6 -7
- package/dist/runtime/app/components/cms/EmptyState.vue.d.ts +1 -0
- package/dist/runtime/app/components/cms/EntryDrawer.d.vue.ts +21 -0
- package/dist/runtime/app/components/cms/EntryDrawer.vue +193 -0
- package/dist/runtime/app/components/cms/EntryDrawer.vue.d.ts +21 -0
- package/dist/runtime/app/components/cms/EntryForm.vue +19 -7
- package/dist/runtime/app/components/cms/FieldInput.d.vue.ts +1 -0
- package/dist/runtime/app/components/cms/FieldInput.vue +13 -27
- package/dist/runtime/app/components/cms/FieldInput.vue.d.ts +1 -0
- package/dist/runtime/app/components/cms/Form.vue +1 -1
- package/dist/runtime/app/components/cms/FormField.d.vue.ts +4 -5
- package/dist/runtime/app/components/cms/FormField.vue +9 -7
- package/dist/runtime/app/components/cms/FormField.vue.d.ts +4 -5
- package/dist/runtime/app/components/cms/JsonField.vue +3 -4
- package/dist/runtime/app/components/cms/LocaleSwitch.d.vue.ts +14 -0
- package/dist/runtime/app/components/cms/LocaleSwitch.vue +29 -0
- package/dist/runtime/app/components/cms/LocaleSwitch.vue.d.ts +14 -0
- package/dist/runtime/app/components/cms/MediaField.vue +7 -17
- package/dist/runtime/app/components/cms/MediaGallery.vue +48 -78
- package/dist/runtime/app/components/cms/MediaThumb.d.vue.ts +6 -0
- package/dist/runtime/app/components/cms/MediaThumb.vue +40 -0
- package/dist/runtime/app/components/cms/MediaThumb.vue.d.ts +6 -0
- package/dist/runtime/app/components/cms/Modal.d.vue.ts +1 -4
- package/dist/runtime/app/components/cms/Modal.vue +8 -7
- package/dist/runtime/app/components/cms/Modal.vue.d.ts +1 -4
- package/dist/runtime/app/components/cms/PageHeader.d.vue.ts +1 -1
- package/dist/runtime/app/components/cms/PageHeader.vue +6 -6
- package/dist/runtime/app/components/cms/PageHeader.vue.d.ts +1 -1
- package/dist/runtime/app/components/cms/RelationField.vue +0 -4
- package/dist/runtime/app/components/cms/SelectMenu.vue +23 -3
- package/dist/runtime/app/components/cms/SlugField.vue +1 -2
- package/dist/runtime/app/components/cms/Spinner.d.vue.ts +3 -0
- package/dist/runtime/app/components/cms/Spinner.vue +5 -0
- package/dist/runtime/app/components/cms/Spinner.vue.d.ts +3 -0
- package/dist/runtime/app/components/cms/StatusBadge.d.vue.ts +6 -0
- package/dist/runtime/app/components/cms/StatusBadge.vue +11 -0
- package/dist/runtime/app/components/cms/StatusBadge.vue.d.ts +6 -0
- package/dist/runtime/app/components/cms/Table.d.vue.ts +3 -6
- package/dist/runtime/app/components/cms/Table.vue +72 -27
- package/dist/runtime/app/components/cms/Table.vue.d.ts +3 -6
- package/dist/runtime/app/components/cms/Toaster.vue +4 -4
- package/dist/runtime/app/components/cms/TranslatableField.d.vue.ts +1 -0
- package/dist/runtime/app/components/cms/TranslatableField.vue +7 -44
- package/dist/runtime/app/components/cms/TranslatableField.vue.d.ts +1 -0
- package/dist/runtime/app/layouts/cms-admin.vue +10 -20
- package/dist/runtime/app/pages/admin-collection.vue +130 -103
- package/dist/runtime/app/pages/admin-entry.vue +6 -24
- package/dist/runtime/app/pages/admin-login.vue +12 -31
- package/dist/runtime/app/pages/admin-media.vue +8 -2
- package/dist/runtime/app/utils/ui.d.ts +0 -7
- package/dist/runtime/app/utils/ui.js +0 -5
- package/dist/runtime/assets/main.css +1 -1
- package/dist/runtime/server/api/collection.get.js +8 -3
- package/dist/runtime/server/utils/relations.d.ts +1 -0
- package/dist/runtime/server/utils/relations.js +25 -0
- package/dist/runtime/shared/index.d.ts +10 -3
- package/package.json +1 -1
|
@@ -1,22 +1,15 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<CmsTranslatableField
|
|
2
|
+
<CmsTranslatableField
|
|
3
|
+
v-if="isTranslatableField(field)"
|
|
4
|
+
v-model="translatable"
|
|
5
|
+
:field="field"
|
|
6
|
+
:locale="locale"
|
|
7
|
+
/>
|
|
3
8
|
<CmsBlocksField v-else-if="field.type === 'blocks'" v-model="blocksValue" :field="field" />
|
|
4
9
|
<CmsRichTextField v-else-if="field.type === 'richtext'" v-model="strOrNull" />
|
|
5
|
-
<CmsTextarea
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
:rows="4"
|
|
9
|
-
size="lg"
|
|
10
|
-
class="w-full"
|
|
11
|
-
/>
|
|
12
|
-
<CmsInput v-else-if="field.type === 'text'" v-model="str" size="lg" class="w-full" />
|
|
13
|
-
<CmsInput
|
|
14
|
-
v-else-if="field.type === 'email'"
|
|
15
|
-
v-model="str"
|
|
16
|
-
type="email"
|
|
17
|
-
size="lg"
|
|
18
|
-
class="w-full"
|
|
19
|
-
/>
|
|
10
|
+
<CmsTextarea v-else-if="field.type === 'text' && field.textarea" v-model="str" :rows="8" />
|
|
11
|
+
<CmsInput v-else-if="field.type === 'text'" v-model="str" />
|
|
12
|
+
<CmsInput v-else-if="field.type === 'email'" v-model="str" type="email" />
|
|
20
13
|
<CmsSlugField v-else-if="field.type === 'slug'" v-model="strOrNull" :source="slugSource" />
|
|
21
14
|
<CmsSelectMenu
|
|
22
15
|
v-else-if="field.type === 'select'"
|
|
@@ -24,8 +17,6 @@
|
|
|
24
17
|
:multiple="field.multiple"
|
|
25
18
|
:items="selectItems"
|
|
26
19
|
placeholder="Select…"
|
|
27
|
-
size="lg"
|
|
28
|
-
class="w-full"
|
|
29
20
|
/>
|
|
30
21
|
<CmsSwitch
|
|
31
22
|
v-else-if="field.type === 'boolean'"
|
|
@@ -37,14 +28,8 @@
|
|
|
37
28
|
"
|
|
38
29
|
/>
|
|
39
30
|
<CmsJsonField v-else-if="field.type === 'json'" v-model="jsonValue" />
|
|
40
|
-
<CmsInput
|
|
41
|
-
|
|
42
|
-
v-model.number="num"
|
|
43
|
-
type="number"
|
|
44
|
-
size="lg"
|
|
45
|
-
class="w-full"
|
|
46
|
-
/>
|
|
47
|
-
<CmsInput v-else-if="field.type === 'date'" v-model="str" type="date" size="lg" class="w-full" />
|
|
31
|
+
<CmsInput v-else-if="field.type === 'number'" v-model.number="num" type="number" />
|
|
32
|
+
<CmsInput v-else-if="field.type === 'date'" v-model="str" type="date" />
|
|
48
33
|
<CmsRelationField
|
|
49
34
|
v-else-if="field.type === 'relation'"
|
|
50
35
|
v-model="relationValue"
|
|
@@ -60,7 +45,8 @@ import { isTranslatableField } from "#nuxt-cms";
|
|
|
60
45
|
import { computed } from "#imports";
|
|
61
46
|
const props = defineProps({
|
|
62
47
|
field: { type: Object, required: true },
|
|
63
|
-
slugSource: { type: [String, null], required: false }
|
|
48
|
+
slugSource: { type: [String, null], required: false },
|
|
49
|
+
locale: { type: String, required: false }
|
|
64
50
|
});
|
|
65
51
|
const model = defineModel({ type: null, ...{ required: true } });
|
|
66
52
|
const selectItems = computed(() => [
|
|
@@ -2,13 +2,12 @@ type __VLS_Props = {
|
|
|
2
2
|
label?: string;
|
|
3
3
|
name?: string;
|
|
4
4
|
required?: boolean;
|
|
5
|
-
ui?: {
|
|
6
|
-
label?: string;
|
|
7
|
-
};
|
|
8
5
|
};
|
|
9
|
-
declare var __VLS_1: {};
|
|
6
|
+
declare var __VLS_1: {}, __VLS_3: {};
|
|
10
7
|
type __VLS_Slots = {} & {
|
|
11
|
-
|
|
8
|
+
'label-actions'?: (props: typeof __VLS_1) => any;
|
|
9
|
+
} & {
|
|
10
|
+
default?: (props: typeof __VLS_3) => any;
|
|
12
11
|
};
|
|
13
12
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
14
13
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
<div class="cms-form-field">
|
|
3
|
+
<div v-if="label || $slots['label-actions']" class="cms-form-label-row">
|
|
4
|
+
<label v-if="label" class="cms-form-label">
|
|
5
|
+
{{ label }}<span v-if="required" class="cms-form-required"> *</span>
|
|
6
|
+
</label>
|
|
7
|
+
<slot name="label-actions" />
|
|
8
|
+
</div>
|
|
6
9
|
<slot />
|
|
7
|
-
<p v-if="error" class="
|
|
10
|
+
<p v-if="error" class="cms-form-error">{{ error }}</p>
|
|
8
11
|
</div>
|
|
9
12
|
</template>
|
|
10
13
|
|
|
@@ -14,8 +17,7 @@ import { CMS_FORM_ERRORS } from "../../utils/ui";
|
|
|
14
17
|
const props = defineProps({
|
|
15
18
|
label: { type: String, required: false },
|
|
16
19
|
name: { type: String, required: false },
|
|
17
|
-
required: { type: Boolean, required: false }
|
|
18
|
-
ui: { type: Object, required: false }
|
|
20
|
+
required: { type: Boolean, required: false }
|
|
19
21
|
});
|
|
20
22
|
const errors = inject(CMS_FORM_ERRORS, null);
|
|
21
23
|
const error = computed(() => props.name && errors ? errors[props.name] : void 0);
|
|
@@ -2,13 +2,12 @@ type __VLS_Props = {
|
|
|
2
2
|
label?: string;
|
|
3
3
|
name?: string;
|
|
4
4
|
required?: boolean;
|
|
5
|
-
ui?: {
|
|
6
|
-
label?: string;
|
|
7
|
-
};
|
|
8
5
|
};
|
|
9
|
-
declare var __VLS_1: {};
|
|
6
|
+
declare var __VLS_1: {}, __VLS_3: {};
|
|
10
7
|
type __VLS_Slots = {} & {
|
|
11
|
-
|
|
8
|
+
'label-actions'?: (props: typeof __VLS_1) => any;
|
|
9
|
+
} & {
|
|
10
|
+
default?: (props: typeof __VLS_3) => any;
|
|
12
11
|
};
|
|
13
12
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
14
13
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="
|
|
2
|
+
<div class="cms-form-field">
|
|
3
3
|
<CmsTextarea
|
|
4
4
|
v-model="text"
|
|
5
5
|
:rows="6"
|
|
6
|
-
|
|
7
|
-
class="w-full font-mono"
|
|
6
|
+
class="font-mono"
|
|
8
7
|
:color="invalid ? 'error' : void 0"
|
|
9
8
|
@blur="commit"
|
|
10
9
|
@update:model-value="invalid = false"
|
|
11
10
|
/>
|
|
12
|
-
<p v-if="invalid" class="
|
|
11
|
+
<p v-if="invalid" class="cms-form-error">Invalid JSON</p>
|
|
13
12
|
</div>
|
|
14
13
|
</template>
|
|
15
14
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
value?: Record<string, string> | null;
|
|
3
|
+
};
|
|
4
|
+
type __VLS_ModelProps = {
|
|
5
|
+
modelValue: string;
|
|
6
|
+
};
|
|
7
|
+
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
8
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
+
"update:modelValue": (value: string) => any;
|
|
10
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
11
|
+
"onUpdate:modelValue"?: ((value: string) => any) | undefined;
|
|
12
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
|
+
declare const _default: typeof __VLS_export;
|
|
14
|
+
export default _default;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="cms-locale-switch" role="tablist" aria-label="Content language">
|
|
3
|
+
<button
|
|
4
|
+
v-for="locale in i18n.locales"
|
|
5
|
+
:key="locale"
|
|
6
|
+
type="button"
|
|
7
|
+
role="tab"
|
|
8
|
+
:aria-selected="model === locale"
|
|
9
|
+
:title="filled(locale) ? locale : `${locale} \u2014 empty`"
|
|
10
|
+
class="cms-locale-option"
|
|
11
|
+
:class="{ 'is-active': model === locale, 'is-empty': !filled(locale) }"
|
|
12
|
+
@click="model = locale"
|
|
13
|
+
>
|
|
14
|
+
{{ locale }}
|
|
15
|
+
</button>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script setup>
|
|
20
|
+
import { useCmsRuntime } from "../../composables/cms-runtime";
|
|
21
|
+
const props = defineProps({
|
|
22
|
+
value: { type: [Object, null], required: false }
|
|
23
|
+
});
|
|
24
|
+
const model = defineModel({ type: String, ...{ required: true } });
|
|
25
|
+
const { i18n } = useCmsRuntime();
|
|
26
|
+
function filled(locale) {
|
|
27
|
+
return !!props.value?.[locale]?.trim();
|
|
28
|
+
}
|
|
29
|
+
</script>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
value?: Record<string, string> | null;
|
|
3
|
+
};
|
|
4
|
+
type __VLS_ModelProps = {
|
|
5
|
+
modelValue: string;
|
|
6
|
+
};
|
|
7
|
+
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
8
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
+
"update:modelValue": (value: string) => any;
|
|
10
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
11
|
+
"onUpdate:modelValue"?: ((value: string) => any) | undefined;
|
|
12
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
|
+
declare const _default: typeof __VLS_export;
|
|
14
|
+
export default _default;
|
|
@@ -1,30 +1,22 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div v-if="model" class="cms-card
|
|
3
|
-
<img
|
|
4
|
-
v-if="kind === 'image' && url"
|
|
5
|
-
:src="url"
|
|
6
|
-
alt=""
|
|
7
|
-
class="max-h-56 w-full bg-(--cms-field) object-cover"
|
|
8
|
-
/>
|
|
2
|
+
<div v-if="model" class="cms-card cms-media-tile">
|
|
3
|
+
<img v-if="kind === 'image' && url" :src="url" alt="" class="cms-media-preview-large" />
|
|
9
4
|
<video
|
|
10
5
|
v-else-if="kind === 'video' && url"
|
|
11
6
|
:src="url"
|
|
12
7
|
controls
|
|
13
8
|
preload="metadata"
|
|
14
9
|
playsinline
|
|
15
|
-
class="
|
|
10
|
+
class="cms-media-preview-large"
|
|
16
11
|
/>
|
|
17
|
-
<div class="
|
|
18
|
-
<CmsIcon :name="icon" class="
|
|
19
|
-
<span class="
|
|
20
|
-
mediaFilename(model)
|
|
21
|
-
}}</span>
|
|
12
|
+
<div class="cms-media-bar">
|
|
13
|
+
<CmsIcon :name="icon" class="cms-media-bar-icon size-4" />
|
|
14
|
+
<span class="cms-media-bar-name" :title="model">{{ mediaFilename(model) }}</span>
|
|
22
15
|
<CmsButton
|
|
23
16
|
icon="photo"
|
|
24
17
|
variant="ghost"
|
|
25
18
|
color="neutral"
|
|
26
19
|
size="xs"
|
|
27
|
-
class="rounded-full"
|
|
28
20
|
aria-label="Change"
|
|
29
21
|
@click="openGallery"
|
|
30
22
|
/>
|
|
@@ -33,7 +25,6 @@
|
|
|
33
25
|
variant="ghost"
|
|
34
26
|
color="neutral"
|
|
35
27
|
size="xs"
|
|
36
|
-
class="rounded-full"
|
|
37
28
|
aria-label="Remove"
|
|
38
29
|
@click="clear"
|
|
39
30
|
/>
|
|
@@ -45,7 +36,7 @@
|
|
|
45
36
|
<span class="text-sm font-medium">Choose from the gallery</span>
|
|
46
37
|
</button>
|
|
47
38
|
|
|
48
|
-
<CmsModal v-model:open="galleryOpen" title="Media"
|
|
39
|
+
<CmsModal v-model:open="galleryOpen" title="Media" size="lg">
|
|
49
40
|
<template #body>
|
|
50
41
|
<CmsMediaGallery selectable :media-type="mediaType" :accept="accept" @select="onSelect" />
|
|
51
42
|
</template>
|
|
@@ -56,7 +47,6 @@
|
|
|
56
47
|
import { computed, ref } from "#imports";
|
|
57
48
|
import { mediaFilename, mediaIconFor, mediaPublicUrl, mediaTypeForKey } from "#nuxt-cms";
|
|
58
49
|
import { useCmsRuntime } from "../../composables/cms-runtime";
|
|
59
|
-
import { CMS_MODAL_UI } from "../../utils/ui";
|
|
60
50
|
defineProps({
|
|
61
51
|
mediaType: { type: null, required: false },
|
|
62
52
|
accept: { type: Array, required: false }
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
body="Set cms.media in nuxt.config to enable uploads."
|
|
7
7
|
/>
|
|
8
8
|
|
|
9
|
-
<div v-else class="
|
|
9
|
+
<div v-else class="cms-media-gallery">
|
|
10
10
|
<CmsMediaUpload
|
|
11
11
|
v-if="!readOnly"
|
|
12
12
|
:multiple="!selectable"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
@uploaded="onUploaded"
|
|
17
17
|
/>
|
|
18
18
|
|
|
19
|
-
<div v-if="!restricted && items.length" class="
|
|
19
|
+
<div v-if="!restricted && items.length" class="cms-media-filters">
|
|
20
20
|
<button
|
|
21
21
|
v-for="f in filters"
|
|
22
22
|
:key="f"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
{{ filterLabels[f] }}
|
|
29
29
|
</button>
|
|
30
30
|
<template v-if="folders.length">
|
|
31
|
-
<span class="
|
|
31
|
+
<span class="cms-media-filter-separator" />
|
|
32
32
|
<button
|
|
33
33
|
v-for="f in folders"
|
|
34
34
|
:key="f"
|
|
@@ -37,40 +37,29 @@
|
|
|
37
37
|
:class="{ 'is-active': folder === f }"
|
|
38
38
|
@click="folder = folder === f ? null : f"
|
|
39
39
|
>
|
|
40
|
-
<CmsIcon name="folder" class="
|
|
40
|
+
<CmsIcon name="folder" class="size-3" />{{ f }}
|
|
41
41
|
</button>
|
|
42
42
|
</template>
|
|
43
43
|
</div>
|
|
44
44
|
|
|
45
|
-
<
|
|
46
|
-
<CmsIcon name="arrow-path" class="size-6 animate-spin text-(--ui-text-dimmed)" />
|
|
47
|
-
</div>
|
|
45
|
+
<CmsSpinner v-if="loading" />
|
|
48
46
|
|
|
49
|
-
<div
|
|
50
|
-
v-else-if="visible.length"
|
|
51
|
-
class="grid gap-4"
|
|
52
|
-
:class="
|
|
53
|
-
selectable ? 'grid-cols-2 sm:grid-cols-3' : 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-4'
|
|
54
|
-
"
|
|
55
|
-
>
|
|
47
|
+
<div v-else-if="visible.length" class="cms-media-grid" :class="{ 'is-compact': selectable }">
|
|
56
48
|
<component
|
|
57
49
|
:is="selectable ? 'button' : 'div'"
|
|
58
|
-
v-for="item in
|
|
50
|
+
v-for="item in paged"
|
|
59
51
|
:key="item.key"
|
|
60
52
|
:type="selectable ? 'button' : void 0"
|
|
61
|
-
class="cms-card
|
|
62
|
-
:class="{
|
|
63
|
-
'hover:border-(--cms-line-strong)': selectable
|
|
64
|
-
}"
|
|
53
|
+
class="cms-card cms-media-tile"
|
|
54
|
+
:class="{ 'is-selectable': selectable }"
|
|
65
55
|
@click="selectable && emit('select', { key: item.key, url: item.url })"
|
|
66
56
|
>
|
|
67
|
-
<div class="
|
|
57
|
+
<div class="cms-media-preview">
|
|
68
58
|
<img
|
|
69
59
|
v-if="item.type === 'image' && item.url"
|
|
70
60
|
:src="item.url"
|
|
71
61
|
alt=""
|
|
72
62
|
loading="lazy"
|
|
73
|
-
class="absolute inset-0 size-full object-cover"
|
|
74
63
|
/>
|
|
75
64
|
<video
|
|
76
65
|
v-else-if="item.type === 'video' && item.url"
|
|
@@ -78,70 +67,45 @@
|
|
|
78
67
|
preload="metadata"
|
|
79
68
|
muted
|
|
80
69
|
playsinline
|
|
81
|
-
class="absolute inset-0 size-full object-cover"
|
|
82
|
-
/>
|
|
83
|
-
<CmsIcon
|
|
84
|
-
v-else
|
|
85
|
-
:name="mediaIconFor(item.type)"
|
|
86
|
-
class="size-8 text-(--ui-text-dimmed)"
|
|
87
70
|
/>
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
class="absolute top-2 right-2 flex gap-1 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100"
|
|
91
|
-
>
|
|
71
|
+
<CmsIcon v-else :name="mediaIconFor(item.type)" class="size-8" />
|
|
72
|
+
<div v-if="!selectable" class="cms-media-actions">
|
|
92
73
|
<CmsButton
|
|
93
74
|
v-if="!readOnly"
|
|
94
75
|
icon="pencil-square"
|
|
95
76
|
size="xs"
|
|
96
77
|
color="neutral"
|
|
97
78
|
variant="solid"
|
|
98
|
-
class="rounded-full"
|
|
99
79
|
aria-label="Edit details"
|
|
100
80
|
@click="openEdit(item)"
|
|
101
81
|
/>
|
|
102
|
-
<CmsButton
|
|
103
|
-
icon="document-duplicate"
|
|
104
|
-
size="xs"
|
|
105
|
-
color="neutral"
|
|
106
|
-
variant="solid"
|
|
107
|
-
class="rounded-full"
|
|
108
|
-
aria-label="Copy URL"
|
|
109
|
-
@click="copy(item)"
|
|
110
|
-
/>
|
|
111
82
|
<CmsButton
|
|
112
83
|
v-if="!readOnly"
|
|
113
84
|
icon="trash"
|
|
114
85
|
size="xs"
|
|
115
86
|
color="error"
|
|
116
87
|
variant="solid"
|
|
117
|
-
class="rounded-full"
|
|
118
88
|
aria-label="Delete"
|
|
119
89
|
@click="remove(item)"
|
|
120
90
|
/>
|
|
121
91
|
</div>
|
|
122
|
-
|
|
123
|
-
<div class="flex flex-col gap-1 px-3.5 py-2.5">
|
|
124
|
-
<div class="truncate text-sm font-medium" :title="item.key">
|
|
125
|
-
{{ mediaFilename(item.key) }}
|
|
126
|
-
</div>
|
|
127
|
-
<div class="cms-kicker">
|
|
92
|
+
<div class="cms-media-info">
|
|
128
93
|
{{ extension(item.key) }} · {{ formatSize(item.size)
|
|
129
94
|
}}<template v-if="item.width && item.height">
|
|
130
95
|
· {{ item.width }}×{{ item.height }}
|
|
131
96
|
</template>
|
|
132
97
|
</div>
|
|
133
98
|
</div>
|
|
99
|
+
<div class="cms-media-meta">
|
|
100
|
+
<div class="cms-media-name" :title="item.key">
|
|
101
|
+
{{ mediaFilename(item.key) }}
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
134
104
|
</component>
|
|
135
105
|
</div>
|
|
136
106
|
|
|
137
107
|
<CmsEmptyState v-else-if="loadError" icon="exclamation-triangle" title="Could not load media">
|
|
138
|
-
<CmsButton
|
|
139
|
-
label="Retry"
|
|
140
|
-
icon="arrow-path"
|
|
141
|
-
variant="subtle"
|
|
142
|
-
class="mt-3 rounded-full px-4"
|
|
143
|
-
@click="reload"
|
|
144
|
-
/>
|
|
108
|
+
<CmsButton label="Retry" icon="arrow-path" variant="subtle" @click="reload" />
|
|
145
109
|
</CmsEmptyState>
|
|
146
110
|
|
|
147
111
|
<CmsEmptyState
|
|
@@ -151,11 +115,18 @@
|
|
|
151
115
|
:body="readOnly ? 'No media registered.' : 'Files you upload will show up here.'"
|
|
152
116
|
/>
|
|
153
117
|
|
|
118
|
+
<CmsPagination
|
|
119
|
+
v-if="!loading && visible.length"
|
|
120
|
+
v-model:page="page"
|
|
121
|
+
:total="visible.length"
|
|
122
|
+
:items-per-page="PAGE_SIZE"
|
|
123
|
+
/>
|
|
124
|
+
|
|
154
125
|
<CmsModal
|
|
155
126
|
v-if="!readOnly"
|
|
156
127
|
:open="!!editing"
|
|
157
128
|
:title="editing ? mediaFilename(editing.key) : ''"
|
|
158
|
-
|
|
129
|
+
size="lg"
|
|
159
130
|
@update:open="
|
|
160
131
|
(open) => {
|
|
161
132
|
if (!open) editing = null;
|
|
@@ -163,20 +134,15 @@
|
|
|
163
134
|
"
|
|
164
135
|
>
|
|
165
136
|
<template #body>
|
|
166
|
-
<div class="
|
|
137
|
+
<div class="cms-form">
|
|
167
138
|
<CmsFormField label="Alt text">
|
|
168
|
-
<CmsInput v-model="editAlt"
|
|
139
|
+
<CmsInput v-model="editAlt" />
|
|
169
140
|
</CmsFormField>
|
|
170
141
|
<CmsFormField label="Folder">
|
|
171
|
-
<CmsInput v-model="editFolder"
|
|
142
|
+
<CmsInput v-model="editFolder" />
|
|
172
143
|
</CmsFormField>
|
|
173
|
-
<div class="
|
|
174
|
-
<CmsButton
|
|
175
|
-
label="Save"
|
|
176
|
-
:loading="editSaving"
|
|
177
|
-
class="rounded-full px-6"
|
|
178
|
-
@click="saveEdit"
|
|
179
|
-
/>
|
|
144
|
+
<div class="cms-actions is-end">
|
|
145
|
+
<CmsButton label="Save" :loading="editSaving" @click="saveEdit" />
|
|
180
146
|
</div>
|
|
181
147
|
</div>
|
|
182
148
|
</template>
|
|
@@ -185,12 +151,12 @@
|
|
|
185
151
|
</template>
|
|
186
152
|
|
|
187
153
|
<script setup>
|
|
188
|
-
import { computed, onMounted, ref } from "#imports";
|
|
154
|
+
import { computed, onMounted, ref, watch } from "#imports";
|
|
189
155
|
import { MEDIA_TYPES, mediaFilename, mediaIconFor } from "#nuxt-cms";
|
|
190
156
|
import { useCmsConfirm } from "../../composables/cms-confirm";
|
|
191
157
|
import { useCmsRuntime } from "../../composables/cms-runtime";
|
|
192
158
|
import { useCmsToast } from "../../composables/cms-toast";
|
|
193
|
-
import {
|
|
159
|
+
import { errorMessage } from "../../utils/ui";
|
|
194
160
|
const filterLabels = {
|
|
195
161
|
all: "All",
|
|
196
162
|
image: "Images",
|
|
@@ -214,8 +180,8 @@ async function reload() {
|
|
|
214
180
|
loading.value = true;
|
|
215
181
|
errorCode.value = null;
|
|
216
182
|
try {
|
|
217
|
-
const
|
|
218
|
-
items.value =
|
|
183
|
+
const result = await $fetch(endpoint);
|
|
184
|
+
items.value = result.items;
|
|
219
185
|
} catch (err) {
|
|
220
186
|
errorCode.value = err.statusCode ?? 500;
|
|
221
187
|
} finally {
|
|
@@ -241,6 +207,18 @@ const visible = computed(() => {
|
|
|
241
207
|
if (folder.value) list = list.filter((item) => item.folder === folder.value);
|
|
242
208
|
return list;
|
|
243
209
|
});
|
|
210
|
+
const PAGE_SIZE = 24;
|
|
211
|
+
const page = ref(1);
|
|
212
|
+
watch([filter, folder], () => {
|
|
213
|
+
page.value = 1;
|
|
214
|
+
});
|
|
215
|
+
watch(visible, (list) => {
|
|
216
|
+
const pageCount = Math.max(1, Math.ceil(list.length / PAGE_SIZE));
|
|
217
|
+
if (page.value > pageCount) page.value = pageCount;
|
|
218
|
+
});
|
|
219
|
+
const paged = computed(
|
|
220
|
+
() => visible.value.slice((page.value - 1) * PAGE_SIZE, page.value * PAGE_SIZE)
|
|
221
|
+
);
|
|
244
222
|
function onUploaded(uploaded) {
|
|
245
223
|
if (props.selectable && uploaded[0]) {
|
|
246
224
|
emit("select", uploaded[0]);
|
|
@@ -295,14 +273,6 @@ function formatSize(bytes) {
|
|
|
295
273
|
}
|
|
296
274
|
return `${Math.round(value / 1024)} TB`;
|
|
297
275
|
}
|
|
298
|
-
async function copy(item) {
|
|
299
|
-
try {
|
|
300
|
-
await navigator.clipboard.writeText(item.url ?? item.key);
|
|
301
|
-
toast.add({ title: "Copied", color: "success" });
|
|
302
|
-
} catch {
|
|
303
|
-
toast.add({ title: "Copy failed", color: "error" });
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
276
|
const confirmAction = useCmsConfirm();
|
|
307
277
|
const removing = ref(false);
|
|
308
278
|
async function remove(item) {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
value?: string | null;
|
|
3
|
+
};
|
|
4
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
5
|
+
declare const _default: typeof __VLS_export;
|
|
6
|
+
export default _default;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span v-if="!value" class="cms-media-thumb-empty">—</span>
|
|
3
|
+
|
|
4
|
+
<img
|
|
5
|
+
v-else-if="kind === 'image' && url"
|
|
6
|
+
:src="url"
|
|
7
|
+
:alt="filename"
|
|
8
|
+
:title="filename"
|
|
9
|
+
loading="lazy"
|
|
10
|
+
class="cms-media-thumb"
|
|
11
|
+
/>
|
|
12
|
+
|
|
13
|
+
<video
|
|
14
|
+
v-else-if="kind === 'video' && url"
|
|
15
|
+
:src="url"
|
|
16
|
+
:title="filename"
|
|
17
|
+
muted
|
|
18
|
+
playsinline
|
|
19
|
+
preload="metadata"
|
|
20
|
+
class="cms-media-thumb"
|
|
21
|
+
/>
|
|
22
|
+
|
|
23
|
+
<span v-else :title="filename" class="cms-media-thumb is-fallback">
|
|
24
|
+
<CmsIcon :name="icon" class="size-4" />
|
|
25
|
+
</span>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup>
|
|
29
|
+
import { computed } from "#imports";
|
|
30
|
+
import { mediaFilename, mediaIconFor, mediaPublicUrl, mediaTypeForKey } from "#nuxt-cms";
|
|
31
|
+
import { useCmsRuntime } from "../../composables/cms-runtime";
|
|
32
|
+
const props = defineProps({
|
|
33
|
+
value: { type: [String, null], required: false }
|
|
34
|
+
});
|
|
35
|
+
const { mediaBaseUrl } = useCmsRuntime();
|
|
36
|
+
const kind = computed(() => props.value ? mediaTypeForKey(props.value) : "file");
|
|
37
|
+
const url = computed(() => props.value ? mediaPublicUrl(mediaBaseUrl, props.value) : null);
|
|
38
|
+
const filename = computed(() => props.value ? mediaFilename(props.value) : "");
|
|
39
|
+
const icon = computed(() => mediaIconFor(kind.value));
|
|
40
|
+
</script>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
value?: string | null;
|
|
3
|
+
};
|
|
4
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
5
|
+
declare const _default: typeof __VLS_export;
|
|
6
|
+
export default _default;
|
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
once teleported outside the admin layout root. -->
|
|
5
5
|
<div v-if="open" class="cms-scope">
|
|
6
6
|
<div class="cms-overlay" @click.self="close">
|
|
7
|
-
<div
|
|
7
|
+
<div
|
|
8
|
+
class="cms-modal cms-rise"
|
|
9
|
+
:class="size ? `is-${size}` : void 0"
|
|
10
|
+
role="dialog"
|
|
11
|
+
aria-modal="true"
|
|
12
|
+
>
|
|
8
13
|
<div v-if="title || $slots.header" class="cms-modal-header">
|
|
9
14
|
<slot name="header">
|
|
10
|
-
<h2
|
|
11
|
-
:class="
|
|
12
|
-
ui?.title ?? 'cms-display text-xl font-medium text-(--ui-text-highlighted)'
|
|
13
|
-
"
|
|
14
|
-
>
|
|
15
|
+
<h2 class="cms-title cms-title-sm">
|
|
15
16
|
{{ title }}
|
|
16
17
|
</h2>
|
|
17
18
|
</slot>
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
import { onBeforeUnmount, watch } from "#imports";
|
|
30
31
|
defineProps({
|
|
31
32
|
title: { type: String, required: false },
|
|
32
|
-
|
|
33
|
+
size: { type: String, required: false }
|
|
33
34
|
});
|
|
34
35
|
const open = defineModel("open", { type: Boolean, ...{ default: false } });
|
|
35
36
|
const emit = defineEmits(["after:leave"]);
|