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