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
@@ -0,0 +1,431 @@
1
+ import {
2
+ MagnifyingGlassIcon,
3
+ PushPinIcon,
4
+ } from "@phosphor-icons/react/dist/ssr";
5
+ import {
6
+ Badge,
7
+ Box,
8
+ Card,
9
+ Flex,
10
+ Spinner,
11
+ Stack,
12
+ Text,
13
+ TextInput,
14
+ } from "@sanity/ui";
15
+ import { type ReactNode, useEffect, useRef, useState } from "react";
16
+ import { asyncScheduler, from, merge, of } from "rxjs";
17
+ import { switchMap, throttleTime } from "rxjs/operators";
18
+ import { type SanityClient, useClient } from "sanity";
19
+ import { usePaneRouter } from "sanity/structure";
20
+ import { apiVersion } from "./env";
21
+ import { pageFolderIcon } from "./icons";
22
+ import {
23
+ getPageCategoryIcon,
24
+ type PageBuilderGroupName,
25
+ } from "./page-builder-config";
26
+ import { pageCategoryExpression } from "./page-category";
27
+ import {
28
+ PINNED_PAGE_ENTRIES,
29
+ PINNED_PAGE_IDS,
30
+ type PinnedPageEntry,
31
+ } from "./pinned-pages";
32
+
33
+ const NO_MATCHES = "No matches found";
34
+ const NO_PAGES_OR_FOLDERS = "No pages or folders here yet";
35
+
36
+ interface PageTreeItem {
37
+ _id: string;
38
+ _originalId?: string;
39
+ _type: "page" | "pageFolder";
40
+ title?: string | null;
41
+ slug?: string | null;
42
+ pageType?: PageBuilderGroupName | null;
43
+ }
44
+
45
+ // `_originalId` is prefixed `drafts.` when an unpublished edit exists.
46
+ const isDraftItem = (item: PageTreeItem) =>
47
+ !!item._originalId?.startsWith("drafts.");
48
+
49
+ type ActiveState = "leaf" | "ancestor" | "none";
50
+
51
+ const PAGE_TREE_ITEM_ID_PREFIX = {
52
+ page: "page-tree-",
53
+ pageFolder: "page-folder-tree-",
54
+ } as const;
55
+
56
+ const PAGE_TREE_PANE_STYLE_ID = "page-tree-pane-style";
57
+
58
+ // Constrain the tree pane width so the slug/title rows stay readable. Runs once
59
+ // (id-guarded) from inside the component so the injection lives in React's tree.
60
+ const usePaneWidthStyle = () => {
61
+ useEffect(() => {
62
+ if (
63
+ typeof document === "undefined" ||
64
+ document.getElementById(PAGE_TREE_PANE_STYLE_ID)
65
+ ) {
66
+ return;
67
+ }
68
+ const style = document.createElement("style");
69
+ style.id = PAGE_TREE_PANE_STYLE_ID;
70
+ style.textContent = `[data-ui="Pane"][id^="page-tree-"]{max-width:350px;}`;
71
+ document.head.appendChild(style);
72
+ }, []);
73
+ };
74
+
75
+ const PAGE_TREE_QUERY = `
76
+ *[
77
+ (
78
+ _type == "pageFolder" &&
79
+ (
80
+ ($folderId == null && !defined(parentFolder._ref)) ||
81
+ parentFolder._ref == $folderId
82
+ )
83
+ ) ||
84
+ (
85
+ _type == "page" &&
86
+ (
87
+ ($folderId == null && !defined(pageFolder._ref)) ||
88
+ pageFolder._ref == $folderId
89
+ )
90
+ )
91
+ ]{
92
+ _id,
93
+ _originalId,
94
+ _type,
95
+ title,
96
+ "slug": slug.current,
97
+ "pageType": ${pageCategoryExpression}
98
+ }
99
+ `;
100
+
101
+ const normalizeId = (id: string) => id.replace(/^drafts\./, "");
102
+
103
+ const getItemId = (item: Pick<PageTreeItem, "_id" | "_type">) =>
104
+ `${PAGE_TREE_ITEM_ID_PREFIX[item._type]}${normalizeId(item._id)}`;
105
+
106
+ const sortItems = (items: PageTreeItem[]) =>
107
+ [...items].sort((a, b) => {
108
+ if (a._type !== b._type) return a._type === "page" ? -1 : 1;
109
+ // Pages without a slug (e.g. the homepage) float to the top.
110
+ if (!a.slug && a._type === "page") return -1;
111
+ if (!b.slug && b._type === "page") return 1;
112
+ return (a.title ?? "").localeCompare(b.title ?? "");
113
+ });
114
+
115
+ interface PageTreePaneProps {
116
+ options?: Record<string, unknown>;
117
+ }
118
+
119
+ export const PageTreePane = ({ options }: PageTreePaneProps) => {
120
+ const folderId = (options?.folderId as string | null | undefined) ?? null;
121
+
122
+ usePaneWidthStyle();
123
+
124
+ const [items, setItems] = useState<PageTreeItem[]>([]);
125
+ const [loading, setLoading] = useState(true);
126
+ const [query, setQuery] = useState("");
127
+
128
+ const baseClient = useClient({ apiVersion });
129
+ // Pinned to drafts so the tree shows in-progress edits and the Draft badge.
130
+ const clientRef = useRef<SanityClient | null>(null);
131
+ if (!clientRef.current) {
132
+ clientRef.current = baseClient.withConfig({ perspective: "drafts" });
133
+ }
134
+ const client = clientRef.current;
135
+
136
+ const { ChildLink, routerPanesState, groupIndex } = usePaneRouter();
137
+ const activeChildId = routerPanesState[groupIndex + 1]?.[0]?.id ?? null;
138
+ const isLeafPane = routerPanesState.length === groupIndex + 2;
139
+
140
+ useEffect(() => {
141
+ setLoading(true);
142
+ // Initial fetch, then refetch on each folder-scoped mutation to stay live.
143
+ const subscription = merge(
144
+ of(null),
145
+ client
146
+ .listen<PageTreeItem>(
147
+ folderId == null
148
+ ? `*[_type in ["page", "pageFolder"] && !defined(parentFolder._ref) && !defined(pageFolder._ref)]`
149
+ : `*[_type in ["page", "pageFolder"] && (parentFolder._ref == $folderId || pageFolder._ref == $folderId)]`,
150
+ { folderId },
151
+ { visibility: "query", events: ["mutation"], includeResult: false },
152
+ )
153
+ .pipe(
154
+ // leading+trailing: react to the first edit at once, coalesce the rest.
155
+ throttleTime(300, asyncScheduler, {
156
+ leading: true,
157
+ trailing: true,
158
+ }),
159
+ ),
160
+ )
161
+ .pipe(
162
+ switchMap(() =>
163
+ from(client.fetch<PageTreeItem[]>(PAGE_TREE_QUERY, { folderId })),
164
+ ),
165
+ )
166
+ .subscribe({
167
+ next: (data) => {
168
+ setItems(data);
169
+ setLoading(false);
170
+ },
171
+ error: () => setLoading(false),
172
+ });
173
+
174
+ return () => subscription.unsubscribe();
175
+ }, [folderId, client]);
176
+
177
+ // Pinned pages get their own section, so drop them from the dynamic list.
178
+ const visible =
179
+ folderId == null
180
+ ? items.filter((item) => !PINNED_PAGE_IDS.has(item._id))
181
+ : items;
182
+ const sorted = sortItems(visible);
183
+ const term = query.toLowerCase().trim();
184
+ const filtered = term
185
+ ? sorted.filter(
186
+ (item) =>
187
+ item.title?.toLowerCase().includes(term) ||
188
+ item.slug?.toLowerCase().includes(term),
189
+ )
190
+ : sorted;
191
+ const showPinned = folderId == null && !term;
192
+
193
+ return (
194
+ <Flex direction="column" style={{ height: "100%" }}>
195
+ <PageTreeSearch
196
+ query={query}
197
+ onQueryChange={setQuery}
198
+ autoFocus={folderId == null}
199
+ />
200
+ <Box style={{ borderBottom: "1px solid var(--card-border-color)" }} />
201
+ {showPinned && (
202
+ <Stack>
203
+ {PINNED_PAGE_ENTRIES.map((entry) => {
204
+ let activeState: ActiveState = "none";
205
+ if (activeChildId === entry.id) {
206
+ activeState = isLeafPane ? "leaf" : "ancestor";
207
+ }
208
+ return (
209
+ <PinnedRow
210
+ key={entry.id}
211
+ entry={entry}
212
+ ChildLink={ChildLink}
213
+ activeState={activeState}
214
+ />
215
+ );
216
+ })}
217
+ </Stack>
218
+ )}
219
+ <TreeBody
220
+ loading={loading}
221
+ items={filtered}
222
+ emptyMessage={term ? NO_MATCHES : NO_PAGES_OR_FOLDERS}
223
+ ChildLink={ChildLink}
224
+ activeChildId={activeChildId}
225
+ isLeafPane={isLeafPane}
226
+ />
227
+ </Flex>
228
+ );
229
+ };
230
+
231
+ interface PageTreeSearchProps {
232
+ query: string;
233
+ onQueryChange: (value: string) => void;
234
+ autoFocus?: boolean;
235
+ }
236
+
237
+ const PageTreeSearch = ({
238
+ query,
239
+ onQueryChange,
240
+ autoFocus,
241
+ }: PageTreeSearchProps) => (
242
+ <Box padding={3}>
243
+ <TextInput
244
+ icon={MagnifyingGlassIcon}
245
+ placeholder="Filter pages and folders…"
246
+ value={query}
247
+ onChange={(e) => onQueryChange(e.currentTarget.value)}
248
+ clearButton={!!query}
249
+ onClear={() => onQueryChange("")}
250
+ autoFocus={autoFocus}
251
+ />
252
+ </Box>
253
+ );
254
+
255
+ interface TreeBodyProps {
256
+ loading: boolean;
257
+ items: PageTreeItem[];
258
+ emptyMessage: string;
259
+ ChildLink: ReturnType<typeof usePaneRouter>["ChildLink"];
260
+ activeChildId: string | null;
261
+ isLeafPane: boolean;
262
+ }
263
+
264
+ const TreeBody = ({
265
+ loading,
266
+ items,
267
+ emptyMessage,
268
+ ChildLink,
269
+ activeChildId,
270
+ isLeafPane,
271
+ }: TreeBodyProps) => (
272
+ <Box style={{ flex: 1, overflowY: "auto" }}>
273
+ {loading && <Loading />}
274
+ {!loading && items.length === 0 && <Empty message={emptyMessage} />}
275
+ {!loading && items.length > 0 && (
276
+ <Stack>
277
+ {items.map((item) => {
278
+ const isActive = getItemId(item) === activeChildId;
279
+ let activeState: ActiveState = "none";
280
+ if (isActive) activeState = isLeafPane ? "leaf" : "ancestor";
281
+ return (
282
+ <ItemRow
283
+ key={item._id}
284
+ item={item}
285
+ ChildLink={ChildLink}
286
+ activeState={activeState}
287
+ />
288
+ );
289
+ })}
290
+ </Stack>
291
+ )}
292
+ </Box>
293
+ );
294
+
295
+ const Loading = () => (
296
+ <Flex justify="center" padding={4}>
297
+ <Spinner />
298
+ </Flex>
299
+ );
300
+
301
+ const Empty = ({ message }: { message: string }) => (
302
+ <Flex padding={4} justify="center">
303
+ <Text muted size={1}>
304
+ {message}
305
+ </Text>
306
+ </Flex>
307
+ );
308
+
309
+ interface ItemRowProps {
310
+ item: PageTreeItem;
311
+ ChildLink: ReturnType<typeof usePaneRouter>["ChildLink"];
312
+ activeState: ActiveState;
313
+ }
314
+
315
+ const ItemIcon = ({ item }: { item: PageTreeItem }) => {
316
+ const Icon =
317
+ item._type === "pageFolder"
318
+ ? pageFolderIcon
319
+ : getPageCategoryIcon(item.pageType);
320
+ return <Icon />;
321
+ };
322
+
323
+ // One presentation for every row in the tree, so pinned entries and documents
324
+ // can't drift apart on spacing or which lines they show.
325
+ const TreeRow = ({
326
+ childId,
327
+ icon,
328
+ title,
329
+ path,
330
+ badge,
331
+ ChildLink,
332
+ activeState,
333
+ }: {
334
+ childId: string;
335
+ icon: ReactNode;
336
+ title: string;
337
+ path: string | null;
338
+ badge?: ReactNode;
339
+ ChildLink: ReturnType<typeof usePaneRouter>["ChildLink"];
340
+ activeState: ActiveState;
341
+ }) => {
342
+ const isLeaf = activeState === "leaf";
343
+ const isAncestor = activeState === "ancestor";
344
+
345
+ return (
346
+ <ChildLink childId={childId}>
347
+ <Card
348
+ as="div"
349
+ padding={3}
350
+ radius={0}
351
+ pressed={isLeaf}
352
+ selected={isAncestor}
353
+ tone={isLeaf ? "primary" : "default"}
354
+ style={{
355
+ cursor: "pointer",
356
+ display: "block",
357
+ ...(isLeaf && {
358
+ backgroundColor: "var(--card-badge-primary-bg-color)",
359
+ color: "var(--card-badge-primary-fg-color)",
360
+ }),
361
+ ...(isAncestor && {
362
+ backgroundColor: "var(--card-muted-bg-color)",
363
+ }),
364
+ }}
365
+ >
366
+ <Flex align="center" gap={3}>
367
+ {/* Sized here rather than by the caller: Phosphor icons are 1em, so
368
+ one left to inherit the ambient font size renders wider than one
369
+ wrapped in <Text> and pushes its whole row out of line. */}
370
+ <Box style={{ flexShrink: 0 }}>
371
+ <Text size={1}>{icon}</Text>
372
+ </Box>
373
+ <Stack space={2} flex={1}>
374
+ <Flex align="center" gap={2}>
375
+ <Text size={1} weight="medium">
376
+ {title}
377
+ </Text>
378
+ {badge}
379
+ </Flex>
380
+ {path && (
381
+ <Text size={0} muted weight="regular">
382
+ {path}
383
+ </Text>
384
+ )}
385
+ </Stack>
386
+ </Flex>
387
+ </Card>
388
+ </ChildLink>
389
+ );
390
+ };
391
+
392
+ const ItemRow = ({ item, ChildLink, activeState }: ItemRowProps) => {
393
+ const fallback = item._type === "page" ? "Untitled Page" : "Untitled Folder";
394
+
395
+ return (
396
+ <TreeRow
397
+ childId={getItemId(item)}
398
+ icon={<ItemIcon item={item} />}
399
+ title={item.title ?? fallback}
400
+ path={item.slug ? `/${item.slug}` : null}
401
+ badge={
402
+ isDraftItem(item) ? (
403
+ <Badge fontSize={0} mode="outline" tone="caution">
404
+ Draft
405
+ </Badge>
406
+ ) : undefined
407
+ }
408
+ ChildLink={ChildLink}
409
+ activeState={activeState}
410
+ />
411
+ );
412
+ };
413
+
414
+ const PinnedRow = ({
415
+ entry,
416
+ ChildLink,
417
+ activeState,
418
+ }: {
419
+ entry: PinnedPageEntry;
420
+ ChildLink: ReturnType<typeof usePaneRouter>["ChildLink"];
421
+ activeState: ActiveState;
422
+ }) => (
423
+ <TreeRow
424
+ childId={entry.id}
425
+ icon={<PushPinIcon />}
426
+ title={entry.title}
427
+ path={entry.path}
428
+ ChildLink={ChildLink}
429
+ activeState={activeState}
430
+ />
431
+ );
@@ -0,0 +1,54 @@
1
+ import { pageIcon, type SanityStudioIcon } from "./icons";
2
+
3
+ export type PinnedPageEntry = {
4
+ /** Sanity document _id. Doubles as the Studio template id. */
5
+ id: string;
6
+ /** Sanity document draft _id (`drafts.${id}`). */
7
+ draftId: string;
8
+ /** Title shown in Studio listings and used as the initial `title` value. */
9
+ title: string;
10
+ /** Icon shown in Studio. */
11
+ icon: SanityStudioIcon;
12
+ /** Public URL path. Used for link resolution + presentation. */
13
+ path: string;
14
+ /** Slug.current seeded into the template. `null` means no slug (homepage). */
15
+ slug: string | null;
16
+ };
17
+
18
+ export const HOMEPAGE_PINNED_PAGE: PinnedPageEntry = {
19
+ id: "page-homepage",
20
+ draftId: "drafts.page-homepage",
21
+ title: "Homepage",
22
+ icon: pageIcon,
23
+ path: "/",
24
+ slug: null,
25
+ };
26
+
27
+ export const BLOG_INDEX_PINNED_PAGE: PinnedPageEntry = {
28
+ id: "page-blog-index",
29
+ draftId: "drafts.page-blog-index",
30
+ title: "Blog Index",
31
+ icon: pageIcon,
32
+ path: "/blog",
33
+ slug: "blog",
34
+ };
35
+
36
+ export const PINNED_PAGE_ENTRIES: PinnedPageEntry[] = [
37
+ HOMEPAGE_PINNED_PAGE,
38
+ BLOG_INDEX_PINNED_PAGE,
39
+ ];
40
+
41
+ export const PINNED_PAGE_IDS: ReadonlySet<string> = new Set(
42
+ PINNED_PAGE_ENTRIES.flatMap((entry) => [entry.id, entry.draftId]),
43
+ );
44
+
45
+ const pinnedPageById = new Map(
46
+ PINNED_PAGE_ENTRIES.flatMap((entry) => [
47
+ [entry.id, entry] as const,
48
+ [entry.draftId, entry] as const,
49
+ ]),
50
+ );
51
+
52
+ /** Look up a pinned page entry by either its published or draft id. */
53
+ export const getPinnedPageById = (id: string | null | undefined) =>
54
+ id ? (pinnedPageById.get(id) ?? null) : null;
@@ -0,0 +1,119 @@
1
+ import type { Observable } from "rxjs";
2
+ import { map } from "rxjs/operators";
3
+ import type { DocumentStore } from "sanity";
4
+ import {
5
+ type DocumentLocationsState,
6
+ defineDocuments,
7
+ presentationTool,
8
+ } from "sanity/presentation";
9
+ import { previewURL } from "./env";
10
+ import { getPinnedPageById, HOMEPAGE_PINNED_PAGE } from "./pinned-pages";
11
+
12
+ const MAX_FOLDER_DEPTH = 6;
13
+
14
+ const buildNestedSlugExpression = (
15
+ parentReferenceField: string,
16
+ leafSlugField: string,
17
+ ) => {
18
+ const cases = Array.from({ length: MAX_FOLDER_DEPTH }, (_, index) => {
19
+ const depth = MAX_FOLDER_DEPTH - index;
20
+ const parentSlugRefs = Array.from({ length: depth }, (_, parentIndex) => {
21
+ const remainingDepth = depth - parentIndex - 1;
22
+ return `${parentReferenceField}${"->parentFolder".repeat(remainingDepth)}->slug.current`;
23
+ });
24
+
25
+ const pathDefinedChecks = parentSlugRefs
26
+ .map((field) => `defined(${field})`)
27
+ .join(" && ");
28
+
29
+ const childPath = [...parentSlugRefs, leafSlugField].join(' + "/" + ');
30
+ const folderRootPath = parentSlugRefs.join(' + "/" + ');
31
+
32
+ return [
33
+ `${pathDefinedChecks} && ${leafSlugField} == "/" => ${folderRootPath}`,
34
+ `${pathDefinedChecks} && defined(${leafSlugField}) => ${childPath}`,
35
+ ];
36
+ }).flat();
37
+
38
+ return `select(${cases.join(",\n ")},${leafSlugField})`;
39
+ };
40
+
41
+ const pageResolvedSlugExpression = buildNestedSlugExpression(
42
+ "pageFolder",
43
+ "slug.current",
44
+ );
45
+
46
+ const CONTENT_TYPE_PREFIXES: Record<string, string> = {
47
+ blogContent: "blog",
48
+ };
49
+
50
+ const resolveSlugLocation = <T extends { title?: string }>(
51
+ documentStore: DocumentStore,
52
+ id: string,
53
+ projection: string,
54
+ toHref: (doc: T) => string | null,
55
+ fallbackTitle: string,
56
+ ): Observable<DocumentLocationsState> =>
57
+ documentStore
58
+ .listenQuery(`*[_id == $id][0]{ ${projection} }`, { id }, {})
59
+ .pipe(
60
+ map((doc: T | null) => {
61
+ const href = doc ? toHref(doc) : null;
62
+ if (!href) return { locations: [] };
63
+ return { locations: [{ title: doc?.title || fallbackTitle, href }] };
64
+ }),
65
+ );
66
+
67
+ export const presentation = presentationTool({
68
+ name: "preview",
69
+ title: "Preview",
70
+ resolve: {
71
+ mainDocuments: defineDocuments([
72
+ {
73
+ route: "/",
74
+ filter: `_type == "page" && _id == "${HOMEPAGE_PINNED_PAGE.id}"`,
75
+ },
76
+ ...Object.entries(CONTENT_TYPE_PREFIXES).map(([type, prefix]) => ({
77
+ route: `/${prefix}/:slug`,
78
+ filter: `_type == "${type}" && slug.current == $slug`,
79
+ })),
80
+ {
81
+ route: "/:slug+",
82
+ filter: `_type == "page" && ${pageResolvedSlugExpression} == $slug`,
83
+ },
84
+ ]),
85
+ locations: (params, { documentStore }) => {
86
+ if (params.type === "page") {
87
+ const pinned = getPinnedPageById(params.id);
88
+ if (pinned) {
89
+ return { locations: [{ title: pinned.title, href: pinned.path }] };
90
+ }
91
+ return resolveSlugLocation<{ title?: string; resolvedSlug?: string }>(
92
+ documentStore,
93
+ params.id,
94
+ `title, "resolvedSlug": ${pageResolvedSlugExpression}`,
95
+ (doc) => (doc.resolvedSlug ? `/${doc.resolvedSlug}` : null),
96
+ "Page",
97
+ );
98
+ }
99
+
100
+ const prefix = CONTENT_TYPE_PREFIXES[params.type];
101
+ if (!prefix) return null;
102
+
103
+ return resolveSlugLocation<{ title?: string; slug?: string }>(
104
+ documentStore,
105
+ params.id,
106
+ `title, "slug": slug.current`,
107
+ (doc) => (doc.slug ? `/${prefix}/${doc.slug}` : null),
108
+ "Untitled",
109
+ );
110
+ },
111
+ },
112
+ previewUrl: {
113
+ origin: previewURL,
114
+ draftMode: {
115
+ enable: "/api/draft-mode/enable",
116
+ disable: "/api/draft-mode/disable",
117
+ },
118
+ },
119
+ });