refactor(post-events): read event data from resolved calendarItem
Deploy / verify (push) Successful in 47s
Deploy / deploy (push) Successful in 53s

Replace direct post.isEvent/eventDate/eventLocation reads with
getPostEventInfo() helper that derives info from the resolved
calendarItem reference. Loaders now resolve "calendarItem" and request
that field instead of legacy flat properties.

Also regenerate iconify mdi subset (map-marker-outline, download,
google, calendar-search, refresh).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Peter Meier
2026-05-07 16:08:16 +02:00
parent fc6c59bc10
commit 892afc2d9f
6 changed files with 112 additions and 61 deletions
+75 -35
View File
@@ -121,6 +121,44 @@ export function filterPostsByTag(
});
}
export type PostEventLocation = {
text?: string;
lat?: number;
lng?: number;
};
export type PostEventInfo = {
isEvent: boolean;
eventDate: string | null;
eventLocation: PostEventLocation | null;
};
/**
* Liest event-Daten aus dem aufgelösten calendarItem-Reference des Posts.
* Voraussetzung: post mit resolve=["all"] oder explizit "calendarItem" geladen,
* sodass `post.calendarItem` als Objekt mit terminZeit/location vorliegt.
*/
export function getPostEventInfo(
post: PostEntry | null | undefined,
): PostEventInfo {
const ci = (post as { calendarItem?: unknown } | null | undefined)?.calendarItem;
if (!ci || typeof ci !== "object") {
return { isEvent: false, eventDate: null, eventLocation: null };
}
const obj = ci as {
terminZeit?: string;
location?: PostEventLocation;
};
if (!obj.terminZeit) {
return { isEvent: false, eventDate: null, eventLocation: null };
}
return {
isEvent: true,
eventDate: obj.terminZeit,
eventLocation: obj.location ?? null,
};
}
/** URL aus post.postImage (RustyCMS img mit src, oder legacy file.url). */
export function getPostImageUrl(
postImage: PostEntry["postImage"],
@@ -200,32 +238,33 @@ export type PostSearchIndexEntry = {
eventDate: string | null;
};
type PostWithEventIndex = PostEntry & {
isEvent?: boolean;
eventDate?: string;
type PostWithImageIndex = PostEntry & {
_resolvedImageUrl?: string;
};
export function buildPostSearchIndex(posts: PostEntry[]): PostSearchIndexEntry[] {
return posts.map((p) => ({
slug: p.slug ?? p._slug ?? "",
headline: p.headline ?? "",
excerpt: p.excerpt ?? "",
subheadline: p.subheadline ?? "",
tags: (p.postTag ?? [])
.map((t) =>
typeof t === "string"
? t
: (t as { name?: string; _slug?: string }).name ??
(t as { _slug?: string })._slug ??
"",
)
.filter(Boolean),
image: (p as PostWithEventIndex)._resolvedImageUrl ?? null,
created: p.created ?? null,
isEvent: Boolean((p as PostWithEventIndex).isEvent),
eventDate: (p as PostWithEventIndex).eventDate ?? null,
}));
return posts.map((p) => {
const ev = getPostEventInfo(p);
return {
slug: p.slug ?? p._slug ?? "",
headline: p.headline ?? "",
excerpt: p.excerpt ?? "",
subheadline: p.subheadline ?? "",
tags: (p.postTag ?? [])
.map((t) =>
typeof t === "string"
? t
: (t as { name?: string; _slug?: string }).name ??
(t as { _slug?: string })._slug ??
"",
)
.filter(Boolean),
image: (p as PostWithImageIndex)._resolvedImageUrl ?? null,
created: p.created ?? null,
isEvent: ev.isEvent,
eventDate: ev.eventDate,
};
});
}
export type LoadPostsResult = {
@@ -260,7 +299,7 @@ export async function loadPostsList(opts: {
// (post → postImage → img-Sidecar; post → postTag → name/icon/color).
[posts, tags] = await Promise.all([
getPosts({
resolve: ["postImage", "postTag"],
resolve: ["postImage", "postTag", "calendarItem"],
depth: 2,
fields: [
"_slug",
@@ -270,8 +309,7 @@ export async function loadPostsList(opts: {
"subheadline",
"excerpt",
"created",
"isEvent",
"eventDate",
"calendarItem",
"hideFromListing",
"postImage.*",
"postTag.name",
@@ -342,16 +380,18 @@ export async function loadPostsList(opts: {
export function selectUpcomingEvents(posts: PostEntry[], max = 4): PostEntry[] {
const now = Date.now();
return (posts as PostWithEventIndex[])
.filter(
(p) => p.isEvent && p.eventDate && new Date(p.eventDate).getTime() >= now,
)
.sort(
(a, b) =>
new Date(a.eventDate ?? 0).getTime() -
new Date(b.eventDate ?? 0).getTime(),
)
.slice(0, max) as PostEntry[];
type Scored = { post: PostEntry; ts: number };
const scored: Scored[] = [];
for (const p of posts) {
const ev = getPostEventInfo(p);
if (!ev.isEvent || !ev.eventDate) continue;
const ts = new Date(ev.eventDate).getTime();
if (ts >= now) scored.push({ post: p, ts });
}
return scored
.sort((a, b) => a.ts - b.ts)
.slice(0, max)
.map((s) => s.post);
}
const POST_BASE = "/post";