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:
@@ -5,6 +5,7 @@
|
||||
import IframeBlock from "./blocks/IframeBlock.svelte";
|
||||
import ImageBlock from "./blocks/ImageBlock.svelte";
|
||||
import ImageGalleryBlock from "./blocks/ImageGalleryBlock.svelte";
|
||||
import FilesBlock from "./blocks/FilesBlock.svelte";
|
||||
import YoutubeVideoBlock from "./blocks/YoutubeVideoBlock.svelte";
|
||||
import QuoteBlock from "./blocks/QuoteBlock.svelte";
|
||||
import QuoteCarouselBlock from "./blocks/QuoteCarouselBlock.svelte";
|
||||
@@ -27,6 +28,7 @@
|
||||
IframeBlockData,
|
||||
ImageBlockData,
|
||||
ImageGalleryBlockData,
|
||||
FilesBlockData,
|
||||
YoutubeVideoBlockData,
|
||||
QuoteBlockData,
|
||||
QuoteCarouselBlockData,
|
||||
@@ -112,6 +114,8 @@
|
||||
<ImageBlock block={item as ImageBlockData} />
|
||||
{:else if blockType(item) === "image_gallery"}
|
||||
<ImageGalleryBlock block={item as ImageGalleryBlockData} />
|
||||
{:else if blockType(item) === "files"}
|
||||
<FilesBlock block={item as FilesBlockData} />
|
||||
{:else if blockType(item) === "youtube_video"}
|
||||
<YoutubeVideoBlock block={item as YoutubeVideoBlockData} />
|
||||
{:else if blockType(item) === "quote"}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
<script lang="ts">
|
||||
import Icon from "@iconify/svelte";
|
||||
import "$lib/iconify-offline";
|
||||
import { getBlockLayoutClasses } from "$lib/block-layout";
|
||||
import type { FilesBlockData } from "$lib/block-types";
|
||||
import {
|
||||
extractCmsFileField,
|
||||
fileExtFromUrl,
|
||||
formatFileSize,
|
||||
getCmsFileUrl,
|
||||
} from "$lib/rusty-file";
|
||||
|
||||
let { block }: { block: FilesBlockData } = $props();
|
||||
|
||||
const layoutClasses = $derived(
|
||||
block.layout ? getBlockLayoutClasses(block.layout) : "col-span-12 mb-4",
|
||||
);
|
||||
|
||||
type Resolved = {
|
||||
href: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
sizeLabel: string | null;
|
||||
ext: string | null;
|
||||
iconName: string;
|
||||
};
|
||||
|
||||
function iconForExt(ext: string | null): string {
|
||||
switch (ext) {
|
||||
case "pdf":
|
||||
return "mdi:file-pdf-box";
|
||||
case "doc":
|
||||
case "docx":
|
||||
case "rtf":
|
||||
case "odt":
|
||||
return "mdi:file-word-box";
|
||||
case "xls":
|
||||
case "xlsx":
|
||||
case "ods":
|
||||
case "csv":
|
||||
return "mdi:file-excel-box";
|
||||
case "ppt":
|
||||
case "pptx":
|
||||
return "mdi:file-powerpoint-box";
|
||||
case "zip":
|
||||
case "tar":
|
||||
case "gz":
|
||||
case "7z":
|
||||
return "mdi:folder-zip";
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
case "png":
|
||||
case "webp":
|
||||
case "gif":
|
||||
case "svg":
|
||||
return "mdi:file-image";
|
||||
case "txt":
|
||||
case "md":
|
||||
return "mdi:file-document-outline";
|
||||
default:
|
||||
return "mdi:file-outline";
|
||||
}
|
||||
}
|
||||
|
||||
const resolved = $derived(
|
||||
((block.items ?? []) as unknown[])
|
||||
.map((raw): Resolved | null => {
|
||||
const field = extractCmsFileField(raw);
|
||||
if (!field) return null;
|
||||
const filename = field.filename ?? field.url.split("/").pop();
|
||||
const href = getCmsFileUrl(field.url, {
|
||||
filename,
|
||||
download: !!block.forceDownload,
|
||||
});
|
||||
const ext = fileExtFromUrl(field.url);
|
||||
const label = field.title ?? filename ?? field.url;
|
||||
return {
|
||||
href,
|
||||
label,
|
||||
description: field.description,
|
||||
sizeLabel: formatFileSize(field.size),
|
||||
ext,
|
||||
iconName: iconForExt(ext),
|
||||
};
|
||||
})
|
||||
.filter((r): r is Resolved => r !== null),
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="files-block {layoutClasses}" data-block-type="files" data-block-slug={block._slug}>
|
||||
{#if block.headline}
|
||||
<h3 class="mb-3 text-lg font-semibold">{block.headline}</h3>
|
||||
{/if}
|
||||
{#if block.description}
|
||||
<p class="mb-3 text-sm text-zinc-700">{block.description}</p>
|
||||
{/if}
|
||||
{#if resolved.length === 0}
|
||||
<p class="text-sm text-zinc-500">Keine Dateien verfügbar.</p>
|
||||
{:else}
|
||||
<ul class="flex flex-col gap-2">
|
||||
{#each resolved as f}
|
||||
<li>
|
||||
<a
|
||||
href={f.href}
|
||||
class="group flex items-start gap-3 rounded-md border border-zinc-200 bg-white px-3 py-2 hover:border-zinc-400 hover:bg-zinc-50 transition-colors"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Icon icon={f.iconName} class="size-6 shrink-0 text-zinc-600" aria-hidden="true" />
|
||||
<span class="flex flex-col min-w-0">
|
||||
<span class="font-medium text-zinc-900 group-hover:underline truncate">{f.label}</span>
|
||||
{#if f.description}
|
||||
<span class="text-sm text-zinc-600">{f.description}</span>
|
||||
{/if}
|
||||
<span class="text-xs text-zinc-500">
|
||||
{#if f.ext}<span class="uppercase">{f.ext}</span>{/if}
|
||||
{#if f.ext && f.sizeLabel} · {/if}
|
||||
{#if f.sizeLabel}{f.sizeLabel}{/if}
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
Reference in New Issue
Block a user