bsmnt 0.2.10 → 0.3.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.
Files changed (163) hide show
  1. package/dist/configs/skills.d.ts +27 -0
  2. package/dist/configs/skills.d.ts.map +1 -0
  3. package/dist/configs/skills.js +18 -0
  4. package/dist/configs/skills.js.map +1 -0
  5. package/dist/configs/skills.json +26 -0
  6. package/dist/helpers/add/hooks-config.d.ts.map +1 -1
  7. package/dist/helpers/add/hooks-config.js +0 -6
  8. package/dist/helpers/add/hooks-config.js.map +1 -1
  9. package/dist/helpers/create/copy-template.d.ts +1 -1
  10. package/dist/helpers/create/copy-template.d.ts.map +1 -1
  11. package/dist/helpers/create/index.d.ts.map +1 -1
  12. package/dist/helpers/create/index.js +2 -1
  13. package/dist/helpers/create/index.js.map +1 -1
  14. package/dist/helpers/create/setup-agent.d.ts.map +1 -1
  15. package/dist/helpers/create/setup-agent.js +15 -5
  16. package/dist/helpers/create/setup-agent.js.map +1 -1
  17. package/dist/helpers/integrate/merge-config.d.ts.map +1 -1
  18. package/dist/helpers/integrate/merge-config.js +1 -2
  19. package/dist/helpers/integrate/merge-config.js.map +1 -1
  20. package/dist/helpers/integrate/sanity/config.d.ts.map +1 -1
  21. package/dist/helpers/integrate/sanity/config.js +5 -10
  22. package/dist/helpers/integrate/sanity/config.js.map +1 -1
  23. package/dist/helpers/integrate/sanity/mergers/layout-merger.d.ts.map +1 -1
  24. package/dist/helpers/integrate/sanity/mergers/layout-merger.js +13 -12
  25. package/dist/helpers/integrate/sanity/mergers/layout-merger.js.map +1 -1
  26. package/dist/helpers/skills/index.d.ts +10 -0
  27. package/dist/helpers/skills/index.d.ts.map +1 -0
  28. package/dist/helpers/skills/index.js +136 -0
  29. package/dist/helpers/skills/index.js.map +1 -0
  30. package/dist/index.js +102 -35
  31. package/dist/index.js.map +1 -1
  32. package/package.json +3 -2
  33. package/src/helpers/integrate/sanity/files/app/api/blog/[slug]/route.ts +2 -1
  34. package/src/helpers/integrate/sanity/files/lib/integrations/sanity/confirm-publish-action.ts +31 -0
  35. package/src/helpers/integrate/sanity/files/lib/integrations/sanity/sanity.config.ts +17 -0
  36. package/src/helpers/integrate/sanity/files/lib/utils/json-ld.tsx +249 -0
  37. package/src/template-hooks/config.js +0 -6
  38. package/src/templates/next-default/app/layout.tsx +18 -0
  39. package/src/templates/next-default/lib/hooks/use-device-detection.ts +1 -1
  40. package/src/templates/next-default/lib/hooks/use-media-breakpoint.ts +1 -1
  41. package/src/templates/next-default/lib/hooks/use-media.ts +29 -0
  42. package/src/templates/next-default/lib/utils/json-ld.tsx +199 -0
  43. package/src/templates/next-default/package.json +1 -1
  44. package/src/templates/next-default/tsconfig.json +1 -0
  45. package/src/templates/next-experiments/app/layout.tsx +18 -0
  46. package/src/templates/next-experiments/lib/hooks/use-device-detection.ts +1 -1
  47. package/src/templates/next-experiments/lib/hooks/use-media-breakpoint.ts +1 -1
  48. package/src/templates/next-experiments/lib/hooks/use-media.ts +29 -0
  49. package/src/templates/next-experiments/lib/utils/json-ld.tsx +199 -0
  50. package/src/templates/next-experiments/package.json +1 -1
  51. package/src/templates/next-experiments/tsconfig.json +1 -0
  52. package/src/templates/next-pagebuilder/.env.example +11 -0
  53. package/src/templates/next-pagebuilder/README.md +23 -0
  54. package/src/templates/next-pagebuilder/_gitignore +67 -0
  55. package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +68 -0
  56. package/src/templates/next-pagebuilder/app/(content)/layout.tsx +13 -0
  57. package/src/templates/next-pagebuilder/app/api/[[...slug]]/route.ts +100 -0
  58. package/src/templates/next-pagebuilder/app/api/draft-mode/disable/route.ts +7 -0
  59. package/src/templates/next-pagebuilder/app/api/draft-mode/enable/route.ts +20 -0
  60. package/src/templates/next-pagebuilder/app/api/revalidate/route.ts +121 -0
  61. package/src/templates/next-pagebuilder/app/favicon.ico +0 -0
  62. package/src/templates/next-pagebuilder/app/layout.tsx +80 -0
  63. package/src/templates/next-pagebuilder/app/robots.ts +15 -0
  64. package/src/templates/next-pagebuilder/app/sitemap.md/route.ts +124 -0
  65. package/src/templates/next-pagebuilder/app/sitemap.xml/route.ts +80 -0
  66. package/src/templates/next-pagebuilder/app/studio/[[...tool]]/page.tsx +8 -0
  67. package/src/templates/next-pagebuilder/biome.json +239 -0
  68. package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +95 -0
  69. package/src/templates/next-pagebuilder/components/layout/header/components/cta-button.tsx +28 -0
  70. package/src/templates/next-pagebuilder/components/layout/header/components/mega-menu-panel.tsx +90 -0
  71. package/src/templates/next-pagebuilder/components/layout/header/components/nav-item-renderer.tsx +98 -0
  72. package/src/templates/next-pagebuilder/components/layout/header/components/nav-leaf-item.tsx +33 -0
  73. package/src/templates/next-pagebuilder/components/layout/header/components/types.ts +7 -0
  74. package/src/templates/next-pagebuilder/components/layout/header/header-client.tsx +110 -0
  75. package/src/templates/next-pagebuilder/components/layout/header/index.tsx +8 -0
  76. package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +45 -0
  77. package/src/templates/next-pagebuilder/components/layout/wrapper/index.tsx +30 -0
  78. package/src/templates/next-pagebuilder/components/page-builder/components/article-content/index.tsx +83 -0
  79. package/src/templates/next-pagebuilder/components/page-builder/components/article-content/related-post-item.tsx +27 -0
  80. package/src/templates/next-pagebuilder/components/page-builder/components/description.tsx +17 -0
  81. package/src/templates/next-pagebuilder/components/page-builder/components/hero.tsx +17 -0
  82. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/content-card.tsx +66 -0
  83. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/content-grid.tsx +42 -0
  84. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/index.tsx +28 -0
  85. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/types.ts +16 -0
  86. package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +36 -0
  87. package/src/templates/next-pagebuilder/components/page-builder/types.ts +23 -0
  88. package/src/templates/next-pagebuilder/components/page-document/index.tsx +91 -0
  89. package/src/templates/next-pagebuilder/components/sanity/draft-mode-toggle.tsx +27 -0
  90. package/src/templates/next-pagebuilder/components/sanity/rich-text.tsx +87 -0
  91. package/src/templates/next-pagebuilder/components/sanity/visual-editing.tsx +27 -0
  92. package/src/templates/next-pagebuilder/components/ui/image/index.tsx +216 -0
  93. package/src/templates/next-pagebuilder/components/ui/link/index.tsx +152 -0
  94. package/src/templates/next-pagebuilder/components/ui/sanity-image/index.tsx +41 -0
  95. package/src/templates/next-pagebuilder/lib/integrations/check-integration.ts +5 -0
  96. package/src/templates/next-pagebuilder/lib/integrations/sanity/client.ts +27 -0
  97. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/disable-draft-mode.tsx +23 -0
  98. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/page-builder-input.tsx +36 -0
  99. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/page-category-input.tsx +50 -0
  100. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/rich-text.tsx +84 -0
  101. package/src/templates/next-pagebuilder/lib/integrations/sanity/confirm-publish-action.ts +40 -0
  102. package/src/templates/next-pagebuilder/lib/integrations/sanity/env.ts +34 -0
  103. package/src/templates/next-pagebuilder/lib/integrations/sanity/fetchers/layout.ts +35 -0
  104. package/src/templates/next-pagebuilder/lib/integrations/sanity/icons.ts +58 -0
  105. package/src/templates/next-pagebuilder/lib/integrations/sanity/live/index.tsx +61 -0
  106. package/src/templates/next-pagebuilder/lib/integrations/sanity/markdown-proxy.config.ts +50 -0
  107. package/src/templates/next-pagebuilder/lib/integrations/sanity/page-builder-config.ts +132 -0
  108. package/src/templates/next-pagebuilder/lib/integrations/sanity/page-category.ts +28 -0
  109. package/src/templates/next-pagebuilder/lib/integrations/sanity/queries.ts +281 -0
  110. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.cli.ts +29 -0
  111. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.config.ts +211 -0
  112. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/index.ts +4 -0
  113. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +89 -0
  114. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/description.ts +29 -0
  115. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/hero.ts +28 -0
  116. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +45 -0
  117. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/content/author.ts +70 -0
  118. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/content/blog-category.ts +55 -0
  119. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/index.ts +96 -0
  120. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/company-data.ts +62 -0
  121. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/footer.ts +79 -0
  122. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/navbar.ts +74 -0
  123. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/link.ts +125 -0
  124. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/logo-field.ts +9 -0
  125. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/metadata.ts +68 -0
  126. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/nav-objects.ts +192 -0
  127. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page-builder.ts +39 -0
  128. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page-folder.ts +124 -0
  129. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page.ts +232 -0
  130. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/richText.ts +63 -0
  131. package/src/templates/next-pagebuilder/lib/integrations/sanity/singletons.ts +44 -0
  132. package/src/templates/next-pagebuilder/lib/integrations/sanity/structure.ts +453 -0
  133. package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/image.ts +8 -0
  134. package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/link.ts +137 -0
  135. package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/page-builder-markdown.ts +81 -0
  136. package/src/templates/next-pagebuilder/lib/scripts/sanity-typegen.ts +45 -0
  137. package/src/templates/next-pagebuilder/lib/styles/cn.ts +5 -0
  138. package/src/templates/next-pagebuilder/lib/styles/global.css +70 -0
  139. package/src/templates/next-pagebuilder/lib/utils/base-url.ts +17 -0
  140. package/src/templates/next-pagebuilder/lib/utils/format-date.ts +8 -0
  141. package/src/templates/next-pagebuilder/lib/utils/json-ld.tsx +213 -0
  142. package/src/templates/next-pagebuilder/lib/utils/metadata.ts +167 -0
  143. package/src/templates/next-pagebuilder/lib/utils/sitemap.ts +37 -0
  144. package/src/templates/next-pagebuilder/lib/utils/slug-tag.ts +6 -0
  145. package/src/templates/next-pagebuilder/next.config.ts +134 -0
  146. package/src/templates/next-pagebuilder/package.json +71 -0
  147. package/src/templates/next-pagebuilder/postcss.config.mjs +39 -0
  148. package/src/templates/next-pagebuilder/proxy.ts +81 -0
  149. package/src/templates/next-pagebuilder/svg.d.ts +5 -0
  150. package/src/templates/next-pagebuilder/tsconfig.json +38 -0
  151. package/src/templates/next-webgl/app/layout.tsx +18 -0
  152. package/src/templates/next-webgl/lib/hooks/use-device-detection.ts +1 -1
  153. package/src/templates/next-webgl/lib/hooks/use-media-breakpoint.ts +1 -1
  154. package/src/templates/next-webgl/lib/hooks/use-media.ts +29 -0
  155. package/src/templates/next-webgl/lib/utils/json-ld.tsx +199 -0
  156. package/src/templates/next-webgl/package.json +1 -1
  157. package/src/templates/next-webgl/tsconfig.json +1 -0
  158. package/plugins/no-anchor-element.grit +0 -11
  159. package/plugins/no-relative-parent-imports.grit +0 -6
  160. package/plugins/no-unnecessary-forwardref.grit +0 -5
  161. package/src/helpers/integrate/sanity/files/lib/scripts/copy-sanity-mcp.ts +0 -23
  162. package/src/helpers/integrate/sanity/files/lib/scripts/generate-page.ts +0 -297
  163. package/src/template-hooks/use-media.ts +0 -33
@@ -0,0 +1,84 @@
1
+ import { PortableText, type PortableTextProps } from "@portabletext/react"
2
+ import { Link } from "@/components/ui/link"
3
+ import { SanityImage } from "@/components/ui/sanity-image"
4
+ import { getLinkAttributes, urlForReference } from "../utils/link"
5
+
6
+ interface RichTextProps {
7
+ content: PortableTextProps["value"]
8
+ }
9
+
10
+ type LinkMarkProps = {
11
+ children: React.ReactNode
12
+ value?: unknown
13
+ }
14
+
15
+ const LinkMark = ({ children, value }: LinkMarkProps) => {
16
+ const href = urlForReference(value as Parameters<typeof urlForReference>[0])
17
+ const attrs = getLinkAttributes(
18
+ value as Parameters<typeof getLinkAttributes>[0]
19
+ )
20
+
21
+ return (
22
+ <Link
23
+ href={href}
24
+ target={attrs.target}
25
+ rel={attrs.rel}
26
+ data-sanity-edit-target
27
+ >
28
+ {children}
29
+ </Link>
30
+ )
31
+ }
32
+
33
+ export const RichText = ({ content }: RichTextProps) => {
34
+ if (!content) return null
35
+
36
+ return (
37
+ <PortableText
38
+ value={content}
39
+ components={{
40
+ types: {
41
+ image: ({ value }) => <SanityImage image={value} maxWidth={1920} />,
42
+ table: ({ value }) => {
43
+ const rows = value?.rows as
44
+ | Array<{ _key?: string; cells?: string[] }>
45
+ | undefined
46
+ if (!rows?.length) return null
47
+ return (
48
+ <table>
49
+ <tbody>
50
+ {rows.map((row, i) => (
51
+ <tr key={row._key ?? `row-${i}`}>
52
+ {row.cells?.map((cell) => {
53
+ const cellKey = `${row._key}-${cell}`
54
+ return i === 0 ? (
55
+ <th key={cellKey}>{cell}</th>
56
+ ) : (
57
+ <td key={cellKey}>{cell}</td>
58
+ )
59
+ })}
60
+ </tr>
61
+ ))}
62
+ </tbody>
63
+ </table>
64
+ )
65
+ },
66
+ },
67
+ marks: {
68
+ link: LinkMark,
69
+ pageReference: LinkMark,
70
+ externalLink: LinkMark,
71
+ },
72
+ block: {
73
+ h1: ({ children }) => <h1 className="h1">{children}</h1>,
74
+ h2: ({ children }) => <h2 className="h2">{children}</h2>,
75
+ h3: ({ children }) => <h3 className="h3">{children}</h3>,
76
+ h4: ({ children }) => <h4 className="h4">{children}</h4>,
77
+ h5: ({ children }) => <h5 className="h5">{children}</h5>,
78
+ h6: ({ children }) => <h6 className="h6">{children}</h6>,
79
+ normal: ({ children }) => <p className="p">{children}</p>,
80
+ },
81
+ }}
82
+ />
83
+ )
84
+ }
@@ -0,0 +1,40 @@
1
+ import { useState } from "react";
2
+ import type {
3
+ DocumentActionComponent,
4
+ DocumentActionDialogProps,
5
+ DocumentActionProps,
6
+ } from "sanity";
7
+
8
+ export function createConfirmPublishAction(
9
+ originalPublishAction: DocumentActionComponent,
10
+ ): DocumentActionComponent {
11
+ const ConfirmPublishAction = (props: DocumentActionProps) => {
12
+ const original = originalPublishAction(props);
13
+ const [dialogOpen, setDialogOpen] = useState(false);
14
+
15
+ if (!original) return null;
16
+
17
+ const { dialog: _originalDialog, ...originalWithoutDialog } = original;
18
+ const dialog: DocumentActionDialogProps | false = dialogOpen
19
+ ? {
20
+ type: "confirm",
21
+ tone: "critical",
22
+ message:
23
+ "This will make these changes live on the production site immediately. Continue?",
24
+ onCancel: () => setDialogOpen(false),
25
+ onConfirm: () => {
26
+ setDialogOpen(false);
27
+ original.onHandle?.();
28
+ },
29
+ }
30
+ : false;
31
+
32
+ return {
33
+ ...originalWithoutDialog,
34
+ onHandle: () => setDialogOpen(true),
35
+ dialog,
36
+ };
37
+ };
38
+
39
+ return ConfirmPublishAction;
40
+ }
@@ -0,0 +1,34 @@
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
+ export const sanityToken = process.env.SANITY_API_READ_TOKEN || ""
29
+
30
+ /** Preview URL for draft mode */
31
+ export const previewURL =
32
+ process.env.NODE_ENV === "development"
33
+ ? "http://localhost:3000"
34
+ : process.env.NEXT_PUBLIC_BASE_URL || ""
@@ -0,0 +1,35 @@
1
+ import { sanityFetch } from "@/lib/integrations/sanity/live"
2
+ import {
3
+ COMPANY_DATA_QUERY,
4
+ FOOTER_QUERY,
5
+ NAVBAR_QUERY,
6
+ } from "@/lib/integrations/sanity/queries"
7
+ import type {
8
+ COMPANY_DATA_QUERY_RESULT,
9
+ FOOTER_QUERY_RESULT,
10
+ NAVBAR_QUERY_RESULT,
11
+ } from "@/lib/integrations/sanity/sanity.types"
12
+
13
+ export type NavbarData = NonNullable<NAVBAR_QUERY_RESULT>
14
+ export type FooterData = NonNullable<FOOTER_QUERY_RESULT>
15
+ export type CompanyData = NonNullable<COMPANY_DATA_QUERY_RESULT>
16
+
17
+ export const SANITY_LAYOUT_TAGS = ["navbar", "footer", "companyData"]
18
+
19
+ export const getNavbar = async (): Promise<NavbarData | null> => {
20
+ const { data } = await sanityFetch({ query: NAVBAR_QUERY, tags: ["navbar"] })
21
+ return data
22
+ }
23
+
24
+ export const getFooter = async (): Promise<FooterData | null> => {
25
+ const { data } = await sanityFetch({ query: FOOTER_QUERY, tags: ["footer"] })
26
+ return data
27
+ }
28
+
29
+ export const getCompanyData = async (): Promise<CompanyData | null> => {
30
+ const { data } = await sanityFetch({
31
+ query: COMPANY_DATA_QUERY,
32
+ tags: ["companyData"],
33
+ })
34
+ return data
35
+ }
@@ -0,0 +1,58 @@
1
+ import type { Icon, IconProps } from "@phosphor-icons/react"
2
+ import {
3
+ ArticleNyTimesIcon,
4
+ BrowsersIcon,
5
+ BuildingsIcon,
6
+ FileTextIcon,
7
+ FolderOpenIcon,
8
+ FootprintsIcon,
9
+ LayoutIcon,
10
+ ListBulletsIcon,
11
+ ListIcon,
12
+ PackageIcon,
13
+ RocketIcon,
14
+ ShapesIcon,
15
+ ShareNetworkIcon,
16
+ TagIcon,
17
+ TextAlignLeftIcon,
18
+ UserCircleIcon,
19
+ } from "@phosphor-icons/react/dist/ssr"
20
+ import { createElement, forwardRef } from "react"
21
+
22
+ export type SanityStudioIcon = Icon
23
+
24
+ const asDuotoneIcon = (IconComponent: Icon): SanityStudioIcon => {
25
+ const DuotoneIcon = forwardRef<SVGSVGElement, IconProps>((props, ref) =>
26
+ createElement(IconComponent, { ...props, ref, weight: "duotone" })
27
+ )
28
+
29
+ DuotoneIcon.displayName = `${IconComponent.displayName ?? "Phosphor"}Duotone`
30
+
31
+ return DuotoneIcon
32
+ }
33
+
34
+ export const pageBuilderCardIcon = asDuotoneIcon(PackageIcon)
35
+ export const pageBuilderCardListIcon = asDuotoneIcon(ListIcon)
36
+
37
+ export const authorIcon = asDuotoneIcon(UserCircleIcon)
38
+
39
+ export const pageListIcon = asDuotoneIcon(ListBulletsIcon)
40
+
41
+ export const blogCategoryIcon = asDuotoneIcon(TagIcon)
42
+
43
+ export const componentsFolderIcon = asDuotoneIcon(ShapesIcon)
44
+
45
+ export const companyDataIcon = asDuotoneIcon(BuildingsIcon)
46
+ export const socialLinksIcon = asDuotoneIcon(ShareNetworkIcon)
47
+ export const navbarIcon = asDuotoneIcon(ListIcon)
48
+ export const footerIcon = asDuotoneIcon(FootprintsIcon)
49
+ export const layoutFolderIcon = asDuotoneIcon(LayoutIcon)
50
+
51
+ export const heroIcon = asDuotoneIcon(RocketIcon)
52
+ export const descriptionIcon = asDuotoneIcon(TextAlignLeftIcon)
53
+ export const contentIcon = asDuotoneIcon(ArticleNyTimesIcon)
54
+
55
+ export const pageIcon = asDuotoneIcon(FileTextIcon)
56
+ export const pagesFolderIcon = asDuotoneIcon(BrowsersIcon)
57
+ export const pageFolderIcon = asDuotoneIcon(FolderOpenIcon)
58
+ export const contentFolderIcon = asDuotoneIcon(FolderOpenIcon)
@@ -0,0 +1,61 @@
1
+ import { NextResponse } from "next/server"
2
+ import { defineLive } from "next-sanity/live"
3
+ import { isSanityConfigured } from "@/lib/integrations/check-integration"
4
+ import { client } from "../client"
5
+ import { sanityToken } from "../env"
6
+
7
+ const isConfigured = isSanityConfigured() && client
8
+
9
+ const liveExports =
10
+ isConfigured && client
11
+ ? defineLive({
12
+ client,
13
+ browserToken: sanityToken,
14
+ serverToken: sanityToken,
15
+ })
16
+ : null
17
+
18
+ export const sanityFetch =
19
+ liveExports?.sanityFetch ?? (async () => ({ data: null }))
20
+
21
+ export const SanityLive = liveExports?.SanityLive ?? (() => null)
22
+
23
+ export const SanityFetch = async <T,>(
24
+ slug: string,
25
+ query: string,
26
+ label = "Content"
27
+ ): Promise<T | NextResponse> => {
28
+ if (!client) {
29
+ return new NextResponse(`# 503 Unavailable\n\nCMS client unavailable.`, {
30
+ status: 503,
31
+ headers: { "Content-Type": "text/markdown; charset=utf-8" },
32
+ })
33
+ }
34
+
35
+ try {
36
+ const { data } = await sanityFetch({
37
+ query,
38
+ params: { slug },
39
+ })
40
+
41
+ const result = data as T | null
42
+
43
+ if (!result) {
44
+ return new NextResponse(`# 404 Not Found\n\nPage not found.`, {
45
+ status: 404,
46
+ headers: { "Content-Type": "text/markdown; charset=utf-8" },
47
+ })
48
+ }
49
+
50
+ return result
51
+ } catch (error) {
52
+ console.error(`Error fetching ${label.toLowerCase()}:`, error)
53
+ return new NextResponse(
54
+ `# 500 Error\n\nFailed to fetch ${label.toLowerCase()}.`,
55
+ {
56
+ status: 500,
57
+ headers: { "Content-Type": "text/markdown; charset=utf-8" },
58
+ }
59
+ )
60
+ }
61
+ }
@@ -0,0 +1,50 @@
1
+ export interface MarkdownRoute {
2
+ /** Regex to match the public-facing URL */
3
+ regex: RegExp
4
+ /** API route path template (under /api/) */
5
+ apiPath: string
6
+ /** Public markdown URL template (for Link headers) */
7
+ publicPath: string
8
+ }
9
+
10
+ /**
11
+ * Routes that serve markdown versions for AI agents.
12
+ * All pages are backed by the shared `page` document type and resolved
13
+ * through the catch-all route handlers.
14
+ *
15
+ * How it works:
16
+ * - `regex`: match the public-facing URL (e.g. /about.md or /about)
17
+ * - `apiPath`: the internal API route that serves the markdown (e.g. /api/about.md)
18
+ * - Proxy rewrites .md URLs and Accept: text/markdown requests to the API route
19
+ * - Route handlers live under /api/ so page.tsx can coexist at the public path
20
+ */
21
+
22
+ /** .md extension routes -- rewrite to API endpoints */
23
+ export const mdExtensionRoutes: MarkdownRoute[] = [
24
+ {
25
+ regex: /^\/(.+)\.md$/,
26
+ apiPath: "/api/[slug].md",
27
+ publicPath: "/[slug].md",
28
+ },
29
+ ]
30
+
31
+ /** Accept: text/markdown routes -- content negotiation for HTML pages */
32
+ export const acceptHeaderRoutes: MarkdownRoute[] = [
33
+ {
34
+ regex: /^\/(.+)$/,
35
+ apiPath: "/api/[slug].md",
36
+ publicPath: "/[slug].md",
37
+ },
38
+ ]
39
+
40
+ /**
41
+ * Paths the proxy should never process.
42
+ * Studio, static files, Next.js internals.
43
+ */
44
+ export const ignoredPaths = [
45
+ "/studio",
46
+ "/api/",
47
+ "/_next/",
48
+ "/favicon.ico",
49
+ "/sitemap.md",
50
+ ]
@@ -0,0 +1,132 @@
1
+ import { contentIcon, pageIcon, type SanityStudioIcon } from "./icons"
2
+ import { singletonComponents } from "./singletons"
3
+
4
+ export type PageBuilderDocumentType =
5
+ | "hero"
6
+ | "description"
7
+ | "blogContent"
8
+ | (typeof singletonComponents)[number]["schemaType"]
9
+
10
+ type PageBuilderReferenceMember = {
11
+ documentType: PageBuilderDocumentType
12
+ name: string
13
+ title: string
14
+ documentId?: string
15
+ groups: PageBuilderGroupName[]
16
+ }
17
+
18
+ export type PageBuilderGroupName =
19
+ | "generic"
20
+ | "blogEntry"
21
+
22
+ export const pageBuilderPageTypes: Array<{
23
+ title: string
24
+ value: PageBuilderGroupName
25
+ }> = [
26
+ { title: "Generic", value: "generic" },
27
+ { title: "Blog Entry", value: "blogEntry" },
28
+ ]
29
+
30
+ const pageCategoryIcons: Record<PageBuilderGroupName, SanityStudioIcon> = {
31
+ generic: pageIcon,
32
+ blogEntry: contentIcon,
33
+ }
34
+
35
+ export const getPageCategoryIcon = (
36
+ pageType: PageBuilderGroupName | undefined | null
37
+ ) => pageCategoryIcons[pageType ?? "generic"]
38
+
39
+ const reusablePageBuilderReferenceMembers: PageBuilderReferenceMember[] = [
40
+ {
41
+ documentType: "hero",
42
+ name: "heroReference",
43
+ title: "Heading",
44
+ groups: ["generic"],
45
+ },
46
+ {
47
+ documentType: "description",
48
+ name: "descriptionReference",
49
+ title: "Description",
50
+ groups: ["generic"],
51
+ },
52
+ {
53
+ documentType: "blogContent",
54
+ name: "blogContentReference",
55
+ title: "Blog Content",
56
+ groups: ["blogEntry"],
57
+ },
58
+ ]
59
+
60
+ const singletonPageBuilderReferenceMembers: PageBuilderReferenceMember[] =
61
+ singletonComponents.map((component) => ({
62
+ documentType: component.schemaType,
63
+ name: `${component.schemaType}Reference`,
64
+ title: component.title,
65
+ documentId: component.documentId,
66
+ groups: ["generic"],
67
+ }))
68
+
69
+ export const pageBuilderReferenceMembers: PageBuilderReferenceMember[] = [
70
+ ...reusablePageBuilderReferenceMembers,
71
+ ...singletonPageBuilderReferenceMembers,
72
+ ]
73
+
74
+ export const getPageBuilderReferenceMember = (name: string) =>
75
+ pageBuilderReferenceMembers.find((member) => member.name === name)
76
+
77
+ export const getPageBuilderReferenceMemberByDocumentType = (
78
+ documentType: PageBuilderDocumentType
79
+ ) =>
80
+ pageBuilderReferenceMembers.find(
81
+ (member) => member.documentType === documentType
82
+ )
83
+
84
+ const isPageBuilderGroupVisible = (
85
+ pageType: PageBuilderGroupName | undefined,
86
+ group: PageBuilderGroupName
87
+ ) => (pageType ?? "generic") === group
88
+
89
+ export const getVisiblePageBuilderReferenceMembers = (
90
+ pageType: PageBuilderGroupName | undefined
91
+ ) =>
92
+ pageBuilderReferenceMembers.filter((member) =>
93
+ member.groups.some((group) => isPageBuilderGroupVisible(pageType, group))
94
+ )
95
+
96
+ export const getVisiblePageBuilderInsertMenuGroups = (
97
+ pageType: PageBuilderGroupName | undefined
98
+ ) =>
99
+ pageBuilderInsertMenuGroups.filter(
100
+ (group) => group.name === (pageType ?? "generic")
101
+ )
102
+
103
+ export const getInvalidPageBuilderDocumentTypes = (
104
+ pageType: PageBuilderGroupName | undefined,
105
+ documentTypes: PageBuilderDocumentType[]
106
+ ) =>
107
+ documentTypes.filter((documentType) => {
108
+ const member = getPageBuilderReferenceMemberByDocumentType(documentType)
109
+
110
+ if (!member) return false
111
+
112
+ return !member.groups.some((group) =>
113
+ isPageBuilderGroupVisible(pageType, group)
114
+ )
115
+ })
116
+
117
+ export const pageBuilderInsertMenuGroups = [
118
+ {
119
+ name: "generic",
120
+ title: "Generic",
121
+ of: pageBuilderReferenceMembers
122
+ .filter((member) => member.groups.includes("generic"))
123
+ .map((member) => member.name),
124
+ },
125
+ {
126
+ name: "blogEntry",
127
+ title: "Blog Entry",
128
+ of: pageBuilderReferenceMembers
129
+ .filter((member) => member.groups.includes("blogEntry"))
130
+ .map((member) => member.name),
131
+ },
132
+ ]
@@ -0,0 +1,28 @@
1
+ import type { PageBuilderGroupName } from "@/lib/integrations/sanity/page-builder-config"
2
+
3
+ type PageCategoryParams = {
4
+ slug?: string | null
5
+ folder?: string | null
6
+ }
7
+
8
+ export const isEmptyPageSlug = (slug?: string | null) =>
9
+ slug == null || slug === "" || slug === "/"
10
+
11
+ export const getGeneratedPageCategory = ({
12
+ slug,
13
+ folder,
14
+ }: PageCategoryParams): PageBuilderGroupName => {
15
+ if (!isEmptyPageSlug(slug) && folder === "blog") return "blogEntry"
16
+
17
+ return "generic"
18
+ }
19
+
20
+ export const pageCategoryExpression = `
21
+ select(
22
+ defined(slug.current) &&
23
+ slug.current != "" &&
24
+ slug.current != "/" &&
25
+ pageFolder->slug.current == "blog" => "blogEntry",
26
+ "generic"
27
+ )
28
+ `