feat: add FilesBlock interface and component integration
- Introduced FilesBlockItem and FilesBlockData interfaces to support file handling in the CMS. - Integrated FilesBlock component into ContentRows.svelte for rendering file blocks. - Updated block type handling to include support for files, enhancing content versatility.
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Datei-URLs für SvelteKit: /cms-files mit persistentem Server-Disk-Cache.
|
||||
* Analog zu rusty-image.ts, aber für generische Dateien (PDF, DOCX, ...).
|
||||
*/
|
||||
|
||||
export interface CmsFileField {
|
||||
url: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
size?: number;
|
||||
mime?: string;
|
||||
filename?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrahiert URL/Title/Description aus einem CMS-File-Field
|
||||
* (string oder { src, title?, description?, mime?, size?, ... }).
|
||||
*/
|
||||
export function extractCmsFileField(field: unknown): CmsFileField | null {
|
||||
if (typeof field === 'string') {
|
||||
const s = field.trim();
|
||||
if (!s) return null;
|
||||
return { url: s.startsWith('//') ? `https:${s}` : s };
|
||||
}
|
||||
if (field && typeof field === 'object') {
|
||||
const o = field as {
|
||||
src?: string;
|
||||
file?: { url?: string };
|
||||
title?: string;
|
||||
description?: string;
|
||||
size?: number;
|
||||
mime?: string;
|
||||
filename?: string;
|
||||
};
|
||||
const rawUrl =
|
||||
typeof o.src === 'string' && o.src.trim()
|
||||
? o.src
|
||||
: typeof o.file?.url === 'string' && o.file.url.trim()
|
||||
? o.file.url
|
||||
: undefined;
|
||||
if (!rawUrl) return null;
|
||||
const url = rawUrl.startsWith('//') ? `https:${rawUrl}` : rawUrl;
|
||||
return {
|
||||
url,
|
||||
...(o.title ? { title: o.title } : {}),
|
||||
...(o.description ? { description: o.description } : {}),
|
||||
...(typeof o.size === 'number' ? { size: o.size } : {}),
|
||||
...(o.mime ? { mime: o.mime } : {}),
|
||||
...(o.filename ? { filename: o.filename } : {}),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export interface CmsFileUrlOptions {
|
||||
/** Filename hint for Content-Disposition header. */
|
||||
filename?: string;
|
||||
/** Force download (Content-Disposition: attachment). */
|
||||
download?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lokale Datei-URL: /cms-files?src=…&name=…&dl=…
|
||||
* `src` = volle Datei-URL oder CMS-Asset-Pfad (`/api/assets/...`).
|
||||
*/
|
||||
export function getCmsFileUrl(src: string, opts: CmsFileUrlOptions = {}): string {
|
||||
const params = new URLSearchParams();
|
||||
params.set('src', src);
|
||||
if (opts.filename) params.set('name', opts.filename);
|
||||
if (opts.download) params.set('dl', '1');
|
||||
return `/cms-files?${params.toString()}`;
|
||||
}
|
||||
|
||||
const SIZE_UNITS = ['B', 'KB', 'MB', 'GB'];
|
||||
|
||||
export function formatFileSize(bytes?: number): string | null {
|
||||
if (typeof bytes !== 'number' || !Number.isFinite(bytes) || bytes <= 0) return null;
|
||||
let b = bytes;
|
||||
let i = 0;
|
||||
while (b >= 1024 && i < SIZE_UNITS.length - 1) {
|
||||
b /= 1024;
|
||||
i++;
|
||||
}
|
||||
const rounded = b >= 10 || i === 0 ? Math.round(b) : Math.round(b * 10) / 10;
|
||||
return `${rounded} ${SIZE_UNITS[i]}`;
|
||||
}
|
||||
|
||||
export function fileExtFromUrl(url: string): string | null {
|
||||
try {
|
||||
const path = url.split('?')[0].split('#')[0];
|
||||
const last = path.split('/').pop() ?? '';
|
||||
const dot = last.lastIndexOf('.');
|
||||
return dot >= 0 ? last.slice(dot + 1).toLowerCase() : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user