feat(deadline-banner): auto mode pulls all calendar_items globally
Deploy / verify (push) Successful in 40s
Deploy / deploy (push) Successful in 59s

Auto-mode deadline_banner now resolves to the next future event across
all calendar_items in the CMS instead of requiring a manually-curated
items[] pool. Server load hooks (+page.server.ts for /, /[...slug],
/post/[slug]) call resolveDeadlineBannerBlocks which fetches the
global calendar_item list once and attaches it as items[] when mode
is "auto" and items[] is not already resolved. Component logic
unchanged — it still picks the soonest future entry.

Also:
- cms.ts: getCalendarItems() bulk list fetcher
- PostOverviewBlock.svelte: design defaults to "cards" when value is
  empty string (not just null/undefined) so existing entries with
  design: "" render

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Peter Meier
2026-04-19 11:02:50 +02:00
parent ffd4f599cd
commit 62d25202a6
9 changed files with 242 additions and 1 deletions
+55
View File
@@ -6,6 +6,7 @@ import {
getTextFragmentBySlug,
getCalendarBySlug,
getCalendarItemBySlug,
getCalendarItems,
} from "./cms";
import type { RowContentLayout } from "./block-types";
import type { CalendarItemData } from "./block-types";
@@ -528,6 +529,60 @@ export async function resolveCalendarBlocks(
}
}
function isDeadlineBannerBlock(
item: unknown,
): item is {
_type?: string;
mode?: string;
items?: unknown[];
} {
return (
typeof item === "object" &&
item !== null &&
(item as { _type?: string })._type === "deadline_banner"
);
}
/**
* Löst deadline_banner-Blöcke im auto-Modus auf: Lädt alle calendar_items
* global und setzt sie als Pool in block.items, sofern dort noch keine
* aufgelösten Einträge liegen. Die Komponente wählt daraus den nächsten
* zukünftigen Termin.
*/
export async function resolveDeadlineBannerBlocks(
layout: RowContentLayout,
): Promise<void> {
const rows = [
layout.row1Content ?? [],
layout.row2Content ?? [],
layout.row3Content ?? [],
];
let pool: CalendarItemData[] | null = null;
const loadPool = async (): Promise<CalendarItemData[]> => {
if (pool === null) {
const items = await getCalendarItems({ locale: "de" });
pool = items as unknown as CalendarItemData[];
}
return pool;
};
for (const content of rows) {
if (!Array.isArray(content)) continue;
for (const item of content) {
if (!isDeadlineBannerBlock(item)) continue;
if (item.mode !== "auto") continue;
const existing = item.items ?? [];
const first = existing[0];
const alreadyResolved =
typeof first === "object" &&
first !== null &&
"terminZeit" in first &&
"title" in first;
if (alreadyResolved) continue;
(item as { items?: CalendarItemData[] }).items = await loadPool();
}
}
}
function isSearchableTextBlock(
item: unknown,
): item is { _type?: string; textFragments?: unknown[] } {