bsmnt 0.13.0 → 0.14.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 (90) hide show
  1. package/dist/modules/features/cms/sanity-pagebuilder/config.d.ts.map +1 -1
  2. package/dist/modules/features/cms/sanity-pagebuilder/config.js +1 -0
  3. package/dist/modules/features/cms/sanity-pagebuilder/config.js.map +1 -1
  4. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.d.ts.map +1 -1
  5. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js +1 -0
  6. package/dist/modules/features/cms/sanity-pagebuilder-cache/config.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/agent-skills/ashby-pagebuilder-block/SKILL.md +29 -11
  9. package/src/agent-skills/hubspot-pagebuilder-block/SKILL.md +31 -12
  10. package/src/agent-skills/page-builder-block/SKILL.md +26 -13
  11. package/src/modules/features/cms/sanity-pagebuilder/files/app/api/revalidate/route.ts +2 -2
  12. package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/rich-text.tsx +1 -1
  13. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/block-actions-item.tsx +2 -2
  14. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/component-preview-frame.tsx +124 -0
  15. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/component-preview-pane.tsx +25 -0
  16. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-input.tsx +3 -26
  17. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-insert-menu.tsx +121 -93
  18. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +2 -2
  19. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +22 -50
  20. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/presentation.ts +1 -18
  21. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +56 -64
  22. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/sanity.config.ts +4 -8
  23. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/index.ts +0 -1
  24. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +2 -2
  25. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +1 -4
  26. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/block.ts +4 -4
  27. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page-builder.ts +4 -18
  28. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/page.ts +11 -37
  29. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/singletons.ts +0 -16
  30. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/structure.ts +35 -33
  31. package/src/modules/features/cms/sanity-pagebuilder/files/lib/utils/studio-embed.ts +22 -0
  32. package/src/modules/features/cms/sanity-pagebuilder-cache/files/app/api/revalidate/route.ts +2 -2
  33. package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/rich-text.tsx +1 -1
  34. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/block-actions-item.tsx +2 -2
  35. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/component-preview-frame.tsx +124 -0
  36. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/component-preview-pane.tsx +25 -0
  37. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-input.tsx +3 -26
  38. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-insert-menu.tsx +121 -93
  39. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/icons.ts +2 -2
  40. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-builder-config.ts +22 -50
  41. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/presentation.ts +1 -18
  42. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/queries.ts +56 -64
  43. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/sanity.config.ts +4 -8
  44. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/index.ts +0 -1
  45. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +2 -2
  46. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/index.ts +1 -4
  47. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/block.ts +4 -4
  48. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page-builder.ts +4 -18
  49. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/page.ts +11 -37
  50. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/singletons.ts +0 -16
  51. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/structure.ts +35 -33
  52. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/utils/studio-embed.ts +22 -0
  53. package/src/modules/features/forms/hubspot/files/_sanity-adapter/form-block.ts +4 -10
  54. package/src/modules/features/forms/hubspot/files/lib/integrations/hubspot/README.md +10 -5
  55. package/src/modules/features/jobs/ashby/files/_sanity-adapter/job-board-block.ts +0 -7
  56. package/src/modules/features/jobs/ashby/files/lib/integrations/ashby/README.md +11 -5
  57. package/src/templates/next-default/lib/utils/README.md +1 -0
  58. package/src/templates/next-default/lib/utils/image-sizes.ts +64 -25
  59. package/src/templates/next-experiments/lib/utils/README.md +1 -0
  60. package/src/templates/next-experiments/lib/utils/image-sizes.ts +64 -25
  61. package/src/templates/next-pagebuilder/AGENTS.md +19 -7
  62. package/src/templates/next-pagebuilder/README.md +15 -3
  63. package/src/templates/next-pagebuilder/app/actions/unlock-gated-content.ts +5 -4
  64. package/src/templates/next-pagebuilder/app/components/page.tsx +81 -0
  65. package/src/templates/next-pagebuilder/components/page-builder/components/gated-content/index.tsx +8 -3
  66. package/src/templates/next-pagebuilder/components/page-builder/library.ts +183 -0
  67. package/src/templates/next-pagebuilder/components/page-builder/registry.tsx +4 -1
  68. package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +3 -1
  69. package/src/templates/next-pagebuilder/components/page-builder/stage.tsx +10 -0
  70. package/src/templates/next-pagebuilder/components/page-document/index.tsx +2 -1
  71. package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +139 -271
  72. package/src/templates/next-pagebuilder/lib/utils/image-sizes.ts +64 -25
  73. package/src/templates/next-pagebuilder-cache/AGENTS.md +19 -7
  74. package/src/templates/next-pagebuilder-cache/README.md +15 -3
  75. package/src/templates/next-pagebuilder-cache/app/actions/unlock-gated-content.ts +5 -4
  76. package/src/templates/next-pagebuilder-cache/app/components/page.tsx +91 -0
  77. package/src/templates/next-pagebuilder-cache/components/page-builder/components/gated-content/index.tsx +8 -3
  78. package/src/templates/next-pagebuilder-cache/components/page-builder/library.ts +183 -0
  79. package/src/templates/next-pagebuilder-cache/components/page-builder/registry.tsx +4 -1
  80. package/src/templates/next-pagebuilder-cache/components/page-builder/renderer.tsx +3 -1
  81. package/src/templates/next-pagebuilder-cache/components/page-builder/stage.tsx +10 -0
  82. package/src/templates/next-pagebuilder-cache/components/page-document/index.tsx +2 -1
  83. package/src/templates/next-pagebuilder-cache/lib/integrations/sanity/sanity.types.ts +139 -271
  84. package/src/templates/next-pagebuilder-cache/lib/utils/image-sizes.ts +64 -25
  85. package/src/templates/next-webgl/lib/utils/README.md +1 -0
  86. package/src/templates/next-webgl/lib/utils/image-sizes.ts +64 -25
  87. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/use-studio-preview-urls.ts +0 -62
  88. package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/studio-preview.ts +0 -37
  89. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/use-studio-preview-urls.ts +0 -62
  90. package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/studio-preview.ts +0 -37
@@ -43,10 +43,6 @@ const pageResolvedSlugExpression = buildNestedSlugExpression(
43
43
  "slug.current",
44
44
  );
45
45
 
46
- const CONTENT_TYPE_PREFIXES: Record<string, string> = {
47
- blogContent: "blog",
48
- };
49
-
50
46
  const resolveSlugLocation = <T extends { title?: string }>(
51
47
  documentStore: DocumentStore,
52
48
  id: string,
@@ -73,10 +69,6 @@ export const presentation = presentationTool({
73
69
  route: "/",
74
70
  filter: `_type == "page" && _id == "${HOMEPAGE_PINNED_PAGE.id}"`,
75
71
  },
76
- ...Object.entries(CONTENT_TYPE_PREFIXES).map(([type, prefix]) => ({
77
- route: `/${prefix}/:slug`,
78
- filter: `_type == "${type}" && slug.current == $slug`,
79
- })),
80
72
  {
81
73
  route: "/:slug+",
82
74
  filter: `_type == "page" && ${pageResolvedSlugExpression} == $slug`,
@@ -97,16 +89,7 @@ export const presentation = presentationTool({
97
89
  );
98
90
  }
99
91
 
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
- );
92
+ return null;
110
93
  },
111
94
  },
112
95
  previewUrl: {
@@ -88,58 +88,53 @@ const richTextLinkProjection = `
88
88
  const pageBuilderProjection = `
89
89
  pageBuilder[!(_key in coalesce(^.hiddenPageBuilderKeys, []))]{
90
90
  _key,
91
- ...@->{
92
- // Gated content withholds its payload from the page query — only the
93
- // teaser and gate config ship to the client. GROQ has no field exclusion,
94
- // so this branch lists safe fields explicitly rather than spreading every
95
- // field. The payload is fetched separately via GATED_CONTENT_QUERY.
96
- _type == "gatedContent" => {
97
- _id,
98
- _type,
99
- anchorId,
100
- presentation,
101
- internalTitle,
102
- teaserHeading,
103
- teaserBody[]{
91
+ // Gated content withholds its payload from the page query — only the
92
+ // teaser and gate config ship to the client. GROQ has no field exclusion,
93
+ // so this branch lists safe fields explicitly rather than spreading every
94
+ // field. The payload is fetched separately via GATED_CONTENT_QUERY.
95
+ _type == "gatedContent" => {
96
+ _type,
97
+ anchorId,
98
+ presentation,
99
+ internalTitle,
100
+ teaserHeading,
101
+ teaserBody[]{
102
+ ${richTextLinkProjection}
103
+ },
104
+ gateType,
105
+ gateHeadline,
106
+ gateDescription,
107
+ submitLabel,
108
+ successMessage,
109
+ kind
110
+ },
111
+ _type != "gatedContent" => {
112
+ ...,
113
+ _type == "richTextBlock" => {
114
+ content[]{
104
115
  ${richTextLinkProjection}
105
- },
106
- gateType,
107
- gateHeadline,
108
- gateDescription,
109
- submitLabel,
110
- successMessage,
111
- kind
116
+ }
112
117
  },
113
- _type != "gatedContent" => {
114
- ...,
115
- _type == "richTextBlock" => {
116
- ...,
117
- content[]{
118
- ${richTextLinkProjection}
119
- }
118
+ _type == "blogContent" => {
119
+ categories[]->{
120
+ _id,
121
+ title,
122
+ slug
120
123
  },
121
- _type == "blogContent" => {
122
- ...,
123
- categories[]->{
124
- _id,
125
- title,
126
- slug
127
- },
128
- author->{
129
- _id,
130
- name,
131
- avatar,
132
- role
133
- },
134
- body[]{
135
- ${richTextLinkProjection}
136
- },
137
- relatedPosts[]->{
138
- _id,
139
- title,
140
- "resolvedSlug": ${pageResolvedSlugExpression},
141
- metadata
142
- }
124
+ author->{
125
+ _id,
126
+ name,
127
+ avatar,
128
+ role
129
+ },
130
+ body[]{
131
+ ${richTextLinkProjection}
132
+ },
133
+ relatedPosts[]->{
134
+ _id,
135
+ title,
136
+ "resolvedSlug": ${pageResolvedSlugExpression},
137
+ metadata
143
138
  }
144
139
  }
145
140
  }
@@ -147,10 +142,13 @@ const pageBuilderProjection = `
147
142
  `;
148
143
 
149
144
  // Gated content payload — fetched on demand by the unlock server action, keyed
150
- // by the block's document id. Kept out of the page query so it never ships to
151
- // the client until the visitor completes the gate.
145
+ // by the hosting page plus the block's `_key`. Kept out of the page query so it
146
+ // never ships to the client until the visitor completes the gate.
152
147
  export const GATED_CONTENT_QUERY = defineQuery(`
153
- *[_type == "gatedContent" && _id == $id][0]{
148
+ *[
149
+ _type == "page" &&
150
+ (_id == $pageId || _id == "drafts." + $pageId)
151
+ ][0].pageBuilder[_key == $blockKey && _type == "gatedContent"][0]{
154
152
  kind,
155
153
  content[]{
156
154
  ${richTextLinkProjection}
@@ -231,16 +229,10 @@ export const P_REV_SLUGS_QUERY = defineQuery(`
231
229
  }
232
230
  `);
233
231
 
234
- // Pages depending on `$documentIds`, one or two hops out: one covers
235
- // `collectionCategory->`, two covers anything reached through a page-builder
236
- // block. All inline the referenced title or slug, so a change there goes stale.
237
232
  export const P_REF_DOC_PAGES_QUERY = defineQuery(`
238
233
  *[
239
234
  _type == "page" &&
240
- (
241
- references($documentIds) ||
242
- count(pageBuilder[_ref in *[references($documentIds)]._id]) > 0
243
- )
235
+ references($documentIds)
244
236
  ] | order(title asc) {
245
237
  _id,
246
238
  title,
@@ -319,7 +311,7 @@ export const ALL_BLOG_ARTICLES_QUERY = defineQuery(`
319
311
  title,
320
312
  "slug": slug,
321
313
  "resolvedSlug": ${pageResolvedSlugExpression},
322
- "blogContent": pageBuilder[_ref in *[_type == "blogContent"]._id][0]->{
314
+ "blogContent": pageBuilder[_type == "blogContent"][0]{
323
315
  thumbnail{
324
316
  ...,
325
317
  alt
@@ -356,14 +348,14 @@ export const BLOG_ARTICLES_PAGINATED_QUERY = defineQuery(`
356
348
  (
357
349
  !defined($category) ||
358
350
  $category == "" ||
359
- $category in pageBuilder[_ref in *[_type == "blogContent"]._id][0]->categories[]->slug.current
351
+ $category in pageBuilder[_type == "blogContent"][0].categories[]->slug.current
360
352
  )
361
353
  ] | order(_updatedAt desc)[$offset...$end] {
362
354
  _id,
363
355
  title,
364
356
  "slug": slug,
365
357
  "resolvedSlug": ${pageResolvedSlugExpression},
366
- "blogContent": pageBuilder[_ref in *[_type == "blogContent"]._id][0]->{
358
+ "blogContent": pageBuilder[_type == "blogContent"][0]{
367
359
  thumbnail{
368
360
  ...,
369
361
  alt
@@ -395,7 +387,7 @@ export const BLOG_ARTICLES_COUNT_QUERY = defineQuery(`
395
387
  (
396
388
  !defined($category) ||
397
389
  $category == "" ||
398
- $category in pageBuilder[_ref in *[_type == "blogContent"]._id][0]->categories[]->slug.current
390
+ $category in pageBuilder[_type == "blogContent"][0].categories[]->slug.current
399
391
  )
400
392
  ])
401
393
  `);
@@ -415,7 +407,7 @@ export const ALL_BLOG_CATEGORIES_QUERY = defineQuery(`
415
407
  export const COLLECTION_PAGES_QUERY = defineQuery(`
416
408
  *[
417
409
  _type == "page" &&
418
- count(pageBuilder[!(_key in coalesce(^.hiddenPageBuilderKeys, [])) && _ref in *[_type == "blogCollection"]._id]) > 0
410
+ count(pageBuilder[!(_key in coalesce(^.hiddenPageBuilderKeys, [])) && _type == "blogCollection"]) > 0
419
411
  ] {
420
412
  "path": ${pageResolvedSlugExpression},
421
413
  "category": collectionCategory->slug.current
@@ -7,7 +7,6 @@ import { dataset, projectId } from "./env";
7
7
  import { PINNED_PAGE_ENTRIES, PINNED_PAGE_IDS } from "./pinned-pages";
8
8
  import { presentation } from "./presentation";
9
9
  import { schema } from "./schemas";
10
- import { singletonComponentTypes } from "./singletons";
11
10
  import { structure } from "./structure";
12
11
 
13
12
  const isProd = process.env.NODE_ENV === "production";
@@ -19,10 +18,9 @@ const singletonDocumentActions = new Set([
19
18
  ]);
20
19
 
21
20
  const isSingletonDocument = (schemaType: string, documentId?: string) =>
22
- singletonComponentTypes.has(schemaType) ||
23
- (schemaType === "page" &&
24
- documentId !== undefined &&
25
- PINNED_PAGE_IDS.has(documentId));
21
+ schemaType === "page" &&
22
+ documentId !== undefined &&
23
+ PINNED_PAGE_IDS.has(documentId);
26
24
 
27
25
  export default defineConfig({
28
26
  basePath: "/studio",
@@ -107,9 +105,7 @@ export default defineConfig({
107
105
  },
108
106
  newDocumentOptions: (previousOptions) =>
109
107
  previousOptions.filter(
110
- (option) =>
111
- !singletonComponentTypes.has(option.templateId) &&
112
- !PINNED_PAGE_IDS.has(option.templateId),
108
+ (option) => !PINNED_PAGE_IDS.has(option.templateId),
113
109
  ),
114
110
  },
115
111
  plugins: [structureTool({ structure }), presentation, media(), table()],
@@ -4,4 +4,3 @@ export { gatedContent } from "./reusable/gated-content";
4
4
  export { hero } from "./reusable/hero";
5
5
  export { richTextBlock } from "./reusable/rich-text-block";
6
6
  export { blogCollection } from "./singleton/content-collection";
7
- export { studioPreview } from "./studio-preview";
@@ -4,7 +4,7 @@ import { defineBlock } from "@/lib/integrations/sanity/schemas/shared/block";
4
4
 
5
5
  export const blogContent = defineBlock({
6
6
  name: "blogContent",
7
- title: "Blog Content",
7
+ title: "Blog Posts",
8
8
  icon: contentIcon,
9
9
  fields: [
10
10
  defineField({
@@ -80,7 +80,7 @@ export const blogContent = defineBlock({
80
80
  },
81
81
  prepare({ title, date, media }) {
82
82
  return {
83
- title: title || "Blog Content",
83
+ title: title || "Blog Posts",
84
84
  subtitle: date || "No date set",
85
85
  media,
86
86
  };
@@ -15,7 +15,6 @@ import {
15
15
  gatedContent,
16
16
  hero,
17
17
  richTextBlock,
18
- studioPreview,
19
18
  } from "./components";
20
19
  import { author } from "./content/author";
21
20
  import { blogCategory } from "./content/blog-category";
@@ -83,7 +82,6 @@ export {
83
82
  spacingSettings,
84
83
  spacingStep,
85
84
  structuredData,
86
- studioPreview,
87
85
  videoJsonLd,
88
86
  };
89
87
 
@@ -121,8 +119,7 @@ export const schema: { types: SchemaTypeDefinition[] } = {
121
119
  description,
122
120
  richTextBlock,
123
121
  gatedContent,
124
- studioPreview,
125
-
122
+
126
123
  // Layout types (singletons for site-wide layout)
127
124
  companyData,
128
125
  navbar,
@@ -1,8 +1,8 @@
1
1
  import {
2
- type DocumentDefinition,
3
2
  defineType,
4
3
  type FieldDefinition,
5
4
  type FieldGroupDefinition,
5
+ type ObjectDefinition,
6
6
  } from "sanity";
7
7
  import { presentationFields } from "./presentation";
8
8
 
@@ -11,7 +11,7 @@ const presentationGroup: FieldGroupDefinition = {
11
11
  title: "Presentation",
12
12
  };
13
13
 
14
- type BlockDefinition = Omit<DocumentDefinition, "type" | "fields" | "groups"> & {
14
+ type BlockDefinition = Omit<ObjectDefinition, "type" | "fields" | "groups"> & {
15
15
  fields: FieldDefinition[];
16
16
  groups?: FieldGroupDefinition[];
17
17
  };
@@ -26,7 +26,7 @@ const withPresentationGroup = (groups: FieldGroupDefinition[] | undefined) => {
26
26
  };
27
27
 
28
28
  /**
29
- * Defines a page-builder block — `type: "document"` plus Anchor ID and
29
+ * Defines a page-builder block — an inline `type: "object"` plus Anchor ID and
30
30
  * Presentation Mode. Use instead of `defineType`.
31
31
  */
32
32
  export const defineBlock = ({ fields, groups, ...rest }: BlockDefinition) => {
@@ -34,7 +34,7 @@ export const defineBlock = ({ fields, groups, ...rest }: BlockDefinition) => {
34
34
 
35
35
  return defineType({
36
36
  ...rest,
37
- type: "document",
37
+ type: "object",
38
38
  ...(blockGroups ? { groups: blockGroups } : {}),
39
39
  fields: [
40
40
  ...fields,
@@ -2,8 +2,8 @@ import { defineArrayMember, defineType } from "sanity";
2
2
  import { BlockActionsItem } from "@/lib/integrations/sanity/components/block-actions-item";
3
3
  import { PageBuilderInput } from "@/lib/integrations/sanity/components/page-builder-input";
4
4
  import {
5
+ pageBuilderBlockMembers,
5
6
  pageBuilderInsertMenuGroups,
6
- pageBuilderReferenceMembers,
7
7
  } from "@/lib/integrations/sanity/page-builder-config";
8
8
 
9
9
  export const pageBuilder = defineType({
@@ -17,27 +17,13 @@ export const pageBuilder = defineType({
17
17
  groups: pageBuilderInsertMenuGroups,
18
18
  },
19
19
  },
20
- of: pageBuilderReferenceMembers.map((member) =>
20
+ of: pageBuilderBlockMembers.map((member) =>
21
21
  defineArrayMember({
22
- name: member.name,
22
+ type: member.documentType,
23
23
  title: member.title,
24
- type: "reference",
25
- to: [{ type: member.documentType }],
26
24
  // Per-placement Hide/Show action; hidden state lives in the owning
27
- // document's `hiddenPageBuilderKeys`, not on the ref itself.
25
+ // document's `hiddenPageBuilderKeys`, not on the block itself.
28
26
  components: { item: BlockActionsItem },
29
- ...(member.documentId
30
- ? {
31
- options: {
32
- disableNew: true,
33
- filter: "_id in [$documentId, $draftDocumentId]",
34
- filterParams: {
35
- documentId: member.documentId,
36
- draftDocumentId: `drafts.${member.documentId}`,
37
- },
38
- },
39
- }
40
- : {}),
41
27
  }),
42
28
  ),
43
29
  });
@@ -5,7 +5,7 @@ import { apiVersion } from "@/lib/integrations/sanity/env";
5
5
  import { pageIcon } from "@/lib/integrations/sanity/icons";
6
6
  import {
7
7
  getInvalidPageBuilderDocumentTypes,
8
- getPageBuilderReferenceMemberByDocumentType,
8
+ getPageBuilderBlockMember,
9
9
  getPageCategoryIcon,
10
10
  type PageBuilderDocumentType,
11
11
  type PageBuilderGroupName,
@@ -24,7 +24,6 @@ type PageValidationDocument = {
24
24
  pageBuilder?: Array<{
25
25
  _key?: string;
26
26
  _type?: string;
27
- _ref?: string;
28
27
  }>;
29
28
  };
30
29
 
@@ -35,11 +34,6 @@ type PageSlugValidationContext = {
35
34
  };
36
35
  };
37
36
 
38
- type ReferencedPageBuilderDocument = {
39
- _id: string;
40
- _type: PageBuilderDocumentType;
41
- };
42
-
43
37
  const PAGE_FOLDER_QUERY = `*[_type == "pageFolder" && _id in [$publishedId, $draftId]][0].slug.current`;
44
38
 
45
39
  async function getGeneratedPageCategoryForDocument(
@@ -164,41 +158,25 @@ export const page = defineType({
164
158
  document,
165
159
  context.getClient,
166
160
  );
167
- const pageBuilderItems = Array.isArray(value)
168
- ? (value as Array<{ _ref?: string }>)
169
- : [];
170
- const referenceIds = pageBuilderItems
171
- .map((item) => item?._ref?.replace(/^drafts\./, ""))
172
- .filter((item): item is string => typeof item === "string");
173
-
174
- if (referenceIds.length === 0) return true;
175
-
176
- const referencedDocuments = await context
177
- .getClient({ apiVersion })
178
- .fetch<ReferencedPageBuilderDocument[]>(
179
- `*[
180
- _id in $publishedIds || _id in $draftIds
181
- ]{
182
- _id,
183
- _type
184
- }`,
185
- {
186
- publishedIds: referenceIds,
187
- draftIds: referenceIds.map((id) => `drafts.${id}`),
188
- },
161
+ const documentTypes = (Array.isArray(value) ? value : [])
162
+ .map((item) => (item as { _type?: string })?._type)
163
+ .filter(
164
+ (type): type is PageBuilderDocumentType =>
165
+ typeof type === "string",
189
166
  );
190
167
 
168
+ if (documentTypes.length === 0) return true;
169
+
191
170
  const invalidDocumentTypes = getInvalidPageBuilderDocumentTypes(
192
171
  pageType,
193
- referencedDocuments.map((entry) => entry._type),
172
+ documentTypes,
194
173
  );
195
174
 
196
175
  if (invalidDocumentTypes.length === 0) return true;
197
176
 
198
- const invalidMemberTitles = invalidDocumentTypes.map(
177
+ const invalidMemberTitles = [...new Set(invalidDocumentTypes)].map(
199
178
  (documentType) =>
200
- getPageBuilderReferenceMemberByDocumentType(documentType)
201
- ?.title ?? documentType,
179
+ getPageBuilderBlockMember(documentType)?.title ?? documentType,
202
180
  );
203
181
 
204
182
  return `These page builder components do not belong to the "${pageType ?? "generic"}" category: ${invalidMemberTitles.join(", ")}`;
@@ -209,10 +187,6 @@ export const page = defineType({
209
187
  title: "Pinned Collection Category",
210
188
  type: "reference",
211
189
  to: [{ type: "blogCategory" }],
212
- // Lives on the page because the collection block is deliberately a
213
- // singleton (see `singletonComponents`) — editors reuse one block rather
214
- // than creating them, so a category set there would apply site-wide. Move
215
- // the block to the reusable list if you'd rather scope it per instance.
216
190
  description:
217
191
  "Turns this page into a category page: the content collection shows only this category. Leave empty to show everything.",
218
192
  }),
@@ -2,22 +2,10 @@ import {
2
2
  companyDataIcon,
3
3
  footerIcon,
4
4
  navbarIcon,
5
- pageListIcon,
6
5
  spacingSettingsIcon,
7
6
  structuredDataIcon,
8
7
  } from "./icons";
9
8
 
10
- // `category` must be one of `componentCategories` in page-builder-config.ts.
11
- export const singletonComponents = [
12
- {
13
- schemaType: "blogCollection",
14
- documentId: "blogCollection",
15
- title: "Blog Collection",
16
- icon: pageListIcon,
17
- category: "list",
18
- },
19
- ] as const;
20
-
21
9
  export const singletonLayout = [
22
10
  {
23
11
  schemaType: "companyData",
@@ -51,10 +39,6 @@ export const singletonLayout = [
51
39
  },
52
40
  ] as const;
53
41
 
54
- export const singletonComponentTypes: Set<string> = new Set(
55
- singletonComponents.map((item) => item.schemaType),
56
- );
57
-
58
42
  export const singletonLayoutTypes: Set<string> = new Set(
59
43
  singletonLayout.map((item) => item.schemaType),
60
44
  );
@@ -6,18 +6,20 @@ import type {
6
6
  StructureResolver,
7
7
  StructureResolverContext,
8
8
  } from "sanity/structure";
9
+ import {
10
+ COMPONENT_LIBRARY,
11
+ getComponentPreviewPath,
12
+ } from "@/components/page-builder/library";
13
+ import { pageBuilderInsertMenuGroups } from "@/lib/integrations/sanity/page-builder-config";
14
+ import { ComponentPreviewPane } from "./components/component-preview-pane";
9
15
  import { apiVersion } from "./env";
10
16
  import {
11
17
  authorIcon,
12
18
  blogCategoryIcon,
19
+ componentIcon,
13
20
  componentsFolderIcon,
14
21
  contentFolderIcon,
15
- contentIcon,
16
- descriptionIcon,
17
- gatedContentIcon,
18
- heroIcon,
19
22
  layoutFolderIcon,
20
- pageBuilderCardIcon,
21
23
  pageFolderIcon,
22
24
  pageIcon,
23
25
  pagesFolderIcon,
@@ -25,7 +27,7 @@ import {
25
27
  } from "./icons";
26
28
  import { PageTreePane } from "./page-tree-pane";
27
29
  import { PINNED_PAGE_ENTRIES } from "./pinned-pages";
28
- import { singletonComponents, singletonLayout } from "./singletons";
30
+ import { singletonLayout } from "./singletons";
29
31
 
30
32
  const PAGE_TREE_ITEM_ID_PREFIX = {
31
33
  page: "page-tree-",
@@ -271,7 +273,7 @@ export const structure: StructureResolver = (S, context) =>
271
273
 
272
274
  S.divider(),
273
275
 
274
- // GROUP 3: Components -- singleton and reusable components
276
+ // GROUP 3: Components -- live block previews, grouped like the insert menu
275
277
  S.listItem()
276
278
  .title("Components")
277
279
  .icon(componentsFolderIcon)
@@ -279,33 +281,33 @@ export const structure: StructureResolver = (S, context) =>
279
281
  S.list()
280
282
  .title("Components")
281
283
  .items([
282
- S.divider().title("Singleton Components"),
283
- ...singletonComponents.map((item) =>
284
- S.listItem()
285
- .title(item.title)
286
- .id(item.documentId)
287
- .icon(item.icon)
288
- .child(
289
- S.document()
290
- .schemaType(item.schemaType)
291
- .documentId(item.documentId),
284
+ ...pageBuilderInsertMenuGroups.flatMap((group) => {
285
+ const entries = COMPONENT_LIBRARY.filter((entry) =>
286
+ group.of.includes(entry.documentType),
287
+ );
288
+
289
+ if (entries.length === 0) return [];
290
+
291
+ return [
292
+ S.divider().title(group.title),
293
+ ...entries.map((entry) =>
294
+ S.listItem()
295
+ .title(entry.title)
296
+ .id(entry.documentType)
297
+ .icon(componentIcon)
298
+ .child(
299
+ S.component()
300
+ .id(`component-${entry.documentType}`)
301
+ .title(entry.title)
302
+ .component(ComponentPreviewPane)
303
+ .options({
304
+ notes: entry.notes,
305
+ path: getComponentPreviewPath(entry.documentType),
306
+ }),
307
+ ),
292
308
  ),
293
- ),
294
- S.divider().title("Reusable Components"),
295
- S.documentTypeListItem("hero").title("Headings").icon(heroIcon),
296
- S.documentTypeListItem("description")
297
- .title("Descriptions")
298
- .icon(descriptionIcon),
299
- S.documentTypeListItem("gatedContent")
300
- .title("Gated Content")
301
- .icon(gatedContentIcon),
302
- S.documentTypeListItem("blogContent")
303
- .title("Blog Content")
304
- .icon(contentIcon),
305
- S.divider(),
306
- S.documentTypeListItem("studioPreview")
307
- .title("Component Previews")
308
- .icon(pageBuilderCardIcon),
309
+ ];
310
+ }),
309
311
  ]),
310
312
  ),
311
313
 
@@ -0,0 +1,22 @@
1
+ import { headers } from "next/headers";
2
+
3
+ const STUDIO_PATH = "/studio";
4
+
5
+ const isStudioReferer = (referer: string | null) => {
6
+ if (!referer) return false;
7
+
8
+ try {
9
+ return new URL(referer).pathname.startsWith(STUDIO_PATH);
10
+ } catch {
11
+ return false;
12
+ }
13
+ };
14
+
15
+ export const isStudioEmbed = async () => {
16
+ const headerList = await headers();
17
+
18
+ return (
19
+ headerList.get("sec-fetch-dest") === "iframe" ||
20
+ isStudioReferer(headerList.get("referer"))
21
+ );
22
+ };
@@ -7,9 +7,10 @@ import {
7
7
  import { hubspotFormField } from "./form-field"
8
8
 
9
9
  /**
10
- * Sample reusable HubSpot form block. Register it in your schema's document
11
- * list (and page-builder config, if you use one), then render its `form` with
12
- * <HubSpotForm /> from the integration core:
10
+ * Sample HubSpot form block. Register it in your schema's type list, then render
11
+ * its `form` with <HubSpotForm /> from the integration core. In a page-builder
12
+ * project re-declare it through `defineBlock` so it becomes an inline block —
13
+ * the `hubspot-pagebuilder-block` skill walks through it:
13
14
  *
14
15
  * @example
15
16
  * import { HubSpotForm } from "@/lib/integrations/hubspot/form"
@@ -51,10 +52,3 @@ export const hubspotFormBlock = defineType({
51
52
  },
52
53
  },
53
54
  })
54
-
55
- export const hubspotFormBlockReference = defineType({
56
- name: "hubspotFormBlockReference",
57
- title: "HubSpot Form Block",
58
- type: "reference",
59
- to: [{ type: "hubspotFormBlock" }],
60
- })