bsmnt 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.changeset/2026-02-11-test-patch-bump.md +5 -0
- package/.changeset/README.md +10 -0
- package/.changeset/config.json +16 -0
- package/.cursor/rules/README.md +184 -0
- package/.cursor/rules/architecture.mdc +437 -0
- package/.cursor/rules/components.mdc +436 -0
- package/.cursor/rules/integrations.mdc +447 -0
- package/.cursor/rules/main.mdc +278 -0
- package/.cursor/rules/styling.mdc +433 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +14 -0
- package/.github/workflows/.gitkeep +0 -0
- package/.github/workflows/ci.yml +37 -0
- package/.github/workflows/release.yml +54 -0
- package/.tldr/cache/call_graph.json +7 -0
- package/.tldr/languages.json +6 -0
- package/.tldr/status +1 -0
- package/.tldrignore +84 -0
- package/.vscode/extensions.json +20 -0
- package/.vscode/settings.json +98 -0
- package/CHANGELOG.md +13 -0
- package/CLAUDE.md +138 -0
- package/README.md +176 -0
- package/bin/index.js +262 -0
- package/biome.json +44 -0
- package/bun.lock +496 -0
- package/changelog/04-02-26.md +86 -0
- package/changelog/05-02-26.md +101 -0
- package/changelog/09-02-26.md +83 -0
- package/docs/fix-studio-hydration.md +46 -0
- package/docs/plans/2026-01-29-sanity-smart-merge-design.md +196 -0
- package/docs/plans/2026-01-29-sanity-smart-merge-implementation.md +695 -0
- package/docs/sanity-setup-steps.md +199 -0
- package/integrations/basehub/README.md +3 -0
- package/integrations/sanity/app/api/draft-mode/disable/route.ts +7 -0
- package/integrations/sanity/app/api/draft-mode/enable/route.ts +21 -0
- package/integrations/sanity/app/api/revalidate/route.ts +37 -0
- package/integrations/sanity/app/layout.tsx +111 -0
- package/integrations/sanity/app/sitemap.ts +80 -0
- package/integrations/sanity/app/studio/[[...tool]]/page.tsx +8 -0
- package/integrations/sanity/app/studio/layout.tsx +7 -0
- package/integrations/sanity/components/ui/sanity-image/index.tsx +37 -0
- package/integrations/sanity/lib/integrations/README.md +58 -0
- package/integrations/sanity/lib/integrations/check-integration.ts +62 -0
- package/integrations/sanity/lib/integrations/sanity/README.md +144 -0
- package/integrations/sanity/lib/integrations/sanity/client.ts +30 -0
- package/integrations/sanity/lib/integrations/sanity/components/disable-draft-mode.tsx +29 -0
- package/integrations/sanity/lib/integrations/sanity/components/rich-text.tsx +73 -0
- package/integrations/sanity/lib/integrations/sanity/env.ts +38 -0
- package/integrations/sanity/lib/integrations/sanity/live/index.tsx +34 -0
- package/integrations/sanity/lib/integrations/sanity/queries.ts +99 -0
- package/integrations/sanity/lib/integrations/sanity/sanity.cli.ts +20 -0
- package/integrations/sanity/lib/integrations/sanity/sanity.config.ts +94 -0
- package/integrations/sanity/lib/integrations/sanity/sanity.types.ts +337 -0
- package/integrations/sanity/lib/integrations/sanity/schema.json +1850 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/article.ts +132 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/example.ts +203 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/index.ts +37 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/link.ts +127 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/metadata.ts +68 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/navigation.ts +39 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/page.ts +77 -0
- package/integrations/sanity/lib/integrations/sanity/schemas/richText.ts +59 -0
- package/integrations/sanity/lib/integrations/sanity/structure.ts +5 -0
- package/integrations/sanity/lib/integrations/sanity/utils/image.ts +11 -0
- package/integrations/sanity/lib/integrations/sanity/utils/link.ts +61 -0
- package/integrations/sanity/lib/scripts/copy-sanity-mcp.ts +23 -0
- package/integrations/sanity/lib/scripts/generate-page.ts +310 -0
- package/integrations/sanity/lib/utils/metadata.ts +190 -0
- package/layers/experiment/components/layout/header/index.tsx +58 -0
- package/layers/experiment/components/layout/navigation-menu.tsx +127 -0
- package/layers/experiment/lib/constants.ts +12 -0
- package/layers/webgl/app/page.tsx +10 -0
- package/layers/webgl/components/webgl/canvas/dynamic.tsx +34 -0
- package/layers/webgl/components/webgl/canvas/index.tsx +43 -0
- package/layers/webgl/components/webgl/components/scene/index.tsx +21 -0
- package/layers/webgpu/.gitkeep +0 -0
- package/package.json +44 -0
- package/plugins/README.md +21 -0
- package/plugins/no-anchor-element.grit +11 -0
- package/plugins/no-relative-parent-imports.grit +6 -0
- package/plugins/no-unnecessary-forwardref.grit +5 -0
- package/src/commands/add-integration.js +325 -0
- package/src/commands/create.js +415 -0
- package/src/commands/setup-sanity.js +426 -0
- package/src/commands/worktree.js +805 -0
- package/src/mergers/check-integration-merger.js +105 -0
- package/src/mergers/config.js +137 -0
- package/src/mergers/index.js +355 -0
- package/src/mergers/layout-merger.js +223 -0
- package/src/mergers/next-config-merger.js +63 -0
- package/src/mergers/sitemap-merger.js +121 -0
- package/tasks/prd-next-starter-dynamic-layers.md +184 -0
- package/tasks/prd.json +153 -0
- package/tasks/progress.txt +115 -0
- package/template-hooks/use-battery.ts +126 -0
- package/template-hooks/use-device-perf.ts +184 -0
- package/template-hooks/use-intersection-observer.ts +32 -0
- package/template-hooks/use-media.ts +33 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Sanity CMS Integration
|
|
2
|
+
|
|
3
|
+
Headless CMS with visual editing.
|
|
4
|
+
|
|
5
|
+
## Environment Variables
|
|
6
|
+
|
|
7
|
+
```env
|
|
8
|
+
# Required
|
|
9
|
+
NEXT_PUBLIC_SANITY_PROJECT_ID="your-project-id"
|
|
10
|
+
NEXT_PUBLIC_SANITY_DATASET="production"
|
|
11
|
+
|
|
12
|
+
# Required for Visual Editing & Live Preview
|
|
13
|
+
NEXT_PUBLIC_SANITY_API_READ_TOKEN="your-viewer-token"
|
|
14
|
+
SANITY_PRIVATE_TOKEN="your-editor-token"
|
|
15
|
+
|
|
16
|
+
# Optional
|
|
17
|
+
NEXT_PUBLIC_SANITY_STUDIO_URL="http://localhost:3000/studio"
|
|
18
|
+
NEXT_PUBLIC_SANITY_API_VERSION="2024-03-15"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
> **Note**: Create tokens in [Sanity Dashboard](https://sanity.io/manage) → Your Project → API → Tokens.
|
|
22
|
+
> - **Viewer** token → `NEXT_PUBLIC_SANITY_API_READ_TOKEN`
|
|
23
|
+
> - **Editor** token → `SANITY_PRIVATE_TOKEN`
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
1. Access Studio at `/studio`
|
|
28
|
+
2. Create content (Pages, Articles)
|
|
29
|
+
3. Click "Present" for visual editing
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
### Fetching Data
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { sanityFetch } from 'next-sanity/live'
|
|
37
|
+
import { pageQuery } from '@/lib/integrations/sanity/queries'
|
|
38
|
+
|
|
39
|
+
export default async function Page({ params }) {
|
|
40
|
+
const { data } = await sanityFetch({
|
|
41
|
+
query: pageQuery,
|
|
42
|
+
params: { slug: params.slug }
|
|
43
|
+
})
|
|
44
|
+
return <YourComponent data={data} />
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Build-time Data Fetching
|
|
49
|
+
|
|
50
|
+
For `generateStaticParams` or other build-time functions, use the client directly:
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
import { client } from '@/lib/integrations/sanity/client'
|
|
54
|
+
import { allArticlesQuery } from '@/lib/integrations/sanity/queries'
|
|
55
|
+
|
|
56
|
+
export async function generateStaticParams() {
|
|
57
|
+
if (!client) return []
|
|
58
|
+
const data = await client.fetch(allArticlesQuery)
|
|
59
|
+
return data.map((item) => ({ slug: item.slug?.current ?? '' }))
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Visual Editing
|
|
64
|
+
|
|
65
|
+
Add `data-sanity` attributes for visual editing:
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
import { RichText } from '@/lib/integrations/sanity/components/rich-text'
|
|
69
|
+
|
|
70
|
+
function MyComponent({ data }) {
|
|
71
|
+
return (
|
|
72
|
+
<div data-sanity={data._id}>
|
|
73
|
+
<h1 data-sanity="title">{data.title}</h1>
|
|
74
|
+
<RichText content={data.content} />
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Image Handling
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
import { urlForImage } from '@/lib/integrations/sanity/utils/image'
|
|
84
|
+
import { SanityImage } from '@/components/ui/sanity-image'
|
|
85
|
+
|
|
86
|
+
// Option 1: Using urlForImage utility
|
|
87
|
+
<img src={urlForImage(image).width(800).url()} alt={image.alt} />
|
|
88
|
+
|
|
89
|
+
// Option 2: Using SanityImage component
|
|
90
|
+
<SanityImage image={image} maxWidth={800} />
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### SEO Metadata
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import { generateSanityMetadata } from '@/lib/utils/metadata'
|
|
97
|
+
|
|
98
|
+
export async function generateMetadata({ params }) {
|
|
99
|
+
const { data } = await sanityFetch({ query: pageQuery, params })
|
|
100
|
+
return generateSanityMetadata({ document: data, url: `/page/${params.slug}` })
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Creating New Content Types
|
|
105
|
+
|
|
106
|
+
1. **Create schema** in `schemaTypes/`:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { defineField, defineType } from 'sanity'
|
|
110
|
+
|
|
111
|
+
export const landing = defineType({
|
|
112
|
+
name: 'landing',
|
|
113
|
+
type: 'document',
|
|
114
|
+
fields: [
|
|
115
|
+
defineField({ name: 'title', type: 'string' }),
|
|
116
|
+
defineField({ name: 'slug', type: 'slug', options: { source: 'title' } }),
|
|
117
|
+
defineField({ name: 'content', type: 'richText' }),
|
|
118
|
+
],
|
|
119
|
+
})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
2. **Add to schema index** in `schemaTypes/index.ts`
|
|
123
|
+
3. **Create query** in `queries.ts`
|
|
124
|
+
4. **Create page** in `app/`
|
|
125
|
+
|
|
126
|
+
## Caching
|
|
127
|
+
|
|
128
|
+
- **Draft mode**: Uses `cache: 'no-store'` automatically
|
|
129
|
+
- **Published content**: ISR with `revalidate: 3600`
|
|
130
|
+
- All queries use `cacheSignal()` for automatic cleanup
|
|
131
|
+
|
|
132
|
+
See [ARCHITECTURE.md](../../../ARCHITECTURE.md) for cache gotchas.
|
|
133
|
+
|
|
134
|
+
## Troubleshooting
|
|
135
|
+
|
|
136
|
+
**Visual editor not loading:**
|
|
137
|
+
- Check env vars are set correctly
|
|
138
|
+
- Verify draft mode routes exist (`/api/draft-mode/enable`)
|
|
139
|
+
|
|
140
|
+
**Content not updating:**
|
|
141
|
+
- Hard refresh browser
|
|
142
|
+
- Check revalidation webhook is configured
|
|
143
|
+
|
|
144
|
+
**Related**: [Sanity Docs](https://www.sanity.io/docs) · [Parent README](../README.md)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createClient, type SanityClient } from "next-sanity";
|
|
2
|
+
import { isSanityConfigured } from "@/lib/integrations/check-integration";
|
|
3
|
+
import { apiVersion, dataset, privateToken, projectId, studioUrl } from "./env";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sanity client instance
|
|
7
|
+
*
|
|
8
|
+
* Returns null if Sanity is not configured (missing env vars).
|
|
9
|
+
* Always check with isSanityConfigured() before using.
|
|
10
|
+
*/
|
|
11
|
+
export const client: SanityClient | null = isSanityConfigured()
|
|
12
|
+
? createClient({
|
|
13
|
+
projectId,
|
|
14
|
+
dataset,
|
|
15
|
+
apiVersion,
|
|
16
|
+
useCdn: true,
|
|
17
|
+
perspective: "published",
|
|
18
|
+
token: privateToken,
|
|
19
|
+
stega: {
|
|
20
|
+
studioUrl,
|
|
21
|
+
filter: (props) => {
|
|
22
|
+
if (props.sourcePath.at(-1) === "title") {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return props.filterDefault(props);
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
: null;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { usePathname } from "next/navigation";
|
|
5
|
+
import { useDraftModeEnvironment } from "next-sanity/hooks";
|
|
6
|
+
|
|
7
|
+
export function DisableDraftMode() {
|
|
8
|
+
const environment = useDraftModeEnvironment();
|
|
9
|
+
const pathname = usePathname();
|
|
10
|
+
|
|
11
|
+
// Only show the disable draft mode button when outside of Presentation Tool
|
|
12
|
+
if (environment !== "live" && environment !== "unknown") {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (pathname.startsWith("/studio")) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Link
|
|
22
|
+
href="/api/draft-mode/disable"
|
|
23
|
+
scroll={false}
|
|
24
|
+
className="dr-p-4 fixed top-safe right-safe z-50 bg-red font-mono text-primary text-sm uppercase"
|
|
25
|
+
>
|
|
26
|
+
Disable Draft Mode
|
|
27
|
+
</Link>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { PortableText, type PortableTextBlock } from "@portabletext/react";
|
|
2
|
+
import { Link } from "@/components/ui/link";
|
|
3
|
+
import { SanityImage } from "@/components/ui/sanity-image";
|
|
4
|
+
|
|
5
|
+
interface RichTextProps {
|
|
6
|
+
content: PortableTextBlock[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface LinkFieldData {
|
|
10
|
+
type: "external" | "internal";
|
|
11
|
+
url?: string;
|
|
12
|
+
blank?: boolean;
|
|
13
|
+
label?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function resolveLinkFieldUrl(linkData: LinkFieldData): string {
|
|
17
|
+
if (!linkData) return "#";
|
|
18
|
+
|
|
19
|
+
// External URL
|
|
20
|
+
if (linkData.type === "external" && linkData.url) {
|
|
21
|
+
return linkData.url;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Internal path (like /home, /hubspot, /r3f, etc.)
|
|
25
|
+
if (linkData.type === "internal" && linkData.url) {
|
|
26
|
+
return linkData.url;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return "#";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function RichText({ content }: RichTextProps) {
|
|
33
|
+
if (!content) return null;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<PortableText
|
|
37
|
+
value={content}
|
|
38
|
+
components={{
|
|
39
|
+
types: {
|
|
40
|
+
image: ({ value }) => <SanityImage image={value} maxWidth={1920} />,
|
|
41
|
+
},
|
|
42
|
+
marks: {
|
|
43
|
+
link: ({ children, value }) => {
|
|
44
|
+
const href = resolveLinkFieldUrl(value);
|
|
45
|
+
const isExternal = value?.type === "url";
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Link
|
|
49
|
+
href={href}
|
|
50
|
+
target={isExternal && value?.blank ? "_blank" : undefined}
|
|
51
|
+
rel={
|
|
52
|
+
isExternal && value?.blank ? "noopener noreferrer" : undefined
|
|
53
|
+
}
|
|
54
|
+
data-sanity-edit-target
|
|
55
|
+
>
|
|
56
|
+
{children}
|
|
57
|
+
</Link>
|
|
58
|
+
);
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
block: {
|
|
62
|
+
h1: ({ children }) => <h1 className="h1">{children}</h1>,
|
|
63
|
+
h2: ({ children }) => <h2 className="h2">{children}</h2>,
|
|
64
|
+
h3: ({ children }) => <h3 className="h3">{children}</h3>,
|
|
65
|
+
h4: ({ children }) => <h4 className="h4">{children}</h4>,
|
|
66
|
+
h5: ({ children }) => <h5 className="h5">{children}</h5>,
|
|
67
|
+
h6: ({ children }) => <h6 className="h6">{children}</h6>,
|
|
68
|
+
normal: ({ children }) => <p className="p">{children}</p>,
|
|
69
|
+
},
|
|
70
|
+
}}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanity Environment Configuration
|
|
3
|
+
*
|
|
4
|
+
* All values have safe defaults to allow the app to build and run
|
|
5
|
+
* without Sanity configured. Use isSanityConfigured() to check
|
|
6
|
+
* if Sanity is properly set up before making API calls.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Sanity API version - defaults to latest stable */
|
|
10
|
+
export const apiVersion =
|
|
11
|
+
process.env.NEXT_PUBLIC_SANITY_API_VERSION || "2024-03-15";
|
|
12
|
+
|
|
13
|
+
/** Sanity dataset name - defaults to 'production' */
|
|
14
|
+
export const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET || "production";
|
|
15
|
+
|
|
16
|
+
/** Sanity project ID - empty string if not configured */
|
|
17
|
+
export const projectId =
|
|
18
|
+
process.env.NEXT_PUBLIC_SANITY_PROJECT_ID ||
|
|
19
|
+
process.env.SANITY_STUDIO_PROJECT_ID ||
|
|
20
|
+
"";
|
|
21
|
+
|
|
22
|
+
/** Sanity Studio URL for visual editing */
|
|
23
|
+
export const studioUrl =
|
|
24
|
+
process.env.NODE_ENV === "development"
|
|
25
|
+
? "http://localhost:3000/studio"
|
|
26
|
+
: `${process.env.NEXT_PUBLIC_BASE_URL || ""}/studio`;
|
|
27
|
+
|
|
28
|
+
/** Public read token for client-side queries */
|
|
29
|
+
export const publicToken = process.env.NEXT_PUBLIC_SANITY_API_READ_TOKEN || "";
|
|
30
|
+
|
|
31
|
+
/** Private token for server-side mutations (optional) */
|
|
32
|
+
export const privateToken = process.env.SANITY_PRIVATE_TOKEN || "";
|
|
33
|
+
|
|
34
|
+
/** Preview URL for draft mode */
|
|
35
|
+
export const previewURL =
|
|
36
|
+
process.env.NODE_ENV === "development"
|
|
37
|
+
? "http://localhost:3000"
|
|
38
|
+
: process.env.NEXT_PUBLIC_BASE_URL || "";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { defineLive } from "next-sanity/live";
|
|
2
|
+
import { isSanityConfigured } from "@/lib/integrations/check-integration";
|
|
3
|
+
import { client } from "../client";
|
|
4
|
+
import { privateToken, publicToken } from "../env";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Sanity Live configuration
|
|
8
|
+
*
|
|
9
|
+
* When Sanity is not configured, provides stub implementation
|
|
10
|
+
* that returns null instead of throwing errors.
|
|
11
|
+
*/
|
|
12
|
+
const isConfigured = isSanityConfigured() && client;
|
|
13
|
+
|
|
14
|
+
const liveExports = isConfigured
|
|
15
|
+
? defineLive({
|
|
16
|
+
// biome-ignore lint/style/noNonNullAssertion: client is checked via isConfigured above
|
|
17
|
+
client: client!,
|
|
18
|
+
browserToken: publicToken,
|
|
19
|
+
serverToken: privateToken,
|
|
20
|
+
})
|
|
21
|
+
: null;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Standard sanityFetch function from next-sanity/live.
|
|
25
|
+
* When Sanity is not configured, returns null data.
|
|
26
|
+
*/
|
|
27
|
+
export const sanityFetch =
|
|
28
|
+
liveExports?.sanityFetch ?? (async () => ({ data: null }));
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Sanity Live component for real-time updates.
|
|
32
|
+
* Returns null when Sanity is not configured.
|
|
33
|
+
*/
|
|
34
|
+
export const SanityLive = liveExports?.SanityLive ?? (() => null);
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { groq } from "next-sanity";
|
|
2
|
+
|
|
3
|
+
// Helper for rich text content with link projections
|
|
4
|
+
const richTextWithLinks = `
|
|
5
|
+
content[]{
|
|
6
|
+
...,
|
|
7
|
+
markDefs[]{
|
|
8
|
+
...,
|
|
9
|
+
_type == "link" => {
|
|
10
|
+
...,
|
|
11
|
+
internalLink->{_type, slug, title}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
const linkWithLabel = `
|
|
18
|
+
link {
|
|
19
|
+
...,
|
|
20
|
+
internalLink->{_type, slug, title}
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
// Page queries
|
|
25
|
+
export const pageQuery = groq`
|
|
26
|
+
*[_type == "page" && slug.current == $slug][0] {
|
|
27
|
+
_id,
|
|
28
|
+
title,
|
|
29
|
+
slug,
|
|
30
|
+
${richTextWithLinks},
|
|
31
|
+
${linkWithLabel},
|
|
32
|
+
metadata,
|
|
33
|
+
publishedAt,
|
|
34
|
+
_updatedAt
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
export const pageByIdQuery = groq`
|
|
39
|
+
*[_type == "page" && _id == $id][0] {
|
|
40
|
+
_id,
|
|
41
|
+
title,
|
|
42
|
+
slug,
|
|
43
|
+
${richTextWithLinks},
|
|
44
|
+
metadata,
|
|
45
|
+
publishedAt,
|
|
46
|
+
_updatedAt
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
// Article queries
|
|
51
|
+
export const articleQuery = groq`
|
|
52
|
+
*[_type == "article" && slug.current == $slug][0] {
|
|
53
|
+
_id,
|
|
54
|
+
title,
|
|
55
|
+
slug,
|
|
56
|
+
excerpt,
|
|
57
|
+
featuredImage,
|
|
58
|
+
${richTextWithLinks},
|
|
59
|
+
categories,
|
|
60
|
+
tags,
|
|
61
|
+
author,
|
|
62
|
+
publishedAt,
|
|
63
|
+
metadata,
|
|
64
|
+
_updatedAt
|
|
65
|
+
}
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
export const allArticlesQuery = groq`
|
|
69
|
+
*[_type == "article"] | order(publishedAt desc) {
|
|
70
|
+
_id,
|
|
71
|
+
title,
|
|
72
|
+
slug,
|
|
73
|
+
excerpt,
|
|
74
|
+
featuredImage,
|
|
75
|
+
categories,
|
|
76
|
+
tags,
|
|
77
|
+
author,
|
|
78
|
+
publishedAt,
|
|
79
|
+
metadata,
|
|
80
|
+
_updatedAt
|
|
81
|
+
}
|
|
82
|
+
`;
|
|
83
|
+
|
|
84
|
+
export const articleByIdQuery = groq`
|
|
85
|
+
*[_type == "article" && _id == $id][0] {
|
|
86
|
+
_id,
|
|
87
|
+
title,
|
|
88
|
+
slug,
|
|
89
|
+
excerpt,
|
|
90
|
+
featuredImage,
|
|
91
|
+
${richTextWithLinks},
|
|
92
|
+
categories,
|
|
93
|
+
tags,
|
|
94
|
+
author,
|
|
95
|
+
publishedAt,
|
|
96
|
+
metadata,
|
|
97
|
+
_updatedAt
|
|
98
|
+
}
|
|
99
|
+
`;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This configuration file lets you run `$ sanity [command]` in this folder
|
|
3
|
+
* Go to https://www.sanity.io/docs/cli to learn more.
|
|
4
|
+
**/
|
|
5
|
+
import { defineCliConfig } from "sanity/cli";
|
|
6
|
+
import { dataset, projectId } from "./env";
|
|
7
|
+
|
|
8
|
+
export default defineCliConfig({
|
|
9
|
+
api: { projectId, dataset },
|
|
10
|
+
project: {
|
|
11
|
+
basePath: "/studio",
|
|
12
|
+
},
|
|
13
|
+
vite: {
|
|
14
|
+
resolve: {
|
|
15
|
+
alias: {
|
|
16
|
+
"~": __dirname,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This configuration is used to for the Sanity Studio that's mounted on the `/app/studio/[[...tool]]/page.tsx` route
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { visionTool } from "@sanity/vision";
|
|
6
|
+
import { defineConfig } from "sanity";
|
|
7
|
+
import {
|
|
8
|
+
defineDocuments,
|
|
9
|
+
defineLocations,
|
|
10
|
+
presentationTool,
|
|
11
|
+
} from "sanity/presentation";
|
|
12
|
+
import { structureTool } from "sanity/structure";
|
|
13
|
+
import { apiVersion, dataset, previewURL, projectId } from "./env";
|
|
14
|
+
import { schema } from "./schemas";
|
|
15
|
+
import { structure } from "./structure";
|
|
16
|
+
|
|
17
|
+
// Helper function for URL resolution
|
|
18
|
+
function resolveHref(documentType?: string, slug?: string): string | undefined {
|
|
19
|
+
switch (documentType) {
|
|
20
|
+
case "page":
|
|
21
|
+
return slug === "sanity" ? "/sanity" : `/sanity/${slug}`;
|
|
22
|
+
case "article":
|
|
23
|
+
return slug ? `/sanity/${slug}` : undefined;
|
|
24
|
+
default:
|
|
25
|
+
console.warn("Invalid document type:", documentType);
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default defineConfig({
|
|
31
|
+
basePath: "/studio",
|
|
32
|
+
projectId,
|
|
33
|
+
dataset,
|
|
34
|
+
schema,
|
|
35
|
+
plugins: [
|
|
36
|
+
// Presentation tool for visual editing
|
|
37
|
+
presentationTool({
|
|
38
|
+
resolve: {
|
|
39
|
+
// Map routes to documents and GROQ filters
|
|
40
|
+
mainDocuments: defineDocuments([
|
|
41
|
+
{
|
|
42
|
+
route: "/sanity",
|
|
43
|
+
filter: `_type == "page" && slug.current == "sanity"`,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
route: "/sanity/:slug",
|
|
47
|
+
filter: `_type == "article" && slug.current == $slug && defined(slug.current)`,
|
|
48
|
+
},
|
|
49
|
+
]),
|
|
50
|
+
locations: {
|
|
51
|
+
page: defineLocations({
|
|
52
|
+
select: {
|
|
53
|
+
title: "title",
|
|
54
|
+
slug: "slug.current",
|
|
55
|
+
},
|
|
56
|
+
resolve: (doc) => ({
|
|
57
|
+
locations: [
|
|
58
|
+
{
|
|
59
|
+
title: doc?.title || "Untitled Page",
|
|
60
|
+
href: resolveHref("page", doc?.slug) ?? "",
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
}),
|
|
64
|
+
}),
|
|
65
|
+
article: defineLocations({
|
|
66
|
+
select: {
|
|
67
|
+
title: "title",
|
|
68
|
+
slug: "slug.current",
|
|
69
|
+
},
|
|
70
|
+
resolve: (doc) => ({
|
|
71
|
+
locations: [
|
|
72
|
+
{
|
|
73
|
+
title: doc?.title || "Untitled Article",
|
|
74
|
+
href: resolveHref("article", doc?.slug) ?? "",
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
}),
|
|
78
|
+
}),
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
previewUrl: {
|
|
82
|
+
origin: previewURL,
|
|
83
|
+
draftMode: {
|
|
84
|
+
enable: "/api/draft-mode/enable",
|
|
85
|
+
disable: "/api/draft-mode/disable",
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
}),
|
|
89
|
+
structureTool({ structure }),
|
|
90
|
+
// Vision is for querying with GROQ from inside the Studio
|
|
91
|
+
// https://www.sanity.io/docs/the-vision-plugin
|
|
92
|
+
visionTool({ defaultApiVersion: apiVersion }),
|
|
93
|
+
],
|
|
94
|
+
});
|