@xleddyl/nuxt-cms 0.1.60 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +137 -9
  3. package/dist/runtime/app/components/cms/BlocksField.vue +86 -10
  4. package/dist/runtime/app/components/cms/BrandMark.d.vue.ts +6 -0
  5. package/dist/runtime/app/components/cms/BrandMark.vue +19 -0
  6. package/dist/runtime/app/components/cms/BrandMark.vue.d.ts +6 -0
  7. package/dist/runtime/app/components/cms/Checkbox.d.vue.ts +11 -0
  8. package/dist/runtime/app/components/cms/Checkbox.vue +21 -0
  9. package/dist/runtime/app/components/cms/Checkbox.vue.d.ts +11 -0
  10. package/dist/runtime/app/components/cms/EmptyState.vue +4 -2
  11. package/dist/runtime/app/components/cms/EntryDrawer.vue +1 -0
  12. package/dist/runtime/app/components/cms/EntryForm.d.vue.ts +1 -0
  13. package/dist/runtime/app/components/cms/EntryForm.vue +105 -30
  14. package/dist/runtime/app/components/cms/EntryForm.vue.d.ts +1 -0
  15. package/dist/runtime/app/components/cms/FieldInput.vue +20 -5
  16. package/dist/runtime/app/components/cms/FormField.d.vue.ts +5 -3
  17. package/dist/runtime/app/components/cms/FormField.vue +8 -2
  18. package/dist/runtime/app/components/cms/FormField.vue.d.ts +5 -3
  19. package/dist/runtime/app/components/cms/Icon.vue +1 -1
  20. package/dist/runtime/app/components/cms/MediaDetail.d.vue.ts +16 -0
  21. package/dist/runtime/app/components/cms/MediaDetail.vue +169 -0
  22. package/dist/runtime/app/components/cms/MediaDetail.vue.d.ts +16 -0
  23. package/dist/runtime/app/components/cms/MediaField.vue +9 -2
  24. package/dist/runtime/app/components/cms/MediaFolderPicker.d.vue.ts +2 -2
  25. package/dist/runtime/app/components/cms/MediaFolderPicker.vue +55 -54
  26. package/dist/runtime/app/components/cms/MediaFolderPicker.vue.d.ts +2 -2
  27. package/dist/runtime/app/components/cms/MediaGallery.d.vue.ts +8 -2
  28. package/dist/runtime/app/components/cms/MediaGallery.vue +927 -336
  29. package/dist/runtime/app/components/cms/MediaGallery.vue.d.ts +8 -2
  30. package/dist/runtime/app/components/cms/MediaUpload.d.vue.ts +1 -0
  31. package/dist/runtime/app/components/cms/MediaUpload.vue +76 -139
  32. package/dist/runtime/app/components/cms/MediaUpload.vue.d.ts +1 -0
  33. package/dist/runtime/app/components/cms/PageHeader.d.vue.ts +5 -3
  34. package/dist/runtime/app/components/cms/PageHeader.vue +16 -6
  35. package/dist/runtime/app/components/cms/PageHeader.vue.d.ts +5 -3
  36. package/dist/runtime/app/components/cms/PasswordInput.d.vue.ts +17 -0
  37. package/dist/runtime/app/components/cms/PasswordInput.vue +77 -0
  38. package/dist/runtime/app/components/cms/PasswordInput.vue.d.ts +17 -0
  39. package/dist/runtime/app/components/cms/SelectMenu.vue +3 -5
  40. package/dist/runtime/app/components/cms/SettingsAccount.d.vue.ts +3 -0
  41. package/dist/runtime/app/components/cms/SettingsAccount.vue +138 -0
  42. package/dist/runtime/app/components/cms/SettingsAccount.vue.d.ts +3 -0
  43. package/dist/runtime/app/components/cms/SettingsColumns.d.vue.ts +16 -0
  44. package/dist/runtime/app/components/cms/SettingsColumns.vue +102 -0
  45. package/dist/runtime/app/components/cms/SettingsColumns.vue.d.ts +16 -0
  46. package/dist/runtime/app/components/cms/SettingsEntry.d.vue.ts +6 -0
  47. package/dist/runtime/app/components/cms/SettingsEntry.vue +133 -0
  48. package/dist/runtime/app/components/cms/SettingsEntry.vue.d.ts +6 -0
  49. package/dist/runtime/app/components/cms/SettingsLayout.d.vue.ts +16 -0
  50. package/dist/runtime/app/components/cms/SettingsLayout.vue +177 -0
  51. package/dist/runtime/app/components/cms/SettingsLayout.vue.d.ts +16 -0
  52. package/dist/runtime/app/components/cms/SettingsModal.d.vue.ts +16 -0
  53. package/dist/runtime/app/components/cms/SettingsModal.vue +179 -0
  54. package/dist/runtime/app/components/cms/SettingsModal.vue.d.ts +16 -0
  55. package/dist/runtime/app/components/cms/SettingsUsers.d.vue.ts +3 -0
  56. package/dist/runtime/app/components/cms/SettingsUsers.vue +270 -0
  57. package/dist/runtime/app/components/cms/SettingsUsers.vue.d.ts +3 -0
  58. package/dist/runtime/app/components/cms/Spinner.vue +2 -2
  59. package/dist/runtime/app/components/cms/Table.d.vue.ts +7 -2
  60. package/dist/runtime/app/components/cms/Table.vue +38 -1
  61. package/dist/runtime/app/components/cms/Table.vue.d.ts +7 -2
  62. package/dist/runtime/app/components/cms/Toaster.vue +1 -1
  63. package/dist/runtime/app/components/cms/WelcomeModal.d.vue.ts +3 -0
  64. package/dist/runtime/app/components/cms/WelcomeModal.vue +69 -0
  65. package/dist/runtime/app/components/cms/WelcomeModal.vue.d.ts +3 -0
  66. package/dist/runtime/app/composables/cms-account.d.ts +11 -0
  67. package/dist/runtime/app/composables/cms-account.js +23 -0
  68. package/dist/runtime/app/composables/cms-media-library.d.ts +75 -0
  69. package/dist/runtime/app/composables/cms-media-library.js +151 -0
  70. package/dist/runtime/app/composables/cms-media-uploader.d.ts +22 -0
  71. package/dist/runtime/app/composables/cms-media-uploader.js +244 -0
  72. package/dist/runtime/app/composables/cms-settings.d.ts +13 -0
  73. package/dist/runtime/app/composables/cms-settings.js +59 -0
  74. package/dist/runtime/app/composables/cms-theme.d.ts +9 -0
  75. package/dist/runtime/app/composables/cms-theme.js +27 -0
  76. package/dist/runtime/app/layouts/cms-admin.d.vue.ts +2 -2
  77. package/dist/runtime/app/layouts/cms-admin.vue +71 -17
  78. package/dist/runtime/app/layouts/cms-admin.vue.d.ts +2 -2
  79. package/dist/runtime/app/pages/admin-collection.vue +127 -101
  80. package/dist/runtime/app/pages/admin-entry.vue +1 -0
  81. package/dist/runtime/app/pages/admin-login.vue +18 -3
  82. package/dist/runtime/app/pages/admin-media.vue +19 -3
  83. package/dist/runtime/app/utils/ui.d.ts +2 -0
  84. package/dist/runtime/app/utils/ui.js +18 -0
  85. package/dist/runtime/assets/main.css +1 -1
  86. package/dist/runtime/server/api/account-password.put.d.ts +4 -0
  87. package/dist/runtime/server/api/account-password.put.js +38 -0
  88. package/dist/runtime/server/api/account-welcome.post.d.ts +4 -0
  89. package/dist/runtime/server/api/account-welcome.post.js +14 -0
  90. package/dist/runtime/server/api/account.put.d.ts +3 -0
  91. package/dist/runtime/server/api/account.put.js +25 -0
  92. package/dist/runtime/server/api/media-delete.post.d.ts +4 -0
  93. package/dist/runtime/server/api/media-delete.post.js +23 -0
  94. package/dist/runtime/server/api/media-folder.delete.js +6 -16
  95. package/dist/runtime/server/api/media-folder.patch.d.ts +5 -0
  96. package/dist/runtime/server/api/media-folder.patch.js +68 -0
  97. package/dist/runtime/server/api/media-folder.post.js +3 -15
  98. package/dist/runtime/server/api/media-move.post.d.ts +5 -0
  99. package/dist/runtime/server/api/media-move.post.js +28 -0
  100. package/dist/runtime/server/api/media-usage.post.d.ts +5 -0
  101. package/dist/runtime/server/api/media-usage.post.js +15 -0
  102. package/dist/runtime/server/api/settings.delete.d.ts +4 -0
  103. package/dist/runtime/server/api/settings.delete.js +12 -0
  104. package/dist/runtime/server/api/settings.get.d.ts +3 -0
  105. package/dist/runtime/server/api/settings.get.js +15 -0
  106. package/dist/runtime/server/api/settings.put.d.ts +3 -0
  107. package/dist/runtime/server/api/settings.put.js +14 -0
  108. package/dist/runtime/server/api/user.delete.d.ts +4 -0
  109. package/dist/runtime/server/api/user.delete.js +12 -0
  110. package/dist/runtime/server/api/user.put.d.ts +3 -0
  111. package/dist/runtime/server/api/user.put.js +27 -0
  112. package/dist/runtime/server/api/users.get.d.ts +3 -0
  113. package/dist/runtime/server/api/users.get.js +11 -0
  114. package/dist/runtime/server/api/users.post.d.ts +3 -0
  115. package/dist/runtime/server/api/users.post.js +32 -0
  116. package/dist/runtime/server/plugins/session-validate.d.ts +2 -0
  117. package/dist/runtime/server/plugins/session-validate.js +13 -0
  118. package/dist/runtime/server/routes/auth/login.post.js +18 -8
  119. package/dist/runtime/server/utils/media-folders.d.ts +86 -0
  120. package/dist/runtime/server/utils/media-folders.js +37 -0
  121. package/dist/runtime/server/utils/media-references.d.ts +7 -1
  122. package/dist/runtime/server/utils/media-references.js +40 -0
  123. package/dist/runtime/server/utils/media-usage.d.ts +2 -0
  124. package/dist/runtime/server/utils/media-usage.js +18 -0
  125. package/dist/runtime/server/utils/password.d.ts +3 -0
  126. package/dist/runtime/server/utils/password.js +31 -0
  127. package/dist/runtime/server/utils/require-admin.d.ts +6 -2
  128. package/dist/runtime/server/utils/require-admin.js +33 -28
  129. package/dist/runtime/server/utils/same-origin.d.ts +2 -0
  130. package/dist/runtime/server/utils/same-origin.js +24 -0
  131. package/dist/runtime/server/utils/settings.d.ts +16 -0
  132. package/dist/runtime/server/utils/settings.js +31 -0
  133. package/dist/runtime/server/utils/users.d.ts +15 -0
  134. package/dist/runtime/server/utils/users.js +34 -0
  135. package/dist/runtime/shared/index.d.ts +29 -1
  136. package/dist/runtime/shared/index.js +36 -2
  137. package/dist/runtime/shared/layout.d.ts +34 -0
  138. package/dist/runtime/shared/layout.js +130 -0
  139. package/dist/runtime/shared/users.d.ts +18 -0
  140. package/dist/runtime/shared/users.js +1 -0
  141. package/dist/runtime/shared/validation.js +5 -4
  142. package/package.json +3 -3
@@ -14,29 +14,72 @@
14
14
  {{ tab.label }}
15
15
  </button>
16
16
  </div>
17
- <CmsFormField
18
- v-for="(field, key) in visibleFields"
19
- :key="key"
20
- :label="field.label"
21
- :name="key"
22
- :required="field.required"
23
- >
24
- <template v-if="hasLocaleSwitch(field) && i18n.locales.length > 1" #label-actions>
25
- <CmsLocaleSwitch
26
- :model-value="localeFor(key)"
27
- :value="
28
- isTranslatableField(field) ? state[key] : null
17
+ <div class="cms-form-layout">
18
+ <div class="cms-form-main">
19
+ <component
20
+ :is="section.title ? 'section' : 'div'"
21
+ v-for="section in sections"
22
+ :key="section.id"
23
+ class="cms-form-section"
24
+ :class="{ 'is-titled': !!section.title, 'is-collapsed': isCollapsed(section) }"
25
+ >
26
+ <button
27
+ v-if="section.title"
28
+ type="button"
29
+ class="cms-form-section-header"
30
+ :aria-expanded="!isCollapsed(section)"
31
+ @click="toggleSection(section.id)"
32
+ >
33
+ <span class="cms-form-section-text">
34
+ <span class="cms-form-section-title">{{ section.title }}</span>
35
+ <span v-if="section.description" class="cms-form-section-description">
36
+ {{ section.description }}
37
+ </span>
38
+ </span>
39
+ <CmsIcon name="chevron-down" class="cms-form-section-chevron size-4" />
40
+ </button>
41
+ <div v-show="!isCollapsed(section)" class="cms-form-rows">
42
+ <div
43
+ v-for="row in section.rows"
44
+ :key="row.join('|')"
45
+ class="cms-form-row"
46
+ :style="{ '--cms-row-cols': row.length }"
47
+ >
48
+ <CmsFormField
49
+ v-for="key in row"
50
+ :key="key"
51
+ :label="fields[key].label"
52
+ :description="fields[key].description"
53
+ :icon="fieldIcon(fields[key])"
54
+ :name="key"
55
+ :required="fields[key].required"
56
+ >
57
+ <template
58
+ v-if="hasLocaleSwitch(fields[key]) && i18n.locales.length > 1"
59
+ #label-actions
60
+ >
61
+ <CmsLocaleSwitch
62
+ :model-value="localeFor(key)"
63
+ :value="
64
+ isTranslatableField(fields[key]) ? state[key] : null
29
65
  "
30
- @update:model-value="(locale) => setLocale(key, locale)"
31
- />
32
- </template>
33
- <CmsFieldInput
34
- v-model="state[key]"
35
- :field="field"
36
- :locale="localeFor(key)"
37
- :slug-source="field.from ? state[field.from] : void 0"
38
- />
39
- </CmsFormField>
66
+ @update:model-value="(locale) => setLocale(key, locale)"
67
+ />
68
+ </template>
69
+ <CmsFieldInput
70
+ v-model="state[key]"
71
+ :field="fields[key]"
72
+ :locale="localeFor(key)"
73
+ :slug-source="
74
+ fields[key].from ? state[fields[key].from] : void 0
75
+ "
76
+ />
77
+ </CmsFormField>
78
+ </div>
79
+ </div>
80
+ </component>
81
+ </div>
82
+ </div>
40
83
  <div v-if="footer" class="cms-form-actions">
41
84
  <CmsButton type="submit" label="Save" :loading="loading" />
42
85
  <CmsButton
@@ -55,14 +98,18 @@ import {
55
98
  fieldTab,
56
99
  hasTranslatableBlockFields,
57
100
  isFieldVisible,
58
- isTranslatableField
101
+ isTranslatableField,
102
+ resolveFormLayout
59
103
  } from "#nuxt-cms";
60
- import { computed, ref, watch } from "#imports";
104
+ import { computed, nextTick, ref, watch } from "#imports";
61
105
  import { buildEntrySchema } from "../../../shared/validation";
62
106
  import { useCmsRuntime } from "../../composables/cms-runtime";
107
+ import { entryFormLayout, useCmsSettingsState } from "../../composables/cms-settings";
108
+ import { fieldIcon } from "../../utils/ui";
63
109
  const props = defineProps({
64
110
  fields: { type: Object, required: true },
65
111
  tabs: { type: Array, required: false },
112
+ entryName: { type: String, required: false },
66
113
  drafts: { type: Boolean, required: false },
67
114
  loading: { type: Boolean, required: false },
68
115
  formId: { type: String, required: false },
@@ -77,13 +124,40 @@ const activeTab = ref(tabList.value[0]?.id ?? "");
77
124
  watch(tabList, (tabs) => {
78
125
  if (!tabs.some((tab) => tab.id === activeTab.value)) activeTab.value = tabs[0]?.id ?? "";
79
126
  });
80
- const visibleFields = computed(
81
- () => Object.fromEntries(
82
- Object.entries(props.fields).filter(
83
- ([, field]) => isFieldVisible(field, state.value) && (!tabList.value.length || fieldTab(field, tabList.value) === activeTab.value)
84
- )
85
- )
127
+ const settings = useCmsSettingsState();
128
+ const fields = computed(() => props.fields);
129
+ const layout = computed(
130
+ () => props.entryName ? entryFormLayout(props.entryName, settings.value, props.fields) : resolveFormLayout(props.fields, void 0)
131
+ );
132
+ function isShown(key) {
133
+ const field = props.fields[key];
134
+ if (!field || !isFieldVisible(field, state.value)) return false;
135
+ if (!tabList.value.length) return true;
136
+ return fieldTab(field, tabList.value) === activeTab.value;
137
+ }
138
+ const sections = computed(
139
+ () => layout.value.main.map((section, index) => ({
140
+ ...section,
141
+ id: `${index}-${section.title ?? ""}`,
142
+ rows: section.rows.map((row) => row.filter((key) => isShown(key))).filter((row) => row.length)
143
+ })).filter((section) => section.rows.length)
86
144
  );
145
+ const toggled = ref({});
146
+ function isCollapsed(section) {
147
+ if (!section.title) return false;
148
+ return toggled.value[section.id] ?? !!section.collapsed;
149
+ }
150
+ function toggleSection(id) {
151
+ const section = sections.value.find((s) => s.id === id);
152
+ if (section) toggled.value = { ...toggled.value, [id]: !isCollapsed(section) };
153
+ }
154
+ function revealField(key) {
155
+ for (const section of sections.value) {
156
+ if (section.rows.some((row) => row.includes(key)) && isCollapsed(section)) {
157
+ toggled.value = { ...toggled.value, [section.id]: false };
158
+ }
159
+ }
160
+ }
87
161
  function onError() {
88
162
  const issue = schema.value.safeParse(state.value).error?.issues[0];
89
163
  const key = issue ? String(issue.path[0] ?? "") : "";
@@ -92,6 +166,7 @@ function onError() {
92
166
  const tab = fieldTab(field, tabList.value);
93
167
  if (tab) activeTab.value = tab;
94
168
  }
169
+ if (key) void nextTick(() => revealField(key));
95
170
  emit("error");
96
171
  }
97
172
  function hasLocaleSwitch(field) {
@@ -2,6 +2,7 @@ import type { CmsEntry, CmsTab } from '#nuxt-cms';
2
2
  type __VLS_Props = {
3
3
  fields: CmsEntry['fields'];
4
4
  tabs?: CmsTab[];
5
+ entryName?: string;
5
6
  drafts?: boolean;
6
7
  loading?: boolean;
7
8
  formId?: string;
@@ -12,16 +12,26 @@
12
12
  :locale="locale"
13
13
  />
14
14
  <CmsRichTextField v-else-if="field.type === 'richtext'" v-model="strOrNull" />
15
- <CmsTextarea v-else-if="field.type === 'text' && field.textarea" v-model="str" :rows="8" />
16
- <CmsInput v-else-if="field.type === 'text'" v-model="str" />
17
- <CmsInput v-else-if="field.type === 'email'" v-model="str" type="email" />
15
+ <CmsTextarea
16
+ v-else-if="field.type === 'text' && field.textarea"
17
+ v-model="str"
18
+ :rows="8"
19
+ :placeholder="field.placeholder"
20
+ />
21
+ <CmsInput v-else-if="field.type === 'text'" v-model="str" :placeholder="field.placeholder" />
22
+ <CmsInput
23
+ v-else-if="field.type === 'email'"
24
+ v-model="str"
25
+ type="email"
26
+ :placeholder="field.placeholder"
27
+ />
18
28
  <CmsSlugField v-else-if="field.type === 'slug'" v-model="strOrNull" :source="slugSource" />
19
29
  <CmsSelectMenu
20
30
  v-else-if="field.type === 'select'"
21
31
  v-model="selValue"
22
32
  :multiple="field.multiple"
23
33
  :items="selectItems"
24
- placeholder="Select…"
34
+ :placeholder="field.placeholder ?? 'Select\u2026'"
25
35
  />
26
36
  <CmsSwitch
27
37
  v-else-if="field.type === 'boolean'"
@@ -33,7 +43,12 @@
33
43
  "
34
44
  />
35
45
  <CmsJsonField v-else-if="field.type === 'json'" v-model="jsonValue" />
36
- <CmsInput v-else-if="field.type === 'number'" v-model.number="num" type="number" />
46
+ <CmsInput
47
+ v-else-if="field.type === 'number'"
48
+ v-model.number="num"
49
+ type="number"
50
+ :placeholder="field.placeholder"
51
+ />
37
52
  <CmsInput v-else-if="field.type === 'date'" v-model="str" type="date" />
38
53
  <CmsRelationField
39
54
  v-else-if="field.type === 'relation'"
@@ -1,13 +1,15 @@
1
1
  type __VLS_Props = {
2
2
  label?: string;
3
+ icon?: string;
4
+ description?: string;
3
5
  name?: string;
4
6
  required?: boolean;
5
7
  };
6
- declare var __VLS_1: {}, __VLS_3: {};
8
+ declare var __VLS_6: {}, __VLS_8: {};
7
9
  type __VLS_Slots = {} & {
8
- 'label-actions'?: (props: typeof __VLS_1) => any;
10
+ 'label-actions'?: (props: typeof __VLS_6) => any;
9
11
  } & {
10
- default?: (props: typeof __VLS_3) => any;
12
+ default?: (props: typeof __VLS_8) => any;
11
13
  };
12
14
  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>;
13
15
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -1,13 +1,17 @@
1
1
  <template>
2
- <div class="cms-form-field">
2
+ <div class="cms-form-field" :class="{ 'is-error': error }">
3
3
  <div v-if="label || $slots['label-actions']" class="cms-form-label-row">
4
4
  <label v-if="label" class="cms-form-label">
5
+ <CmsIcon v-if="icon" :name="icon" class="cms-form-label-icon size-3.5" />
5
6
  {{ label }}<span v-if="required" class="cms-form-required"> *</span>
6
7
  </label>
7
8
  <slot name="label-actions" />
8
9
  </div>
9
10
  <slot />
10
- <p v-if="error" class="cms-form-error">{{ error }}</p>
11
+ <p v-if="description" class="cms-form-description">{{ description }}</p>
12
+ <p v-if="error" class="cms-form-error">
13
+ <CmsIcon name="exclamation-circle" class="size-3.5 shrink-0" />{{ error }}
14
+ </p>
11
15
  </div>
12
16
  </template>
13
17
 
@@ -16,6 +20,8 @@ import { computed, inject } from "#imports";
16
20
  import { CMS_FORM_ERRORS } from "../../utils/ui";
17
21
  const props = defineProps({
18
22
  label: { type: String, required: false },
23
+ icon: { type: String, required: false },
24
+ description: { type: String, required: false },
19
25
  name: { type: String, required: false },
20
26
  required: { type: Boolean, required: false }
21
27
  });
@@ -1,13 +1,15 @@
1
1
  type __VLS_Props = {
2
2
  label?: string;
3
+ icon?: string;
4
+ description?: string;
3
5
  name?: string;
4
6
  required?: boolean;
5
7
  };
6
- declare var __VLS_1: {}, __VLS_3: {};
8
+ declare var __VLS_6: {}, __VLS_8: {};
7
9
  type __VLS_Slots = {} & {
8
- 'label-actions'?: (props: typeof __VLS_1) => any;
10
+ 'label-actions'?: (props: typeof __VLS_6) => any;
9
11
  } & {
10
- default?: (props: typeof __VLS_3) => any;
12
+ default?: (props: typeof __VLS_8) => any;
11
13
  };
12
14
  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>;
13
15
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <component :is="icon" v-if="icon" class="size-5 stroke-[1.5]" />
2
+ <component :is="icon" v-if="icon" :data-icon="name" class="size-5 stroke-[1.5]" />
3
3
  </template>
4
4
 
5
5
  <script setup>
@@ -0,0 +1,16 @@
1
+ import type { MediaItem } from '#nuxt-cms';
2
+ import type { CmsMediaLibrary } from '../../composables/cms-media-library.js';
3
+ type __VLS_Props = {
4
+ item: MediaItem | null;
5
+ library: CmsMediaLibrary;
6
+ readOnly: boolean;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ delete: (item: MediaItem) => any;
10
+ close: () => any;
11
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
+ onDelete?: ((item: MediaItem) => any) | undefined;
13
+ onClose?: (() => any) | undefined;
14
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
15
+ declare const _default: typeof __VLS_export;
16
+ export default _default;
@@ -0,0 +1,169 @@
1
+ <template>
2
+ <CmsDrawer :open="!!item" :title="item ? mediaFilename(item.key) : ''" @close="emit('close')">
3
+ <div v-if="item" class="cms-media-detail">
4
+ <div class="cms-media-detail-preview">
5
+ <img v-if="item.type === 'image' && item.url" :src="item.url" :alt="item.alt ?? ''" />
6
+ <video
7
+ v-else-if="item.type === 'video' && item.url"
8
+ :src="item.url"
9
+ controls
10
+ preload="metadata"
11
+ playsinline
12
+ />
13
+ <CmsIcon v-else :name="mediaIconFor(item.type)" class="size-12" />
14
+ </div>
15
+
16
+ <div v-if="item.url" class="cms-actions">
17
+ <CmsButton
18
+ :label="copied ? 'Copied' : 'Copy URL'"
19
+ :icon="copied ? 'check' : 'clipboard-document'"
20
+ variant="soft"
21
+ color="neutral"
22
+ size="sm"
23
+ @click="copyUrl"
24
+ />
25
+ <a
26
+ :href="item.url"
27
+ target="_blank"
28
+ rel="noopener"
29
+ class="cms-btn cms-btn-sm cms-btn-neutral cms-btn-ghost"
30
+ >
31
+ <CmsIcon name="arrow-top-right-on-square" class="size-4 shrink-0" />Open
32
+ </a>
33
+ </div>
34
+
35
+ <dl class="cms-media-detail-facts">
36
+ <template v-for="fact in facts" :key="fact.label">
37
+ <dt>{{ fact.label }}</dt>
38
+ <dd :class="{ 'is-mono': fact.mono }" :title="fact.value">{{ fact.value }}</dd>
39
+ </template>
40
+ </dl>
41
+
42
+ <div class="cms-form">
43
+ <CmsFormField label="Alt text">
44
+ <CmsInput
45
+ v-model="alt"
46
+ placeholder="Describe the image for screen readers"
47
+ @keydown.enter.prevent="save"
48
+ />
49
+ </CmsFormField>
50
+ <CmsFormField v-if="!readOnly" label="Folder">
51
+ <CmsMediaFolderPicker v-model="folder" :folders="folders" />
52
+ </CmsFormField>
53
+ </div>
54
+
55
+ <section class="cms-media-detail-usage">
56
+ <h3 class="cms-media-section-title">Used in</h3>
57
+ <CmsSpinner v-if="usageLoading" />
58
+ <p v-else-if="usageList === null" class="cms-form-hint">
59
+ Could not check where this file is used.
60
+ </p>
61
+ <p v-else-if="!usageList.length" class="cms-form-hint">Not used in any entry.</p>
62
+ <ul v-else class="cms-media-usage-list">
63
+ <li v-for="(entry, index) in usageList" :key="index">
64
+ <NuxtLink
65
+ :to="usageLink(entry)"
66
+ class="cms-media-usage-link"
67
+ @click="emit('close')"
68
+ >
69
+ <span class="cms-media-usage-title">{{ entry.title ?? entry.label }}</span>
70
+ <span class="cms-media-usage-meta">{{ entry.label }} · {{ entry.field }}</span>
71
+ </NuxtLink>
72
+ </li>
73
+ </ul>
74
+ </section>
75
+ </div>
76
+
77
+ <template #footer>
78
+ <CmsButton
79
+ v-if="!readOnly"
80
+ label="Delete"
81
+ icon="trash"
82
+ variant="ghost"
83
+ color="error"
84
+ @click="item && emit('delete', item)"
85
+ />
86
+ <span v-else />
87
+ <div class="cms-actions">
88
+ <CmsButton label="Close" variant="ghost" color="neutral" @click="emit('close')" />
89
+ <CmsButton label="Save" :loading="saving" :disabled="!dirty" @click="save" />
90
+ </div>
91
+ </template>
92
+ </CmsDrawer>
93
+ </template>
94
+
95
+ <script setup>
96
+ import { computed, ref, watch } from "#imports";
97
+ import { formatFileSize, mediaFilename, mediaIconFor } from "#nuxt-cms";
98
+ import { usageLink } from "../../composables/cms-media-library";
99
+ const props = defineProps({
100
+ item: { type: [Object, null], required: true },
101
+ library: { type: null, required: true },
102
+ readOnly: { type: Boolean, required: true }
103
+ });
104
+ const emit = defineEmits(["close", "delete"]);
105
+ const folders = computed(() => props.library.folders.value);
106
+ const alt = ref("");
107
+ const folder = ref(null);
108
+ const saving = ref(false);
109
+ const copied = ref(false);
110
+ const usageList = ref([]);
111
+ const usageLoading = ref(false);
112
+ watch(
113
+ () => props.item?.key,
114
+ async (key) => {
115
+ if (!props.item || !key) return;
116
+ alt.value = props.item.alt ?? "";
117
+ folder.value = props.item.folder ?? null;
118
+ copied.value = false;
119
+ usageLoading.value = true;
120
+ const usage = await props.library.usage([key]);
121
+ if (props.item?.key !== key) return;
122
+ usageList.value = usage ? usage[key] ?? [] : null;
123
+ usageLoading.value = false;
124
+ },
125
+ { immediate: true }
126
+ );
127
+ const dirty = computed(() => {
128
+ if (!props.item) return false;
129
+ return (alt.value.trim() || null) !== (props.item.alt ?? null) || !props.readOnly && folder.value !== (props.item.folder ?? null);
130
+ });
131
+ const facts = computed(() => {
132
+ const item = props.item;
133
+ if (!item) return [];
134
+ const list = [
135
+ { label: "Folder", value: item.folder ?? "Library root" },
136
+ { label: "Type", value: item.mime ?? item.type }
137
+ ];
138
+ if (item.size != null) list.push({ label: "Size", value: formatFileSize(item.size) });
139
+ if (item.width && item.height) {
140
+ list.push({ label: "Dimensions", value: `${item.width} \xD7 ${item.height}` });
141
+ }
142
+ if (item.createdAt) {
143
+ list.push({ label: "Uploaded", value: new Date(item.createdAt).toLocaleString() });
144
+ }
145
+ list.push({ label: "Key", value: item.key, mono: true });
146
+ return list;
147
+ });
148
+ async function copyUrl() {
149
+ if (!props.item?.url) return;
150
+ const url = new URL(props.item.url, window.location.origin).toString();
151
+ try {
152
+ await navigator.clipboard.writeText(url);
153
+ copied.value = true;
154
+ setTimeout(() => copied.value = false, 1500);
155
+ } catch {
156
+ copied.value = false;
157
+ }
158
+ }
159
+ async function save() {
160
+ if (!props.item || !dirty.value || saving.value) return;
161
+ saving.value = true;
162
+ const ok = await props.library.updateItem(props.item.key, {
163
+ alt: alt.value.trim() || null,
164
+ ...props.readOnly ? {} : { folder: folder.value }
165
+ });
166
+ saving.value = false;
167
+ if (ok) emit("close");
168
+ }
169
+ </script>
@@ -0,0 +1,16 @@
1
+ import type { MediaItem } from '#nuxt-cms';
2
+ import type { CmsMediaLibrary } from '../../composables/cms-media-library.js';
3
+ type __VLS_Props = {
4
+ item: MediaItem | null;
5
+ library: CmsMediaLibrary;
6
+ readOnly: boolean;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ delete: (item: MediaItem) => any;
10
+ close: () => any;
11
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
+ onDelete?: ((item: MediaItem) => any) | undefined;
13
+ onClose?: (() => any) | undefined;
14
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
15
+ declare const _default: typeof __VLS_export;
16
+ export default _default;
@@ -55,13 +55,19 @@
55
55
 
56
56
  <CmsModal v-model:open="galleryOpen" title="Media" size="lg">
57
57
  <template #body>
58
- <CmsMediaGallery selectable :media-type="mediaType" :accept="accept" @select="onSelect" />
58
+ <CmsMediaGallery
59
+ v-model:folder="pickerFolder"
60
+ selectable
61
+ :media-type="mediaType"
62
+ :accept="accept"
63
+ @select="onSelect"
64
+ />
59
65
  </template>
60
66
  </CmsModal>
61
67
  </template>
62
68
 
63
69
  <script setup>
64
- import { computed, ref } from "#imports";
70
+ import { computed, ref, useState } from "#imports";
65
71
  import { mediaFilename, mediaIconFor, mediaPublicUrl, mediaTypeForKey } from "#nuxt-cms";
66
72
  import { useCmsMediaKeys } from "../../composables/cms-media-keys";
67
73
  import { useCmsRuntime } from "../../composables/cms-runtime";
@@ -73,6 +79,7 @@ const model = defineModel({ type: [String, null], ...{ required: true } });
73
79
  const { mediaBaseUrl } = useCmsRuntime();
74
80
  const mediaKeys = useCmsMediaKeys();
75
81
  const galleryOpen = ref(false);
82
+ const pickerFolder = useState("cms-media-picker-folder", () => null);
76
83
  function onSelect(item) {
77
84
  mediaKeys.remember(item.key);
78
85
  model.value = item.key;
@@ -1,5 +1,7 @@
1
1
  type __VLS_Props = {
2
2
  folders: string[];
3
+ exclude?: string | null;
4
+ disabled?: (string | null)[];
3
5
  };
4
6
  type __VLS_ModelProps = {
5
7
  modelValue?: string | null;
@@ -7,10 +9,8 @@ type __VLS_ModelProps = {
7
9
  type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
8
10
  declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
11
  "update:modelValue": (value: string | null) => any;
10
- create: (folder: string) => any;
11
12
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
12
13
  "onUpdate:modelValue"?: ((value: string | null) => any) | undefined;
13
- onCreate?: ((folder: string) => any) | undefined;
14
14
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
15
15
  declare const _default: typeof __VLS_export;
16
16
  export default _default;