@xleddyl/nuxt-cms 0.1.39 → 0.1.40
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/runtime/app/components/cms/MediaField.d.vue.ts +1 -1
- package/dist/runtime/app/components/cms/MediaField.vue.d.ts +1 -1
- package/dist/runtime/app/components/cms/MediaGallery.d.vue.ts +1 -1
- package/dist/runtime/app/components/cms/MediaGallery.vue +19 -8
- package/dist/runtime/app/components/cms/MediaGallery.vue.d.ts +1 -1
- package/dist/runtime/app/components/cms/MediaUpload.d.vue.ts +1 -1
- package/dist/runtime/app/components/cms/MediaUpload.vue +25 -4
- package/dist/runtime/app/components/cms/MediaUpload.vue.d.ts +1 -1
- package/dist/runtime/server/utils/media-sync.d.ts +3 -0
- package/dist/runtime/server/utils/media-sync.js +218 -5
- package/dist/runtime/shared/index.d.ts +4 -2
- package/dist/runtime/shared/index.js +10 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { MediaType } from '#nuxt-cms';
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
selectable?: boolean;
|
|
4
|
-
mediaType?: MediaType;
|
|
4
|
+
mediaType?: MediaType | MediaType[];
|
|
5
5
|
accept?: string[];
|
|
6
6
|
};
|
|
7
7
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
@@ -243,7 +243,13 @@
|
|
|
243
243
|
|
|
244
244
|
<script setup>
|
|
245
245
|
import { computed, onMounted, ref, watch } from "#imports";
|
|
246
|
-
import {
|
|
246
|
+
import {
|
|
247
|
+
MEDIA_TYPES,
|
|
248
|
+
mediaFilename,
|
|
249
|
+
mediaIconFor,
|
|
250
|
+
mediaTypeFilter,
|
|
251
|
+
normalizeMediaFolder
|
|
252
|
+
} from "#nuxt-cms";
|
|
247
253
|
import { useCmsConfirm } from "../../composables/cms-confirm";
|
|
248
254
|
import { useCmsRuntime } from "../../composables/cms-runtime";
|
|
249
255
|
import { useCmsToast } from "../../composables/cms-toast";
|
|
@@ -298,12 +304,16 @@ const sourceHint = computed(() => {
|
|
|
298
304
|
onMounted(reload);
|
|
299
305
|
const notConfigured = computed(() => errorCode.value === 501);
|
|
300
306
|
const loadError = computed(() => errorCode.value !== null && errorCode.value !== 501);
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
307
|
+
const allowedTypes = computed(() => mediaTypeFilter(props.mediaType));
|
|
308
|
+
const filters = computed(() => [
|
|
309
|
+
"all",
|
|
310
|
+
...allowedTypes.value ?? MEDIA_TYPES
|
|
311
|
+
]);
|
|
305
312
|
const filter = ref("all");
|
|
306
|
-
const showTypeFilters = computed(() =>
|
|
313
|
+
const showTypeFilters = computed(() => filters.value.length > 2 && items.value.length > 0);
|
|
314
|
+
watch(filters, (list) => {
|
|
315
|
+
if (!list.includes(filter.value)) filter.value = "all";
|
|
316
|
+
});
|
|
307
317
|
const search = ref("");
|
|
308
318
|
const draftFolders = ref([]);
|
|
309
319
|
onMounted(() => {
|
|
@@ -350,8 +360,9 @@ watch(usedFolders, (used) => {
|
|
|
350
360
|
}
|
|
351
361
|
});
|
|
352
362
|
const visible = computed(() => {
|
|
353
|
-
const
|
|
354
|
-
let list =
|
|
363
|
+
const allowed = allowedTypes.value;
|
|
364
|
+
let list = allowed ? items.value.filter((item) => allowed.includes(item.type)) : items.value;
|
|
365
|
+
if (filter.value !== "all") list = list.filter((item) => item.type === filter.value);
|
|
355
366
|
if (folder.value) list = list.filter((item) => item.folder === folder.value);
|
|
356
367
|
const query = search.value.trim().toLowerCase();
|
|
357
368
|
if (query) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { MediaType } from '#nuxt-cms';
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
selectable?: boolean;
|
|
4
|
-
mediaType?: MediaType;
|
|
4
|
+
mediaType?: MediaType | MediaType[];
|
|
5
5
|
accept?: string[];
|
|
6
6
|
};
|
|
7
7
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
</template>
|
|
37
37
|
|
|
38
38
|
<script setup>
|
|
39
|
-
import { formatFileSize } from "#nuxt-cms";
|
|
39
|
+
import { formatFileSize, mediaTypeAccept } from "#nuxt-cms";
|
|
40
40
|
import { computed, ref } from "#imports";
|
|
41
41
|
import { useCmsRuntime } from "../../composables/cms-runtime";
|
|
42
42
|
import { useCmsToast } from "../../composables/cms-toast";
|
|
@@ -50,7 +50,6 @@ const emit = defineEmits(["uploaded"]);
|
|
|
50
50
|
const toast = useCmsToast();
|
|
51
51
|
const { mediaMaxFileSize } = useCmsRuntime();
|
|
52
52
|
async function imageDimensions(file) {
|
|
53
|
-
if (!file.type.startsWith("image/")) return {};
|
|
54
53
|
try {
|
|
55
54
|
const bitmap = await createImageBitmap(file);
|
|
56
55
|
const dims = { width: bitmap.width, height: bitmap.height };
|
|
@@ -60,13 +59,35 @@ async function imageDimensions(file) {
|
|
|
60
59
|
return {};
|
|
61
60
|
}
|
|
62
61
|
}
|
|
62
|
+
function videoDimensions(file) {
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
const url = URL.createObjectURL(file);
|
|
65
|
+
const video = document.createElement("video");
|
|
66
|
+
const settle = (dims) => {
|
|
67
|
+
URL.revokeObjectURL(url);
|
|
68
|
+
resolve(dims);
|
|
69
|
+
};
|
|
70
|
+
video.preload = "metadata";
|
|
71
|
+
video.muted = true;
|
|
72
|
+
video.onloadedmetadata = () => settle(
|
|
73
|
+
video.videoWidth && video.videoHeight ? { width: video.videoWidth, height: video.videoHeight } : {}
|
|
74
|
+
);
|
|
75
|
+
video.onerror = () => settle({});
|
|
76
|
+
video.src = url;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async function mediaDimensions(file) {
|
|
80
|
+
if (file.type.startsWith("image/")) return imageDimensions(file);
|
|
81
|
+
if (file.type.startsWith("video/")) return videoDimensions(file);
|
|
82
|
+
return {};
|
|
83
|
+
}
|
|
63
84
|
const input = ref(null);
|
|
64
85
|
const dragOver = ref(false);
|
|
65
86
|
const uploading = ref(false);
|
|
66
87
|
const done = ref(0);
|
|
67
88
|
const total = ref(0);
|
|
68
89
|
const acceptAttr = computed(
|
|
69
|
-
() => props.accept?.length ? props.accept
|
|
90
|
+
() => (props.accept?.length ? props.accept : mediaTypeAccept(props.mediaType))?.join(",")
|
|
70
91
|
);
|
|
71
92
|
function matchesAccept(file) {
|
|
72
93
|
if (!acceptAttr.value) return true;
|
|
@@ -100,7 +121,7 @@ async function uploadOne(file) {
|
|
|
100
121
|
folder: presign.folder,
|
|
101
122
|
mime: file.type || null,
|
|
102
123
|
size: file.size,
|
|
103
|
-
...await
|
|
124
|
+
...await mediaDimensions(file)
|
|
104
125
|
}
|
|
105
126
|
});
|
|
106
127
|
done.value++;
|
|
@@ -24,5 +24,8 @@ export declare function jpegImageSize(bytes: Uint8Array): ImageSize | null;
|
|
|
24
24
|
export declare function webpImageSize(bytes: Uint8Array): ImageSize | null;
|
|
25
25
|
export declare function gifImageSize(bytes: Uint8Array): ImageSize | null;
|
|
26
26
|
export declare function imageSizeFromBuffer(bytes: Uint8Array): ImageSize | null;
|
|
27
|
+
export declare function mp4VideoSize(bytes: Uint8Array): ImageSize | null;
|
|
28
|
+
export declare function matroskaVideoSize(bytes: Uint8Array): ImageSize | null;
|
|
29
|
+
export declare function videoSizeFromBuffer(bytes: Uint8Array): ImageSize | null;
|
|
27
30
|
export declare function scanMediaDirectory(root: string): Promise<ScannedMediaFile[]>;
|
|
28
31
|
export declare function readMediaFileMeta(root: string, file: ScannedMediaFile): Promise<MediaFileMeta>;
|
|
@@ -9,7 +9,9 @@ export const MEDIA_SYNC_MIME_TYPES = {
|
|
|
9
9
|
gif: "image/gif",
|
|
10
10
|
svg: "image/svg+xml",
|
|
11
11
|
mp4: "video/mp4",
|
|
12
|
+
m4v: "video/x-m4v",
|
|
12
13
|
webm: "video/webm",
|
|
14
|
+
mkv: "video/x-matroska",
|
|
13
15
|
mov: "video/quicktime",
|
|
14
16
|
mp3: "audio/mpeg",
|
|
15
17
|
wav: "audio/wav",
|
|
@@ -18,6 +20,7 @@ export const MEDIA_SYNC_MIME_TYPES = {
|
|
|
18
20
|
pdf: "application/pdf"
|
|
19
21
|
};
|
|
20
22
|
const HEADER_BYTES = 65536;
|
|
23
|
+
const MOOV_MAX_BYTES = 4 * 1024 * 1024;
|
|
21
24
|
export function mediaSyncExtension(key) {
|
|
22
25
|
const name = key.split("/").pop() ?? key;
|
|
23
26
|
const dot = name.lastIndexOf(".");
|
|
@@ -157,14 +160,213 @@ export function gifImageSize(bytes) {
|
|
|
157
160
|
export function imageSizeFromBuffer(bytes) {
|
|
158
161
|
return pngImageSize(bytes) ?? gifImageSize(bytes) ?? webpImageSize(bytes) ?? jpegImageSize(bytes);
|
|
159
162
|
}
|
|
163
|
+
const ISO_BMFF_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
164
|
+
"ftyp",
|
|
165
|
+
"styp",
|
|
166
|
+
"moov",
|
|
167
|
+
"moof",
|
|
168
|
+
"mdat",
|
|
169
|
+
"free",
|
|
170
|
+
"skip",
|
|
171
|
+
"wide",
|
|
172
|
+
"pnot",
|
|
173
|
+
"meta",
|
|
174
|
+
"uuid"
|
|
175
|
+
]);
|
|
176
|
+
const FIXED_POINT_16_16 = 65536;
|
|
177
|
+
function readBoxHeader(bytes, offset) {
|
|
178
|
+
if (offset + 8 > bytes.length) return null;
|
|
179
|
+
const data = view(bytes);
|
|
180
|
+
const declared = data.getUint32(offset);
|
|
181
|
+
const type = ascii(bytes, offset + 4, 4);
|
|
182
|
+
if (declared === 1) {
|
|
183
|
+
if (offset + 16 > bytes.length) return null;
|
|
184
|
+
const size = data.getUint32(offset + 8) * 2 ** 32 + data.getUint32(offset + 12);
|
|
185
|
+
return { type, headerLength: 16, size };
|
|
186
|
+
}
|
|
187
|
+
return { type, headerLength: 8, size: declared === 0 ? null : declared };
|
|
188
|
+
}
|
|
189
|
+
function readBoxRange(bytes, offset, end) {
|
|
190
|
+
const box = readBoxHeader(bytes, offset);
|
|
191
|
+
if (!box) return null;
|
|
192
|
+
const size = box.size ?? end - offset;
|
|
193
|
+
if (size < box.headerLength) return null;
|
|
194
|
+
return {
|
|
195
|
+
type: box.type,
|
|
196
|
+
start: offset + box.headerLength,
|
|
197
|
+
end: Math.min(end, offset + size),
|
|
198
|
+
next: offset + size
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function findBox(bytes, start, end, type) {
|
|
202
|
+
let offset = start;
|
|
203
|
+
while (offset + 8 <= end) {
|
|
204
|
+
const box = readBoxRange(bytes, offset, end);
|
|
205
|
+
if (!box || box.next <= offset) return null;
|
|
206
|
+
if (box.type === type) return box;
|
|
207
|
+
offset = box.next;
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
function trackHeaderSize(bytes, start, end) {
|
|
212
|
+
const version = bytes[start];
|
|
213
|
+
if (version === void 0) return null;
|
|
214
|
+
const matrixOffset = start + (version === 1 ? 52 : 40);
|
|
215
|
+
const sizeOffset = start + (version === 1 ? 88 : 76);
|
|
216
|
+
if (sizeOffset + 8 > end) return null;
|
|
217
|
+
const data = view(bytes);
|
|
218
|
+
const width = Math.round(data.getUint32(sizeOffset) / FIXED_POINT_16_16);
|
|
219
|
+
const height = Math.round(data.getUint32(sizeOffset + 4) / FIXED_POINT_16_16);
|
|
220
|
+
if (!width || !height) return null;
|
|
221
|
+
const quarterTurn = data.getInt32(matrixOffset) === 0 && Math.abs(data.getInt32(matrixOffset + 4)) === FIXED_POINT_16_16;
|
|
222
|
+
return quarterTurn ? { width: height, height: width } : { width, height };
|
|
223
|
+
}
|
|
224
|
+
function moovVideoSize(bytes, start, end) {
|
|
225
|
+
let offset = start;
|
|
226
|
+
while (offset + 8 <= end) {
|
|
227
|
+
const box = readBoxRange(bytes, offset, end);
|
|
228
|
+
if (!box || box.next <= offset) return null;
|
|
229
|
+
if (box.type === "trak") {
|
|
230
|
+
const header = findBox(bytes, box.start, box.end, "tkhd");
|
|
231
|
+
const size = header ? trackHeaderSize(bytes, header.start, header.end) : null;
|
|
232
|
+
if (size) return size;
|
|
233
|
+
}
|
|
234
|
+
offset = box.next;
|
|
235
|
+
}
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
export function mp4VideoSize(bytes) {
|
|
239
|
+
const first = readBoxHeader(bytes, 0);
|
|
240
|
+
if (!first || !ISO_BMFF_TOP_LEVEL.has(first.type)) return null;
|
|
241
|
+
const moov = findBox(bytes, 0, bytes.length, "moov");
|
|
242
|
+
return moov ? moovVideoSize(bytes, moov.start, moov.end) : null;
|
|
243
|
+
}
|
|
244
|
+
const EBML_SIGNATURE = [26, 69, 223, 163];
|
|
245
|
+
const EBML_SEGMENT = 408125543;
|
|
246
|
+
const EBML_TRACKS = 374648427;
|
|
247
|
+
const EBML_TRACK_ENTRY = 174;
|
|
248
|
+
const EBML_VIDEO = 224;
|
|
249
|
+
const EBML_PIXEL_WIDTH = 176;
|
|
250
|
+
const EBML_PIXEL_HEIGHT = 186;
|
|
251
|
+
const EBML_DISPLAY_WIDTH = 21680;
|
|
252
|
+
const EBML_DISPLAY_HEIGHT = 21690;
|
|
253
|
+
const EBML_PARENTS = /* @__PURE__ */ new Set([EBML_SEGMENT, EBML_TRACKS, EBML_TRACK_ENTRY]);
|
|
254
|
+
function readEbmlVint(bytes, offset, keepMarker) {
|
|
255
|
+
const first = bytes[offset];
|
|
256
|
+
if (!first) return null;
|
|
257
|
+
let length = 1;
|
|
258
|
+
let mask = 128;
|
|
259
|
+
while (length <= 8 && !(first & mask)) {
|
|
260
|
+
length++;
|
|
261
|
+
mask >>= 1;
|
|
262
|
+
}
|
|
263
|
+
if (length > 8 || offset + length > bytes.length) return null;
|
|
264
|
+
let value = keepMarker ? first : first & mask - 1;
|
|
265
|
+
for (let index = 1; index < length; index++) value = value * 256 + bytes[offset + index];
|
|
266
|
+
return { value, length, unknown: !keepMarker && value === 2 ** (7 * length) - 1 };
|
|
267
|
+
}
|
|
268
|
+
function readEbmlElement(bytes, offset, end) {
|
|
269
|
+
const id = readEbmlVint(bytes, offset, true);
|
|
270
|
+
if (!id) return null;
|
|
271
|
+
const size = readEbmlVint(bytes, offset + id.length, false);
|
|
272
|
+
if (!size) return null;
|
|
273
|
+
const start = offset + id.length + size.length;
|
|
274
|
+
const elementEnd = size.unknown ? end : Math.min(end, start + size.value);
|
|
275
|
+
return { id: id.value, start, end: elementEnd, next: Math.max(start, elementEnd) };
|
|
276
|
+
}
|
|
277
|
+
function readEbmlUint(bytes, start, end) {
|
|
278
|
+
let value = 0;
|
|
279
|
+
for (let index = start; index < end && index < bytes.length; index++) {
|
|
280
|
+
value = value * 256 + bytes[index];
|
|
281
|
+
}
|
|
282
|
+
return value;
|
|
283
|
+
}
|
|
284
|
+
function matroskaTrackSize(bytes, start, end) {
|
|
285
|
+
let pixelWidth = 0;
|
|
286
|
+
let pixelHeight = 0;
|
|
287
|
+
let displayWidth = 0;
|
|
288
|
+
let displayHeight = 0;
|
|
289
|
+
let offset = start;
|
|
290
|
+
while (offset < end) {
|
|
291
|
+
const element = readEbmlElement(bytes, offset, end);
|
|
292
|
+
if (!element || element.next <= offset) break;
|
|
293
|
+
if (element.id === EBML_PIXEL_WIDTH) {
|
|
294
|
+
pixelWidth = readEbmlUint(bytes, element.start, element.end);
|
|
295
|
+
} else if (element.id === EBML_PIXEL_HEIGHT) {
|
|
296
|
+
pixelHeight = readEbmlUint(bytes, element.start, element.end);
|
|
297
|
+
} else if (element.id === EBML_DISPLAY_WIDTH) {
|
|
298
|
+
displayWidth = readEbmlUint(bytes, element.start, element.end);
|
|
299
|
+
} else if (element.id === EBML_DISPLAY_HEIGHT) {
|
|
300
|
+
displayHeight = readEbmlUint(bytes, element.start, element.end);
|
|
301
|
+
}
|
|
302
|
+
offset = element.next;
|
|
303
|
+
}
|
|
304
|
+
const width = displayWidth || pixelWidth;
|
|
305
|
+
const height = displayHeight || pixelHeight;
|
|
306
|
+
return width && height ? { width, height } : null;
|
|
307
|
+
}
|
|
308
|
+
function matroskaVideoSizeIn(bytes, start, end) {
|
|
309
|
+
let offset = start;
|
|
310
|
+
while (offset < end) {
|
|
311
|
+
const element = readEbmlElement(bytes, offset, end);
|
|
312
|
+
if (!element || element.next <= offset) return null;
|
|
313
|
+
if (element.id === EBML_VIDEO) {
|
|
314
|
+
const size = matroskaTrackSize(bytes, element.start, element.end);
|
|
315
|
+
if (size) return size;
|
|
316
|
+
} else if (EBML_PARENTS.has(element.id)) {
|
|
317
|
+
const size = matroskaVideoSizeIn(bytes, element.start, element.end);
|
|
318
|
+
if (size) return size;
|
|
319
|
+
}
|
|
320
|
+
offset = element.next;
|
|
321
|
+
}
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
export function matroskaVideoSize(bytes) {
|
|
325
|
+
if (!startsWith(bytes, 0, EBML_SIGNATURE)) return null;
|
|
326
|
+
return matroskaVideoSizeIn(bytes, 0, bytes.length);
|
|
327
|
+
}
|
|
328
|
+
export function videoSizeFromBuffer(bytes) {
|
|
329
|
+
return mp4VideoSize(bytes) ?? matroskaVideoSize(bytes);
|
|
330
|
+
}
|
|
331
|
+
async function readChunk(handle, position, length) {
|
|
332
|
+
if (length <= 0) return new Uint8Array(0);
|
|
333
|
+
const buffer = new Uint8Array(length);
|
|
334
|
+
const { bytesRead } = await handle.read(buffer, 0, length, position);
|
|
335
|
+
return buffer.subarray(0, bytesRead);
|
|
336
|
+
}
|
|
160
337
|
async function readHeader(path, size) {
|
|
161
338
|
const length = Math.min(size, HEADER_BYTES);
|
|
162
339
|
if (length <= 0) return null;
|
|
163
340
|
const handle = await open(path, "r");
|
|
164
341
|
try {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
342
|
+
return await readChunk(handle, 0, length);
|
|
343
|
+
} finally {
|
|
344
|
+
await handle.close();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async function readMoovVideoSize(handle, fileSize) {
|
|
348
|
+
let position = 0;
|
|
349
|
+
while (position + 8 <= fileSize) {
|
|
350
|
+
const header = await readChunk(handle, position, 16);
|
|
351
|
+
const box = header.length >= 8 ? readBoxHeader(header, 0) : null;
|
|
352
|
+
if (!box) return null;
|
|
353
|
+
const size = box.size ?? fileSize - position;
|
|
354
|
+
if (size < box.headerLength) return null;
|
|
355
|
+
if (box.type === "moov") {
|
|
356
|
+
return mp4VideoSize(await readChunk(handle, position, Math.min(size, MOOV_MAX_BYTES)));
|
|
357
|
+
}
|
|
358
|
+
position += size;
|
|
359
|
+
}
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
async function readVideoSize(path, fileSize) {
|
|
363
|
+
if (fileSize <= 0) return null;
|
|
364
|
+
const handle = await open(path, "r");
|
|
365
|
+
try {
|
|
366
|
+
const head = await readChunk(handle, 0, Math.min(fileSize, HEADER_BYTES));
|
|
367
|
+
return videoSizeFromBuffer(head) ?? await readMoovVideoSize(handle, fileSize);
|
|
368
|
+
} catch {
|
|
369
|
+
return null;
|
|
168
370
|
} finally {
|
|
169
371
|
await handle.close();
|
|
170
372
|
}
|
|
@@ -189,12 +391,23 @@ export async function scanMediaDirectory(root) {
|
|
|
189
391
|
await walk(root, "");
|
|
190
392
|
return files.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
|
|
191
393
|
}
|
|
394
|
+
const IMAGE_SIZE_MIME_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp", "image/gif"]);
|
|
395
|
+
const VIDEO_SIZE_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
396
|
+
"video/mp4",
|
|
397
|
+
"video/x-m4v",
|
|
398
|
+
"video/quicktime",
|
|
399
|
+
"video/webm",
|
|
400
|
+
"video/x-matroska"
|
|
401
|
+
]);
|
|
192
402
|
export async function readMediaFileMeta(root, file) {
|
|
193
403
|
const mime = mediaMimeForKey(file.key);
|
|
404
|
+
const path = join(root, ...file.key.split("/"));
|
|
194
405
|
let size = null;
|
|
195
|
-
if (mime
|
|
196
|
-
const header = await readHeader(
|
|
406
|
+
if (mime && IMAGE_SIZE_MIME_TYPES.has(mime)) {
|
|
407
|
+
const header = await readHeader(path, file.size);
|
|
197
408
|
size = header ? imageSizeFromBuffer(header) : null;
|
|
409
|
+
} else if (mime && VIDEO_SIZE_MIME_TYPES.has(mime)) {
|
|
410
|
+
size = await readVideoSize(path, file.size);
|
|
198
411
|
}
|
|
199
412
|
return {
|
|
200
413
|
key: file.key,
|
|
@@ -13,6 +13,8 @@ export declare function mediaTypeForKey(key: string): MediaType;
|
|
|
13
13
|
export declare function mediaFilename(key: string): string;
|
|
14
14
|
export declare function mediaTypeFor(mime: string | null | undefined, key: string): MediaType;
|
|
15
15
|
export declare function mediaIconFor(type: MediaType): string;
|
|
16
|
+
export declare function mediaTypeFilter(mediaType: MediaType | MediaType[] | null | undefined): MediaType[] | null;
|
|
17
|
+
export declare function mediaTypeAccept(mediaType: MediaType | MediaType[] | null | undefined): string[] | null;
|
|
16
18
|
export declare function mediaPublicUrl(baseUrl: string | null | undefined, key: string): string | null;
|
|
17
19
|
export declare const DEFAULT_MEDIA_MAX_FILE_SIZE: number;
|
|
18
20
|
export declare function formatFileSize(bytes: number): string;
|
|
@@ -60,7 +62,7 @@ export interface FieldConfig {
|
|
|
60
62
|
multiple?: boolean;
|
|
61
63
|
from?: string;
|
|
62
64
|
blocks?: Record<string, BlockConfig>;
|
|
63
|
-
mediaType?: MediaType;
|
|
65
|
+
mediaType?: MediaType | MediaType[];
|
|
64
66
|
accept?: string[];
|
|
65
67
|
to?: string;
|
|
66
68
|
cardinality?: 'many-to-one' | 'one-to-one' | 'many-to-many';
|
|
@@ -138,7 +140,7 @@ export interface JsonFieldInput extends FieldInputBase {
|
|
|
138
140
|
}
|
|
139
141
|
export interface MediaFieldInput extends FieldInputBase {
|
|
140
142
|
type: 'media';
|
|
141
|
-
mediaType?: MediaType;
|
|
143
|
+
mediaType?: MediaType | MediaType[];
|
|
142
144
|
accept?: string[];
|
|
143
145
|
translatable?: boolean;
|
|
144
146
|
}
|
|
@@ -38,6 +38,16 @@ export function mediaTypeFor(mime, key) {
|
|
|
38
38
|
export function mediaIconFor(type) {
|
|
39
39
|
return type === "image" ? "photo" : type === "video" ? "film" : "document";
|
|
40
40
|
}
|
|
41
|
+
export function mediaTypeFilter(mediaType) {
|
|
42
|
+
const requested = Array.isArray(mediaType) ? mediaType : mediaType ? [mediaType] : [];
|
|
43
|
+
const types = [...new Set(requested.filter((type) => MEDIA_TYPES.includes(type)))];
|
|
44
|
+
if (!types.length || types.includes("file")) return null;
|
|
45
|
+
return types;
|
|
46
|
+
}
|
|
47
|
+
export function mediaTypeAccept(mediaType) {
|
|
48
|
+
const types = mediaTypeFilter(mediaType);
|
|
49
|
+
return types ? types.map((type) => `${type}/*`) : null;
|
|
50
|
+
}
|
|
41
51
|
export function mediaPublicUrl(baseUrl, key) {
|
|
42
52
|
return baseUrl ? `${baseUrl.replace(/\/+$/, "")}/${key}` : null;
|
|
43
53
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xleddyl/nuxt-cms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.40",
|
|
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)",
|