feat(image): enhance image handling with focal points and responsive support
- Added support for extracting focal points and alt text from CMS image fields. - Introduced responsive image handling in TopBanner component, allowing for AVIF/WebP sources and LQIP placeholders. - Updated image transformation functions to accommodate focal point parameters for better cropping. - Refactored image URL resolution logic across various components and routes to utilize new image field structure.
This commit is contained in:
+30
-16
@@ -9,7 +9,7 @@ import {
|
||||
} from "./cms";
|
||||
import type { RowContentLayout } from "./block-types";
|
||||
import type { CalendarItemData } from "./block-types";
|
||||
import { ensureTransformedImage } from "./rusty-image";
|
||||
import { ensureTransformedImage, extractCmsImageField, type CmsImageField } from "./rusty-image";
|
||||
|
||||
/** Aus der Tag-API: Anzeigename plus optionales Icon und Farbe (CMS-Felder icon / color). */
|
||||
export type TagMeta = {
|
||||
@@ -124,18 +124,28 @@ export function filterPostsByTag(
|
||||
export function getPostImageUrl(
|
||||
postImage: PostEntry["postImage"],
|
||||
): string | null {
|
||||
if (!postImage || typeof postImage === "string") return null;
|
||||
return getPostImageField(postImage)?.url ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL + Focal + Alt aus post.postImage. Unterstützt RustyCMS-image-Typ
|
||||
* und legacy file.url / fields.file.url.
|
||||
*/
|
||||
export function getPostImageField(
|
||||
postImage: PostEntry["postImage"],
|
||||
): CmsImageField | null {
|
||||
if (!postImage) return null;
|
||||
if (typeof postImage === "string") return extractCmsImageField(postImage);
|
||||
const field = extractCmsImageField(postImage);
|
||||
if (field) return field;
|
||||
const obj = postImage as {
|
||||
src?: string;
|
||||
file?: { url?: string };
|
||||
fields?: { file?: { url?: string } };
|
||||
};
|
||||
const url = obj?.src ?? obj?.file?.url ?? obj?.fields?.file?.url;
|
||||
return typeof url === "string"
|
||||
? url.startsWith("//")
|
||||
? `https:${url}`
|
||||
: url
|
||||
: null;
|
||||
const legacy = obj?.fields?.file?.url;
|
||||
if (typeof legacy === "string" && legacy.trim()) {
|
||||
return { url: legacy.startsWith("//") ? `https:${legacy}` : legacy };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Filtert Posts: behält nur solche, die mindestens einen der Tag-Slugs haben. */
|
||||
@@ -237,14 +247,16 @@ export async function loadPostsList(opts: {
|
||||
const tagsMap = await getTagsMap();
|
||||
for (const p of posts) resolvePostTagsInPost(p, tagsMap);
|
||||
for (const post of posts) {
|
||||
const raw = getPostImageUrl(post.postImage);
|
||||
if (raw) {
|
||||
const field = getPostImageField(post.postImage);
|
||||
if (field) {
|
||||
try {
|
||||
const url = await ensureTransformedImage(raw, {
|
||||
const url = await ensureTransformedImage(field.url, {
|
||||
width: 400,
|
||||
height: 267,
|
||||
fit: "cover",
|
||||
format: "webp",
|
||||
focalX: field.focal?.x,
|
||||
focalY: field.focal?.y,
|
||||
});
|
||||
(post as PostEntry & { _resolvedImageUrl?: string })._resolvedImageUrl = url;
|
||||
} catch {
|
||||
@@ -417,14 +429,16 @@ export async function resolvePostOverviewBlocks(
|
||||
for (const post of posts) resolvePostTagsInPost(post, tagsMap);
|
||||
}
|
||||
for (const post of item.postsResolved as PostEntry[]) {
|
||||
const raw = getPostImageUrl(post.postImage);
|
||||
if (raw) {
|
||||
const field = getPostImageField(post.postImage);
|
||||
if (field) {
|
||||
try {
|
||||
const url = await ensureTransformedImage(raw, {
|
||||
const url = await ensureTransformedImage(field.url, {
|
||||
width: 400,
|
||||
height: 267,
|
||||
fit: "cover",
|
||||
format: "webp",
|
||||
focalX: field.focal?.x,
|
||||
focalY: field.focal?.y,
|
||||
});
|
||||
(
|
||||
post as PostEntry & { _resolvedImageUrl?: string }
|
||||
|
||||
Reference in New Issue
Block a user