Initial SvelteKit frontend port of windwiderstand.de
Deploy / verify (push) Failing after 32s
Deploy / deploy (push) Has been skipped

Full parity with Astro site: content rows, post/tag routes, pagination,
event badges + OSM map, comments, Live-Search via /api/search-index,
CMS image proxy, RSS, sitemap.

Deploy: Dockerfile + docker-compose.prod.yml + Gitea Actions workflow
to build + push to git.pm86.de registry and ssh-deploy to Contabo.
This commit is contained in:
Peter Meier
2026-04-17 22:01:30 +02:00
parent 852cef0980
commit 2fef91a548
137 changed files with 28585 additions and 0 deletions
+128
View File
@@ -0,0 +1,128 @@
import type { PageServerLoad } from './$types';
import { getPostBySlug, getPageConfigBySlug, getPageConfigs } from '$lib/cms';
import {
resolveContentImages,
ensureTransformedImage,
processMarkdownHtmlImages,
} from '$lib/rusty-image';
import {
resolvePostTagsInPost,
resolvePostOverviewBlocks,
resolveSearchableTextBlocks,
getPostImageUrl,
formatPostDate,
} from '$lib/blog-utils';
import { POST_RESOLVE } from '$lib/constants';
import { error } from '@sveltejs/kit';
import { marked } from 'marked';
marked.setOptions({ gfm: true });
export const load: PageServerLoad = async ({ params, locals, url }) => {
const { slug } = params;
const translations = locals.translations ?? {};
const post = await getPostBySlug(slug, {
locale: 'de',
resolve: POST_RESOLVE,
});
if (!post) {
error(404, 'Beitrag nicht gefunden');
}
await resolveContentImages(post);
const tagsMap = await resolvePostOverviewBlocks(post);
await resolveSearchableTextBlocks(post, tagsMap);
resolvePostTagsInPost(post, tagsMap);
const rawPostImageUrl = getPostImageUrl(post.postImage);
const postImageUrl = rawPostImageUrl
? await ensureTransformedImage(rawPostImageUrl, {
width: 150,
height: 200,
fit: 'cover',
})
: null;
const postDate = formatPostDate(post.created);
const postContent = (post as { content?: string }).content;
let contentHtml =
typeof postContent === 'string' && postContent.trim()
? (marked.parse(postContent) as string)
: '';
if (contentHtml) {
contentHtml = await processMarkdownHtmlImages(contentHtml);
}
const hasRowContent =
((post as { row1Content?: unknown[] }).row1Content?.length ?? 0) > 0 ||
((post as { row2Content?: unknown[] }).row2Content?.length ?? 0) > 0 ||
((post as { row3Content?: unknown[] }).row3Content?.length ?? 0) > 0;
const isEvent = !!(post as { isEvent?: boolean }).isEvent;
const eventDateRaw = (post as { eventDate?: string }).eventDate ?? null;
const eventDateLabel =
isEvent && eventDateRaw
? new Date(eventDateRaw).toLocaleDateString('de-DE', {
weekday: 'long',
day: '2-digit',
month: 'long',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
})
: null;
const eventLocation = isEvent
? ((post as { eventLocation?: { text?: string; lat?: number; lng?: number } }).eventLocation ?? null)
: null;
const hasCoords = !!(eventLocation?.lat && eventLocation?.lng);
const _lat = eventLocation?.lat ?? 0;
const _lng = eventLocation?.lng ?? 0;
const mapEmbedUrl = hasCoords
? `https://www.openstreetmap.org/export/embed.html?bbox=${_lng - 0.01},${_lat - 0.007},${_lng + 0.01},${_lat + 0.007}&layer=mapnik&marker=${_lat},${_lng}`
: null;
const mapLinkUrl = hasCoords
? `https://www.openstreetmap.org/?mlat=${eventLocation!.lat}&mlon=${eventLocation!.lng}#map=15/${eventLocation!.lat}/${eventLocation!.lng}`
: eventLocation?.text
? `https://www.openstreetmap.org/search?query=${encodeURIComponent(eventLocation.text)}`
: null;
const pageConfig =
(await getPageConfigBySlug('page-config-default', { locale: 'de' })) ??
(await getPageConfigs({ locale: 'de', per_page: 1 }))[0] ??
null;
const commentSlotRaw =
(pageConfig as { postCommentSlot?: string } | null)?.postCommentSlot ?? '';
const commentSlot = commentSlotRaw
.replace(/\{\{PAGE_ID\}\}/g, slug)
.replace(/\{\{PAGE_URL\}\}/g, url.href)
.replace(/\{\{PAGE_TITLE\}\}/g, post.headline ?? post.linkName ?? slug);
const showCommentSection = (post as { showCommentSection?: boolean }).showCommentSection !== false;
return {
post,
postImageUrl,
postDate,
contentHtml,
hasRowContent,
isEvent,
eventDateRaw,
eventDateLabel,
eventLocationText: eventLocation?.text ?? null,
mapEmbedUrl,
mapLinkUrl,
commentSlot: showCommentSection ? commentSlot : '',
translations,
seoTitle: post.seoTitle ?? post.headline ?? post.linkName ?? slug,
seoDescription: post.seoDescription ?? post.subheadline ?? post.excerpt ?? '',
socialImage: postImageUrl ?? undefined,
breadcrumbItems: [
{ href: '/', label: 'Start' },
{ href: '/posts', label: 'Beiträge' },
{ label: post.headline ?? post.linkName ?? post.slug ?? post._slug ?? slug },
],
};
};