bsmnt 0.2.7 → 0.2.9
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/README.md +1 -1
- package/dist/helpers/create/index.d.ts +2 -1
- package/dist/helpers/create/index.d.ts.map +1 -1
- package/dist/helpers/create/index.js +16 -5
- package/dist/helpers/create/index.js.map +1 -1
- package/dist/helpers/create/init-git.d.ts +12 -0
- package/dist/helpers/create/init-git.d.ts.map +1 -0
- package/dist/helpers/create/init-git.js +34 -0
- package/dist/helpers/create/init-git.js.map +1 -0
- package/dist/helpers/create/setup-agent.d.ts +3 -2
- package/dist/helpers/create/setup-agent.d.ts.map +1 -1
- package/dist/helpers/create/setup-agent.js +42 -31
- package/dist/helpers/create/setup-agent.js.map +1 -1
- package/dist/helpers/create/setup-sanity.d.ts.map +1 -1
- package/dist/helpers/create/setup-sanity.js +31 -25
- package/dist/helpers/create/setup-sanity.js.map +1 -1
- package/dist/helpers/create/update-package.d.ts.map +1 -1
- package/dist/helpers/create/update-package.js +10 -0
- package/dist/helpers/create/update-package.js.map +1 -1
- package/dist/helpers/integrate/index.d.ts.map +1 -1
- package/dist/helpers/integrate/index.js +27 -13
- package/dist/helpers/integrate/index.js.map +1 -1
- package/dist/helpers/integrate/merge-config.d.ts.map +1 -1
- package/dist/helpers/integrate/merge-config.js +5 -0
- package/dist/helpers/integrate/merge-config.js.map +1 -1
- package/dist/helpers/integrate/merge-orchestrator.js +1 -1
- package/dist/helpers/integrate/merge-orchestrator.js.map +1 -1
- package/dist/helpers/integrate/sanity/config.d.ts.map +1 -1
- package/dist/helpers/integrate/sanity/config.js +12 -1
- package/dist/helpers/integrate/sanity/config.js.map +1 -1
- package/dist/helpers/integrate/sanity/mergers/sitemap-merger.d.ts.map +1 -1
- package/dist/helpers/integrate/sanity/mergers/sitemap-merger.js +2 -21
- package/dist/helpers/integrate/sanity/mergers/sitemap-merger.js.map +1 -1
- package/dist/index.js +48 -19
- package/dist/index.js.map +1 -1
- package/index.js +4 -3
- package/package.json +7 -2
- package/src/helpers/integrate/sanity/files/app/api/blog/[slug]/route.ts +75 -0
- package/src/helpers/integrate/sanity/files/app/blog/[slug]/page.tsx +56 -0
- package/src/helpers/integrate/sanity/files/app/sitemap.md/route.ts +82 -0
- package/src/helpers/integrate/sanity/files/app/sitemap.ts +2 -21
- package/src/helpers/integrate/sanity/files/lib/integrations/README.md +1 -1
- package/src/helpers/integrate/sanity/files/lib/integrations/sanity/MARKDOWN-PROXY.md +273 -0
- package/src/helpers/integrate/sanity/files/lib/integrations/sanity/README.md +9 -9
- package/src/helpers/integrate/sanity/files/lib/integrations/sanity/components/rich-text.tsx +2 -2
- package/src/helpers/integrate/sanity/files/lib/integrations/sanity/live/index.tsx +53 -8
- package/src/helpers/integrate/sanity/files/lib/integrations/sanity/markdown-proxy.config.ts +49 -0
- package/src/helpers/integrate/sanity/files/lib/integrations/sanity/queries.ts +7 -40
- package/src/helpers/integrate/sanity/files/lib/integrations/sanity/sanity.cli.ts +5 -0
- package/src/helpers/integrate/sanity/files/lib/integrations/sanity/sanity.config.ts +2 -22
- package/src/helpers/integrate/sanity/files/lib/integrations/sanity/sanity.types.ts +1 -24
- package/src/helpers/integrate/sanity/files/lib/integrations/sanity/schema.json +2 -124
- package/src/helpers/integrate/sanity/files/lib/integrations/sanity/schemas/index.ts +1 -3
- package/src/helpers/integrate/sanity/files/lib/integrations/sanity/schemas/link.ts +2 -2
- package/src/helpers/integrate/sanity/files/lib/integrations/sanity/utils/link.ts +25 -2
- package/src/helpers/integrate/sanity/files/lib/scripts/generate-page.ts +17 -30
- package/src/helpers/integrate/sanity/files/lib/utils/metadata.ts +10 -10
- package/src/helpers/integrate/sanity/files/lib/utils/portable-text-to-markdown.ts +24 -0
- package/src/helpers/integrate/sanity/files/proxy.ts +66 -0
- package/src/templates/next-default/components/ui/image/image.module.css +5 -0
- package/src/templates/next-default/components/ui/image/index.tsx +1 -2
- package/src/templates/next-default/lib/scripts/generate.ts +2 -2
- package/src/templates/next-default/lib/utils/metadata.ts +1 -1
- package/src/templates/next-default/next.config.ts +0 -1
- package/src/templates/next-experiments/components/ui/image/image.module.css +5 -0
- package/src/templates/next-experiments/components/ui/image/index.tsx +3 -2
- package/src/templates/next-experiments/lib/scripts/generate.ts +2 -2
- package/src/templates/next-experiments/lib/utils/metadata.ts +1 -1
- package/src/templates/next-experiments/next.config.ts +0 -1
- package/src/templates/next-webgl/components/ui/image/image.module.css +5 -0
- package/src/templates/next-webgl/components/ui/image/index.tsx +1 -2
- package/src/templates/next-webgl/lib/scripts/generate.ts +2 -2
- package/src/templates/next-webgl/lib/utils/metadata.ts +1 -1
- package/src/templates/next-webgl/next.config.ts +0 -1
- package/src/helpers/integrate/sanity/files/lib/integrations/sanity/schemas/page.ts +0 -77
|
@@ -17,10 +17,8 @@ import { structure } from "./structure";
|
|
|
17
17
|
// Helper function for URL resolution
|
|
18
18
|
function resolveHref(documentType?: string, slug?: string): string | undefined {
|
|
19
19
|
switch (documentType) {
|
|
20
|
-
case "page":
|
|
21
|
-
return slug === "sanity" ? "/sanity" : `/sanity/${slug}`;
|
|
22
20
|
case "article":
|
|
23
|
-
return slug ? `/
|
|
21
|
+
return slug ? `/blog/${slug}` : undefined;
|
|
24
22
|
default:
|
|
25
23
|
console.warn("Invalid document type:", documentType);
|
|
26
24
|
return undefined;
|
|
@@ -39,29 +37,11 @@ export default defineConfig({
|
|
|
39
37
|
// Map routes to documents and GROQ filters
|
|
40
38
|
mainDocuments: defineDocuments([
|
|
41
39
|
{
|
|
42
|
-
route: "/
|
|
43
|
-
filter: `_type == "page" && slug.current == "sanity"`,
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
route: "/sanity/:slug",
|
|
40
|
+
route: "/blog/:slug",
|
|
47
41
|
filter: `_type == "article" && slug.current == $slug && defined(slug.current)`,
|
|
48
42
|
},
|
|
49
43
|
]),
|
|
50
44
|
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
45
|
article: defineLocations({
|
|
66
46
|
select: {
|
|
67
47
|
title: "title",
|
|
@@ -41,13 +41,6 @@ export type Navigation = {
|
|
|
41
41
|
}>;
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
export type PageReference = {
|
|
45
|
-
_ref: string;
|
|
46
|
-
_type: "reference";
|
|
47
|
-
_weak?: boolean;
|
|
48
|
-
[internalGroqTypeReferenceTo]?: "page";
|
|
49
|
-
};
|
|
50
|
-
|
|
51
44
|
export type ArticleReference = {
|
|
52
45
|
_ref: string;
|
|
53
46
|
_type: "reference";
|
|
@@ -58,7 +51,7 @@ export type ArticleReference = {
|
|
|
58
51
|
export type Link = {
|
|
59
52
|
_type: "link";
|
|
60
53
|
linkType?: "internal" | "external";
|
|
61
|
-
internalLink?:
|
|
54
|
+
internalLink?: ArticleReference;
|
|
62
55
|
externalUrl?: string;
|
|
63
56
|
text?: string;
|
|
64
57
|
openInNewTab?: boolean;
|
|
@@ -201,20 +194,6 @@ export type Article = {
|
|
|
201
194
|
tags?: string[];
|
|
202
195
|
};
|
|
203
196
|
|
|
204
|
-
export type Page = {
|
|
205
|
-
_id: string;
|
|
206
|
-
_type: "page";
|
|
207
|
-
_createdAt: string;
|
|
208
|
-
_updatedAt: string;
|
|
209
|
-
_rev: string;
|
|
210
|
-
slug?: Slug;
|
|
211
|
-
publishedAt?: string;
|
|
212
|
-
metadata?: Metadata;
|
|
213
|
-
title?: string;
|
|
214
|
-
content?: RichText;
|
|
215
|
-
link?: Link;
|
|
216
|
-
};
|
|
217
|
-
|
|
218
197
|
export type SanityImagePaletteSwatch = {
|
|
219
198
|
_type: "sanity.imagePaletteSwatch";
|
|
220
199
|
background?: string;
|
|
@@ -314,7 +293,6 @@ export type Geopoint = {
|
|
|
314
293
|
export type AllSanitySchemaTypes =
|
|
315
294
|
| SanityImageAssetReference
|
|
316
295
|
| Navigation
|
|
317
|
-
| PageReference
|
|
318
296
|
| ArticleReference
|
|
319
297
|
| Link
|
|
320
298
|
| SanityImageCrop
|
|
@@ -324,7 +302,6 @@ export type AllSanitySchemaTypes =
|
|
|
324
302
|
| RichText
|
|
325
303
|
| Slug
|
|
326
304
|
| Article
|
|
327
|
-
| Page
|
|
328
305
|
| SanityImagePaletteSwatch
|
|
329
306
|
| SanityImagePalette
|
|
330
307
|
| SanityImageDimensions
|
|
@@ -158,36 +158,6 @@
|
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
},
|
|
161
|
-
{
|
|
162
|
-
"type": "type",
|
|
163
|
-
"name": "page.reference",
|
|
164
|
-
"value": {
|
|
165
|
-
"type": "object",
|
|
166
|
-
"attributes": {
|
|
167
|
-
"_ref": {
|
|
168
|
-
"type": "objectAttribute",
|
|
169
|
-
"value": {
|
|
170
|
-
"type": "string"
|
|
171
|
-
}
|
|
172
|
-
},
|
|
173
|
-
"_type": {
|
|
174
|
-
"type": "objectAttribute",
|
|
175
|
-
"value": {
|
|
176
|
-
"type": "string",
|
|
177
|
-
"value": "reference"
|
|
178
|
-
}
|
|
179
|
-
},
|
|
180
|
-
"_weak": {
|
|
181
|
-
"type": "objectAttribute",
|
|
182
|
-
"value": {
|
|
183
|
-
"type": "boolean"
|
|
184
|
-
},
|
|
185
|
-
"optional": true
|
|
186
|
-
}
|
|
187
|
-
},
|
|
188
|
-
"dereferencesTo": "page"
|
|
189
|
-
}
|
|
190
|
-
},
|
|
191
161
|
{
|
|
192
162
|
"type": "type",
|
|
193
163
|
"name": "article.reference",
|
|
@@ -251,17 +221,8 @@
|
|
|
251
221
|
"internalLink": {
|
|
252
222
|
"type": "objectAttribute",
|
|
253
223
|
"value": {
|
|
254
|
-
"type": "
|
|
255
|
-
"
|
|
256
|
-
{
|
|
257
|
-
"type": "inline",
|
|
258
|
-
"name": "page.reference"
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
"type": "inline",
|
|
262
|
-
"name": "article.reference"
|
|
263
|
-
}
|
|
264
|
-
]
|
|
224
|
+
"type": "inline",
|
|
225
|
+
"name": "article.reference"
|
|
265
226
|
},
|
|
266
227
|
"optional": true
|
|
267
228
|
},
|
|
@@ -1189,89 +1150,6 @@
|
|
|
1189
1150
|
}
|
|
1190
1151
|
}
|
|
1191
1152
|
},
|
|
1192
|
-
{
|
|
1193
|
-
"name": "page",
|
|
1194
|
-
"type": "document",
|
|
1195
|
-
"attributes": {
|
|
1196
|
-
"_id": {
|
|
1197
|
-
"type": "objectAttribute",
|
|
1198
|
-
"value": {
|
|
1199
|
-
"type": "string"
|
|
1200
|
-
}
|
|
1201
|
-
},
|
|
1202
|
-
"_type": {
|
|
1203
|
-
"type": "objectAttribute",
|
|
1204
|
-
"value": {
|
|
1205
|
-
"type": "string",
|
|
1206
|
-
"value": "page"
|
|
1207
|
-
}
|
|
1208
|
-
},
|
|
1209
|
-
"_createdAt": {
|
|
1210
|
-
"type": "objectAttribute",
|
|
1211
|
-
"value": {
|
|
1212
|
-
"type": "string"
|
|
1213
|
-
}
|
|
1214
|
-
},
|
|
1215
|
-
"_updatedAt": {
|
|
1216
|
-
"type": "objectAttribute",
|
|
1217
|
-
"value": {
|
|
1218
|
-
"type": "string"
|
|
1219
|
-
}
|
|
1220
|
-
},
|
|
1221
|
-
"_rev": {
|
|
1222
|
-
"type": "objectAttribute",
|
|
1223
|
-
"value": {
|
|
1224
|
-
"type": "string"
|
|
1225
|
-
}
|
|
1226
|
-
},
|
|
1227
|
-
"slug": {
|
|
1228
|
-
"type": "objectAttribute",
|
|
1229
|
-
"value": {
|
|
1230
|
-
"type": "inline",
|
|
1231
|
-
"name": "slug"
|
|
1232
|
-
},
|
|
1233
|
-
"optional": true
|
|
1234
|
-
},
|
|
1235
|
-
"publishedAt": {
|
|
1236
|
-
"type": "objectAttribute",
|
|
1237
|
-
"value": {
|
|
1238
|
-
"type": "string"
|
|
1239
|
-
},
|
|
1240
|
-
"optional": true
|
|
1241
|
-
},
|
|
1242
|
-
"metadata": {
|
|
1243
|
-
"type": "objectAttribute",
|
|
1244
|
-
"value": {
|
|
1245
|
-
"type": "inline",
|
|
1246
|
-
"name": "metadata"
|
|
1247
|
-
},
|
|
1248
|
-
"optional": true
|
|
1249
|
-
},
|
|
1250
|
-
"title": {
|
|
1251
|
-
"type": "objectAttribute",
|
|
1252
|
-
"value": {
|
|
1253
|
-
"type": "string"
|
|
1254
|
-
},
|
|
1255
|
-
"optional": true
|
|
1256
|
-
},
|
|
1257
|
-
"content": {
|
|
1258
|
-
"type": "objectAttribute",
|
|
1259
|
-
"value": {
|
|
1260
|
-
"type": "inline",
|
|
1261
|
-
"name": "richText"
|
|
1262
|
-
},
|
|
1263
|
-
"optional": true
|
|
1264
|
-
},
|
|
1265
|
-
"link": {
|
|
1266
|
-
"type": "objectAttribute",
|
|
1267
|
-
"value": {
|
|
1268
|
-
"type": "inline",
|
|
1269
|
-
"name": "link"
|
|
1270
|
-
},
|
|
1271
|
-
"optional": true
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
},
|
|
1275
1153
|
{
|
|
1276
1154
|
"name": "sanity.imagePaletteSwatch",
|
|
1277
1155
|
"type": "type",
|
|
@@ -12,11 +12,10 @@ import { example } from "./example";
|
|
|
12
12
|
import { link } from "./link";
|
|
13
13
|
import { metadata } from "./metadata";
|
|
14
14
|
import { navigation } from "./navigation";
|
|
15
|
-
import { page } from "./page";
|
|
16
15
|
import { richText } from "./richText";
|
|
17
16
|
|
|
18
17
|
// Re-export all schemas for convenience
|
|
19
|
-
export { link, metadata, richText, article, example,
|
|
18
|
+
export { link, metadata, richText, article, example, navigation };
|
|
20
19
|
|
|
21
20
|
// Schema collection for Sanity configuration
|
|
22
21
|
export const schema: { types: SchemaTypeDefinition[] } = {
|
|
@@ -27,7 +26,6 @@ export const schema: { types: SchemaTypeDefinition[] } = {
|
|
|
27
26
|
richText,
|
|
28
27
|
|
|
29
28
|
// Document types (content pages)
|
|
30
|
-
page,
|
|
31
29
|
article,
|
|
32
30
|
example,
|
|
33
31
|
|
|
@@ -24,7 +24,7 @@ export const link = defineType({
|
|
|
24
24
|
name: "internalLink",
|
|
25
25
|
title: "Internal Link",
|
|
26
26
|
type: "reference",
|
|
27
|
-
to: [{ type: "
|
|
27
|
+
to: [{ type: "article" }],
|
|
28
28
|
hidden: ({ parent }) => parent?.linkType !== "internal",
|
|
29
29
|
validation: (Rule) =>
|
|
30
30
|
Rule.custom((value, context) => {
|
|
@@ -76,7 +76,7 @@ export const link = defineType({
|
|
|
76
76
|
const title = text || internalTitle || externalUrl || "Untitled Link";
|
|
77
77
|
const subtitle =
|
|
78
78
|
linkType === "internal"
|
|
79
|
-
? `Internal: ${internalTitle || "No
|
|
79
|
+
? `Internal: ${internalTitle || "No document selected"}`
|
|
80
80
|
: `External: ${externalUrl || "No URL"}`;
|
|
81
81
|
|
|
82
82
|
return {
|
|
@@ -1,11 +1,34 @@
|
|
|
1
1
|
import type { Link } from "../sanity.types";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Mapping of Sanity document types to their public markdown route paths.
|
|
5
|
+
*/
|
|
6
|
+
export const CONTENT_TYPE_TO_PATH: Record<string, string> = {
|
|
7
|
+
article: "/blog",
|
|
8
|
+
};
|
|
9
|
+
|
|
3
10
|
type InternalReference = {
|
|
4
11
|
_type: string;
|
|
5
12
|
slug?: { current: string };
|
|
6
13
|
title?: string;
|
|
7
14
|
};
|
|
8
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Resolves a Sanity reference to a full Markdown URL.
|
|
18
|
+
* Uses the CONTENT_TYPE_TO_PATH mapping for consistency.
|
|
19
|
+
*/
|
|
20
|
+
export const resolveMarkdownUrl = (
|
|
21
|
+
ref: InternalReference,
|
|
22
|
+
baseUrl: string,
|
|
23
|
+
): string => {
|
|
24
|
+
const path = CONTENT_TYPE_TO_PATH[ref._type];
|
|
25
|
+
if (!path || !ref.slug?.current) {
|
|
26
|
+
// Fallback to blog if type is unknown
|
|
27
|
+
return `${baseUrl}/blog/${ref.slug?.current || ""}.md`;
|
|
28
|
+
}
|
|
29
|
+
return `${baseUrl}${path}/${ref.slug.current}.md`;
|
|
30
|
+
};
|
|
31
|
+
|
|
9
32
|
export const urlForReference = (link: Link): string => {
|
|
10
33
|
if (!link) return "#";
|
|
11
34
|
|
|
@@ -36,8 +59,8 @@ function resolveDocumentUrl(documentType: string, slug?: string): string {
|
|
|
36
59
|
if (!slug) return "#";
|
|
37
60
|
|
|
38
61
|
switch (documentType) {
|
|
39
|
-
case "
|
|
40
|
-
return
|
|
62
|
+
case "article":
|
|
63
|
+
return `/blog/${slug}`;
|
|
41
64
|
default:
|
|
42
65
|
console.warn("Unknown document type for URL resolution:", documentType);
|
|
43
66
|
return "#";
|
|
@@ -122,16 +122,9 @@ const generatePageContent = (
|
|
|
122
122
|
imports.push(
|
|
123
123
|
`import { isSanityConfigured } from '@/lib/integrations/check-integration'`,
|
|
124
124
|
);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
);
|
|
129
|
-
imports.push(
|
|
130
|
-
`import type { Page } from '@/lib/integrations/sanity/sanity.types'`,
|
|
131
|
-
);
|
|
132
|
-
imports.push(
|
|
133
|
-
`import { generateSanityMetadata } from '@/lib/utils/metadata'`,
|
|
134
|
-
);
|
|
125
|
+
// sanityFetch and generateSanityMetadata are available for use:
|
|
126
|
+
// import { sanityFetch } from 'next-sanity/live'
|
|
127
|
+
// import { generateSanityMetadata } from '@/lib/utils/metadata'
|
|
135
128
|
}
|
|
136
129
|
|
|
137
130
|
// Build wrapper props
|
|
@@ -154,10 +147,11 @@ const generatePageContent = (
|
|
|
154
147
|
)
|
|
155
148
|
}
|
|
156
149
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
150
|
+
// TODO: Create a GROQ query for this page's content type
|
|
151
|
+
// const { data } = await sanityFetch({
|
|
152
|
+
// query: yourQuery,
|
|
153
|
+
// params: { slug: '${pageName}' },
|
|
154
|
+
// })
|
|
161
155
|
|
|
162
156
|
return (
|
|
163
157
|
<Wrapper ${wrapperProps.join(" ")}>
|
|
@@ -165,7 +159,7 @@ const generatePageContent = (
|
|
|
165
159
|
<div className="container">
|
|
166
160
|
<h1>${title}</h1>
|
|
167
161
|
{/* Your content here */}
|
|
168
|
-
{/*
|
|
162
|
+
{/* Fetch data from Sanity using a custom query */}
|
|
169
163
|
</div>
|
|
170
164
|
</section>
|
|
171
165
|
</Wrapper>
|
|
@@ -196,23 +190,16 @@ export async function generateMetadata(): Promise<Metadata> {
|
|
|
196
190
|
}
|
|
197
191
|
}
|
|
198
192
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
193
|
+
// TODO: Fetch metadata from Sanity using a custom query
|
|
194
|
+
// const { data } = await sanityFetch({
|
|
195
|
+
// query: yourQuery,
|
|
196
|
+
// params: { slug: '${pageName}' },
|
|
197
|
+
// })
|
|
203
198
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
description: '${title} page description',
|
|
208
|
-
}
|
|
199
|
+
return {
|
|
200
|
+
title: '${title}',
|
|
201
|
+
description: '${title} page description',
|
|
209
202
|
}
|
|
210
|
-
|
|
211
|
-
return generateSanityMetadata({
|
|
212
|
-
document: data,
|
|
213
|
-
url: '/${pageName}',
|
|
214
|
-
type: 'website',
|
|
215
|
-
})
|
|
216
203
|
}`;
|
|
217
204
|
} else {
|
|
218
205
|
metadataExport = `
|
|
@@ -35,14 +35,14 @@ const APP_BASE_URL =
|
|
|
35
35
|
* @example
|
|
36
36
|
* ```ts
|
|
37
37
|
* export async function generateMetadata({ params }) {
|
|
38
|
-
* const
|
|
38
|
+
* const article = await fetchArticle(params.slug)
|
|
39
39
|
*
|
|
40
40
|
* return generatePageMetadata({
|
|
41
|
-
* title:
|
|
42
|
-
* description:
|
|
43
|
-
* image: { url:
|
|
44
|
-
* url: `/
|
|
45
|
-
* noIndex:
|
|
41
|
+
* title: article.metadata?.title || article.title,
|
|
42
|
+
* description: article.metadata?.description,
|
|
43
|
+
* image: { url: article.metadata?.image?.asset?.url },
|
|
44
|
+
* url: `/blog/${params.slug}`,
|
|
45
|
+
* noIndex: article.metadata?.noIndex,
|
|
46
46
|
* })
|
|
47
47
|
* }
|
|
48
48
|
* ```
|
|
@@ -56,7 +56,7 @@ export function generatePageMetadata(
|
|
|
56
56
|
keywords,
|
|
57
57
|
image,
|
|
58
58
|
url,
|
|
59
|
-
siteName = "
|
|
59
|
+
siteName = "Basement",
|
|
60
60
|
noIndex = false,
|
|
61
61
|
type = "website",
|
|
62
62
|
publishedTime,
|
|
@@ -126,18 +126,18 @@ export function generatePageMetadata(
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
/**
|
|
129
|
-
* Generate metadata specifically for Sanity CMS
|
|
129
|
+
* Generate metadata specifically for Sanity CMS content
|
|
130
130
|
*
|
|
131
131
|
* @example
|
|
132
132
|
* ```ts
|
|
133
133
|
* import { sanityFetch } from '@/integrations/sanity/live'
|
|
134
134
|
*
|
|
135
135
|
* export async function generateMetadata({ params }) {
|
|
136
|
-
* const { data } = await sanityFetch({ query:
|
|
136
|
+
* const { data } = await sanityFetch({ query: ARTICLE_QUERY, params })
|
|
137
137
|
*
|
|
138
138
|
* return generateSanityMetadata({
|
|
139
139
|
* document: data,
|
|
140
|
-
* url: `/
|
|
140
|
+
* url: `/blog/${params.slug}`,
|
|
141
141
|
* })
|
|
142
142
|
* }
|
|
143
143
|
* ```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { portableTextToMarkdown as toMarkdown } from "@portabletext/markdown";
|
|
2
|
+
import type { PortableTextBlock } from "@portabletext/types";
|
|
3
|
+
import { urlForImage } from "@/lib/integrations/sanity/utils/image";
|
|
4
|
+
|
|
5
|
+
export function portableTextToMarkdown(
|
|
6
|
+
value: PortableTextBlock[] | undefined | null,
|
|
7
|
+
): string {
|
|
8
|
+
if (!value) return "";
|
|
9
|
+
|
|
10
|
+
return toMarkdown(value, {
|
|
11
|
+
types: {
|
|
12
|
+
image: ({ value: img }) => {
|
|
13
|
+
if (!img?.asset?._ref) return "";
|
|
14
|
+
try {
|
|
15
|
+
const url = urlForImage(img).url();
|
|
16
|
+
const alt = img.alt || "Image";
|
|
17
|
+
return ``;
|
|
18
|
+
} catch {
|
|
19
|
+
return "";
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import type { NextRequest } from "next/server";
|
|
3
|
+
import {
|
|
4
|
+
mdExtensionRoutes,
|
|
5
|
+
acceptHeaderRoutes,
|
|
6
|
+
ignoredPaths,
|
|
7
|
+
} from "@/lib/integrations/sanity/markdown-proxy.config";
|
|
8
|
+
|
|
9
|
+
export function proxy(request: NextRequest) {
|
|
10
|
+
const { pathname } = request.nextUrl;
|
|
11
|
+
const acceptHeader = request.headers.get("accept") || "";
|
|
12
|
+
|
|
13
|
+
if (ignoredPaths.some((p) => pathname.startsWith(p))) {
|
|
14
|
+
return NextResponse.next();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 1. Rewrite .md extension URLs to API routes
|
|
18
|
+
// e.g. /blog/my-post.md → /api/blog/my-post.md
|
|
19
|
+
for (const route of mdExtensionRoutes) {
|
|
20
|
+
const match = route.regex.exec(pathname);
|
|
21
|
+
if (match) {
|
|
22
|
+
const slug = match[1];
|
|
23
|
+
const url = request.nextUrl.clone();
|
|
24
|
+
url.pathname = route.apiPath.replace("[slug]", slug!);
|
|
25
|
+
return NextResponse.rewrite(url);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 2. Accept: text/markdown — rewrite to markdown API route
|
|
30
|
+
if (acceptHeader.includes("text/markdown")) {
|
|
31
|
+
for (const route of acceptHeaderRoutes) {
|
|
32
|
+
const match = route.regex.exec(pathname);
|
|
33
|
+
if (match) {
|
|
34
|
+
const slug = match[1];
|
|
35
|
+
const url = request.nextUrl.clone();
|
|
36
|
+
url.pathname = route.apiPath.replace("[slug]", slug!);
|
|
37
|
+
return NextResponse.rewrite(url);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 3. Add Link header for markdown discoverability on HTML page requests
|
|
43
|
+
const response = NextResponse.next();
|
|
44
|
+
|
|
45
|
+
for (const route of acceptHeaderRoutes) {
|
|
46
|
+
const match = route.regex.exec(pathname);
|
|
47
|
+
if (match) {
|
|
48
|
+
const slug = match[1];
|
|
49
|
+
const mdUrl = route.publicPath.replace("[slug]", slug!);
|
|
50
|
+
response.headers.set(
|
|
51
|
+
"Link",
|
|
52
|
+
`<${mdUrl}>; rel="alternate"; type="text/markdown"`,
|
|
53
|
+
);
|
|
54
|
+
response.headers.set("Vary", "Accept");
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return response;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const config = {
|
|
63
|
+
matcher: [
|
|
64
|
+
"/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)",
|
|
65
|
+
],
|
|
66
|
+
};
|
|
@@ -10,7 +10,6 @@ import cn from "clsx"
|
|
|
10
10
|
import NextImage, { type ImageProps as NextImageProps } from "next/image"
|
|
11
11
|
import type { CSSProperties, Ref } from "react"
|
|
12
12
|
import { breakpoints } from "@/lib/styles/config"
|
|
13
|
-
import s from "./image.module.css"
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Enhanced Image component props extending Next.js Image.
|
|
@@ -205,7 +204,7 @@ export function Image({
|
|
|
205
204
|
objectFit,
|
|
206
205
|
...style,
|
|
207
206
|
}}
|
|
208
|
-
className={cn(className, block &&
|
|
207
|
+
className={cn(className, block && "block w-full")}
|
|
209
208
|
sizes={finalSizes}
|
|
210
209
|
src={src}
|
|
211
210
|
unoptimized={unoptimized || isSvg}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Basement Generator CLI
|
|
4
4
|
*
|
|
5
5
|
* Interactive scaffolding for new pages and components.
|
|
6
6
|
* Generates pre-configured templates following project conventions.
|
|
@@ -24,7 +24,7 @@ import { createPage, promptPageConfig } from "./generate-page"
|
|
|
24
24
|
const main = async (): Promise<void> => {
|
|
25
25
|
console.clear()
|
|
26
26
|
|
|
27
|
-
p.intro("
|
|
27
|
+
p.intro("Basement Generator")
|
|
28
28
|
|
|
29
29
|
// Ask what to create
|
|
30
30
|
const createType = await p.select({
|
|
@@ -10,7 +10,6 @@ import cn from "clsx";
|
|
|
10
10
|
import NextImage, { type ImageProps as NextImageProps } from "next/image";
|
|
11
11
|
import type { CSSProperties, Ref } from "react";
|
|
12
12
|
import { breakpoints } from "@/lib/styles/config";
|
|
13
|
-
import s from "./image.module.css";
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Enhanced Image component props extending Next.js Image.
|
|
@@ -29,6 +28,8 @@ export type ImageProps = Omit<NextImageProps, "objectFit" | "alt"> & {
|
|
|
29
28
|
desktopSize?: `${number}vw`;
|
|
30
29
|
/** Ref for accessing the underlying img element */
|
|
31
30
|
ref?: Ref<HTMLImageElement>;
|
|
31
|
+
/** Alt text for accessibility (required for meaningful images) */
|
|
32
|
+
alt?: string;
|
|
32
33
|
/** Aspect ratio for automatic placeholder and layout stability */
|
|
33
34
|
aspectRatio?: number;
|
|
34
35
|
};
|
|
@@ -203,7 +204,7 @@ export function Image({
|
|
|
203
204
|
objectFit,
|
|
204
205
|
...style,
|
|
205
206
|
}}
|
|
206
|
-
className={cn(className, block &&
|
|
207
|
+
className={cn(className, block && "block w-full")}
|
|
207
208
|
sizes={finalSizes}
|
|
208
209
|
src={src}
|
|
209
210
|
unoptimized={unoptimized || isSvg}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Basement Generator CLI
|
|
4
4
|
*
|
|
5
5
|
* Interactive scaffolding for new pages and components.
|
|
6
6
|
* Generates pre-configured templates following project conventions.
|
|
@@ -24,7 +24,7 @@ import { createPage, promptPageConfig } from "./generate-page"
|
|
|
24
24
|
const main = async (): Promise<void> => {
|
|
25
25
|
console.clear()
|
|
26
26
|
|
|
27
|
-
p.intro("
|
|
27
|
+
p.intro("Basement Generator")
|
|
28
28
|
|
|
29
29
|
// Ask what to create
|
|
30
30
|
const createType = await p.select({
|