added image support
Build and Deploy / build-and-deploy (push) Successful in 6m50s

This commit is contained in:
2026-05-18 07:01:11 +02:00
parent 58f7ca2d5c
commit 92a80e8759
9 changed files with 25 additions and 546 deletions
-3
View File
@@ -37,9 +37,6 @@ export default defineConfig({
.schemaType('theaterHomepage') .schemaType('theaterHomepage')
.documentId('theaterHomepage') .documentId('theaterHomepage')
), ),
S.divider(),
S.documentTypeListItem('post').title('Blog Posts'),
S.documentTypeListItem('page').title('Pages'),
]), ]),
}), }),
visionTool(), visionTool(),
-234
View File
@@ -1,234 +0,0 @@
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import { sanityClient } from '../../sanity/client';
import { renderPortableText } from '../../sanity/portableText';
import { urlFor } from '../../sanity/imageUrl';
import { allPostsQuery } from '../../sanity/queries';
export async function getStaticPaths() {
const posts = await sanityClient.fetch(allPostsQuery);
return posts.map((post: any) => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props as { post: any };
const bodyHtml = renderPortableText(post.body);
---
<BaseLayout title={post.title} description={post.excerpt}>
<article class="blog-post">
<header class="post-header">
<div class="container">
<h1>{post.title}</h1>
<p class="post-meta">
<time datetime={post.date}>
{new Date(post.date).toLocaleDateString('de-DE', {
year: 'numeric',
month: 'long',
day: 'numeric'
})}
</time>
</p>
</div>
</header>
{post.coverImage && (
<div class="post-cover">
<img src={urlFor(post.coverImage)} alt={post.title} />
</div>
)}
<div class="post-content">
<div class="container">
<div class="prose" set:html={bodyHtml} />
</div>
</div>
<footer class="post-footer">
<div class="container">
<a href="/blog" class="back-link">← Back to all posts</a>
</div>
</footer>
</article>
</BaseLayout>
<style>
.post-header {
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
color: white;
padding: 4rem 0 3rem;
text-align: center;
}
.post-header h1 {
font-size: 3rem;
margin-bottom: 1rem;
line-height: 1.2;
}
.post-meta {
font-size: 1.1rem;
opacity: 0.9;
}
.post-cover {
width: 100%;
max-height: 500px;
overflow: hidden;
background-color: #f8f9fa;
}
.post-cover img {
width: 100%;
height: 100%;
object-fit: cover;
}
.post-content {
padding: 3rem 0;
}
.prose {
max-width: 800px;
margin: 0 auto;
font-size: 1.125rem;
line-height: 1.8;
color: #333;
}
.prose :global(h1) {
font-size: 2.5rem;
margin-top: 2.5rem;
margin-bottom: 1rem;
color: #2c3e50;
line-height: 1.2;
}
.prose :global(h2) {
font-size: 2rem;
margin-top: 2rem;
margin-bottom: 0.75rem;
color: #2c3e50;
line-height: 1.3;
}
.prose :global(h3) {
font-size: 1.5rem;
margin-top: 1.5rem;
margin-bottom: 0.5rem;
color: #2c3e50;
}
.prose :global(p) {
margin-bottom: 1.5rem;
}
.prose :global(ul),
.prose :global(ol) {
margin-bottom: 1.5rem;
padding-left: 2rem;
}
.prose :global(li) {
margin-bottom: 0.5rem;
}
.prose :global(a) {
color: #8b0000;
text-decoration: underline;
}
.prose :global(a:hover) {
color: #c0392b;
}
.prose :global(strong) {
font-weight: 700;
color: #2c3e50;
}
.prose :global(blockquote) {
border-left: 4px solid #8b0000;
padding-left: 1.5rem;
margin: 2rem 0;
font-style: italic;
color: #666;
}
.prose :global(code) {
background-color: #f8f9fa;
padding: 0.2rem 0.4rem;
border-radius: 3px;
font-family: 'Courier New', monospace;
font-size: 0.9em;
}
.prose :global(pre) {
background-color: #2c3e50;
color: white;
padding: 1.5rem;
border-radius: 6px;
overflow-x: auto;
margin: 2rem 0;
}
.prose :global(pre code) {
background-color: transparent;
padding: 0;
}
.prose :global(img) {
max-width: 100%;
height: auto;
border-radius: 6px;
margin: 2rem 0;
}
.post-footer {
padding: 2rem 0 3rem;
border-top: 1px solid #e1e8ed;
}
.back-link {
color: #8b0000;
text-decoration: none;
font-weight: 600;
font-size: 1.1rem;
}
.back-link:hover {
color: #c0392b;
}
@media (max-width: 768px) {
.post-header h1 {
font-size: 2rem;
}
.post-meta {
font-size: 1rem;
}
.prose {
font-size: 1rem;
}
.prose :global(h1) {
font-size: 2rem;
}
.prose :global(h2) {
font-size: 1.5rem;
}
.prose :global(h3) {
font-size: 1.25rem;
}
.post-cover {
max-height: 300px;
}
}
</style>
-180
View File
@@ -1,180 +0,0 @@
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import { sanityClient } from '../../sanity/client';
import { urlFor } from '../../sanity/imageUrl';
import { allPostsQuery } from '../../sanity/queries';
const allPosts = await sanityClient.fetch(allPostsQuery);
---
<BaseLayout title="Blog" description="Latest news and updates from Theater Ziefen">
<div class="blog-header">
<div class="container">
<h1>Blog</h1>
<p>Stay updated with the latest news and behind-the-scenes insights</p>
</div>
</div>
<div class="blog-content">
<div class="container">
{allPosts.length === 0 ? (
<p class="no-posts">No blog posts yet. Check back soon!</p>
) : (
<div class="posts-list">
{allPosts.map((post: any) => (
<article class="post-item">
{post.coverImage && (
<div class="post-image">
<img src={urlFor(post.coverImage)} alt={post.title} />
</div>
)}
<div class="post-content">
<h2>
<a href={`/blog/${post.slug}`}>
{post.title}
</a>
</h2>
<p class="post-meta">
<time datetime={post.date}>
{new Date(post.date).toLocaleDateString('de-DE', {
year: 'numeric',
month: 'long',
day: 'numeric'
})}
</time>
</p>
<p class="post-excerpt">{post.excerpt}</p>
<a href={`/blog/${post.slug}`} class="read-more">
Read full post →
</a>
</div>
</article>
))}
</div>
)}
</div>
</div>
</BaseLayout>
<style>
.blog-header {
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
color: white;
padding: 4rem 0 3rem;
text-align: center;
}
.blog-header h1 {
font-size: 3rem;
margin-bottom: 0.5rem;
}
.blog-header p {
font-size: 1.25rem;
opacity: 0.9;
}
.blog-content {
padding: 3rem 0;
}
.no-posts {
text-align: center;
font-size: 1.25rem;
color: #666;
padding: 4rem 0;
}
.posts-list {
display: flex;
flex-direction: column;
gap: 3rem;
max-width: 900px;
margin: 0 auto;
}
.post-item {
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.post-item:hover {
transform: translateY(-4px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.post-image {
width: 100%;
height: 300px;
overflow: hidden;
}
.post-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.post-content {
padding: 2rem;
}
.post-content h2 {
font-size: 2rem;
margin-bottom: 0.75rem;
}
.post-content h2 a {
color: #2c3e50;
text-decoration: none;
}
.post-content h2 a:hover {
color: #8b0000;
}
.post-meta {
color: #999;
font-size: 0.95rem;
margin-bottom: 1rem;
}
.post-excerpt {
color: #666;
line-height: 1.7;
margin-bottom: 1.5rem;
font-size: 1.05rem;
}
.read-more {
color: #8b0000;
text-decoration: none;
font-weight: 600;
font-size: 1rem;
}
.read-more:hover {
color: #c0392b;
}
@media (max-width: 768px) {
.blog-header h1 {
font-size: 2.5rem;
}
.blog-header p {
font-size: 1.1rem;
}
.post-content h2 {
font-size: 1.5rem;
}
.post-image {
height: 200px;
}
}
</style>
+12 -1
View File
@@ -1,6 +1,17 @@
import { toHTML } from '@portabletext/to-html' import { toHTML } from '@portabletext/to-html'
import { urlFor } from './imageUrl'
export function renderPortableText(blocks: any[]): string { export function renderPortableText(blocks: any[]): string {
if (!blocks?.length) return '' if (!blocks?.length) return ''
return toHTML(blocks) return toHTML(blocks, {
components: {
types: {
image: ({ value }) => {
const url = urlFor(value)
if (!url) return ''
return `<img src="${url}" alt="${value.alt ?? ''}" />`
},
},
},
})
} }
-33
View File
@@ -1,34 +1 @@
export const theaterHomepageQuery = `*[_type == "theaterHomepage" && _id == "theaterHomepage"][0]` export const theaterHomepageQuery = `*[_type == "theaterHomepage" && _id == "theaterHomepage"][0]`
export const allPostsQuery = `
*[_type == "post"] | order(date desc) {
_id,
title,
"slug": slug.current,
date,
excerpt,
coverImage,
body
}
`
export const postBySlugQuery = `
*[_type == "post" && slug.current == $slug][0] {
_id,
title,
"slug": slug.current,
date,
excerpt,
coverImage,
body
}
`
export const pageBySlugQuery = `
*[_type == "page" && slug.current == $slug][0] {
_id,
title,
description,
body
}
`
+1 -3
View File
@@ -1,5 +1,3 @@
import { postSchema } from './post'
import { pageSchema } from './page'
import { theaterHomepageSchema } from './theaterHomepage' import { theaterHomepageSchema } from './theaterHomepage'
export const schemaTypes = [postSchema, pageSchema, theaterHomepageSchema] export const schemaTypes = [theaterHomepageSchema]
-33
View File
@@ -1,33 +0,0 @@
import { defineType, defineField } from 'sanity'
export const pageSchema = defineType({
name: 'page',
title: 'Pages',
type: 'document',
fields: [
defineField({
name: 'title',
title: 'Title',
type: 'string',
validation: (r) => r.required(),
}),
defineField({
name: 'slug',
title: 'Slug',
type: 'slug',
options: { source: 'title' },
}),
defineField({
name: 'description',
title: 'Description',
type: 'string',
}),
defineField({
name: 'body',
title: 'Content',
type: 'array',
of: [{ type: 'block' }],
validation: (r) => r.required(),
}),
],
})
-47
View File
@@ -1,47 +0,0 @@
import { defineType, defineField } from 'sanity'
export const postSchema = defineType({
name: 'post',
title: 'Blog Posts',
type: 'document',
fields: [
defineField({
name: 'title',
title: 'Title',
type: 'string',
validation: (r) => r.required(),
}),
defineField({
name: 'slug',
title: 'Slug',
type: 'slug',
options: { source: 'title' },
validation: (r) => r.required(),
}),
defineField({
name: 'date',
title: 'Publication Date',
type: 'datetime',
validation: (r) => r.required(),
}),
defineField({
name: 'excerpt',
title: 'Excerpt',
type: 'string',
validation: (r) => r.required(),
}),
defineField({
name: 'coverImage',
title: 'Cover Image',
type: 'image',
options: { hotspot: true },
}),
defineField({
name: 'body',
title: 'Body',
type: 'array',
of: [{ type: 'block' }, { type: 'image' }],
validation: (r) => r.required(),
}),
],
})
+12 -12
View File
@@ -33,8 +33,8 @@ export const theaterHomepageSchema = defineType({
fields: [ fields: [
defineField({ name: 'title_de', title: 'Title (German)', type: 'string', validation: (r) => r.required() }), defineField({ name: 'title_de', title: 'Title (German)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'title_en', title: 'Title (English)', type: 'string', validation: (r) => r.required() }), defineField({ name: 'title_en', title: 'Title (English)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'content_de', title: 'Content (German)', type: 'array', of: [{ type: 'block' }] }), defineField({ name: 'content_de', title: 'Content (German)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'content_en', title: 'Content (English)', type: 'array', of: [{ type: 'block' }] }), defineField({ name: 'content_en', title: 'Content (English)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
], ],
}), }),
@@ -46,8 +46,8 @@ export const theaterHomepageSchema = defineType({
fields: [ fields: [
defineField({ name: 'title_de', title: 'Title (German)', type: 'string', validation: (r) => r.required() }), defineField({ name: 'title_de', title: 'Title (German)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'title_en', title: 'Title (English)', type: 'string', validation: (r) => r.required() }), defineField({ name: 'title_en', title: 'Title (English)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'content_de', title: 'Content (German)', type: 'array', of: [{ type: 'block' }] }), defineField({ name: 'content_de', title: 'Content (German)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'content_en', title: 'Content (English)', type: 'array', of: [{ type: 'block' }] }), defineField({ name: 'content_en', title: 'Content (English)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ defineField({
name: 'organizations', name: 'organizations',
title: 'Organizations', title: 'Organizations',
@@ -76,8 +76,8 @@ export const theaterHomepageSchema = defineType({
fields: [ fields: [
defineField({ name: 'title_de', title: 'Title (German)', type: 'string', validation: (r) => r.required() }), defineField({ name: 'title_de', title: 'Title (German)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'title_en', title: 'Title (English)', type: 'string', validation: (r) => r.required() }), defineField({ name: 'title_en', title: 'Title (English)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'content_de', title: 'Content (German)', type: 'array', of: [{ type: 'block' }] }), defineField({ name: 'content_de', title: 'Content (German)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'content_en', title: 'Content (English)', type: 'array', of: [{ type: 'block' }] }), defineField({ name: 'content_en', title: 'Content (English)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
], ],
}), }),
@@ -89,8 +89,8 @@ export const theaterHomepageSchema = defineType({
fields: [ fields: [
defineField({ name: 'title_de', title: 'Title (German)', type: 'string', validation: (r) => r.required() }), defineField({ name: 'title_de', title: 'Title (German)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'title_en', title: 'Title (English)', type: 'string', validation: (r) => r.required() }), defineField({ name: 'title_en', title: 'Title (English)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'content_de', title: 'Content (German)', type: 'array', of: [{ type: 'block' }] }), defineField({ name: 'content_de', title: 'Content (German)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'content_en', title: 'Content (English)', type: 'array', of: [{ type: 'block' }] }), defineField({ name: 'content_en', title: 'Content (English)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'director_name', title: 'Director Name', type: 'string' }), defineField({ name: 'director_name', title: 'Director Name', type: 'string' }),
defineField({ name: 'director_email', title: 'Director Email', type: 'string' }), defineField({ name: 'director_email', title: 'Director Email', type: 'string' }),
defineField({ name: 'director_phone', title: 'Director Phone', type: 'string' }), defineField({ name: 'director_phone', title: 'Director Phone', type: 'string' }),
@@ -133,8 +133,8 @@ export const theaterHomepageSchema = defineType({
fields: [ fields: [
defineField({ name: 'title_de', title: 'Title (German)', type: 'string', validation: (r) => r.required() }), defineField({ name: 'title_de', title: 'Title (German)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'title_en', title: 'Title (English)', type: 'string', validation: (r) => r.required() }), defineField({ name: 'title_en', title: 'Title (English)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'content_de', title: 'Content (German)', type: 'array', of: [{ type: 'block' }] }), defineField({ name: 'content_de', title: 'Content (German)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'content_en', title: 'Content (English)', type: 'array', of: [{ type: 'block' }] }), defineField({ name: 'content_en', title: 'Content (English)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
], ],
}), }),
@@ -163,8 +163,8 @@ export const theaterHomepageSchema = defineType({
fields: [ fields: [
defineField({ name: 'title_de', title: 'Title (German)', type: 'string', validation: (r) => r.required() }), defineField({ name: 'title_de', title: 'Title (German)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'title_en', title: 'Title (English)', type: 'string', validation: (r) => r.required() }), defineField({ name: 'title_en', title: 'Title (English)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'content_de', title: 'Content (German)', type: 'array', of: [{ type: 'block' }] }), defineField({ name: 'content_de', title: 'Content (German)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'content_en', title: 'Content (English)', type: 'array', of: [{ type: 'block' }] }), defineField({ name: 'content_en', title: 'Content (English)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'ticket_url', title: 'Ticket Purchase URL', type: 'url' }), defineField({ name: 'ticket_url', title: 'Ticket Purchase URL', type: 'url' }),
], ],
}), }),