bsmnt 0.2.11 → 0.3.1

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 (188) hide show
  1. package/dist/helpers/create/copy-template.d.ts +1 -1
  2. package/dist/helpers/create/copy-template.d.ts.map +1 -1
  3. package/dist/helpers/create/index.d.ts.map +1 -1
  4. package/dist/helpers/create/index.js +2 -1
  5. package/dist/helpers/create/index.js.map +1 -1
  6. package/dist/helpers/integrate/merge-config.d.ts.map +1 -1
  7. package/dist/helpers/integrate/merge-config.js +0 -2
  8. package/dist/helpers/integrate/merge-config.js.map +1 -1
  9. package/dist/helpers/integrate/sanity/config.d.ts.map +1 -1
  10. package/dist/helpers/integrate/sanity/config.js +2 -4
  11. package/dist/helpers/integrate/sanity/config.js.map +1 -1
  12. package/dist/index.js +84 -35
  13. package/dist/index.js.map +1 -1
  14. package/index.js +2 -2
  15. package/package.json +1 -1
  16. package/src/templates/next-default/.vscode/settings.json +1 -1
  17. package/src/templates/next-default/README.md +6 -7
  18. package/src/templates/next-default/app/layout.tsx +17 -4
  19. package/src/templates/next-default/biome.json +1 -1
  20. package/src/templates/next-default/css.d.ts +1 -0
  21. package/src/templates/next-default/lib/README.md +4 -8
  22. package/src/templates/next-default/lib/hooks/use-media.ts +3 -1
  23. package/src/templates/next-default/lib/styles/global.css +182 -0
  24. package/src/templates/next-default/lib/utils/json-ld.tsx +13 -18
  25. package/src/templates/next-default/lib/utils/portable-text-to-markdown.ts +83 -0
  26. package/src/templates/next-default/package.json +3 -3
  27. package/src/templates/next-experiments/.vscode/settings.json +1 -1
  28. package/src/templates/next-experiments/README.md +6 -7
  29. package/src/templates/next-experiments/app/layout.tsx +17 -4
  30. package/src/templates/next-experiments/biome.json +1 -1
  31. package/src/templates/next-experiments/css.d.ts +1 -0
  32. package/src/templates/next-experiments/lib/README.md +4 -8
  33. package/src/templates/next-experiments/lib/hooks/use-media.ts +3 -1
  34. package/src/templates/next-experiments/lib/styles/global.css +182 -0
  35. package/src/templates/next-experiments/lib/utils/json-ld.tsx +13 -18
  36. package/src/templates/next-experiments/lib/utils/portable-text-to-markdown.ts +83 -0
  37. package/src/templates/next-experiments/package.json +3 -3
  38. package/src/templates/next-pagebuilder/.env.example +11 -0
  39. package/src/templates/next-pagebuilder/README.md +23 -0
  40. package/src/templates/next-pagebuilder/_gitignore +67 -0
  41. package/src/templates/next-pagebuilder/app/(content)/[[...slug]]/page.tsx +68 -0
  42. package/src/templates/next-pagebuilder/app/(content)/layout.tsx +13 -0
  43. package/src/templates/next-pagebuilder/app/api/[[...slug]]/route.ts +100 -0
  44. package/src/templates/next-pagebuilder/app/api/draft-mode/disable/route.ts +7 -0
  45. package/src/templates/next-pagebuilder/app/api/draft-mode/enable/route.ts +20 -0
  46. package/src/templates/next-pagebuilder/app/api/revalidate/route.ts +121 -0
  47. package/src/templates/next-pagebuilder/app/favicon.ico +0 -0
  48. package/src/templates/next-pagebuilder/app/layout.tsx +80 -0
  49. package/src/templates/next-pagebuilder/app/robots.ts +15 -0
  50. package/src/templates/next-pagebuilder/app/sitemap.md/route.ts +124 -0
  51. package/src/templates/next-pagebuilder/app/sitemap.xml/route.ts +80 -0
  52. package/src/templates/next-pagebuilder/app/studio/[[...tool]]/page.tsx +8 -0
  53. package/src/templates/next-pagebuilder/biome.json +239 -0
  54. package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +95 -0
  55. package/src/templates/next-pagebuilder/components/layout/header/components/cta-button.tsx +28 -0
  56. package/src/templates/next-pagebuilder/components/layout/header/components/mega-menu-panel.tsx +90 -0
  57. package/src/templates/next-pagebuilder/components/layout/header/components/nav-item-renderer.tsx +98 -0
  58. package/src/templates/next-pagebuilder/components/layout/header/components/nav-leaf-item.tsx +33 -0
  59. package/src/templates/next-pagebuilder/components/layout/header/components/types.ts +7 -0
  60. package/src/templates/next-pagebuilder/components/layout/header/header-client.tsx +110 -0
  61. package/src/templates/next-pagebuilder/components/layout/header/index.tsx +8 -0
  62. package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +45 -0
  63. package/src/templates/next-pagebuilder/components/layout/wrapper/index.tsx +30 -0
  64. package/src/templates/next-pagebuilder/components/page-builder/components/article-content/index.tsx +83 -0
  65. package/src/templates/next-pagebuilder/components/page-builder/components/article-content/related-post-item.tsx +27 -0
  66. package/src/templates/next-pagebuilder/components/page-builder/components/description.tsx +17 -0
  67. package/src/templates/next-pagebuilder/components/page-builder/components/hero.tsx +17 -0
  68. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/content-card.tsx +66 -0
  69. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/content-grid.tsx +42 -0
  70. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/index.tsx +28 -0
  71. package/src/templates/next-pagebuilder/components/page-builder/components/post-collection/types.ts +16 -0
  72. package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +36 -0
  73. package/src/templates/next-pagebuilder/components/page-builder/types.ts +23 -0
  74. package/src/templates/next-pagebuilder/components/page-document/index.tsx +91 -0
  75. package/src/templates/next-pagebuilder/components/sanity/draft-mode-toggle.tsx +27 -0
  76. package/src/templates/next-pagebuilder/components/sanity/rich-text.tsx +87 -0
  77. package/src/templates/next-pagebuilder/components/sanity/visual-editing.tsx +27 -0
  78. package/src/templates/next-pagebuilder/components/ui/image/index.tsx +216 -0
  79. package/src/templates/next-pagebuilder/components/ui/link/index.tsx +152 -0
  80. package/src/templates/next-pagebuilder/components/ui/sanity-image/index.tsx +41 -0
  81. package/src/templates/next-pagebuilder/lib/integrations/check-integration.ts +5 -0
  82. package/src/templates/next-pagebuilder/lib/integrations/sanity/client.ts +27 -0
  83. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/disable-draft-mode.tsx +23 -0
  84. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/page-builder-input.tsx +36 -0
  85. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/page-category-input.tsx +50 -0
  86. package/src/templates/next-pagebuilder/lib/integrations/sanity/components/rich-text.tsx +84 -0
  87. package/src/templates/next-pagebuilder/lib/integrations/sanity/confirm-publish-action.ts +40 -0
  88. package/src/templates/next-pagebuilder/lib/integrations/sanity/env.ts +34 -0
  89. package/src/templates/next-pagebuilder/lib/integrations/sanity/fetchers/layout.ts +35 -0
  90. package/src/templates/next-pagebuilder/lib/integrations/sanity/icons.ts +58 -0
  91. package/src/templates/next-pagebuilder/lib/integrations/sanity/live/index.tsx +61 -0
  92. package/src/templates/next-pagebuilder/lib/integrations/sanity/markdown-proxy.config.ts +50 -0
  93. package/src/templates/next-pagebuilder/lib/integrations/sanity/page-builder-config.ts +132 -0
  94. package/src/templates/next-pagebuilder/lib/integrations/sanity/page-category.ts +28 -0
  95. package/src/templates/next-pagebuilder/lib/integrations/sanity/queries.ts +281 -0
  96. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.cli.ts +29 -0
  97. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.config.ts +211 -0
  98. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/index.ts +4 -0
  99. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +89 -0
  100. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/description.ts +29 -0
  101. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/reusable/hero.ts +28 -0
  102. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +45 -0
  103. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/content/author.ts +70 -0
  104. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/content/blog-category.ts +55 -0
  105. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/index.ts +96 -0
  106. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/company-data.ts +62 -0
  107. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/footer.ts +79 -0
  108. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/layout/navbar.ts +74 -0
  109. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/link.ts +125 -0
  110. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/logo-field.ts +9 -0
  111. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/metadata.ts +68 -0
  112. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/nav-objects.ts +192 -0
  113. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page-builder.ts +39 -0
  114. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page-folder.ts +124 -0
  115. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/page.ts +232 -0
  116. package/src/templates/next-pagebuilder/lib/integrations/sanity/schemas/shared/richText.ts +63 -0
  117. package/src/templates/next-pagebuilder/lib/integrations/sanity/singletons.ts +44 -0
  118. package/src/templates/next-pagebuilder/lib/integrations/sanity/structure.ts +453 -0
  119. package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/image.ts +8 -0
  120. package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/link.ts +137 -0
  121. package/src/templates/next-pagebuilder/lib/integrations/sanity/utils/page-builder-markdown.ts +81 -0
  122. package/src/templates/next-pagebuilder/lib/scripts/sanity-typegen.ts +45 -0
  123. package/src/templates/next-pagebuilder/lib/styles/cn.ts +5 -0
  124. package/src/templates/next-pagebuilder/lib/styles/global.css +70 -0
  125. package/src/templates/next-pagebuilder/lib/utils/base-url.ts +17 -0
  126. package/src/templates/next-pagebuilder/lib/utils/format-date.ts +8 -0
  127. package/src/templates/next-pagebuilder/lib/utils/json-ld.tsx +213 -0
  128. package/src/templates/next-pagebuilder/lib/utils/metadata.ts +167 -0
  129. package/src/templates/next-pagebuilder/lib/utils/sitemap.ts +37 -0
  130. package/src/templates/next-pagebuilder/lib/utils/slug-tag.ts +6 -0
  131. package/src/templates/next-pagebuilder/next.config.ts +134 -0
  132. package/src/templates/next-pagebuilder/package.json +71 -0
  133. package/src/templates/next-pagebuilder/postcss.config.mjs +39 -0
  134. package/src/templates/next-pagebuilder/proxy.ts +81 -0
  135. package/src/templates/next-pagebuilder/svg.d.ts +5 -0
  136. package/src/templates/next-pagebuilder/tsconfig.json +38 -0
  137. package/src/templates/next-webgl/.vscode/settings.json +1 -1
  138. package/src/templates/next-webgl/README.md +6 -7
  139. package/src/templates/next-webgl/app/layout.tsx +17 -4
  140. package/src/templates/next-webgl/biome.json +1 -1
  141. package/src/templates/next-webgl/css.d.ts +1 -0
  142. package/src/templates/next-webgl/lib/README.md +4 -8
  143. package/src/templates/next-webgl/lib/hooks/use-media.ts +3 -1
  144. package/src/templates/next-webgl/lib/styles/global.css +182 -0
  145. package/src/templates/next-webgl/lib/utils/json-ld.tsx +13 -18
  146. package/src/templates/next-webgl/lib/utils/portable-text-to-markdown.ts +83 -0
  147. package/src/templates/next-webgl/package.json +3 -3
  148. package/src/helpers/integrate/sanity/files/lib/scripts/copy-sanity-mcp.ts +0 -23
  149. package/src/helpers/integrate/sanity/files/lib/scripts/generate-page.ts +0 -297
  150. package/src/templates/next-default/lib/scripts/dev.ts +0 -32
  151. package/src/templates/next-default/lib/styles/README.md +0 -13
  152. package/src/templates/next-default/lib/styles/fonts.ts +0 -20
  153. package/src/templates/next-default/lib/styles/index.css +0 -3
  154. package/src/templates/next-default/lib/styles/tokens.css +0 -179
  155. package/src/templates/next-default/lib/utils/README.md +0 -40
  156. package/src/templates/next-default/lib/utils/easings.ts +0 -240
  157. package/src/templates/next-default/lib/utils/fetch.ts +0 -84
  158. package/src/templates/next-default/lib/utils/global-css.d.ts +0 -1
  159. package/src/templates/next-default/lib/utils/math.ts +0 -236
  160. package/src/templates/next-default/lib/utils/strings.ts +0 -246
  161. package/src/templates/next-default/lib/utils/types.d.ts +0 -15
  162. package/src/templates/next-default/lib/utils/viewport.ts +0 -199
  163. package/src/templates/next-experiments/lib/scripts/dev.ts +0 -32
  164. package/src/templates/next-experiments/lib/styles/README.md +0 -13
  165. package/src/templates/next-experiments/lib/styles/fonts.ts +0 -20
  166. package/src/templates/next-experiments/lib/styles/index.css +0 -3
  167. package/src/templates/next-experiments/lib/styles/tokens.css +0 -179
  168. package/src/templates/next-experiments/lib/utils/README.md +0 -40
  169. package/src/templates/next-experiments/lib/utils/easings.ts +0 -240
  170. package/src/templates/next-experiments/lib/utils/fetch.ts +0 -84
  171. package/src/templates/next-experiments/lib/utils/global-css.d.ts +0 -1
  172. package/src/templates/next-experiments/lib/utils/math.ts +0 -236
  173. package/src/templates/next-experiments/lib/utils/strings.ts +0 -246
  174. package/src/templates/next-experiments/lib/utils/types.d.ts +0 -15
  175. package/src/templates/next-experiments/lib/utils/viewport.ts +0 -199
  176. package/src/templates/next-webgl/lib/scripts/dev.ts +0 -32
  177. package/src/templates/next-webgl/lib/styles/README.md +0 -13
  178. package/src/templates/next-webgl/lib/styles/fonts.ts +0 -20
  179. package/src/templates/next-webgl/lib/styles/index.css +0 -3
  180. package/src/templates/next-webgl/lib/styles/tokens.css +0 -179
  181. package/src/templates/next-webgl/lib/utils/README.md +0 -40
  182. package/src/templates/next-webgl/lib/utils/easings.ts +0 -240
  183. package/src/templates/next-webgl/lib/utils/fetch.ts +0 -84
  184. package/src/templates/next-webgl/lib/utils/global-css.d.ts +0 -1
  185. package/src/templates/next-webgl/lib/utils/math.ts +0 -236
  186. package/src/templates/next-webgl/lib/utils/strings.ts +0 -246
  187. package/src/templates/next-webgl/lib/utils/types.d.ts +0 -15
  188. package/src/templates/next-webgl/lib/utils/viewport.ts +0 -199
@@ -0,0 +1,124 @@
1
+ import { defineField, defineType } from "sanity"
2
+ import { apiVersion } from "@/lib/integrations/sanity/env"
3
+ import { pageFolderIcon } from "@/lib/integrations/sanity/icons"
4
+
5
+ type FolderValidationDocument = {
6
+ _id?: string
7
+ }
8
+
9
+ export const pageFolder = defineType({
10
+ name: "pageFolder",
11
+ title: "Page Folder",
12
+ type: "document",
13
+ icon: pageFolderIcon,
14
+ fields: [
15
+ defineField({
16
+ name: "title",
17
+ title: "Title",
18
+ type: "string",
19
+ validation: (Rule) => Rule.required(),
20
+ }),
21
+ defineField({
22
+ name: "parentFolder",
23
+ title: "Parent Folder",
24
+ type: "reference",
25
+ to: [{ type: "pageFolder" }],
26
+ description: "Optional parent folder for nesting inside the Pages tree.",
27
+ hidden: true,
28
+ options: {
29
+ disableNew: true,
30
+ filter: ({ document }) => {
31
+ const currentId = (document as FolderValidationDocument | undefined)
32
+ ?._id
33
+ const normalizedId = currentId?.replace(/^drafts\./, "")
34
+
35
+ return normalizedId
36
+ ? {
37
+ filter: `_type == "pageFolder" && !(_id in [$id, "drafts." + $id])`,
38
+ params: { id: normalizedId },
39
+ }
40
+ : { filter: `_type == "pageFolder"` }
41
+ },
42
+ },
43
+ validation: (Rule) =>
44
+ Rule.custom((value, context) => {
45
+ const document = context.document as
46
+ | FolderValidationDocument
47
+ | undefined
48
+ const currentId = document?._id?.replace(/^drafts\./, "")
49
+
50
+ if (value?._ref && currentId && value._ref === currentId) {
51
+ return "A folder cannot be its own parent"
52
+ }
53
+
54
+ return true
55
+ }),
56
+ }),
57
+ defineField({
58
+ name: "slug",
59
+ title: "Slug",
60
+ type: "slug",
61
+ description:
62
+ "Required full folder path for organization, for example blog or blog/guides.",
63
+ options: {
64
+ source: "title",
65
+ maxLength: 200,
66
+ },
67
+ validation: (Rule) =>
68
+ Rule.required().custom(async (slug, context) => {
69
+ if (!slug?.current) {
70
+ return "Slug is required"
71
+ }
72
+
73
+ if (!/^[a-z0-9-/]+$/.test(slug.current)) {
74
+ return "Slug must be lowercase with hyphens, letters, numbers, and forward slashes only"
75
+ }
76
+
77
+ if (slug.current.startsWith("/") || slug.current.endsWith("/")) {
78
+ return "Slug must not start or end with a forward slash"
79
+ }
80
+
81
+ const document = context.document as
82
+ | FolderValidationDocument
83
+ | undefined
84
+ const id = document?._id?.replace(/^drafts\./, "")
85
+
86
+ if (!id) return true
87
+
88
+ const client = context.getClient({ apiVersion })
89
+ const existing = await client.fetch<number>(
90
+ `count(*[
91
+ _type == "pageFolder" &&
92
+ slug.current == $slug &&
93
+ !(_id in [$id, "drafts." + $id])
94
+ ])`,
95
+ {
96
+ slug: slug.current,
97
+ id,
98
+ }
99
+ )
100
+
101
+ return existing === 0 || "Another page folder already uses this slug"
102
+ }),
103
+ }),
104
+ ],
105
+ preview: {
106
+ select: {
107
+ title: "title",
108
+ slug: "slug.current",
109
+ },
110
+ prepare({ title, slug }) {
111
+ return {
112
+ title: title || "Untitled Folder",
113
+ subtitle: slug ? `/${slug}` : "Folder",
114
+ }
115
+ },
116
+ },
117
+ orderings: [
118
+ {
119
+ title: "Title A-Z",
120
+ name: "titleAsc",
121
+ by: [{ field: "title", direction: "asc" }],
122
+ },
123
+ ],
124
+ })
@@ -0,0 +1,232 @@
1
+ import { defineField, defineType } from "sanity"
2
+ import { apiVersion } from "@/lib/integrations/sanity/env"
3
+ import { pageIcon } from "@/lib/integrations/sanity/icons"
4
+ import {
5
+ getInvalidPageBuilderDocumentTypes,
6
+ getPageBuilderReferenceMemberByDocumentType,
7
+ getPageCategoryIcon,
8
+ type PageBuilderDocumentType,
9
+ type PageBuilderGroupName,
10
+ pageBuilderPageTypes,
11
+ } from "@/lib/integrations/sanity/page-builder-config"
12
+ import { getGeneratedPageCategory } from "@/lib/integrations/sanity/page-category"
13
+
14
+ type PageValidationDocument = {
15
+ _id?: string
16
+ type?: PageBuilderGroupName
17
+ pageFolder?: {
18
+ _ref?: string
19
+ }
20
+ slug?: { current?: string | null }
21
+ pageBuilder?: Array<{
22
+ _key?: string
23
+ _type?: string
24
+ _ref?: string
25
+ }>
26
+ }
27
+
28
+ type PageSlugValidationContext = {
29
+ document?: PageValidationDocument | undefined
30
+ getClient: (options: { apiVersion: string }) => {
31
+ fetch: <T>(query: string, params?: Record<string, unknown>) => Promise<T>
32
+ }
33
+ }
34
+
35
+ type ReferencedPageBuilderDocument = {
36
+ _id: string
37
+ _type: PageBuilderDocumentType
38
+ }
39
+
40
+ const PAGE_FOLDER_QUERY = `*[_type == "pageFolder" && _id in [$publishedId, $draftId]][0].slug.current`
41
+
42
+ async function getGeneratedPageCategoryForDocument(
43
+ document: PageValidationDocument | undefined,
44
+ getClient: PageSlugValidationContext["getClient"]
45
+ ) {
46
+ const pageFolderId = document?.pageFolder?._ref?.replace(/^drafts\./, "")
47
+ let folder: string | null = null
48
+
49
+ if (pageFolderId) {
50
+ folder = await getClient({ apiVersion }).fetch<string | null>(
51
+ PAGE_FOLDER_QUERY,
52
+ { draftId: `drafts.${pageFolderId}`, publishedId: pageFolderId }
53
+ )
54
+ }
55
+
56
+ return getGeneratedPageCategory({
57
+ folder,
58
+ slug: document?.slug?.current ?? null,
59
+ })
60
+ }
61
+
62
+ async function isPageSlugUniqueInFolder(
63
+ slug: string | null,
64
+ context: PageSlugValidationContext
65
+ ) {
66
+ const id = context.document?._id?.replace(/^drafts\./, "")
67
+
68
+ if (!id) return true
69
+
70
+ const pageFolderId = context.document?.pageFolder?._ref ?? null
71
+ const client = context.getClient({ apiVersion })
72
+ const existing = await client.fetch<number>(
73
+ `count(*[
74
+ _type == "page" &&
75
+ !(_id in [$id, "drafts." + $id]) &&
76
+ (
77
+ ($pageFolderId == null && !defined(pageFolder._ref)) ||
78
+ pageFolder._ref == $pageFolderId
79
+ ) &&
80
+ (
81
+ ($slug == null && !defined(slug.current)) ||
82
+ slug.current == $slug
83
+ )
84
+ ])`,
85
+ {
86
+ id,
87
+ pageFolderId,
88
+ slug,
89
+ }
90
+ )
91
+
92
+ return existing === 0
93
+ }
94
+
95
+ export const page = defineType({
96
+ name: "page",
97
+ title: "Page",
98
+ type: "document",
99
+ icon: pageIcon,
100
+ fields: [
101
+ defineField({
102
+ name: "title",
103
+ title: "Title",
104
+ type: "string",
105
+ validation: (Rule) => Rule.required(),
106
+ }),
107
+ defineField({
108
+ name: "type",
109
+ title: "Page Category",
110
+ type: "string",
111
+ options: { list: pageBuilderPageTypes },
112
+ initialValue: "generic",
113
+ hidden: true,
114
+ readOnly: true,
115
+ }),
116
+ defineField({
117
+ name: "pageFolder",
118
+ title: "Folder",
119
+ type: "reference",
120
+ to: [{ type: "pageFolder" }],
121
+ description: "Optional Studio folder for organizing this page.",
122
+ hidden: true,
123
+ }),
124
+ defineField({
125
+ name: "slug",
126
+ title: "Slug",
127
+ type: "slug",
128
+ description: "URL path without leading slash. Leave blank for homepage.",
129
+ options: {
130
+ source: "title",
131
+ maxLength: 200,
132
+ isUnique: (slug, context) => isPageSlugUniqueInFolder(slug, context),
133
+ },
134
+ validation: (Rule) =>
135
+ Rule.custom(async (slug, context) => {
136
+ return (
137
+ (await isPageSlugUniqueInFolder(slug?.current ?? null, {
138
+ document: context.document as PageValidationDocument | undefined,
139
+ getClient: context.getClient,
140
+ })) || "Another page in this folder already uses this slug"
141
+ )
142
+ }),
143
+ }),
144
+ defineField({
145
+ name: "pageBuilder",
146
+ title: "Page Builder",
147
+ type: "pageBuilder",
148
+ description: "Composable content blocks for this page",
149
+ validation: (Rule) =>
150
+ Rule.custom(async (value, context) => {
151
+ const document = context.document as
152
+ | PageValidationDocument
153
+ | undefined
154
+ const pageType = await getGeneratedPageCategoryForDocument(
155
+ document,
156
+ context.getClient
157
+ )
158
+ const pageBuilderItems = Array.isArray(value)
159
+ ? (value as Array<{ _ref?: string }>)
160
+ : []
161
+ const referenceIds = pageBuilderItems
162
+ .map((item) => item?._ref?.replace(/^drafts\./, ""))
163
+ .filter((item): item is string => typeof item === "string")
164
+
165
+ if (referenceIds.length === 0) return true
166
+
167
+ const referencedDocuments = await context
168
+ .getClient({ apiVersion })
169
+ .fetch<ReferencedPageBuilderDocument[]>(
170
+ `*[
171
+ _id in $publishedIds || _id in $draftIds
172
+ ]{
173
+ _id,
174
+ _type
175
+ }`,
176
+ {
177
+ publishedIds: referenceIds,
178
+ draftIds: referenceIds.map((id) => `drafts.${id}`),
179
+ }
180
+ )
181
+
182
+ const invalidDocumentTypes = getInvalidPageBuilderDocumentTypes(
183
+ pageType,
184
+ referencedDocuments.map((entry) => entry._type)
185
+ )
186
+
187
+ if (invalidDocumentTypes.length === 0) return true
188
+
189
+ const invalidMemberTitles = invalidDocumentTypes.map(
190
+ (documentType) =>
191
+ getPageBuilderReferenceMemberByDocumentType(documentType)
192
+ ?.title ?? documentType
193
+ )
194
+
195
+ return `These page builder components do not belong to the "${pageType ?? "generic"}" category: ${invalidMemberTitles.join(", ")}`
196
+ }),
197
+ }),
198
+ defineField({
199
+ name: "metadata",
200
+ title: "SEO & Metadata",
201
+ type: "metadata",
202
+ }),
203
+ ],
204
+ preview: {
205
+ select: {
206
+ id: "_id",
207
+ title: "title",
208
+ folder: "pageFolder->slug.current",
209
+ slug: "slug.current",
210
+ },
211
+ prepare({ folder, id, title, slug }) {
212
+ let path = "No slug"
213
+ if (id === "page-homepage") {
214
+ path = "/"
215
+ } else if (slug) {
216
+ path = `/${slug}`
217
+ }
218
+ return {
219
+ title: title || "Untitled",
220
+ subtitle: path,
221
+ media: getPageCategoryIcon(getGeneratedPageCategory({ folder, slug })),
222
+ }
223
+ },
224
+ },
225
+ orderings: [
226
+ {
227
+ title: "Title A-Z",
228
+ name: "titleAsc",
229
+ by: [{ field: "title", direction: "asc" }],
230
+ },
231
+ ],
232
+ })
@@ -0,0 +1,63 @@
1
+ import { defineType } from "sanity"
2
+ import { linkAnnotations } from "./link"
3
+
4
+ export const richText = defineType({
5
+ name: "richText",
6
+ title: "Rich Text",
7
+ type: "array",
8
+ of: [
9
+ {
10
+ type: "block",
11
+ styles: [
12
+ { title: "Normal", value: "normal" },
13
+ { title: "H1", value: "h1" },
14
+ { title: "H2", value: "h2" },
15
+ { title: "H3", value: "h3" },
16
+ { title: "H4", value: "h4" },
17
+ { title: "H5", value: "h5" },
18
+ { title: "H6", value: "h6" },
19
+ { title: "Quote", value: "blockquote" },
20
+ ],
21
+ lists: [
22
+ { title: "Bullet", value: "bullet" },
23
+ { title: "Number", value: "number" },
24
+ ],
25
+ marks: {
26
+ decorators: [
27
+ { title: "Strong", value: "strong" },
28
+ { title: "Emphasis", value: "em" },
29
+ { title: "Code", value: "code" },
30
+ { title: "Underline", value: "underline" },
31
+ { title: "Strike", value: "strike-through" },
32
+ ],
33
+ annotations: linkAnnotations,
34
+ },
35
+ },
36
+ {
37
+ type: "table",
38
+ title: "Table",
39
+ },
40
+ {
41
+ type: "image",
42
+ title: "Image",
43
+ options: {
44
+ hotspot: true,
45
+ },
46
+ fields: [
47
+ {
48
+ name: "alt",
49
+ title: "Alt Text",
50
+ type: "string",
51
+ description: "Alternative text for screen readers",
52
+ validation: (Rule) => Rule.required(),
53
+ },
54
+ {
55
+ name: "caption",
56
+ title: "Caption",
57
+ type: "string",
58
+ description: "Optional caption displayed below the image",
59
+ },
60
+ ],
61
+ },
62
+ ],
63
+ })
@@ -0,0 +1,44 @@
1
+ import {
2
+ companyDataIcon,
3
+ footerIcon,
4
+ navbarIcon,
5
+ pageListIcon,
6
+ } from "./icons"
7
+
8
+ export const singletonComponents = [
9
+ {
10
+ schemaType: "blogCollection",
11
+ documentId: "blogCollection",
12
+ title: "Blog Collection",
13
+ icon: pageListIcon,
14
+ },
15
+ ] as const
16
+
17
+ export const singletonLayout = [
18
+ {
19
+ schemaType: "companyData",
20
+ documentId: "companyData",
21
+ title: "Company Data",
22
+ icon: companyDataIcon,
23
+ },
24
+ {
25
+ schemaType: "navbar",
26
+ documentId: "navbar",
27
+ title: "Navbar",
28
+ icon: navbarIcon,
29
+ },
30
+ {
31
+ schemaType: "footer",
32
+ documentId: "footer",
33
+ title: "Footer",
34
+ icon: footerIcon,
35
+ },
36
+ ] as const
37
+
38
+ export const singletonComponentTypes: Set<string> = new Set(
39
+ singletonComponents.map((item) => item.schemaType)
40
+ )
41
+
42
+ export const singletonLayoutTypes: Set<string> = new Set(
43
+ singletonLayout.map((item) => item.schemaType)
44
+ )