diff --git a/src/lib/cms.ts b/src/lib/cms.ts index 100796b..40529af 100644 --- a/src/lib/cms.ts +++ b/src/lib/cms.ts @@ -625,6 +625,42 @@ export async function getPostBySlug( return cached("post", key, fetchFn); } +/** + * Generic single-entry fetch by `(collection, slug)`. Used by the embed/widget + * route that renders a single block standalone. + * + * Resolves nested references via `_resolve=all` by default so block components + * see the same data shape as inside a page row. + */ +export async function getEntryBySlug>( + collection: string, + slug: string, + options?: ContentGetOptions, +): Promise { + const resolveKey = options?.resolve?.join(",") ?? "all"; + const fieldsKey = options?.fields?.join(",") ?? ""; + const depthKey = options?.depth ?? ""; + const key = `entry:${collection}:${slug}:${options?.locale ?? ""}:${resolveKey}:${depthKey}:${fieldsKey}`; + const fetchFn = async (): Promise => { + const base = getBaseUrl(); + const u = new URL(`${base}/api/content/${encodeURIComponent(collection)}/${encodeURIComponent(slug)}`); + if (options?.locale) u.searchParams.set("_locale", options.locale); + u.searchParams.set("_resolve", options?.resolve?.length ? options.resolve.join(",") : "all"); + if (typeof options?.depth === "number") u.searchParams.set("_depth", String(options.depth)); + if (options?.fields?.length) u.searchParams.set("_fields", options.fields.join(",")); + if (options?.preview) u.searchParams.set("preview", options.preview); + if (options?.previewDraft) u.searchParams.set("preview_draft", options.previewDraft); + const res = await cmsFetch(u.toString()); + if (res.status === 404) return null; + if (!res.ok) { + throw new Error(`RustyCMS getEntry failed: ${res.status} for ${collection}/${slug}`); + } + return (await res.json()) as T; + }; + if (options?.preview || options?.previewDraft) return fetchFn(); + return cached(collection, key, fetchFn); +} + /** Für Post-URLs wird slug bevorzugt (nicht _slug). Führender Slash wird ignoriert. */ export async function getPostSlugs(): Promise { const items = await getPosts(); diff --git a/src/lib/components/BlockRenderer.svelte b/src/lib/components/BlockRenderer.svelte new file mode 100644 index 0000000..9c5f742 --- /dev/null +++ b/src/lib/components/BlockRenderer.svelte @@ -0,0 +1,95 @@ + + +{#if type === "markdown"} + +{:else if type === "headline"} + +{:else if type === "html"} + +{:else if type === "iframe"} + +{:else if type === "image"} + +{:else if type === "image_gallery"} + +{:else if type === "files"} + +{:else if type === "youtube_video"} + +{:else if type === "youtube_video_gallery"} + +{:else if type === "quote"} + +{:else if type === "quote_carousel"} + +{:else if type === "link_list"} + +{:else if type === "post_overview"} + +{:else if type === "searchable_text"} + +{:else if type === "calendar"} + +{:else if type === "organisations"} + +{:else if type === "opnform"} + +{:else if type === "deadline_banner"} + +{:else if type === "live_strommix"} + +{:else} +
+ Unbekannter Block: {type} +
+{/if} diff --git a/src/routes/(embed)/+layout@.svelte b/src/routes/(embed)/+layout@.svelte new file mode 100644 index 0000000..e90eb80 --- /dev/null +++ b/src/routes/(embed)/+layout@.svelte @@ -0,0 +1,21 @@ + + +
+ {@render children()} +
+ + diff --git a/src/routes/(embed)/widget/[collection]/[slug]/+page.server.ts b/src/routes/(embed)/widget/[collection]/[slug]/+page.server.ts new file mode 100644 index 0000000..c1fc5e2 --- /dev/null +++ b/src/routes/(embed)/widget/[collection]/[slug]/+page.server.ts @@ -0,0 +1,32 @@ +import { error } from "@sveltejs/kit"; +import { getEntryBySlug } from "$lib/cms"; +import type { PageServerLoad } from "./$types"; + +export const load: PageServerLoad = async ({ params, url, setHeaders }) => { + const { collection, slug } = params; + const preview = url.searchParams.get("preview") ?? undefined; + const locale = url.searchParams.get("_locale") ?? undefined; + + const entry = await getEntryBySlug>(collection, slug, { + resolve: ["all"], + preview, + locale, + }); + if (!entry) throw error(404, `Entry not found: ${collection}/${slug}`); + + // _type is needed by BlockRenderer's switch. Server backfills it from the + // route param so the renderer sees the same shape as inside a page row. + if (typeof entry._type !== "string") { + (entry as Record)._type = collection; + } + + // Embeds: skip search engines, mild caching for public reads. + setHeaders({ + "x-robots-tag": "noindex", + "cache-control": preview + ? "private, no-store" + : "public, s-maxage=60, stale-while-revalidate=300", + }); + + return { collection, slug, entry, preview: !!preview }; +}; diff --git a/src/routes/(embed)/widget/[collection]/[slug]/+page.svelte b/src/routes/(embed)/widget/[collection]/[slug]/+page.svelte new file mode 100644 index 0000000..1cc8f88 --- /dev/null +++ b/src/routes/(embed)/widget/[collection]/[slug]/+page.svelte @@ -0,0 +1,23 @@ + + + + {data.collection} / {data.slug} + + + +
+ +
+ +