@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.
- package/dist/module.json +1 -1
- package/dist/module.mjs +53 -23
- package/dist/runtime/app/components/cms/MediaDetail.d.vue.ts +16 -0
- package/dist/runtime/app/components/cms/MediaDetail.vue +169 -0
- package/dist/runtime/app/components/cms/MediaDetail.vue.d.ts +16 -0
- package/dist/runtime/app/components/cms/MediaField.vue +9 -2
- package/dist/runtime/app/components/cms/MediaFolderPicker.d.vue.ts +2 -2
- package/dist/runtime/app/components/cms/MediaFolderPicker.vue +55 -54
- package/dist/runtime/app/components/cms/MediaFolderPicker.vue.d.ts +2 -2
- package/dist/runtime/app/components/cms/MediaGallery.d.vue.ts +8 -2
- package/dist/runtime/app/components/cms/MediaGallery.vue +913 -336
- package/dist/runtime/app/components/cms/MediaGallery.vue.d.ts +8 -2
- package/dist/runtime/app/components/cms/MediaUpload.vue +10 -103
- package/dist/runtime/app/composables/cms-media-library.d.ts +75 -0
- package/dist/runtime/app/composables/cms-media-library.js +151 -0
- package/dist/runtime/app/composables/cms-media-uploader.d.ts +12 -0
- package/dist/runtime/app/composables/cms-media-uploader.js +117 -0
- package/dist/runtime/app/pages/admin-media.vue +14 -2
- package/dist/runtime/assets/main.css +1 -1
- package/dist/runtime/server/api/media-delete.post.d.ts +4 -0
- package/dist/runtime/server/api/media-delete.post.js +23 -0
- package/dist/runtime/server/api/media-folder.delete.js +6 -16
- package/dist/runtime/server/api/media-folder.patch.d.ts +5 -0
- package/dist/runtime/server/api/media-folder.patch.js +68 -0
- package/dist/runtime/server/api/media-folder.post.js +3 -15
- package/dist/runtime/server/api/media-move.post.d.ts +5 -0
- package/dist/runtime/server/api/media-move.post.js +28 -0
- package/dist/runtime/server/api/media-usage.post.d.ts +5 -0
- package/dist/runtime/server/api/media-usage.post.js +15 -0
- package/dist/runtime/server/utils/media-folders.d.ts +86 -0
- package/dist/runtime/server/utils/media-folders.js +37 -0
- package/dist/runtime/server/utils/media-references.d.ts +7 -1
- package/dist/runtime/server/utils/media-references.js +40 -0
- package/dist/runtime/server/utils/media-usage.d.ts +2 -0
- package/dist/runtime/server/utils/media-usage.js +18 -0
- package/dist/runtime/shared/index.d.ts +23 -1
- package/dist/runtime/shared/index.js +86 -2
- 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
|
-
|
|
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.
|
|
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)",
|