bsmnt 0.10.0 → 0.12.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 (343) hide show
  1. package/dist/application/add-integration/index.js.map +1 -1
  2. package/dist/application/shared/node-version.js.map +1 -1
  3. package/dist/core/create/internal-ids.d.ts +3 -0
  4. package/dist/core/create/internal-ids.d.ts.map +1 -1
  5. package/dist/core/create/internal-ids.js +4 -0
  6. package/dist/core/create/internal-ids.js.map +1 -1
  7. package/dist/domain/agents.d.ts.map +1 -1
  8. package/dist/domain/animations.d.ts.map +1 -1
  9. package/dist/domain/cms.d.ts.map +1 -1
  10. package/dist/domain/forms.d.ts.map +1 -1
  11. package/dist/domain/hooks.d.ts.map +1 -1
  12. package/dist/domain/skills.d.ts.map +1 -1
  13. package/dist/domain/templates.d.ts +10 -0
  14. package/dist/domain/templates.d.ts.map +1 -1
  15. package/dist/domain/templates.js +5 -0
  16. package/dist/domain/templates.js.map +1 -1
  17. package/dist/index.js +18 -5
  18. package/dist/index.js.map +1 -1
  19. package/dist/infrastructure/agents/register-sanity-mcp.js +3 -8
  20. package/dist/infrastructure/agents/register-sanity-mcp.js.map +1 -1
  21. package/dist/infrastructure/agents/register-storybook-mcp.d.ts +18 -0
  22. package/dist/infrastructure/agents/register-storybook-mcp.d.ts.map +1 -0
  23. package/dist/infrastructure/agents/register-storybook-mcp.js +157 -0
  24. package/dist/infrastructure/agents/register-storybook-mcp.js.map +1 -0
  25. package/dist/infrastructure/create/lifecycle-registry.d.ts.map +1 -1
  26. package/dist/infrastructure/create/lifecycle-registry.js +39 -1
  27. package/dist/infrastructure/create/lifecycle-registry.js.map +1 -1
  28. package/dist/infrastructure/create/merge-registry.d.ts.map +1 -1
  29. package/dist/infrastructure/create/merge-registry.js +4 -0
  30. package/dist/infrastructure/create/merge-registry.js.map +1 -1
  31. package/dist/infrastructure/create/sanity-auth.js.map +1 -1
  32. package/dist/infrastructure/create/setup-agent.d.ts +6 -1
  33. package/dist/infrastructure/create/setup-agent.d.ts.map +1 -1
  34. package/dist/infrastructure/create/setup-agent.js +23 -21
  35. package/dist/infrastructure/create/setup-agent.js.map +1 -1
  36. package/dist/infrastructure/create/setup-dotenvx.js.map +1 -1
  37. package/dist/infrastructure/create/setup-remote.js.map +1 -1
  38. package/dist/infrastructure/create/setup-sanity.d.ts.map +1 -1
  39. package/dist/infrastructure/create/setup-sanity.js +12 -1
  40. package/dist/infrastructure/create/setup-sanity.js.map +1 -1
  41. package/dist/infrastructure/integrations/inject-integration.d.ts.map +1 -1
  42. package/dist/infrastructure/integrations/inject-integration.js +2 -0
  43. package/dist/infrastructure/integrations/inject-integration.js.map +1 -1
  44. package/dist/infrastructure/update/notifier.js.map +1 -1
  45. package/dist/modules/features/cms/index.d.ts.map +1 -1
  46. package/dist/modules/features/cms/index.js +2 -0
  47. package/dist/modules/features/cms/index.js.map +1 -1
  48. package/dist/modules/features/cms/sanity/config.d.ts.map +1 -1
  49. package/dist/modules/features/cms/sanity/config.js +1 -0
  50. package/dist/modules/features/cms/sanity/config.js.map +1 -1
  51. package/dist/modules/features/cms/sanity/index.d.ts.map +1 -1
  52. package/dist/modules/features/cms/sanity/index.js +10 -1
  53. package/dist/modules/features/cms/sanity/index.js.map +1 -1
  54. package/dist/modules/features/cms/sanity/mergers/check-integration.d.ts.map +1 -1
  55. package/dist/modules/features/cms/sanity/mergers/check-integration.js +15 -6
  56. package/dist/modules/features/cms/sanity/mergers/check-integration.js.map +1 -1
  57. package/dist/modules/features/cms/sanity/mergers/knip.d.ts.map +1 -1
  58. package/dist/modules/features/cms/sanity/mergers/knip.js +1 -0
  59. package/dist/modules/features/cms/sanity/mergers/knip.js.map +1 -1
  60. package/dist/modules/features/cms/sanity/mergers/layout.js.map +1 -1
  61. package/dist/modules/features/cms/sanity/mergers/package-json.d.ts +12 -0
  62. package/dist/modules/features/cms/sanity/mergers/package-json.d.ts.map +1 -0
  63. package/dist/modules/features/cms/sanity/mergers/package-json.js +19 -0
  64. package/dist/modules/features/cms/sanity/mergers/package-json.js.map +1 -0
  65. package/dist/modules/features/cms/sanity-pagebuilder/index.d.ts.map +1 -1
  66. package/dist/modules/features/cms/sanity-pagebuilder/index.js +7 -1
  67. package/dist/modules/features/cms/sanity-pagebuilder/index.js.map +1 -1
  68. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.d.ts +14 -0
  69. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.d.ts.map +1 -0
  70. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js +50 -0
  71. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js.map +1 -0
  72. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.d.ts +3 -0
  73. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.d.ts.map +1 -0
  74. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js +55 -0
  75. package/dist/modules/features/cms/sanity-pagebuilder-cache/index.js.map +1 -0
  76. package/dist/modules/presets/index.d.ts.map +1 -1
  77. package/dist/modules/presets/index.js +2 -0
  78. package/dist/modules/presets/index.js.map +1 -1
  79. package/dist/modules/presets/next-pagebuilder-cache.d.ts +3 -0
  80. package/dist/modules/presets/next-pagebuilder-cache.d.ts.map +1 -0
  81. package/dist/modules/presets/next-pagebuilder-cache.js +10 -0
  82. package/dist/modules/presets/next-pagebuilder-cache.js.map +1 -0
  83. package/dist/paths.d.ts +6 -0
  84. package/dist/paths.d.ts.map +1 -1
  85. package/dist/paths.js +6 -0
  86. package/dist/paths.js.map +1 -1
  87. package/package.json +5 -2
  88. package/src/modules/features/cms/sanity/files/components/ui/sanity-image/index.tsx +36 -4
  89. package/src/modules/features/cms/sanity/files/lib/integrations/check-integration.ts +7 -6
  90. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/README.md +1 -1
  91. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/components/rich-text.tsx +8 -1
  92. package/src/modules/features/cms/sanity/files/lib/integrations/sanity/env.ts +2 -2
  93. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/[[...slug]]/route.ts +4 -0
  94. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/revalidate/route.ts +172 -24
  95. package/src/modules/features/cms/sanity-pagebuilder/files/app/feed.xml/route.ts +14 -7
  96. package/src/modules/features/cms/sanity-pagebuilder/files/app/sitemap.md/route.ts +2 -2
  97. package/src/modules/features/cms/sanity-pagebuilder/files/app/studio/[[...tool]]/page.tsx +16 -1
  98. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/rich-text.tsx +8 -1
  99. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/visual-editing.tsx +2 -2
  100. package/src/modules/features/cms/sanity-pagebuilder/files/components/ui/sanity-image/index.tsx +36 -4
  101. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/check-integration.ts +6 -5
  102. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/client.ts +8 -0
  103. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-input.tsx +26 -7
  104. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-insert-menu.tsx +242 -0
  105. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/slug-notices.tsx +6 -16
  106. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/use-studio-preview-urls.ts +62 -0
  107. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/env.ts +2 -2
  108. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/layout.ts +22 -52
  109. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +2 -2
  110. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/live/index.tsx +3 -1
  111. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +78 -33
  112. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-tree-pane.tsx +70 -57
  113. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +119 -29
  114. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/revalidate-paths.ts +70 -0
  115. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/index.ts +2 -0
  116. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/rich-text-block.ts +39 -0
  117. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/studio-preview.ts +37 -0
  118. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +5 -0
  119. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page.ts +12 -0
  120. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/singletons.ts +2 -0
  121. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/structure.ts +5 -0
  122. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/tags.ts +56 -0
  123. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/utils/page-builder-markdown.ts +12 -0
  124. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/sitemap.ts +11 -8
  125. package/src/modules/features/cms/sanity-pagebuilder-cache/files/.knip.json +8 -0
  126. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/[[...slug]]/route.ts +112 -0
  127. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/draft-mode/disable/route.ts +7 -0
  128. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/draft-mode/enable/route.ts +20 -0
  129. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/revalidate/route.ts +292 -0
  130. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/feed.xml/route.ts +66 -0
  131. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/sitemap.md/route.ts +132 -0
  132. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/studio/[[...tool]]/layout.tsx +26 -0
  133. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/studio/[[...tool]]/page.tsx +23 -0
  134. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/draft-mode-toggle.tsx +27 -0
  135. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/rich-text.tsx +94 -0
  136. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/visual-editing.tsx +11 -0
  137. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/ui/sanity-image/index.tsx +83 -0
  138. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/check-integration.ts +6 -0
  139. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/client.ts +37 -0
  140. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/block-actions-item.tsx +81 -0
  141. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/disable-draft-mode.tsx +23 -0
  142. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-input.tsx +58 -0
  143. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-insert-menu.tsx +242 -0
  144. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-category-input.tsx +50 -0
  145. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/same-as-input.tsx +66 -0
  146. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/slug-field.tsx +24 -0
  147. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/slug-input.tsx +14 -0
  148. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/slug-notices.tsx +125 -0
  149. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/use-studio-preview-urls.ts +62 -0
  150. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/confirm-publish-action.ts +40 -0
  151. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/env.ts +34 -0
  152. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/fetchers/layout.ts +39 -0
  153. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/fetchers/redirects.ts +22 -0
  154. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/icons.ts +81 -0
  155. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/live/index.tsx +140 -0
  156. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/markdown-proxy.config.ts +50 -0
  157. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-builder-config.ts +182 -0
  158. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-category.ts +28 -0
  159. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-tree-pane.tsx +431 -0
  160. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/pinned-pages.ts +54 -0
  161. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/presentation.ts +119 -0
  162. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/queries.ts +548 -0
  163. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/revalidate-paths.ts +70 -0
  164. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/sanity.cli.ts +29 -0
  165. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/sanity.config.ts +116 -0
  166. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/index.ts +7 -0
  167. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +89 -0
  168. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/description.ts +29 -0
  169. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/gated-content.ts +178 -0
  170. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/hero.ts +28 -0
  171. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/rich-text-block.ts +39 -0
  172. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +45 -0
  173. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/studio-preview.ts +37 -0
  174. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/content/author.ts +70 -0
  175. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/content/blog-category.ts +55 -0
  176. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/index.ts +137 -0
  177. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/company-data.ts +70 -0
  178. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/footer.ts +79 -0
  179. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/navbar.ts +75 -0
  180. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/redirect.ts +152 -0
  181. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/structured-data.ts +124 -0
  182. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/link.ts +125 -0
  183. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/logo-field.ts +9 -0
  184. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/metadata.ts +132 -0
  185. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/nav-objects.ts +192 -0
  186. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page-builder.ts +43 -0
  187. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page-folder.ts +124 -0
  188. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page.ts +265 -0
  189. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/richText.ts +63 -0
  190. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/structured-data-blocks.ts +436 -0
  191. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/singletons.ts +53 -0
  192. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/structure.ts +339 -0
  193. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/tags.ts +89 -0
  194. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/image.ts +8 -0
  195. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/link.ts +140 -0
  196. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/page-builder-markdown.ts +113 -0
  197. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/utils/page-json-ld.ts +208 -0
  198. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/scripts/sanity-typegen.ts +36 -0
  199. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/check-redirect.ts +30 -0
  200. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/json-ld.tsx +522 -0
  201. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/metadata.ts +189 -0
  202. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/rss.ts +96 -0
  203. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/sitemap.ts +58 -0
  204. package/src/modules/features/cms/sanity-pagebuilder-cache/files/proxy.ts +90 -0
  205. package/src/modules/features/env/dotenvx/files/vercelignore +5 -0
  206. package/src/templates/next-default/.biome/plugins/no-fill-without-sizes.grit +11 -0
  207. package/src/templates/next-default/.env.example +2 -2
  208. package/src/templates/next-default/.github/workflows/ci.yml +1 -1
  209. package/src/templates/next-default/.vscode/settings.json +0 -2
  210. package/src/templates/next-default/biome.json +2 -1
  211. package/src/templates/next-default/components/ui/image/index.tsx +21 -15
  212. package/src/templates/next-default/knip.json +1 -1
  213. package/src/templates/next-default/lefthook.yml +1 -1
  214. package/src/templates/next-default/lib/utils/README.md +18 -5
  215. package/src/templates/next-default/lib/utils/image-sizes.ts +72 -0
  216. package/src/templates/next-default/lib/utils/merge-refs.ts +56 -0
  217. package/src/templates/next-default/lib/utils/strings.ts +1 -51
  218. package/src/templates/next-default/next.config.ts +12 -7
  219. package/src/templates/next-default/package.json +2 -5
  220. package/src/templates/next-experiments/.biome/plugins/no-fill-without-sizes.grit +11 -0
  221. package/src/templates/next-experiments/.env.example +2 -2
  222. package/src/templates/next-experiments/.github/workflows/ci.yml +1 -1
  223. package/src/templates/next-experiments/.vscode/settings.json +0 -2
  224. package/src/templates/next-experiments/app/page.tsx +1 -1
  225. package/src/templates/next-experiments/biome.json +2 -1
  226. package/src/templates/next-experiments/components/ui/image/index.tsx +21 -15
  227. package/src/templates/next-experiments/knip.json +1 -1
  228. package/src/templates/next-experiments/lefthook.yml +1 -1
  229. package/src/templates/next-experiments/lib/utils/README.md +18 -5
  230. package/src/templates/next-experiments/lib/utils/image-sizes.ts +72 -0
  231. package/src/templates/next-experiments/lib/utils/merge-refs.ts +56 -0
  232. package/src/templates/next-experiments/lib/utils/strings.ts +1 -51
  233. package/src/templates/next-experiments/next.config.ts +12 -7
  234. package/src/templates/next-experiments/package.json +2 -5
  235. package/src/templates/next-pagebuilder/.biome/plugins/no-fill-without-sizes.grit +11 -0
  236. package/src/templates/next-pagebuilder/.env.example +1 -0
  237. package/src/templates/next-pagebuilder/.github/workflows/ci.yml +1 -1
  238. package/src/templates/next-pagebuilder/.vscode/settings.json +0 -2
  239. package/src/templates/next-pagebuilder/README.md +64 -0
  240. package/src/templates/next-pagebuilder/app/sitemap.xml/route.ts +3 -1
  241. package/src/templates/next-pagebuilder/biome.json +4 -2
  242. package/src/templates/next-pagebuilder/components/layout/footer/index.tsx +3 -6
  243. package/src/templates/next-pagebuilder/components/layout/header/index.tsx +3 -2
  244. package/src/templates/next-pagebuilder/components/layout/json-ld/index.tsx +3 -8
  245. package/src/templates/next-pagebuilder/components/page-builder/components/article-content/index.tsx +5 -0
  246. package/src/templates/next-pagebuilder/components/page-builder/components/article-content/related-post-item.tsx +1 -0
  247. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/content-filters.tsx +23 -40
  248. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/index.tsx +88 -58
  249. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/skeleton.tsx +4 -16
  250. package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/types.ts +14 -1
  251. package/src/templates/next-pagebuilder/components/page-builder/components/rich-text-block.tsx +16 -0
  252. package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +21 -7
  253. package/src/templates/next-pagebuilder/components/page-document/index.tsx +1 -0
  254. package/src/templates/next-pagebuilder/components/ui/image/index.tsx +21 -12
  255. package/src/templates/next-pagebuilder/knip.json +3 -2
  256. package/src/templates/next-pagebuilder/lefthook.yml +1 -1
  257. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +1221 -977
  258. package/src/templates/next-pagebuilder/lib/utils/image-sizes.ts +72 -0
  259. package/src/templates/next-pagebuilder/lib/utils/merge-refs.ts +56 -0
  260. package/src/templates/next-pagebuilder/next.config.ts +6 -6
  261. package/src/templates/next-pagebuilder/package.json +4 -6
  262. package/src/templates/next-pagebuilder-cache/.biome/plugins/README.md +21 -0
  263. package/src/templates/next-pagebuilder-cache/.biome/plugins/no-anchor-element.grit +12 -0
  264. package/src/templates/next-pagebuilder-cache/.biome/plugins/no-fill-without-sizes.grit +11 -0
  265. package/src/templates/next-pagebuilder-cache/.biome/plugins/no-relative-parent-imports.grit +10 -0
  266. package/src/templates/next-pagebuilder-cache/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
  267. package/src/templates/next-pagebuilder-cache/.env.example +40 -0
  268. package/src/templates/next-pagebuilder-cache/.github/PULL_REQUEST_TEMPLATE.md +13 -0
  269. package/src/templates/next-pagebuilder-cache/.github/workflows/ci.yml +105 -0
  270. package/src/templates/next-pagebuilder-cache/.vscode/extensions.json +19 -0
  271. package/src/templates/next-pagebuilder-cache/.vscode/settings.json +103 -0
  272. package/src/templates/next-pagebuilder-cache/.zed/settings.json +47 -0
  273. package/src/templates/next-pagebuilder-cache/README.md +212 -0
  274. package/src/templates/next-pagebuilder-cache/_gitignore +65 -0
  275. package/src/templates/next-pagebuilder-cache/app/(content)/[[...slug]]/page.tsx +124 -0
  276. package/src/templates/next-pagebuilder-cache/app/(content)/layout.tsx +28 -0
  277. package/src/templates/next-pagebuilder-cache/app/actions/unlock-gated-content.ts +53 -0
  278. package/src/templates/next-pagebuilder-cache/app/favicon.ico +0 -0
  279. package/src/templates/next-pagebuilder-cache/app/layout.tsx +83 -0
  280. package/src/templates/next-pagebuilder-cache/app/robots.ts +13 -0
  281. package/src/templates/next-pagebuilder-cache/app/sitemap.xml/route.ts +84 -0
  282. package/src/templates/next-pagebuilder-cache/biome.json +258 -0
  283. package/src/templates/next-pagebuilder-cache/components/layout/footer/index.tsx +127 -0
  284. package/src/templates/next-pagebuilder-cache/components/layout/header/components/cta-button.tsx +28 -0
  285. package/src/templates/next-pagebuilder-cache/components/layout/header/components/mega-menu-panel.tsx +90 -0
  286. package/src/templates/next-pagebuilder-cache/components/layout/header/components/nav-item-renderer.tsx +98 -0
  287. package/src/templates/next-pagebuilder-cache/components/layout/header/components/nav-leaf-item.tsx +33 -0
  288. package/src/templates/next-pagebuilder-cache/components/layout/header/components/types.ts +7 -0
  289. package/src/templates/next-pagebuilder-cache/components/layout/header/header-client.tsx +110 -0
  290. package/src/templates/next-pagebuilder-cache/components/layout/header/index.tsx +32 -0
  291. package/src/templates/next-pagebuilder-cache/components/layout/json-ld/index.tsx +79 -0
  292. package/src/templates/next-pagebuilder-cache/components/layout/wrapper/index.tsx +33 -0
  293. package/src/templates/next-pagebuilder-cache/components/page-builder/components/article-content/index.tsx +83 -0
  294. package/src/templates/next-pagebuilder-cache/components/page-builder/components/article-content/related-post-item.tsx +28 -0
  295. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-card.tsx +66 -0
  296. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-filters.tsx +76 -0
  297. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-grid.tsx +40 -0
  298. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/content-pagination-nav.tsx +71 -0
  299. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/index.tsx +306 -0
  300. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/skeleton.tsx +41 -0
  301. package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/types.ts +32 -0
  302. package/src/templates/next-pagebuilder-cache/components/page-builder/components/description.tsx +17 -0
  303. package/src/templates/next-pagebuilder-cache/components/page-builder/components/gated-content/index.tsx +169 -0
  304. package/src/templates/next-pagebuilder-cache/components/page-builder/components/hero.tsx +17 -0
  305. package/src/templates/next-pagebuilder-cache/components/page-builder/components/rich-text-block.tsx +16 -0
  306. package/src/templates/next-pagebuilder-cache/components/page-builder/renderer.tsx +71 -0
  307. package/src/templates/next-pagebuilder-cache/components/page-builder/types.ts +25 -0
  308. package/src/templates/next-pagebuilder-cache/components/page-document/index.tsx +147 -0
  309. package/src/templates/next-pagebuilder-cache/components/ui/image/index.tsx +225 -0
  310. package/src/templates/next-pagebuilder-cache/components/ui/link/index.tsx +152 -0
  311. package/src/templates/next-pagebuilder-cache/components.json +24 -0
  312. package/src/templates/next-pagebuilder-cache/knip.json +35 -0
  313. package/src/templates/next-pagebuilder-cache/lefthook.yml +25 -0
  314. package/src/templates/next-pagebuilder-cache/lib/integrations/sanity/sanity.types.ts +1588 -0
  315. package/src/templates/next-pagebuilder-cache/lib/scripts/setup-ci-secrets.ts +117 -0
  316. package/src/templates/next-pagebuilder-cache/lib/styles/cn.ts +5 -0
  317. package/src/templates/next-pagebuilder-cache/lib/styles/global.css +69 -0
  318. package/src/templates/next-pagebuilder-cache/lib/utils/base-url.ts +17 -0
  319. package/src/templates/next-pagebuilder-cache/lib/utils/format-date.ts +8 -0
  320. package/src/templates/next-pagebuilder-cache/lib/utils/image-sizes.ts +72 -0
  321. package/src/templates/next-pagebuilder-cache/lib/utils/slug-tag.ts +6 -0
  322. package/src/templates/next-pagebuilder-cache/next.config.ts +177 -0
  323. package/src/templates/next-pagebuilder-cache/package.json +62 -0
  324. package/src/templates/next-pagebuilder-cache/postcss.config.mjs +65 -0
  325. package/src/templates/next-pagebuilder-cache/svg.d.ts +6 -0
  326. package/src/templates/next-pagebuilder-cache/tsconfig.json +38 -0
  327. package/src/templates/next-webgl/.biome/plugins/no-fill-without-sizes.grit +11 -0
  328. package/src/templates/next-webgl/.env.example +2 -2
  329. package/src/templates/next-webgl/.github/workflows/ci.yml +1 -1
  330. package/src/templates/next-webgl/.vscode/settings.json +0 -2
  331. package/src/templates/next-webgl/biome.json +2 -1
  332. package/src/templates/next-webgl/components/ui/image/index.tsx +21 -15
  333. package/src/templates/next-webgl/knip.json +1 -1
  334. package/src/templates/next-webgl/lefthook.yml +1 -1
  335. package/src/templates/next-webgl/lib/utils/README.md +18 -5
  336. package/src/templates/next-webgl/lib/utils/image-sizes.ts +72 -0
  337. package/src/templates/next-webgl/lib/utils/merge-refs.ts +56 -0
  338. package/src/templates/next-webgl/lib/utils/strings.ts +1 -51
  339. package/src/templates/next-webgl/next.config.ts +12 -7
  340. package/src/templates/next-webgl/package.json +4 -7
  341. package/src/templates/next-experiments/tsconfig.tsbuildinfo +0 -1
  342. package/src/templates/next-pagebuilder/app/actions/refresh.ts +0 -5
  343. package/src/templates/next-webgl/tsconfig.tsbuildinfo +0 -1
@@ -1,7 +1,6 @@
1
1
  import { draftMode } from "next/headers";
2
2
  import { VisualEditing } from "next-sanity/visual-editing";
3
3
  import { Suspense } from "react";
4
- import { refreshAction } from "@/app/actions/refresh";
5
4
  import { DraftModeToggle } from "@/components/sanity/draft-mode-toggle";
6
5
  import { isSanityConfigured } from "@/lib/integrations/check-integration";
7
6
  import { SanityLive } from "@/lib/integrations/sanity/live";
@@ -16,7 +15,8 @@ async function VisualEditingInner() {
16
15
  <>
17
16
  <DraftModeToggle />
18
17
  <VisualEditing />
19
- <SanityLive action={refreshAction} />
18
+ {/* No `action`: in draft mode the default is already `"refresh"`. */}
19
+ <SanityLive />
20
20
  </>
21
21
  );
22
22
  }
@@ -2,7 +2,8 @@ import { getImageDimensions } from "@sanity/asset-utils";
2
2
  import { Image, type ImageProps } from "@/components/ui/image";
3
3
  import { urlForImage } from "@/lib/integrations/sanity/utils/image";
4
4
 
5
- interface SanityImageProps extends Omit<ImageProps, "src" | "aspectRatio"> {
5
+ interface SanityImageBaseProps
6
+ extends Omit<ImageProps, "src" | "aspectRatio" | "sizes" | "width" | "height"> {
6
7
  image: {
7
8
  asset?: {
8
9
  _ref: string;
@@ -15,6 +16,38 @@ interface SanityImageProps extends Omit<ImageProps, "src" | "aspectRatio"> {
15
16
  maxWidth?: number;
16
17
  }
17
18
 
19
+ type SanityImageProps = SanityImageBaseProps &
20
+ (
21
+ | { sizes: string; width?: never; height?: never }
22
+ | { width: number; height: number; sizes?: string }
23
+ );
24
+
25
+ const MAX_SOURCE_WIDTH = 3840;
26
+
27
+ const isGifRef = (ref: string) => ref.endsWith("-gif");
28
+
29
+ const getImageSrc = (
30
+ image: SanityImageProps["image"],
31
+ maxWidth: number,
32
+ croppedWidth: number,
33
+ ) => {
34
+ const builder = urlForImage(image);
35
+ const ref = image.asset?._ref ?? "";
36
+
37
+ if (ref.endsWith("-svg")) return builder.url();
38
+
39
+ const requestWidth = Math.min(maxWidth, MAX_SOURCE_WIDTH, croppedWidth);
40
+ const { left, right, top, bottom } = image.crop ?? {};
41
+ const hasCrop = Boolean(left || right || top || bottom);
42
+ const isGif = isGifRef(ref);
43
+
44
+ if (!(isGif || hasCrop) && requestWidth >= croppedWidth) {
45
+ return builder.url();
46
+ }
47
+
48
+ return builder.width(requestWidth).url();
49
+ };
50
+
18
51
  export function SanityImage({
19
52
  image,
20
53
  maxWidth = 1920,
@@ -39,11 +72,10 @@ export function SanityImage({
39
72
 
40
73
  return (
41
74
  <Image
42
- src={urlForImage(image)
43
- .width(Math.min(maxWidth, sourceWidth))
44
- .url()}
75
+ src={getImageSrc(image, maxWidth, sourceWidth)}
45
76
  alt={alt || image.alt || ""}
46
77
  aspectRatio={sourceWidth / sourceHeight}
78
+ unoptimized={isGifRef(image.asset._ref)}
47
79
  {...imageProps}
48
80
  {...props}
49
81
  />
@@ -1,5 +1,6 @@
1
- export const isSanityConfigured = () =>
2
- Boolean(
3
- process.env.NEXT_PUBLIC_SANITY_PROJECT_ID &&
4
- process.env.NEXT_PUBLIC_SANITY_DATASET,
5
- );
1
+ import { dataset, projectId } from "@/lib/integrations/sanity/env";
2
+
3
+ // Derived from env.ts, not a second read of `process.env`. Read directly these
4
+ // disagreed — env.ts also accepts `SANITY_STUDIO_PROJECT_ID` and defaults the
5
+ // dataset — so a resolvable project reported itself unconfigured.
6
+ export const isSanityConfigured = () => Boolean(projectId && dataset);
@@ -25,3 +25,11 @@ export const client: SanityClient | null = isSanityConfigured()
25
25
  },
26
26
  })
27
27
  : null;
28
+
29
+ /**
30
+ * Non-CDN clone for the publish webhook, which has to see the mutation it was
31
+ * notified about rather than whatever the CDN still holds. Reading a stale slug
32
+ * there invalidates the old path and misses the new one.
33
+ */
34
+ export const directClient: SanityClient | null =
35
+ client?.withConfig({ useCdn: false }) ?? null;
@@ -1,25 +1,43 @@
1
1
  import { type ArrayOfObjectsInputProps, useFormValue } from "sanity";
2
+ import { PageBuilderInsertMenu } from "@/lib/integrations/sanity/components/page-builder-insert-menu";
3
+ import { useStudioPreviewUrls } from "@/lib/integrations/sanity/components/use-studio-preview-urls";
2
4
  import {
5
+ blockPreviewKey,
3
6
  getVisiblePageBuilderInsertMenuGroups,
4
- getVisiblePageBuilderReferenceMembers,
7
+ getVisiblePageBuilderMemberNames,
5
8
  type PageBuilderGroupName,
9
+ pageBuilderReferenceMembers,
6
10
  } from "@/lib/integrations/sanity/page-builder-config";
7
11
 
12
+ const BLOCK_PREVIEW_KEYS = pageBuilderReferenceMembers.map((member) =>
13
+ blockPreviewKey(member.name),
14
+ );
15
+
16
+ function useInsertMenuViews() {
17
+ const previewUrls = useStudioPreviewUrls(BLOCK_PREVIEW_KEYS);
18
+
19
+ return [
20
+ {
21
+ name: "grid" as const,
22
+ previewImageUrl: (schemaTypeName: string) =>
23
+ previewUrls?.[blockPreviewKey(schemaTypeName)],
24
+ },
25
+ { name: "list" as const },
26
+ ];
27
+ }
28
+
8
29
  export function PageBuilderInput(props: ArrayOfObjectsInputProps) {
9
30
  const pageTypeValue = useFormValue(["type"]);
10
31
  const pageType =
11
32
  typeof pageTypeValue === "string"
12
33
  ? (pageTypeValue as PageBuilderGroupName)
13
34
  : undefined;
14
-
15
- const visibleMemberNames = new Set(
16
- getVisiblePageBuilderReferenceMembers(pageType).map(
17
- (member) => member.name,
18
- ),
19
- );
35
+ const views = useInsertMenuViews();
36
+ const visibleMemberNames = getVisiblePageBuilderMemberNames(pageType);
20
37
 
21
38
  return props.renderDefault({
22
39
  ...props,
40
+ arrayFunctions: PageBuilderInsertMenu,
23
41
  schemaType: {
24
42
  ...props.schemaType,
25
43
  of: props.schemaType.of.filter(
@@ -32,6 +50,7 @@ export function PageBuilderInput(props: ArrayOfObjectsInputProps) {
32
50
  insertMenu: {
33
51
  ...props.schemaType.options?.insertMenu,
34
52
  groups: getVisiblePageBuilderInsertMenuGroups(pageType),
53
+ views,
35
54
  },
36
55
  },
37
56
  },
@@ -0,0 +1,242 @@
1
+ import { AddIcon, SearchIcon } from "@sanity/icons";
2
+ import {
3
+ Box,
4
+ Button,
5
+ Card,
6
+ Dialog,
7
+ Flex,
8
+ Grid,
9
+ Stack,
10
+ Text,
11
+ TextInput,
12
+ } from "@sanity/ui";
13
+ import { useMemo, useState } from "react";
14
+ import type { ArrayInputFunctionsProps, ArraySchemaType } from "sanity";
15
+
16
+ type ArrayItem = { _key: string };
17
+ type MemberSchemaType = ArraySchemaType["of"][number];
18
+
19
+ type CreateItem = (memberType: MemberSchemaType) => ArrayItem;
20
+
21
+ type InsertMenuSection = {
22
+ name: string;
23
+ title: string;
24
+ members: MemberSchemaType[];
25
+ };
26
+
27
+ const SCROLL_HEIGHT = "min(60vh, 34rem)";
28
+ const TILE_ASPECT_RATIO = "3 / 2";
29
+
30
+ const memberTitle = (member: MemberSchemaType) => member.title ?? member.name;
31
+
32
+ function BlockTile({
33
+ member,
34
+ previewUrl,
35
+ onSelect,
36
+ }: {
37
+ member: MemberSchemaType;
38
+ previewUrl: string | undefined;
39
+ onSelect: () => void;
40
+ }) {
41
+ const Icon = member.icon;
42
+
43
+ return (
44
+ <Card
45
+ as="button"
46
+ border
47
+ onClick={onSelect}
48
+ overflow="hidden"
49
+ padding={0}
50
+ radius={2}
51
+ style={{ cursor: "pointer", textAlign: "left", width: "100%" }}
52
+ type="button"
53
+ >
54
+ <Stack space={0}>
55
+ <Box
56
+ style={{
57
+ aspectRatio: TILE_ASPECT_RATIO,
58
+ background: "var(--card-muted-bg-color)",
59
+ position: "relative",
60
+ }}
61
+ >
62
+ {previewUrl ? (
63
+ // biome-ignore lint/performance/noImgElement: Studio-only CMS asset
64
+ <img
65
+ alt=""
66
+ src={`${previewUrl}?w=640&fit=max&auto=format`}
67
+ style={{
68
+ height: "100%",
69
+ inset: 0,
70
+ objectFit: "contain",
71
+ position: "absolute",
72
+ width: "100%",
73
+ }}
74
+ />
75
+ ) : (
76
+ <Flex
77
+ align="center"
78
+ justify="center"
79
+ style={{ inset: 0, position: "absolute" }}
80
+ >
81
+ {Icon ? <Icon /> : null}
82
+ </Flex>
83
+ )}
84
+ </Box>
85
+ <Box padding={2}>
86
+ <Text size={1} textOverflow="ellipsis" weight="medium">
87
+ {memberTitle(member)}
88
+ </Text>
89
+ </Box>
90
+ </Stack>
91
+ </Card>
92
+ );
93
+ }
94
+
95
+ function InsertMenuDialog({
96
+ sections,
97
+ previewImageUrl,
98
+ onSelect,
99
+ onClose,
100
+ }: {
101
+ sections: InsertMenuSection[];
102
+ previewImageUrl: ((schemaTypeName: string) => string | undefined) | undefined;
103
+ onSelect: (member: MemberSchemaType) => void;
104
+ onClose: () => void;
105
+ }) {
106
+ const [search, setSearch] = useState("");
107
+
108
+ const visibleSections = useMemo(() => {
109
+ const query = search.trim().toLowerCase();
110
+ if (!query) return sections;
111
+ return sections
112
+ .map((section) => ({
113
+ ...section,
114
+ members: section.members.filter((member) =>
115
+ memberTitle(member).toLowerCase().includes(query),
116
+ ),
117
+ }))
118
+ .filter((section) => section.members.length > 0);
119
+ }, [search, sections]);
120
+
121
+ return (
122
+ <Dialog
123
+ header="Components"
124
+ id="page-builder-insert"
125
+ onClose={onClose}
126
+ open
127
+ width={3}
128
+ >
129
+ <Stack padding={4} space={4}>
130
+ <TextInput
131
+ autoFocus
132
+ icon={SearchIcon}
133
+ onChange={(event) => setSearch(event.currentTarget.value)}
134
+ placeholder="Search components…"
135
+ value={search}
136
+ />
137
+ <Box style={{ height: SCROLL_HEIGHT, overflowY: "auto" }}>
138
+ {visibleSections.length === 0 ? (
139
+ <Text muted size={1}>
140
+ No components match “{search}”.
141
+ </Text>
142
+ ) : (
143
+ <Stack space={5}>
144
+ {visibleSections.map((section) => (
145
+ <Stack key={section.name} space={3}>
146
+ <Text muted size={1} weight="semibold">
147
+ {section.title}
148
+ </Text>
149
+ <Grid columns={[2, 3, 4]} gap={3}>
150
+ {section.members.map((member) => (
151
+ <BlockTile
152
+ key={member.name}
153
+ member={member}
154
+ onSelect={() => onSelect(member)}
155
+ previewUrl={previewImageUrl?.(member.name)}
156
+ />
157
+ ))}
158
+ </Grid>
159
+ </Stack>
160
+ ))}
161
+ </Stack>
162
+ )}
163
+ </Box>
164
+ </Stack>
165
+ </Dialog>
166
+ );
167
+ }
168
+
169
+ /**
170
+ * Pass as `arrayFunctions`. Reads `insertMenu.groups` and `insertMenu.views`, so
171
+ * a schema already configuring the built-in menu needs no extra wiring.
172
+ *
173
+ * Replaces the built-in popover, which is anchored to the array grid element and
174
+ * so clips on page scroll and resizes when switching tabs.
175
+ */
176
+ export function PageBuilderInsertMenu(
177
+ props: ArrayInputFunctionsProps<ArrayItem, ArraySchemaType>,
178
+ ) {
179
+ const { onItemAppend, onValueCreate, readOnly, schemaType } = props;
180
+ const [open, setOpen] = useState(false);
181
+
182
+ const insertMenu = schemaType.options?.insertMenu;
183
+ const members = schemaType.of;
184
+
185
+ const sections = useMemo<InsertMenuSection[]>(() => {
186
+ const byName = new Map(members.map((member) => [member.name, member]));
187
+ const groups = insertMenu?.groups;
188
+ if (!groups) {
189
+ return [{ name: "all", title: "Components", members: [...members] }];
190
+ }
191
+ return groups
192
+ .map((group) => ({
193
+ name: group.name,
194
+ title: group.title ?? group.name,
195
+ members: (group.of ?? [])
196
+ .map((name) => byName.get(name))
197
+ .filter((member) => member !== undefined),
198
+ }))
199
+ .filter((section) => section.members.length > 0);
200
+ }, [members, insertMenu]);
201
+
202
+ const previewImageUrl = useMemo(() => {
203
+ const gridView = insertMenu?.views?.find((view) => view.name === "grid");
204
+ return gridView && "previewImageUrl" in gridView
205
+ ? gridView.previewImageUrl
206
+ : undefined;
207
+ }, [insertMenu]);
208
+
209
+ const insert = (member: MemberSchemaType) => {
210
+ onItemAppend((onValueCreate as unknown as CreateItem)(member));
211
+ setOpen(false);
212
+ };
213
+
214
+ const onlyMember = members.length === 1 ? members[0] : undefined;
215
+
216
+ if (schemaType.options?.disableActions?.includes("add")) return null;
217
+
218
+ return (
219
+ <>
220
+ <Grid>
221
+ <Button
222
+ disabled={readOnly === true}
223
+ fontSize={1}
224
+ icon={AddIcon}
225
+ mode="ghost"
226
+ onClick={() => (onlyMember ? insert(onlyMember) : setOpen(true))}
227
+ padding={3}
228
+ text={onlyMember ? "Add item" : "Add item…"}
229
+ />
230
+ </Grid>
231
+
232
+ {open && (
233
+ <InsertMenuDialog
234
+ onClose={() => setOpen(false)}
235
+ onSelect={insert}
236
+ previewImageUrl={previewImageUrl}
237
+ sections={sections}
238
+ />
239
+ )}
240
+ </>
241
+ );
242
+ }
@@ -4,19 +4,7 @@ import { useClient, useFormValue } from "sanity";
4
4
  import { useRouter } from "sanity/router";
5
5
  import { apiVersion } from "@/lib/integrations/sanity/env";
6
6
  import { isEmptyPageSlug } from "@/lib/integrations/sanity/page-category";
7
-
8
- // Mirrors `pageResolvedSlugExpression`'s chain; leaf joins in JS so this
9
- // tracks the live slug, not the last saved draft.
10
- const FOLDER_PATH_QUERY = `*[_id == $folderId][0]{
11
- "path": array::join(array::compact([
12
- parentFolder->parentFolder->parentFolder->parentFolder->parentFolder->slug.current,
13
- parentFolder->parentFolder->parentFolder->parentFolder->slug.current,
14
- parentFolder->parentFolder->parentFolder->slug.current,
15
- parentFolder->parentFolder->slug.current,
16
- parentFolder->slug.current,
17
- slug.current
18
- ]), "/")
19
- }.path`;
7
+ import { FOLDER_PATH_QUERY } from "@/lib/integrations/sanity/queries";
20
8
 
21
9
  const REDIRECT_QUERY = `*[_type == "redirect" && source.current == $source][0]._id`;
22
10
 
@@ -54,9 +42,11 @@ export function SlugRedirectNotice({ pinnedPath }: SlugRedirectNoticeProps) {
54
42
  const published = client.withConfig({ perspective: "published" });
55
43
 
56
44
  published
57
- .fetch<string | null>(FOLDER_PATH_QUERY, { folderId })
58
- .then((path) => {
59
- if (active) setFolderPath(path ?? "");
45
+ // Leaf joins in JS below, so this tracks the live slug rather than the
46
+ // last saved draft.
47
+ .fetch<{ path: string | null } | null>(FOLDER_PATH_QUERY, { folderId })
48
+ .then((folder) => {
49
+ if (active) setFolderPath(folder?.path ?? "");
60
50
  })
61
51
  .catch(() => {
62
52
  if (active) setFolderPath("");
@@ -0,0 +1,62 @@
1
+ import { useEffect, useState } from "react";
2
+ import { useClient } from "sanity";
3
+ import { apiVersion } from "@/lib/integrations/sanity/env";
4
+
5
+ const PREVIEW_ASSETS_QUERY = `*[
6
+ _type == "studioPreview" &&
7
+ key in $keys &&
8
+ defined(preview.asset._ref)
9
+ ] | order(_updatedAt desc) {
10
+ key,
11
+ "url": preview.asset->url
12
+ }`;
13
+
14
+ type PreviewDocument = {
15
+ key: string | null;
16
+ url: string | null;
17
+ };
18
+
19
+ export type StudioPreviewUrls = Record<string, string>;
20
+
21
+ const SEPARATOR = " ";
22
+
23
+ /** `undefined` while in flight, so callers can tell loading from "no preview". */
24
+ export function useStudioPreviewUrls(keys: readonly string[]) {
25
+ const client = useClient({ apiVersion });
26
+ const [urls, setUrls] = useState<StudioPreviewUrls>();
27
+ // Keys off contents, not array identity — callers pass a freshly mapped array.
28
+ const keyList = keys.join(SEPARATOR);
29
+
30
+ useEffect(() => {
31
+ if (!keyList) {
32
+ setUrls({});
33
+ return;
34
+ }
35
+
36
+ let cancelled = false;
37
+
38
+ client
39
+ .fetch<PreviewDocument[]>(PREVIEW_ASSETS_QUERY, {
40
+ keys: keyList.split(SEPARATOR),
41
+ })
42
+ .then((documents) => {
43
+ if (cancelled) return;
44
+ const next: StudioPreviewUrls = {};
45
+ for (const document of documents) {
46
+ if (document.key && document.url && !next[document.key]) {
47
+ next[document.key] = document.url;
48
+ }
49
+ }
50
+ setUrls(next);
51
+ })
52
+ .catch(() => {
53
+ if (!cancelled) setUrls({});
54
+ });
55
+
56
+ return () => {
57
+ cancelled = true;
58
+ };
59
+ }, [client, keyList]);
60
+
61
+ return urls;
62
+ }
@@ -6,9 +6,9 @@
6
6
  * if Sanity is properly set up before making API calls.
7
7
  */
8
8
 
9
- /** Sanity API version - defaults to latest stable */
9
+ /** Floor, not a preference: Content Releases need 2025-02-19 or later. */
10
10
  export const apiVersion =
11
- process.env.NEXT_PUBLIC_SANITY_API_VERSION || "2024-03-15";
11
+ process.env.NEXT_PUBLIC_SANITY_API_VERSION || "2025-02-19";
12
12
 
13
13
  /** Sanity dataset name - defaults to 'production' */
14
14
  export const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET || "production";
@@ -1,22 +1,13 @@
1
1
  import { cache } from "react";
2
2
  import { fetchSanity } from "@/lib/integrations/sanity/live";
3
- import {
4
- COMPANY_DATA_QUERY,
5
- FOOTER_QUERY,
6
- NAVBAR_QUERY,
7
- STRUCTURED_DATA_QUERY,
8
- } from "@/lib/integrations/sanity/queries";
9
- import type {
10
- COMPANY_DATA_QUERY_RESULT,
11
- FOOTER_QUERY_RESULT,
12
- NAVBAR_QUERY_RESULT,
13
- STRUCTURED_DATA_QUERY_RESULT,
14
- } from "@/lib/integrations/sanity/sanity.types";
3
+ import { LAYOUT_QUERY } from "@/lib/integrations/sanity/queries";
4
+ import type { LAYOUT_QUERY_RESULT } from "@/lib/integrations/sanity/sanity.types";
15
5
 
16
- export type NavbarData = NonNullable<NAVBAR_QUERY_RESULT>;
17
- export type FooterData = NonNullable<FOOTER_QUERY_RESULT>;
18
- export type CompanyData = NonNullable<COMPANY_DATA_QUERY_RESULT>;
19
- export type StructuredData = NonNullable<STRUCTURED_DATA_QUERY_RESULT>;
6
+ export type LayoutData = LAYOUT_QUERY_RESULT;
7
+ export type NavbarData = NonNullable<LayoutData["navbar"]>;
8
+ export type FooterData = NonNullable<LayoutData["footer"]>;
9
+ export type CompanyData = NonNullable<LayoutData["companyData"]>;
10
+ export type StructuredData = NonNullable<LayoutData["structuredData"]>;
20
11
 
21
12
  // Singleton document types that, when published, should bust the layout cache.
22
13
  export const SANITY_LAYOUT_TAGS = [
@@ -26,42 +17,21 @@ export const SANITY_LAYOUT_TAGS = [
26
17
  "structuredData",
27
18
  ];
28
19
 
29
- // Wrapped in React cache so components sharing a singleton (e.g. Footer + JsonLd
30
- // both reading companyData) only hit Sanity once per request.
31
- export const getNavbarData = cache(async (): Promise<NavbarData | null> => {
32
- const { data } = await fetchSanity<NavbarData>({
33
- query: NAVBAR_QUERY,
34
- tags: ["navbar"],
20
+ const EMPTY_LAYOUT: LayoutData = {
21
+ navbar: null,
22
+ footer: null,
23
+ companyData: null,
24
+ structuredData: null,
25
+ };
26
+
27
+ // One query for all four layout singletons, wrapped in React cache so the header,
28
+ // footer and json-ld share a single round trip. Tagged with all four `_type` tags
29
+ // because it reads all four documents.
30
+ export const getLayoutData = cache(async (): Promise<LayoutData> => {
31
+ const { data } = await fetchSanity<LayoutData>({
32
+ query: LAYOUT_QUERY,
33
+ tags: SANITY_LAYOUT_TAGS,
35
34
  });
36
35
 
37
- return data ?? null;
36
+ return data ?? EMPTY_LAYOUT;
38
37
  });
39
-
40
- export const getFooterData = cache(async (): Promise<FooterData | null> => {
41
- const { data } = await fetchSanity<FooterData>({
42
- query: FOOTER_QUERY,
43
- tags: ["footer"],
44
- });
45
-
46
- return data ?? null;
47
- });
48
-
49
- export const getCompanyData = cache(async (): Promise<CompanyData | null> => {
50
- const { data } = await fetchSanity<CompanyData>({
51
- query: COMPANY_DATA_QUERY,
52
- tags: ["companyData"],
53
- });
54
-
55
- return data ?? null;
56
- });
57
-
58
- export const getStructuredData = cache(
59
- async (): Promise<StructuredData | null> => {
60
- const { data } = await fetchSanity<StructuredData>({
61
- query: STRUCTURED_DATA_QUERY,
62
- tags: ["structuredData"],
63
- });
64
-
65
- return data ?? null;
66
- },
67
- );
@@ -17,13 +17,13 @@ import {
17
17
  ListIcon,
18
18
  LockKeyIcon,
19
19
  PackageIcon,
20
- PushPinIcon,
21
20
  QuestionIcon,
22
21
  RocketIcon,
23
22
  ShapesIcon,
24
23
  ShareNetworkIcon,
25
24
  TagIcon,
26
25
  TextAlignLeftIcon,
26
+ TextTIcon,
27
27
  TreeStructureIcon,
28
28
  UserCircleIcon,
29
29
  VideoCameraIcon,
@@ -71,11 +71,11 @@ export const customJsonLdIcon = asDuotoneIcon(BracketsCurlyIcon);
71
71
 
72
72
  export const heroIcon = asDuotoneIcon(RocketIcon);
73
73
  export const descriptionIcon = asDuotoneIcon(TextAlignLeftIcon);
74
+ export const richTextIcon = asDuotoneIcon(TextTIcon);
74
75
  export const contentIcon = asDuotoneIcon(ArticleNyTimesIcon);
75
76
  export const gatedContentIcon = asDuotoneIcon(LockKeyIcon);
76
77
 
77
78
  export const pageIcon = asDuotoneIcon(FileTextIcon);
78
- export const pinIcon = asDuotoneIcon(PushPinIcon);
79
79
  export const pagesFolderIcon = asDuotoneIcon(BrowsersIcon);
80
80
  export const pageFolderIcon = asDuotoneIcon(FolderOpenIcon);
81
81
  export const contentFolderIcon = asDuotoneIcon(FolderOpenIcon);
@@ -26,7 +26,9 @@ type SanityFetchOptions = {
26
26
  params?: QueryParams;
27
27
  tags?: string[];
28
28
  stega?: boolean | undefined;
29
- perspective?: "published" | "drafts" | "previewDrafts";
29
+ // `previewDrafts` was the old name for `drafts` and is gone from the API
30
+ // version this template pins.
31
+ perspective?: "published" | "drafts";
30
32
  };
31
33
 
32
34
  export async function fetchSanity<T = unknown>({