- Install @sanity/client + @portabletext/to-html - studio/ — Sanity v3 project with post schema (title, slug, date, category, summary, cover image with hotspot, Portable Text body). Run with `cd studio && npm install && sanity dev` after setting SANITY_STUDIO_PROJECT_ID. - src/lib/sanity.ts — typed client, GROQ queries, Portable Text → HTML conversion - src/lib/news.ts — adds getArticles() / getArticle() async functions that prefer Sanity when SANITY_PROJECT_ID is set; fall back to local markdown otherwise - events/index.astro, events/[slug].astro, events/page/[page].astro — switch to async getArticles()/getArticle(); [slug].astro drops getStaticPaths() in favour of SSR request-time fetch with redirect-on-not-found - .env.example — documents SANITY_PROJECT_ID / SANITY_DATASET / SANITY_API_TOKEN Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
116 lines
2.9 KiB
TypeScript
116 lines
2.9 KiB
TypeScript
import { defineField, defineType } from 'sanity';
|
|
|
|
export const postType = defineType({
|
|
name: 'post',
|
|
title: 'Post',
|
|
type: 'document',
|
|
fields: [
|
|
defineField({
|
|
name: 'title',
|
|
title: 'Title',
|
|
type: 'string',
|
|
validation: (r) => r.required(),
|
|
}),
|
|
defineField({
|
|
name: 'slug',
|
|
title: 'Slug',
|
|
type: 'slug',
|
|
options: { source: 'title', maxLength: 96 },
|
|
validation: (r) => r.required(),
|
|
}),
|
|
defineField({
|
|
name: 'date',
|
|
title: 'Date',
|
|
type: 'date',
|
|
options: { dateFormat: 'YYYY-MM-DD' },
|
|
validation: (r) => r.required(),
|
|
}),
|
|
defineField({
|
|
name: 'category',
|
|
title: 'Category',
|
|
type: 'string',
|
|
options: {
|
|
list: [
|
|
{ title: 'News', value: 'News' },
|
|
{ title: 'Events', value: 'Events' },
|
|
{ title: 'Fundraising', value: 'Fundraising' },
|
|
],
|
|
},
|
|
initialValue: 'News',
|
|
validation: (r) => r.required(),
|
|
}),
|
|
defineField({
|
|
name: 'summary',
|
|
title: 'Summary',
|
|
type: 'text',
|
|
rows: 3,
|
|
description: 'One or two sentences shown in article cards and meta descriptions.',
|
|
validation: (r) => r.required().max(300),
|
|
}),
|
|
defineField({
|
|
name: 'image',
|
|
title: 'Cover image',
|
|
type: 'image',
|
|
options: { hotspot: true },
|
|
fields: [
|
|
defineField({
|
|
name: 'alt',
|
|
title: 'Alt text',
|
|
type: 'string',
|
|
}),
|
|
],
|
|
}),
|
|
defineField({
|
|
name: 'body',
|
|
title: 'Body',
|
|
type: 'array',
|
|
of: [
|
|
{
|
|
type: 'block',
|
|
styles: [
|
|
{ title: 'Normal', value: 'normal' },
|
|
{ title: 'Heading 2', value: 'h2' },
|
|
{ title: 'Heading 3', value: 'h3' },
|
|
{ title: 'Quote', value: 'blockquote' },
|
|
],
|
|
marks: {
|
|
decorators: [
|
|
{ title: 'Bold', value: 'strong' },
|
|
{ title: 'Italic', value: 'em' },
|
|
],
|
|
annotations: [
|
|
{
|
|
name: 'link',
|
|
type: 'object',
|
|
title: 'Link',
|
|
fields: [
|
|
{
|
|
name: 'href',
|
|
type: 'url',
|
|
title: 'URL',
|
|
validation: (r) =>
|
|
r.uri({ allowRelative: true, scheme: ['http', 'https', 'mailto', 'tel'] }),
|
|
},
|
|
{
|
|
name: 'blank',
|
|
type: 'boolean',
|
|
title: 'Open in new tab',
|
|
initialValue: false,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{ type: 'image', options: { hotspot: true } },
|
|
],
|
|
}),
|
|
],
|
|
preview: {
|
|
select: {
|
|
title: 'title',
|
|
subtitle: 'date',
|
|
media: 'image',
|
|
},
|
|
},
|
|
});
|