init
This commit is contained in:
30
src/sanity/client.ts
Normal file
30
src/sanity/client.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { createClient, type QueryParams } from 'next-sanity'
|
||||
import { apiVersion, dataset, projectId } from './env'
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV === 'development'
|
||||
|
||||
export const client = createClient({
|
||||
projectId,
|
||||
dataset,
|
||||
apiVersion,
|
||||
useCdn: isDevelopment ? false : true,
|
||||
})
|
||||
|
||||
export async function sanityFetch<const QueryString extends string>({
|
||||
query,
|
||||
params = {},
|
||||
revalidate = 60,
|
||||
tags = [],
|
||||
}: {
|
||||
query: QueryString
|
||||
params?: QueryParams
|
||||
revalidate?: number | false
|
||||
tags?: string[]
|
||||
}) {
|
||||
return client.fetch(query, params, {
|
||||
next: {
|
||||
revalidate: isDevelopment || tags.length ? false : revalidate,
|
||||
tags,
|
||||
},
|
||||
})
|
||||
}
|
20
src/sanity/env.ts
Normal file
20
src/sanity/env.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export const apiVersion =
|
||||
process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2024-07-25'
|
||||
|
||||
export const dataset = assertValue(
|
||||
process.env.NEXT_PUBLIC_SANITY_DATASET,
|
||||
'Missing environment variable: NEXT_PUBLIC_SANITY_DATASET',
|
||||
)
|
||||
|
||||
export const projectId = assertValue(
|
||||
process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
|
||||
'Missing environment variable: NEXT_PUBLIC_SANITY_PROJECT_ID',
|
||||
)
|
||||
|
||||
function assertValue<T>(v: T | undefined, errorMessage: string): T {
|
||||
if (v === undefined) {
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
9
src/sanity/image.ts
Normal file
9
src/sanity/image.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import createImageUrlBuilder from '@sanity/image-url'
|
||||
import type { SanityImageSource } from '@sanity/image-url/lib/types/types'
|
||||
import { dataset, projectId } from './env'
|
||||
|
||||
const builder = createImageUrlBuilder({ projectId, dataset })
|
||||
|
||||
export function image(source: SanityImageSource) {
|
||||
return builder.image(source).auto('format')
|
||||
}
|
131
src/sanity/queries.ts
Normal file
131
src/sanity/queries.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { defineQuery } from 'next-sanity'
|
||||
import { sanityFetch } from './client'
|
||||
|
||||
const TOTAL_POSTS_QUERY = defineQuery(/* groq */ `count(*[
|
||||
_type == "post"
|
||||
&& defined(slug.current)
|
||||
&& (isFeatured != true || defined($category))
|
||||
&& select(defined($category) => $category in categories[]->slug.current, true)
|
||||
])`)
|
||||
|
||||
export async function getPostsCount(category?: string) {
|
||||
return await sanityFetch({
|
||||
query: TOTAL_POSTS_QUERY,
|
||||
params: { category: category ?? null },
|
||||
})
|
||||
}
|
||||
|
||||
const POSTS_QUERY = defineQuery(/* groq */ `*[
|
||||
_type == "post"
|
||||
&& defined(slug.current)
|
||||
&& (isFeatured != true || defined($category))
|
||||
&& select(defined($category) => $category in categories[]->slug.current, true)
|
||||
]|order(publishedAt desc)[$startIndex...$endIndex]{
|
||||
title,
|
||||
"slug": slug.current,
|
||||
publishedAt,
|
||||
excerpt,
|
||||
author->{
|
||||
name,
|
||||
image,
|
||||
},
|
||||
}`)
|
||||
|
||||
export async function getPosts(
|
||||
startIndex: number,
|
||||
endIndex: number,
|
||||
category?: string,
|
||||
) {
|
||||
return await sanityFetch({
|
||||
query: POSTS_QUERY,
|
||||
params: {
|
||||
startIndex,
|
||||
endIndex,
|
||||
category: category ?? null,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const FEATURED_POSTS_QUERY = defineQuery(/* groq */ `*[
|
||||
_type == "post"
|
||||
&& isFeatured == true
|
||||
&& defined(slug.current)
|
||||
]|order(publishedAt desc)[0...$quantity]{
|
||||
title,
|
||||
"slug": slug.current,
|
||||
publishedAt,
|
||||
mainImage,
|
||||
excerpt,
|
||||
author->{
|
||||
name,
|
||||
image,
|
||||
},
|
||||
}`)
|
||||
|
||||
export async function getFeaturedPosts(quantity: number) {
|
||||
return await sanityFetch({
|
||||
query: FEATURED_POSTS_QUERY,
|
||||
params: { quantity },
|
||||
})
|
||||
}
|
||||
|
||||
const FEED_POSTS_QUERY = defineQuery(/* groq */ `*[
|
||||
_type == "post"
|
||||
&& defined(slug.current)
|
||||
]|order(isFeatured, publishedAt desc){
|
||||
title,
|
||||
"slug": slug.current,
|
||||
publishedAt,
|
||||
mainImage,
|
||||
excerpt,
|
||||
author->{
|
||||
name,
|
||||
},
|
||||
}`)
|
||||
|
||||
export async function getPostsForFeed() {
|
||||
return await sanityFetch({
|
||||
query: FEED_POSTS_QUERY,
|
||||
})
|
||||
}
|
||||
|
||||
const POST_QUERY = defineQuery(/* groq */ `*[
|
||||
_type == "post"
|
||||
&& slug.current == $slug
|
||||
][0]{
|
||||
publishedAt,
|
||||
title,
|
||||
mainImage,
|
||||
excerpt,
|
||||
body,
|
||||
author->{
|
||||
name,
|
||||
image,
|
||||
},
|
||||
categories[]->{
|
||||
title,
|
||||
"slug": slug.current,
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
export async function getPost(slug: string) {
|
||||
return await sanityFetch({
|
||||
query: POST_QUERY,
|
||||
params: { slug },
|
||||
})
|
||||
}
|
||||
|
||||
const CATEGORIES_QUERY = defineQuery(/* groq */ `*[
|
||||
_type == "category"
|
||||
&& count(*[_type == "post" && defined(slug.current) && ^._id in categories[]._ref]) > 0
|
||||
]|order(title asc){
|
||||
title,
|
||||
"slug": slug.current,
|
||||
}`)
|
||||
|
||||
export async function getCategories() {
|
||||
return await sanityFetch({
|
||||
query: CATEGORIES_QUERY,
|
||||
})
|
||||
}
|
10
src/sanity/schema.ts
Normal file
10
src/sanity/schema.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { SchemaTypeDefinition } from 'sanity'
|
||||
|
||||
import { authorType } from './types/author'
|
||||
import { blockContentType } from './types/block-content'
|
||||
import { categoryType } from './types/category'
|
||||
import { postType } from './types/post'
|
||||
|
||||
export const schema: { types: SchemaTypeDefinition[] } = {
|
||||
types: [blockContentType, categoryType, postType, authorType],
|
||||
}
|
458
src/sanity/types.ts
Normal file
458
src/sanity/types.ts
Normal file
@@ -0,0 +1,458 @@
|
||||
/**
|
||||
* ---------------------------------------------------------------------------------
|
||||
* This file has been generated by Sanity TypeGen.
|
||||
* Command: `sanity typegen generate`
|
||||
*
|
||||
* Any modifications made directly to this file will be overwritten the next time
|
||||
* the TypeScript definitions are generated. Please make changes to the Sanity
|
||||
* schema definitions and/or GROQ queries if you need to update these types.
|
||||
*
|
||||
* For more information on how to use Sanity TypeGen, visit the official documentation:
|
||||
* https://www.sanity.io/docs/sanity-typegen
|
||||
* ---------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
// Source: schema.json
|
||||
export type SanityImagePaletteSwatch = {
|
||||
_type: 'sanity.imagePaletteSwatch'
|
||||
background?: string
|
||||
foreground?: string
|
||||
population?: number
|
||||
title?: string
|
||||
}
|
||||
|
||||
export type SanityImagePalette = {
|
||||
_type: 'sanity.imagePalette'
|
||||
darkMuted?: SanityImagePaletteSwatch
|
||||
lightVibrant?: SanityImagePaletteSwatch
|
||||
darkVibrant?: SanityImagePaletteSwatch
|
||||
vibrant?: SanityImagePaletteSwatch
|
||||
dominant?: SanityImagePaletteSwatch
|
||||
lightMuted?: SanityImagePaletteSwatch
|
||||
muted?: SanityImagePaletteSwatch
|
||||
}
|
||||
|
||||
export type SanityImageDimensions = {
|
||||
_type: 'sanity.imageDimensions'
|
||||
height?: number
|
||||
width?: number
|
||||
aspectRatio?: number
|
||||
}
|
||||
|
||||
export type SanityFileAsset = {
|
||||
_id: string
|
||||
_type: 'sanity.fileAsset'
|
||||
_createdAt: string
|
||||
_updatedAt: string
|
||||
_rev: string
|
||||
originalFilename?: string
|
||||
label?: string
|
||||
title?: string
|
||||
description?: string
|
||||
altText?: string
|
||||
sha1hash?: string
|
||||
extension?: string
|
||||
mimeType?: string
|
||||
size?: number
|
||||
assetId?: string
|
||||
uploadId?: string
|
||||
path?: string
|
||||
url?: string
|
||||
source?: SanityAssetSourceData
|
||||
}
|
||||
|
||||
export type Geopoint = {
|
||||
_type: 'geopoint'
|
||||
lat?: number
|
||||
lng?: number
|
||||
alt?: number
|
||||
}
|
||||
|
||||
export type Post = {
|
||||
_id: string
|
||||
_type: 'post'
|
||||
_createdAt: string
|
||||
_updatedAt: string
|
||||
_rev: string
|
||||
title?: string
|
||||
slug?: Slug
|
||||
publishedAt?: string
|
||||
isFeatured?: boolean
|
||||
author?: {
|
||||
_ref: string
|
||||
_type: 'reference'
|
||||
_weak?: boolean
|
||||
[internalGroqTypeReferenceTo]?: 'author'
|
||||
}
|
||||
mainImage?: {
|
||||
asset?: {
|
||||
_ref: string
|
||||
_type: 'reference'
|
||||
_weak?: boolean
|
||||
[internalGroqTypeReferenceTo]?: 'sanity.imageAsset'
|
||||
}
|
||||
hotspot?: SanityImageHotspot
|
||||
crop?: SanityImageCrop
|
||||
alt?: string
|
||||
_type: 'image'
|
||||
}
|
||||
categories?: Array<{
|
||||
_ref: string
|
||||
_type: 'reference'
|
||||
_weak?: boolean
|
||||
_key: string
|
||||
[internalGroqTypeReferenceTo]?: 'category'
|
||||
}>
|
||||
excerpt?: string
|
||||
body?: Array<
|
||||
| {
|
||||
children?: Array<{
|
||||
marks?: Array<string>
|
||||
text?: string
|
||||
_type: 'span'
|
||||
_key: string
|
||||
}>
|
||||
style?: 'normal' | 'h2' | 'h3' | 'blockquote'
|
||||
listItem?: 'bullet' | 'number'
|
||||
markDefs?: Array<{
|
||||
href?: string
|
||||
_type: 'link'
|
||||
_key: string
|
||||
}>
|
||||
level?: number
|
||||
_type: 'block'
|
||||
_key: string
|
||||
}
|
||||
| {
|
||||
asset?: {
|
||||
_ref: string
|
||||
_type: 'reference'
|
||||
_weak?: boolean
|
||||
[internalGroqTypeReferenceTo]?: 'sanity.imageAsset'
|
||||
}
|
||||
hotspot?: SanityImageHotspot
|
||||
crop?: SanityImageCrop
|
||||
alt?: string
|
||||
_type: 'image'
|
||||
_key: string
|
||||
}
|
||||
>
|
||||
}
|
||||
|
||||
export type Author = {
|
||||
_id: string
|
||||
_type: 'author'
|
||||
_createdAt: string
|
||||
_updatedAt: string
|
||||
_rev: string
|
||||
name?: string
|
||||
slug?: Slug
|
||||
image?: {
|
||||
asset?: {
|
||||
_ref: string
|
||||
_type: 'reference'
|
||||
_weak?: boolean
|
||||
[internalGroqTypeReferenceTo]?: 'sanity.imageAsset'
|
||||
}
|
||||
hotspot?: SanityImageHotspot
|
||||
crop?: SanityImageCrop
|
||||
_type: 'image'
|
||||
}
|
||||
}
|
||||
|
||||
export type Category = {
|
||||
_id: string
|
||||
_type: 'category'
|
||||
_createdAt: string
|
||||
_updatedAt: string
|
||||
_rev: string
|
||||
title?: string
|
||||
slug?: Slug
|
||||
}
|
||||
|
||||
export type Slug = {
|
||||
_type: 'slug'
|
||||
current?: string
|
||||
source?: string
|
||||
}
|
||||
|
||||
export type BlockContent = Array<
|
||||
| {
|
||||
children?: Array<{
|
||||
marks?: Array<string>
|
||||
text?: string
|
||||
_type: 'span'
|
||||
_key: string
|
||||
}>
|
||||
style?: 'normal' | 'h2' | 'h3' | 'blockquote'
|
||||
listItem?: 'bullet' | 'number'
|
||||
markDefs?: Array<{
|
||||
href?: string
|
||||
_type: 'link'
|
||||
_key: string
|
||||
}>
|
||||
level?: number
|
||||
_type: 'block'
|
||||
_key: string
|
||||
}
|
||||
| {
|
||||
asset?: {
|
||||
_ref: string
|
||||
_type: 'reference'
|
||||
_weak?: boolean
|
||||
[internalGroqTypeReferenceTo]?: 'sanity.imageAsset'
|
||||
}
|
||||
hotspot?: SanityImageHotspot
|
||||
crop?: SanityImageCrop
|
||||
alt?: string
|
||||
_type: 'image'
|
||||
_key: string
|
||||
}
|
||||
>
|
||||
|
||||
export type SanityImageCrop = {
|
||||
_type: 'sanity.imageCrop'
|
||||
top?: number
|
||||
bottom?: number
|
||||
left?: number
|
||||
right?: number
|
||||
}
|
||||
|
||||
export type SanityImageHotspot = {
|
||||
_type: 'sanity.imageHotspot'
|
||||
x?: number
|
||||
y?: number
|
||||
height?: number
|
||||
width?: number
|
||||
}
|
||||
|
||||
export type SanityImageAsset = {
|
||||
_id: string
|
||||
_type: 'sanity.imageAsset'
|
||||
_createdAt: string
|
||||
_updatedAt: string
|
||||
_rev: string
|
||||
originalFilename?: string
|
||||
label?: string
|
||||
title?: string
|
||||
description?: string
|
||||
altText?: string
|
||||
sha1hash?: string
|
||||
extension?: string
|
||||
mimeType?: string
|
||||
size?: number
|
||||
assetId?: string
|
||||
uploadId?: string
|
||||
path?: string
|
||||
url?: string
|
||||
metadata?: SanityImageMetadata
|
||||
source?: SanityAssetSourceData
|
||||
}
|
||||
|
||||
export type SanityAssetSourceData = {
|
||||
_type: 'sanity.assetSourceData'
|
||||
name?: string
|
||||
id?: string
|
||||
url?: string
|
||||
}
|
||||
|
||||
export type SanityImageMetadata = {
|
||||
_type: 'sanity.imageMetadata'
|
||||
location?: Geopoint
|
||||
dimensions?: SanityImageDimensions
|
||||
palette?: SanityImagePalette
|
||||
lqip?: string
|
||||
blurHash?: string
|
||||
hasAlpha?: boolean
|
||||
isOpaque?: boolean
|
||||
}
|
||||
|
||||
export type AllSanitySchemaTypes =
|
||||
| SanityImagePaletteSwatch
|
||||
| SanityImagePalette
|
||||
| SanityImageDimensions
|
||||
| SanityFileAsset
|
||||
| Geopoint
|
||||
| Post
|
||||
| Author
|
||||
| Category
|
||||
| Slug
|
||||
| BlockContent
|
||||
| SanityImageCrop
|
||||
| SanityImageHotspot
|
||||
| SanityImageAsset
|
||||
| SanityAssetSourceData
|
||||
| SanityImageMetadata
|
||||
export declare const internalGroqTypeReferenceTo: unique symbol
|
||||
// Source: ./src/sanity/queries.ts
|
||||
// Variable: TOTAL_POSTS_QUERY
|
||||
// Query: count(*[ _type == "post" && defined(slug.current) && (isFeatured != true || defined($category)) && select(defined($category) => $category in categories[]->slug.current, true)])
|
||||
export type TOTAL_POSTS_QUERYResult = number
|
||||
// Variable: POSTS_QUERY
|
||||
// Query: *[ _type == "post" && defined(slug.current) && (isFeatured != true || defined($category)) && select(defined($category) => $category in categories[]->slug.current, true)]|order(publishedAt desc)[$startIndex...$endIndex]{ title, "slug": slug.current, publishedAt, excerpt, author->{ name, image, },}
|
||||
export type POSTS_QUERYResult = Array<{
|
||||
title: string | null
|
||||
slug: string | null
|
||||
publishedAt: string | null
|
||||
excerpt: string | null
|
||||
author: {
|
||||
name: string | null
|
||||
image: {
|
||||
asset?: {
|
||||
_ref: string
|
||||
_type: 'reference'
|
||||
_weak?: boolean
|
||||
[internalGroqTypeReferenceTo]?: 'sanity.imageAsset'
|
||||
}
|
||||
hotspot?: SanityImageHotspot
|
||||
crop?: SanityImageCrop
|
||||
_type: 'image'
|
||||
} | null
|
||||
} | null
|
||||
}>
|
||||
// Variable: FEATURED_POSTS_QUERY
|
||||
// Query: *[ _type == "post" && isFeatured == true && defined(slug.current)]|order(publishedAt desc)[0...$quantity]{ title, "slug": slug.current, publishedAt, mainImage, excerpt, author->{ name, image, },}
|
||||
export type FEATURED_POSTS_QUERYResult = Array<{
|
||||
title: string | null
|
||||
slug: string | null
|
||||
publishedAt: string | null
|
||||
mainImage: {
|
||||
asset?: {
|
||||
_ref: string
|
||||
_type: 'reference'
|
||||
_weak?: boolean
|
||||
[internalGroqTypeReferenceTo]?: 'sanity.imageAsset'
|
||||
}
|
||||
hotspot?: SanityImageHotspot
|
||||
crop?: SanityImageCrop
|
||||
alt?: string
|
||||
_type: 'image'
|
||||
} | null
|
||||
excerpt: string | null
|
||||
author: {
|
||||
name: string | null
|
||||
image: {
|
||||
asset?: {
|
||||
_ref: string
|
||||
_type: 'reference'
|
||||
_weak?: boolean
|
||||
[internalGroqTypeReferenceTo]?: 'sanity.imageAsset'
|
||||
}
|
||||
hotspot?: SanityImageHotspot
|
||||
crop?: SanityImageCrop
|
||||
_type: 'image'
|
||||
} | null
|
||||
} | null
|
||||
}>
|
||||
// Variable: FEED_POSTS_QUERY
|
||||
// Query: *[ _type == "post" && defined(slug.current)]|order(isFeatured, publishedAt desc){ title, "slug": slug.current, publishedAt, mainImage, excerpt, author->{ name, },}
|
||||
export type FEED_POSTS_QUERYResult = Array<{
|
||||
title: string | null
|
||||
slug: string | null
|
||||
publishedAt: string | null
|
||||
mainImage: {
|
||||
asset?: {
|
||||
_ref: string
|
||||
_type: 'reference'
|
||||
_weak?: boolean
|
||||
[internalGroqTypeReferenceTo]?: 'sanity.imageAsset'
|
||||
}
|
||||
hotspot?: SanityImageHotspot
|
||||
crop?: SanityImageCrop
|
||||
alt?: string
|
||||
_type: 'image'
|
||||
} | null
|
||||
excerpt: string | null
|
||||
author: {
|
||||
name: string | null
|
||||
} | null
|
||||
}>
|
||||
// Variable: POST_QUERY
|
||||
// Query: *[ _type == "post" && slug.current == $slug][0]{ publishedAt, title, mainImage, excerpt, body, author->{ name, image, }, categories[]->{ title, "slug": slug.current, }}
|
||||
export type POST_QUERYResult = {
|
||||
publishedAt: string | null
|
||||
title: string | null
|
||||
mainImage: {
|
||||
asset?: {
|
||||
_ref: string
|
||||
_type: 'reference'
|
||||
_weak?: boolean
|
||||
[internalGroqTypeReferenceTo]?: 'sanity.imageAsset'
|
||||
}
|
||||
hotspot?: SanityImageHotspot
|
||||
crop?: SanityImageCrop
|
||||
alt?: string
|
||||
_type: 'image'
|
||||
} | null
|
||||
excerpt: string | null
|
||||
body: Array<
|
||||
| {
|
||||
children?: Array<{
|
||||
marks?: Array<string>
|
||||
text?: string
|
||||
_type: 'span'
|
||||
_key: string
|
||||
}>
|
||||
style?: 'blockquote' | 'h2' | 'h3' | 'normal'
|
||||
listItem?: 'bullet' | 'number'
|
||||
markDefs?: Array<{
|
||||
href?: string
|
||||
_type: 'link'
|
||||
_key: string
|
||||
}>
|
||||
level?: number
|
||||
_type: 'block'
|
||||
_key: string
|
||||
}
|
||||
| {
|
||||
asset?: {
|
||||
_ref: string
|
||||
_type: 'reference'
|
||||
_weak?: boolean
|
||||
[internalGroqTypeReferenceTo]?: 'sanity.imageAsset'
|
||||
}
|
||||
hotspot?: SanityImageHotspot
|
||||
crop?: SanityImageCrop
|
||||
alt?: string
|
||||
_type: 'image'
|
||||
_key: string
|
||||
}
|
||||
> | null
|
||||
author: {
|
||||
name: string | null
|
||||
image: {
|
||||
asset?: {
|
||||
_ref: string
|
||||
_type: 'reference'
|
||||
_weak?: boolean
|
||||
[internalGroqTypeReferenceTo]?: 'sanity.imageAsset'
|
||||
}
|
||||
hotspot?: SanityImageHotspot
|
||||
crop?: SanityImageCrop
|
||||
_type: 'image'
|
||||
} | null
|
||||
} | null
|
||||
categories: Array<{
|
||||
title: string | null
|
||||
slug: string | null
|
||||
}> | null
|
||||
} | null
|
||||
// Variable: CATEGORIES_QUERY
|
||||
// Query: *[ _type == "category" && count(*[_type == "post" && defined(slug.current) && ^._id in categories[]._ref]) > 0]|order(title asc){ title, "slug": slug.current,}
|
||||
export type CATEGORIES_QUERYResult = Array<{
|
||||
title: string | null
|
||||
slug: string | null
|
||||
}>
|
||||
|
||||
// Query TypeMap
|
||||
import '@sanity/client'
|
||||
declare module '@sanity/client' {
|
||||
interface SanityQueries {
|
||||
'count(*[\n _type == "post"\n && defined(slug.current)\n && (isFeatured != true || defined($category))\n && select(defined($category) => $category in categories[]->slug.current, true)\n])': TOTAL_POSTS_QUERYResult
|
||||
'*[\n _type == "post"\n && defined(slug.current)\n && (isFeatured != true || defined($category))\n && select(defined($category) => $category in categories[]->slug.current, true)\n]|order(publishedAt desc)[$startIndex...$endIndex]{\n title,\n "slug": slug.current,\n publishedAt,\n excerpt,\n author->{\n name,\n image,\n },\n}': POSTS_QUERYResult
|
||||
'*[\n _type == "post"\n && isFeatured == true\n && defined(slug.current)\n]|order(publishedAt desc)[0...$quantity]{\n title,\n "slug": slug.current,\n publishedAt,\n mainImage,\n excerpt,\n author->{\n name,\n image,\n },\n}': FEATURED_POSTS_QUERYResult
|
||||
'*[\n _type == "post"\n && defined(slug.current)\n]|order(isFeatured, publishedAt desc){\n title,\n "slug": slug.current,\n publishedAt,\n mainImage,\n excerpt,\n author->{\n name,\n },\n}': FEED_POSTS_QUERYResult
|
||||
'*[\n _type == "post"\n && slug.current == $slug\n][0]{\n publishedAt,\n title,\n mainImage,\n excerpt,\n body,\n author->{\n name,\n image,\n },\n categories[]->{\n title,\n "slug": slug.current,\n }\n}\n': POST_QUERYResult
|
||||
'*[\n _type == "category"\n && count(*[_type == "post" && defined(slug.current) && ^._id in categories[]._ref]) > 0\n]|order(title asc){\n title,\n "slug": slug.current,\n}': CATEGORIES_QUERYResult
|
||||
}
|
||||
}
|
36
src/sanity/types/author.ts
Normal file
36
src/sanity/types/author.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { UserIcon } from '@heroicons/react/16/solid'
|
||||
import { defineField, defineType } from 'sanity'
|
||||
|
||||
export const authorType = defineType({
|
||||
name: 'author',
|
||||
title: 'Author',
|
||||
type: 'document',
|
||||
icon: UserIcon,
|
||||
fields: [
|
||||
defineField({
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
}),
|
||||
defineField({
|
||||
name: 'slug',
|
||||
type: 'slug',
|
||||
options: {
|
||||
source: 'name',
|
||||
maxLength: 96,
|
||||
},
|
||||
}),
|
||||
defineField({
|
||||
name: 'image',
|
||||
type: 'image',
|
||||
options: {
|
||||
hotspot: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
preview: {
|
||||
select: {
|
||||
title: 'name',
|
||||
media: 'image',
|
||||
},
|
||||
},
|
||||
})
|
70
src/sanity/types/block-content.ts
Normal file
70
src/sanity/types/block-content.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { ImageIcon } from '@sanity/icons'
|
||||
import { defineArrayMember, defineType } from 'sanity'
|
||||
|
||||
export const blockContentType = defineType({
|
||||
title: 'Block Content',
|
||||
name: 'blockContent',
|
||||
type: 'array',
|
||||
of: [
|
||||
defineArrayMember({
|
||||
type: 'block',
|
||||
styles: [
|
||||
{ title: 'Normal', value: 'normal' },
|
||||
{ title: 'H2', value: 'h2' },
|
||||
{ title: 'H3', value: 'h3' },
|
||||
{ title: 'Quote', value: 'blockquote' },
|
||||
],
|
||||
marks: {
|
||||
decorators: [
|
||||
{ title: 'Strong', value: 'strong' },
|
||||
{ title: 'Emphasis', value: 'em' },
|
||||
{ title: 'Code', value: 'code' },
|
||||
],
|
||||
annotations: [
|
||||
{
|
||||
title: 'URL',
|
||||
name: 'link',
|
||||
type: 'object',
|
||||
fields: [
|
||||
{
|
||||
title: 'URL',
|
||||
name: 'href',
|
||||
type: 'url',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
defineArrayMember({
|
||||
title: 'Separator',
|
||||
name: 'separator',
|
||||
type: 'object',
|
||||
fields: [
|
||||
{
|
||||
name: 'style',
|
||||
title: 'Style',
|
||||
type: 'string',
|
||||
options: {
|
||||
list: [
|
||||
{ title: 'Line', value: 'line' },
|
||||
{ title: 'Space', value: 'space' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
defineArrayMember({
|
||||
type: 'image',
|
||||
icon: ImageIcon,
|
||||
options: { hotspot: true },
|
||||
fields: [
|
||||
{
|
||||
name: 'alt',
|
||||
type: 'string',
|
||||
title: 'Alternative Text',
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
21
src/sanity/types/category.ts
Normal file
21
src/sanity/types/category.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { TagIcon } from '@heroicons/react/16/solid'
|
||||
import { defineField, defineType } from 'sanity'
|
||||
|
||||
export const categoryType = defineType({
|
||||
name: 'category',
|
||||
type: 'document',
|
||||
icon: TagIcon,
|
||||
fields: [
|
||||
defineField({
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
}),
|
||||
defineField({
|
||||
name: 'slug',
|
||||
type: 'slug',
|
||||
options: {
|
||||
source: 'title',
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
116
src/sanity/types/post.ts
Normal file
116
src/sanity/types/post.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { DocumentIcon } from '@heroicons/react/16/solid'
|
||||
import { groq } from 'next-sanity'
|
||||
import { defineField, defineType } from 'sanity'
|
||||
import { apiVersion } from '../env'
|
||||
|
||||
export const postType = defineType({
|
||||
name: 'post',
|
||||
title: 'Post',
|
||||
type: 'document',
|
||||
icon: DocumentIcon,
|
||||
fields: [
|
||||
defineField({
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
validation: (Rule) => Rule.required(),
|
||||
}),
|
||||
defineField({
|
||||
name: 'slug',
|
||||
type: 'slug',
|
||||
options: {
|
||||
source: 'title',
|
||||
},
|
||||
validation: (Rule) =>
|
||||
Rule.required().error('A slug is required for the post URL.'),
|
||||
}),
|
||||
defineField({
|
||||
name: 'publishedAt',
|
||||
type: 'datetime',
|
||||
validation: (Rule) =>
|
||||
Rule.required().error(
|
||||
'A publication date is required for ordering posts.',
|
||||
),
|
||||
}),
|
||||
defineField({
|
||||
name: 'isFeatured',
|
||||
type: 'boolean',
|
||||
initialValue: false,
|
||||
validation: (Rule) =>
|
||||
Rule.custom(async (isFeatured, { getClient }) => {
|
||||
if (isFeatured !== true) {
|
||||
return true
|
||||
}
|
||||
|
||||
let featuredPosts = await getClient({ apiVersion })
|
||||
.withConfig({ perspective: 'previewDrafts' })
|
||||
.fetch<number>(
|
||||
groq`count(*[_type == 'post' && isFeatured == true])`,
|
||||
)
|
||||
|
||||
return featuredPosts > 3
|
||||
? 'Only 3 posts can be featured at a time.'
|
||||
: true
|
||||
}),
|
||||
}),
|
||||
defineField({
|
||||
name: 'author',
|
||||
type: 'reference',
|
||||
to: { type: 'author' },
|
||||
}),
|
||||
defineField({
|
||||
name: 'mainImage',
|
||||
type: 'image',
|
||||
options: {
|
||||
hotspot: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'alt',
|
||||
type: 'string',
|
||||
title: 'Alternative text',
|
||||
},
|
||||
],
|
||||
}),
|
||||
defineField({
|
||||
name: 'categories',
|
||||
type: 'array',
|
||||
of: [{ type: 'reference', to: { type: 'category' } }],
|
||||
}),
|
||||
defineField({
|
||||
name: 'excerpt',
|
||||
type: 'text',
|
||||
rows: 3,
|
||||
}),
|
||||
defineField({
|
||||
name: 'body',
|
||||
type: 'blockContent',
|
||||
}),
|
||||
],
|
||||
preview: {
|
||||
select: {
|
||||
title: 'title',
|
||||
media: 'mainImage',
|
||||
author: 'author.name',
|
||||
isFeatured: 'isFeatured',
|
||||
},
|
||||
prepare({ title, author, media, isFeatured }) {
|
||||
return {
|
||||
title,
|
||||
subtitle: [isFeatured && 'Featured', author && `By ${author}`]
|
||||
.filter(Boolean)
|
||||
.join(' | '),
|
||||
media,
|
||||
}
|
||||
},
|
||||
},
|
||||
orderings: [
|
||||
{
|
||||
name: 'isFeaturedAndPublishedAtDesc',
|
||||
title: 'Featured & Latest Published',
|
||||
by: [
|
||||
{ field: 'isFeatured', direction: 'desc' },
|
||||
{ field: 'publishedAt', direction: 'desc' },
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
Reference in New Issue
Block a user