@xleddyl/nuxt-cms 0.1.59 → 0.1.61

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 (38) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +53 -23
  3. package/dist/runtime/app/components/cms/MediaDetail.d.vue.ts +16 -0
  4. package/dist/runtime/app/components/cms/MediaDetail.vue +169 -0
  5. package/dist/runtime/app/components/cms/MediaDetail.vue.d.ts +16 -0
  6. package/dist/runtime/app/components/cms/MediaField.vue +9 -2
  7. package/dist/runtime/app/components/cms/MediaFolderPicker.d.vue.ts +2 -2
  8. package/dist/runtime/app/components/cms/MediaFolderPicker.vue +55 -54
  9. package/dist/runtime/app/components/cms/MediaFolderPicker.vue.d.ts +2 -2
  10. package/dist/runtime/app/components/cms/MediaGallery.d.vue.ts +8 -2
  11. package/dist/runtime/app/components/cms/MediaGallery.vue +913 -336
  12. package/dist/runtime/app/components/cms/MediaGallery.vue.d.ts +8 -2
  13. package/dist/runtime/app/components/cms/MediaUpload.vue +10 -103
  14. package/dist/runtime/app/composables/cms-media-library.d.ts +75 -0
  15. package/dist/runtime/app/composables/cms-media-library.js +151 -0
  16. package/dist/runtime/app/composables/cms-media-uploader.d.ts +12 -0
  17. package/dist/runtime/app/composables/cms-media-uploader.js +117 -0
  18. package/dist/runtime/app/pages/admin-media.vue +14 -2
  19. package/dist/runtime/assets/main.css +1 -1
  20. package/dist/runtime/server/api/media-delete.post.d.ts +4 -0
  21. package/dist/runtime/server/api/media-delete.post.js +23 -0
  22. package/dist/runtime/server/api/media-folder.delete.js +6 -16
  23. package/dist/runtime/server/api/media-folder.patch.d.ts +5 -0
  24. package/dist/runtime/server/api/media-folder.patch.js +68 -0
  25. package/dist/runtime/server/api/media-folder.post.js +3 -15
  26. package/dist/runtime/server/api/media-move.post.d.ts +5 -0
  27. package/dist/runtime/server/api/media-move.post.js +28 -0
  28. package/dist/runtime/server/api/media-usage.post.d.ts +5 -0
  29. package/dist/runtime/server/api/media-usage.post.js +15 -0
  30. package/dist/runtime/server/utils/media-folders.d.ts +86 -0
  31. package/dist/runtime/server/utils/media-folders.js +37 -0
  32. package/dist/runtime/server/utils/media-references.d.ts +7 -1
  33. package/dist/runtime/server/utils/media-references.js +40 -0
  34. package/dist/runtime/server/utils/media-usage.d.ts +2 -0
  35. package/dist/runtime/server/utils/media-usage.js +18 -0
  36. package/dist/runtime/shared/index.d.ts +23 -1
  37. package/dist/runtime/shared/index.js +86 -2
  38. package/package.json +1 -1
@@ -23,7 +23,15 @@ export declare const MEDIA_FOLDER_MAX_DEPTH = 4;
23
23
  export declare const MEDIA_FOLDER_MARKER = ".keep";
24
24
  export declare function mediaFolderMarkerKey(folder: string): string;
25
25
  export declare function isMediaFolderMarker(key: string): boolean;
26
+ export declare function mediaFolderSegments(value: string | null | undefined): string[];
26
27
  export declare function normalizeMediaFolder(value: string | null | undefined): string | null;
28
+ export declare function mediaFolderDepth(folder: string | null | undefined): number;
29
+ export declare function mediaFolderParent(folder: string): string | null;
30
+ export declare function mediaFolderName(folder: string): string;
31
+ export declare function mediaFolderAncestors(folder: string): string[];
32
+ export declare function isWithinMediaFolder(folder: string | null | undefined, parent: string | null): boolean;
33
+ export declare function rebaseMediaFolder(folder: string, from: string, to: string): string;
34
+ export declare function expandMediaFolders(folders: Iterable<string | null | undefined>): string[];
27
35
  export interface MediaItem {
28
36
  id?: number;
29
37
  key: string;
@@ -43,6 +51,14 @@ export interface MediaSourceInfo {
43
51
  root: string;
44
52
  builtAt: string | null;
45
53
  }
54
+ export interface MediaUsage {
55
+ collection: string;
56
+ label: string;
57
+ kind: CmsEntryKind;
58
+ id: string | null;
59
+ title: string | null;
60
+ field: string;
61
+ }
46
62
  export interface BlockConfig {
47
63
  label: string;
48
64
  fields: Record<string, FieldConfig>;
@@ -72,6 +88,8 @@ export interface FieldConfig {
72
88
  blocks?: Record<string, BlockConfig>;
73
89
  mediaType?: MediaType | MediaType[];
74
90
  accept?: string[];
91
+ mobile?: boolean;
92
+ mobileOf?: string;
75
93
  to?: string;
76
94
  cardinality?: 'many-to-one' | 'one-to-one' | 'many-to-many';
77
95
  onDelete?: 'set null' | 'cascade' | 'restrict';
@@ -121,6 +139,9 @@ export interface CmsEntry {
121
139
  tabs?: CmsTab[];
122
140
  table?: CmsTable;
123
141
  }
142
+ export declare const MOBILE_MEDIA_SUFFIX = "Mobile";
143
+ export declare function mobileMediaKey(key: string): string;
144
+ export declare function expandMobileMedia<T extends Record<string, unknown>>(config: T): T;
124
145
  export declare function entryTabs(entry: Pick<CmsEntry, 'tabs'>): CmsTab[];
125
146
  export declare function fieldTab(field: FieldConfig, tabs: CmsTab[]): string | undefined;
126
147
  export declare function fieldsByTab(fields: Record<string, FieldConfig>, tabs: CmsTab[]): Record<string, Record<string, FieldConfig>>;
@@ -191,6 +212,7 @@ export interface MediaFieldInput extends FieldInputBase {
191
212
  mediaType?: MediaType | MediaType[];
192
213
  accept?: string[];
193
214
  translatable?: boolean;
215
+ mobile?: boolean;
194
216
  }
195
217
  export interface RelationFieldInput extends FieldInputBase {
196
218
  type: 'relation';
@@ -199,7 +221,7 @@ export interface RelationFieldInput extends FieldInputBase {
199
221
  onDelete?: 'set null' | 'cascade' | 'restrict';
200
222
  }
201
223
  type BlockField<T> = Omit<T, 'private' | 'showIf' | 'tab'>;
202
- export type BlockFieldInput = BlockField<TextFieldInput> | BlockField<RichtextFieldInput> | BlockField<NumberFieldInput> | BlockField<BooleanFieldInput> | BlockField<DateFieldInput> | BlockField<EmailFieldInput> | BlockField<SelectFieldInput> | BlockField<JsonFieldInput> | BlockField<MediaFieldInput>;
224
+ export type BlockFieldInput = BlockField<TextFieldInput> | BlockField<RichtextFieldInput> | BlockField<NumberFieldInput> | BlockField<BooleanFieldInput> | BlockField<DateFieldInput> | BlockField<EmailFieldInput> | BlockField<SelectFieldInput> | BlockField<JsonFieldInput> | Omit<BlockField<MediaFieldInput>, 'mobile'>;
203
225
  export interface BlockInput {
204
226
  label: string;
205
227
  fields: Record<string, BlockFieldInput>;
@@ -73,11 +73,43 @@ export function mediaFolderMarkerKey(folder) {
73
73
  export function isMediaFolderMarker(key) {
74
74
  return key.endsWith(`/${MEDIA_FOLDER_MARKER}`);
75
75
  }
76
+ export function mediaFolderSegments(value) {
77
+ return value ? value.split("/").map(slugify).filter(Boolean) : [];
78
+ }
76
79
  export function normalizeMediaFolder(value) {
77
- if (!value) return null;
78
- const segments = value.split("/").map(slugify).filter(Boolean).slice(0, MEDIA_FOLDER_MAX_DEPTH);
80
+ const segments = mediaFolderSegments(value).slice(0, MEDIA_FOLDER_MAX_DEPTH);
79
81
  return segments.length ? segments.join("/") : null;
80
82
  }
83
+ export function mediaFolderDepth(folder) {
84
+ return folder ? folder.split("/").length : 0;
85
+ }
86
+ export function mediaFolderParent(folder) {
87
+ const index = folder.lastIndexOf("/");
88
+ return index === -1 ? null : folder.slice(0, index);
89
+ }
90
+ export function mediaFolderName(folder) {
91
+ return folder.slice(folder.lastIndexOf("/") + 1);
92
+ }
93
+ export function mediaFolderAncestors(folder) {
94
+ const segments = folder.split("/");
95
+ return segments.map((_, index) => segments.slice(0, index + 1).join("/"));
96
+ }
97
+ export function isWithinMediaFolder(folder, parent) {
98
+ if (!parent) return true;
99
+ return !!folder && (folder === parent || folder.startsWith(`${parent}/`));
100
+ }
101
+ export function rebaseMediaFolder(folder, from, to) {
102
+ if (folder === from) return to;
103
+ return folder.startsWith(`${from}/`) ? `${to}${folder.slice(from.length)}` : folder;
104
+ }
105
+ export function expandMediaFolders(folders) {
106
+ const expanded = /* @__PURE__ */ new Set();
107
+ for (const folder of folders) {
108
+ if (!folder) continue;
109
+ for (const ancestor of mediaFolderAncestors(folder)) expanded.add(ancestor);
110
+ }
111
+ return [...expanded].sort();
112
+ }
81
113
  export function isPrivateField(field) {
82
114
  return !!field.private;
83
115
  }
@@ -192,6 +224,58 @@ export function localizeBlocks(field, value, locale, defaultLocale) {
192
224
  return value.map((item) => localizeBlock(field, item, locale, defaultLocale));
193
225
  }
194
226
  export const CMS_GRAPHQL_BATCH_LIMIT = 20;
227
+ export const MOBILE_MEDIA_SUFFIX = "Mobile";
228
+ export function mobileMediaKey(key) {
229
+ return `${key}${MOBILE_MEDIA_SUFFIX}`;
230
+ }
231
+ function mobileMediaField(key, field) {
232
+ const { mobile: _mobile, required: _required, ...rest } = field;
233
+ return {
234
+ ...rest,
235
+ label: `${field.label} (mobile)`,
236
+ mobileOf: key
237
+ };
238
+ }
239
+ function withMobileMedia(fields, removed = () => false) {
240
+ const expanded = {};
241
+ for (const [key, field] of Object.entries(fields)) {
242
+ if (field?.mobileOf && Object.hasOwn(fields, field.mobileOf)) continue;
243
+ expanded[key] = field;
244
+ const mobileKey = mobileMediaKey(key);
245
+ if (field?.mobile && field.type === "media" && !Object.hasOwn(fields, mobileKey)) {
246
+ expanded[mobileKey] = mobileMediaField(key, field);
247
+ } else if (field?.mobile && fields[mobileKey]?.mobileOf === key) {
248
+ expanded[mobileKey] = fields[mobileKey];
249
+ } else if (!field && removed(key) && !Object.hasOwn(fields, mobileKey)) {
250
+ expanded[mobileKey] = null;
251
+ }
252
+ }
253
+ return expanded;
254
+ }
255
+ export function expandMobileMedia(config) {
256
+ for (const entry of Object.values(config)) {
257
+ if (!entry?.fields) continue;
258
+ const shared = entry.fields;
259
+ const hasMobile = (key) => !!shared[key]?.mobile && shared[key]?.type === "media";
260
+ entry.fields = withMobileMedia(shared);
261
+ if (entry.overrides) {
262
+ entry.overrides = Object.fromEntries(
263
+ Object.entries(entry.overrides).map(([path, override]) => [
264
+ path,
265
+ withMobileMedia(override, hasMobile)
266
+ ])
267
+ );
268
+ }
269
+ if (entry.columns) {
270
+ entry.columns = [
271
+ ...new Set(
272
+ entry.columns.flatMap((key) => hasMobile(key) ? [key, mobileMediaKey(key)] : [key])
273
+ )
274
+ ];
275
+ }
276
+ }
277
+ return config;
278
+ }
195
279
  export function entryTabs(entry) {
196
280
  return entry.tabs?.length ? entry.tabs : [];
197
281
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xleddyl/nuxt-cms",
3
- "version": "0.1.59",
3
+ "version": "0.1.61",
4
4
  "description": "Lightweight CMS that ships with your Nuxt app: runs on the Nitro server, content types defined in code, /cms admin panel, GraphQL API, SQLite or Postgres. No external CMS needed!",
5
5
  "license": "MIT",
6
6
  "author": "Edoardo Alberti (https://github.com/xleddyl)",