refactor(post-events): read event data from resolved calendarItem
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:
+75
-35
@@ -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";
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import Tag from "./Tag.svelte";
|
||||
import Pagination from "./Pagination.svelte";
|
||||
import type { PostEntry } from "$lib/cms";
|
||||
import { postHref } from "$lib/blog-utils";
|
||||
import { postHref, getPostEventInfo } from "$lib/blog-utils";
|
||||
import { t as tStatic, T } from "$lib/translations";
|
||||
import type { Translations } from "$lib/translations";
|
||||
import {
|
||||
@@ -34,8 +34,6 @@
|
||||
eventDate: string | null;
|
||||
};
|
||||
|
||||
type EventPost = PostEntry & { eventDate?: string; isEvent?: boolean };
|
||||
|
||||
let {
|
||||
posts = [],
|
||||
tags = [],
|
||||
@@ -56,7 +54,7 @@
|
||||
totalPosts?: number;
|
||||
basePath?: string;
|
||||
translations?: Translations | null;
|
||||
upcomingEvents?: EventPost[];
|
||||
upcomingEvents?: PostEntry[];
|
||||
searchIndex?: SearchIndexEntry[] | null;
|
||||
} = $props();
|
||||
|
||||
@@ -193,12 +191,13 @@
|
||||
</h2>
|
||||
<ul class="not-prose list-none space-y-2 pl-0">
|
||||
{#each upcomingEvents as ev}
|
||||
{@const evInfo = getPostEventInfo(ev)}
|
||||
<li>
|
||||
<a
|
||||
href={postHref(ev)}
|
||||
class="group flex flex-wrap items-center gap-2 no-underline"
|
||||
>
|
||||
<EventBadges eventDate={ev.eventDate} />
|
||||
<EventBadges eventDate={evInfo.eventDate} />
|
||||
<span
|
||||
class="text-sm text-stein-900 group-hover:text-stein-900"
|
||||
>{ev.headline}</span
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import EventBadges from "./EventBadges.svelte";
|
||||
import RustyImage from "./RustyImage.svelte";
|
||||
import Card from "./Card.svelte";
|
||||
import { getPostEventInfo } from "$lib/blog-utils";
|
||||
import { useTranslate, T } from "$lib/translations";
|
||||
|
||||
const t = useTranslate();
|
||||
@@ -33,15 +34,9 @@
|
||||
const focal = $derived(post._imageFocal ?? null);
|
||||
const fallbackImg = $derived(post._resolvedImageUrl);
|
||||
|
||||
type PostWithEvent = PostWithImage & {
|
||||
isEvent?: boolean;
|
||||
eventDate?: string;
|
||||
eventLocation?: { text?: string };
|
||||
};
|
||||
const p = $derived(post as PostWithEvent);
|
||||
|
||||
const eventDate = $derived(p.isEvent && p.eventDate ? p.eventDate : null);
|
||||
const locationText = $derived(p.eventLocation?.text ?? null);
|
||||
const eventInfo = $derived(getPostEventInfo(post));
|
||||
const eventDate = $derived(eventInfo.eventDate);
|
||||
const locationText = $derived(eventInfo.eventLocation?.text ?? null);
|
||||
|
||||
const tagSlugs = $derived(
|
||||
(post.postTag ?? []).map((t) =>
|
||||
@@ -50,7 +45,7 @@
|
||||
);
|
||||
const hasTerminTag = $derived(tagSlugs.includes("tag-termin"));
|
||||
const isPastEvent = $derived(
|
||||
hasTerminTag && !!p.eventDate && new Date(p.eventDate).getTime() < Date.now(),
|
||||
hasTerminTag && !!eventDate && new Date(eventDate).getTime() < Date.now(),
|
||||
);
|
||||
</script>
|
||||
|
||||
|
||||
@@ -66,6 +66,18 @@
|
||||
"printer-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M19 8c1.66 0 3 1.34 3 3v6h-4v4H6v-4H2v-6c0-1.66 1.34-3 3-3h1V3h12v5zM8 5v3h8V5zm8 14v-4H8v4zm2-4h2v-4c0-.55-.45-1-1-1H5c-.55 0-1 .45-1 1v4h2v-2h12zm1-3.5c0 .55-.45 1-1 1s-1-.45-1-1s.45-1 1-1s1 .45 1 1\"/>"
|
||||
},
|
||||
"map-marker-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12 6.5A2.5 2.5 0 0 1 14.5 9a2.5 2.5 0 0 1-2.5 2.5A2.5 2.5 0 0 1 9.5 9A2.5 2.5 0 0 1 12 6.5M12 2a7 7 0 0 1 7 7c0 5.25-7 13-7 13S5 14.25 5 9a7 7 0 0 1 7-7m0 2a5 5 0 0 0-5 5c0 1 0 3 5 9.71C17 12 17 10 17 9a5 5 0 0 0-5-5\"/>"
|
||||
},
|
||||
"download": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 20h14v-2H5m14-9h-4V3H9v6H5l7 7z\"/>"
|
||||
},
|
||||
"google": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M21.35 11.1h-9.17v2.73h6.51c-.33 3.81-3.5 5.44-6.5 5.44C8.36 19.27 5 16.25 5 12c0-4.1 3.2-7.27 7.2-7.27c3.09 0 4.9 1.97 4.9 1.97L19 4.72S16.56 2 12.1 2C6.42 2 2.03 6.8 2.03 12c0 5.05 4.13 10 10.22 10c5.35 0 9.25-3.67 9.25-9.09c0-1.15-.15-1.81-.15-1.81\"/>"
|
||||
},
|
||||
"calendar-search": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M15.5 12c2.5 0 4.5 2 4.5 4.5c0 .88-.25 1.71-.69 2.4l3.08 3.1L21 23.39l-3.12-3.07c-.69.43-1.51.68-2.38.68c-2.5 0-4.5-2-4.5-4.5s2-4.5 4.5-4.5m0 2a2.5 2.5 0 0 0-2.5 2.5a2.5 2.5 0 0 0 2.5 2.5a2.5 2.5 0 0 0 2.5-2.5a2.5 2.5 0 0 0-2.5-2.5M19 8H5v11h4.5c.31.75.76 1.42 1.31 2H5a2 2 0 0 1-2-2V5c0-1.11.89-2 2-2h1V1h2v2h8V1h2v2h1a2 2 0 0 1 2 2v8.03c-.5-.81-1.2-1.49-2-2.03z\"/>"
|
||||
},
|
||||
"arrow-right": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M4 11v2h12l-5.5 5.5l1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5L16 11z\"/>"
|
||||
},
|
||||
@@ -84,15 +96,18 @@
|
||||
"transmission-tower": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m8.28 5.45l-1.78-.9L7.76 2h8.47l1.27 2.55l-1.78.89L15 4H9zM18.62 8h-4.53l-.79-3h-2.6l-.79 3H5.38L4.1 10.55l1.79.89l.73-1.44h10.76l.72 1.45l1.79-.89zm-.85 14H15.7l-.24-.9L12 15.9l-3.47 5.2l-.23.9H6.23l2.89-11h2.07l-.36 1.35L12 14.1l1.16-1.75l-.35-1.35h2.07zm-6.37-7l-.9-1.35l-1.18 4.48zm3.28 3.12l-1.18-4.48l-.9 1.36z\"/>"
|
||||
},
|
||||
"cloud-off-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M19.8 22.6L17.15 20H6.5q-2.3 0-3.9-1.6T1 14.5q0-1.92 1.19-3.42q1.19-1.51 3.06-1.93q.08-.2.15-.39q.1-.19.15-.41L1.4 4.2l1.4-1.4l18.4 18.4M6.5 18h8.65L7.1 9.95q-.05.28-.07.55q-.03.23-.03.5h-.5q-1.45 0-2.47 1.03Q3 13.05 3 14.5T4.03 17q1.02 1 2.47 1m15.1.75l-1.45-1.4q.43-.35.64-.81T21 15.5q0-1.05-.73-1.77q-.72-.73-1.77-.73H17v-2q0-2.07-1.46-3.54Q14.08 6 12 6q-.67 0-1.3.16q-.63.17-1.2.52L8.05 5.23q.88-.6 1.86-.92Q10.9 4 12 4q2.93 0 4.96 2.04Q19 8.07 19 11q1.73.2 2.86 1.5q1.14 1.28 1.14 3q0 1-.37 1.81q-.38.84-1.03 1.44m-6.77-6.72\"/>"
|
||||
},
|
||||
"database-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12 3C7.58 3 4 4.79 4 7v10c0 2.21 3.59 4 8 4s8-1.79 8-4V7c0-2.21-3.58-4-8-4m6 14c0 .5-2.13 2-6 2s-6-1.5-6-2v-2.23c1.61.78 3.72 1.23 6 1.23s4.39-.45 6-1.23zm0-4.55c-1.3.95-3.58 1.55-6 1.55s-4.7-.6-6-1.55V9.64c1.47.83 3.61 1.36 6 1.36s4.53-.53 6-1.36zM12 9C8.13 9 6 7.5 6 7s2.13-2 6-2s6 1.5 6 2s-2.13 2-6 2\"/>"
|
||||
},
|
||||
"refresh": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M17.65 6.35A7.96 7.96 0 0 0 12 4a8 8 0 0 0-8 8a8 8 0 0 0 8 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18a6 6 0 0 1-6-6a6 6 0 0 1 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4z\"/>"
|
||||
},
|
||||
"alert-circle": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2\"/>"
|
||||
},
|
||||
"cloud-off-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M19.8 22.6L17.15 20H6.5q-2.3 0-3.9-1.6T1 14.5q0-1.92 1.19-3.42q1.19-1.51 3.06-1.93q.08-.2.15-.39q.1-.19.15-.41L1.4 4.2l1.4-1.4l18.4 18.4M6.5 18h8.65L7.1 9.95q-.05.28-.07.55q-.03.23-.03.5h-.5q-1.45 0-2.47 1.03Q3 13.05 3 14.5T4.03 17q1.02 1 2.47 1m15.1.75l-1.45-1.4q.43-.35.64-.81T21 15.5q0-1.05-.73-1.77q-.72-.73-1.77-.73H17v-2q0-2.07-1.46-3.54Q14.08 6 12 6q-.67 0-1.3.16q-.63.17-1.2.52L8.05 5.23q.88-.6 1.86-.92Q10.9 4 12 4q2.93 0 4.96 2.04Q19 8.07 19 11q1.73.2 2.86 1.5q1.14 1.28 1.14 3q0 1-.37 1.81q-.38.84-1.03 1.44m-6.77-6.72\"/>"
|
||||
},
|
||||
"play": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M8 5.14v14l11-7z\"/>"
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
sortPostsByDate,
|
||||
getPostImageUrl,
|
||||
postHref,
|
||||
getPostEventInfo,
|
||||
} from '$lib/blog-utils';
|
||||
import { ensureTransformedImage } from '$lib/rusty-image';
|
||||
|
||||
@@ -34,7 +35,7 @@ export const GET: RequestHandler = async () => {
|
||||
try {
|
||||
[pages, posts] = await Promise.all([
|
||||
getPages(),
|
||||
getPosts({ resolve: ['postImage'] }),
|
||||
getPosts({ resolve: ['postImage', 'calendarItem'] }),
|
||||
]);
|
||||
} catch {
|
||||
return json({ hits: [] satisfies SearchHit[] });
|
||||
@@ -58,6 +59,7 @@ export const GET: RequestHandler = async () => {
|
||||
/* optional */
|
||||
}
|
||||
}
|
||||
const ev = getPostEventInfo(p);
|
||||
return {
|
||||
type: 'post',
|
||||
slug: p.slug ?? p._slug ?? '',
|
||||
@@ -67,8 +69,8 @@ export const GET: RequestHandler = async () => {
|
||||
excerpt: p.excerpt ?? '',
|
||||
image,
|
||||
created: p.created ?? null,
|
||||
isEvent: Boolean((p as { isEvent?: boolean }).isEvent),
|
||||
eventDate: (p as { eventDate?: string }).eventDate ?? null,
|
||||
isEvent: ev.isEvent,
|
||||
eventDate: ev.eventDate,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
resolveDeadlineBannerBlocks,
|
||||
getPostImageField,
|
||||
formatPostDate,
|
||||
getPostEventInfo,
|
||||
} from '$lib/blog-utils';
|
||||
import { POST_RESOLVE, POST_SOCIAL_IMAGE_TRANSFORM, SITE_NAME } from '$lib/constants';
|
||||
import { env } from '$env/dynamic/public';
|
||||
@@ -110,8 +111,9 @@ export const load: PageServerLoad = async ({ params, locals, url, setHeaders })
|
||||
const postHeadline = post.headline ?? post.linkName ?? slug;
|
||||
const postDescription = post.seoDescription ?? post.subheadline ?? post.excerpt ?? '';
|
||||
|
||||
const isEvent = !!(post as { isEvent?: boolean }).isEvent;
|
||||
const eventDateRaw = (post as { eventDate?: string }).eventDate ?? null;
|
||||
const eventInfo = getPostEventInfo(post);
|
||||
const isEvent = eventInfo.isEvent;
|
||||
const eventDateRaw = eventInfo.eventDate;
|
||||
const eventDateLabel =
|
||||
isEvent && eventDateRaw
|
||||
? new Date(eventDateRaw).toLocaleDateString('de-DE', {
|
||||
@@ -123,9 +125,7 @@ export const load: PageServerLoad = async ({ params, locals, url, setHeaders })
|
||||
minute: '2-digit',
|
||||
})
|
||||
: null;
|
||||
const eventLocation = isEvent
|
||||
? ((post as { eventLocation?: { text?: string; lat?: number; lng?: number } }).eventLocation ?? null)
|
||||
: null;
|
||||
const eventLocation = eventInfo.eventLocation;
|
||||
|
||||
const hasCoords = !!(eventLocation?.lat && eventLocation?.lng);
|
||||
const _lat = eventLocation?.lat ?? 0;
|
||||
|
||||
Reference in New Issue
Block a user