bsmnt 0.12.0 → 0.13.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/application/add-integration/index.d.ts.map +1 -1
- package/dist/application/add-integration/index.js +23 -0
- package/dist/application/add-integration/index.js.map +1 -1
- package/dist/domain/consent.d.ts +17 -0
- package/dist/domain/consent.d.ts.map +1 -0
- package/dist/domain/consent.js +13 -0
- package/dist/domain/consent.js.map +1 -0
- package/dist/domain/integration.d.ts +19 -2
- package/dist/domain/integration.d.ts.map +1 -1
- package/dist/domain/integration.js +10 -1
- package/dist/domain/integration.js.map +1 -1
- package/dist/domain/jobs.d.ts +17 -0
- package/dist/domain/jobs.d.ts.map +1 -0
- package/dist/domain/jobs.js +13 -0
- package/dist/domain/jobs.js.map +1 -0
- package/dist/domain/skills.d.ts +23 -2
- package/dist/domain/skills.d.ts.map +1 -1
- package/dist/domain/skills.js +23 -2
- package/dist/domain/skills.js.map +1 -1
- package/dist/domain/templates.d.ts +7 -1
- package/dist/domain/templates.d.ts.map +1 -1
- package/dist/domain/templates.js +2 -0
- package/dist/domain/templates.js.map +1 -1
- package/dist/index.js +34 -9
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/create/setup-agent.d.ts.map +1 -1
- package/dist/infrastructure/create/setup-agent.js +11 -4
- package/dist/infrastructure/create/setup-agent.js.map +1 -1
- package/dist/infrastructure/integrations/inject-integration.d.ts.map +1 -1
- package/dist/infrastructure/integrations/inject-integration.js +36 -2
- package/dist/infrastructure/integrations/inject-integration.js.map +1 -1
- package/dist/modules/features/consent/ketch/config.d.ts +18 -0
- package/dist/modules/features/consent/ketch/config.d.ts.map +1 -0
- package/dist/modules/features/consent/ketch/config.js +24 -0
- package/dist/modules/features/consent/ketch/config.js.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/biome.d.ts +16 -0
- package/dist/modules/features/consent/ketch/mergers/biome.d.ts.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/biome.js +31 -0
- package/dist/modules/features/consent/ketch/mergers/biome.js.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/check-integration.d.ts +13 -0
- package/dist/modules/features/consent/ketch/mergers/check-integration.d.ts.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/check-integration.js +64 -0
- package/dist/modules/features/consent/ketch/mergers/check-integration.js.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/env-example.d.ts +12 -0
- package/dist/modules/features/consent/ketch/mergers/env-example.d.ts.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/env-example.js +28 -0
- package/dist/modules/features/consent/ketch/mergers/env-example.js.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/footer.d.ts +17 -0
- package/dist/modules/features/consent/ketch/mergers/footer.d.ts.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/footer.js +40 -0
- package/dist/modules/features/consent/ketch/mergers/footer.js.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/knip.d.ts +18 -0
- package/dist/modules/features/consent/ketch/mergers/knip.d.ts.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/knip.js +37 -0
- package/dist/modules/features/consent/ketch/mergers/knip.js.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/layout.d.ts +25 -0
- package/dist/modules/features/consent/ketch/mergers/layout.d.ts.map +1 -0
- package/dist/modules/features/consent/ketch/mergers/layout.js +55 -0
- package/dist/modules/features/consent/ketch/mergers/layout.js.map +1 -0
- package/dist/modules/features/jobs/ashby/config.d.ts +19 -0
- package/dist/modules/features/jobs/ashby/config.d.ts.map +1 -0
- package/dist/modules/features/jobs/ashby/config.js +29 -0
- package/dist/modules/features/jobs/ashby/config.js.map +1 -0
- package/dist/modules/features/jobs/ashby/mergers/check-integration.d.ts +14 -0
- package/dist/modules/features/jobs/ashby/mergers/check-integration.d.ts.map +1 -0
- package/dist/modules/features/jobs/ashby/mergers/check-integration.js +78 -0
- package/dist/modules/features/jobs/ashby/mergers/check-integration.js.map +1 -0
- package/dist/modules/features/jobs/ashby/mergers/env-example.d.ts +12 -0
- package/dist/modules/features/jobs/ashby/mergers/env-example.d.ts.map +1 -0
- package/dist/modules/features/jobs/ashby/mergers/env-example.js +27 -0
- package/dist/modules/features/jobs/ashby/mergers/env-example.js.map +1 -0
- package/dist/modules/features/jobs/ashby/mergers/knip.d.ts +18 -0
- package/dist/modules/features/jobs/ashby/mergers/knip.d.ts.map +1 -0
- package/dist/modules/features/jobs/ashby/mergers/knip.js +41 -0
- package/dist/modules/features/jobs/ashby/mergers/knip.js.map +1 -0
- package/dist/paths.d.ts +12 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +12 -0
- package/dist/paths.js.map +1 -1
- package/package.json +3 -1
- package/src/agent-skills/ashby-pagebuilder-block/SKILL.md +277 -0
- package/src/agent-skills/hubspot-pagebuilder-block/SKILL.md +58 -25
- package/src/agent-skills/ketch-consent-check/SKILL.md +197 -0
- package/src/agent-skills/ketch-consent-setup/SKILL.md +326 -0
- package/src/agent-skills/ketch-consent-setup/references/ui-customization.md +329 -0
- package/src/agent-skills/page-builder-block/SKILL.md +150 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/anchor-id-input.tsx +17 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/presentation-input.tsx +146 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/spacing-steps-input.tsx +84 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/fetchers/layout.ts +6 -3
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/icons.ts +8 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/queries.ts +12 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/description.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/gated-content.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/hero.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/reusable/rich-text-block.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +8 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/layout/spacing-settings.ts +120 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/anchor.ts +75 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/block.ts +46 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/presentation.ts +44 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/shared/spacing.ts +164 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/singletons.ts +7 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/anchor-id-input.tsx +17 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/presentation-input.tsx +146 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/spacing-steps-input.tsx +84 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/fetchers/layout.ts +4 -2
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/icons.ts +8 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/queries.ts +12 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/blog-content.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/description.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/gated-content.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/hero.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/reusable/rich-text-block.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/singleton/content-collection.ts +3 -3
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/index.ts +8 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/layout/spacing-settings.ts +120 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/anchor.ts +75 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/block.ts +46 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/presentation.ts +44 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/shared/spacing.ts +164 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/singletons.ts +7 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/tags.ts +2 -0
- package/src/modules/features/consent/ketch/files/lib/integrations/check-integration.ts +25 -0
- package/src/modules/features/consent/ketch/files/lib/integrations/ketch/README.md +70 -0
- package/src/modules/features/consent/ketch/files/lib/integrations/ketch/categories.ts +20 -0
- package/src/modules/features/consent/ketch/files/lib/integrations/ketch/consent-store.ts +107 -0
- package/src/modules/features/consent/ketch/files/lib/integrations/ketch/env.ts +20 -0
- package/src/modules/features/consent/ketch/files/lib/integrations/ketch/ketch-experience.css +136 -0
- package/src/modules/features/consent/ketch/files/lib/integrations/ketch/preferences-button.tsx +51 -0
- package/src/modules/features/consent/ketch/files/lib/integrations/ketch/script.tsx +8 -0
- package/src/modules/features/consent/ketch/files/lib/integrations/ketch/types.ts +15 -0
- package/src/modules/features/consent/ketch/files/lib/integrations/ketch/use-consent.ts +26 -0
- package/src/modules/features/jobs/ashby/files/_sanity-adapter/job-board-block.ts +53 -0
- package/src/modules/features/jobs/ashby/files/lib/integrations/ashby/README.md +134 -0
- package/src/modules/features/jobs/ashby/files/lib/integrations/ashby/env.ts +3 -0
- package/src/modules/features/jobs/ashby/files/lib/integrations/ashby/index.ts +40 -0
- package/src/modules/features/jobs/ashby/files/lib/integrations/ashby/normalize.ts +124 -0
- package/src/modules/features/jobs/ashby/files/lib/integrations/ashby/types.ts +20 -0
- package/src/modules/features/jobs/ashby/files/lib/integrations/check-integration.ts +25 -0
- package/src/templates/next-pagebuilder/AGENTS.md +129 -0
- package/src/templates/next-pagebuilder/CLAUDE.md +3 -0
- package/src/templates/next-pagebuilder/README.md +70 -0
- package/src/templates/next-pagebuilder/app/(content)/layout.tsx +2 -0
- package/src/templates/next-pagebuilder/components/layout/spacing-scale/index.tsx +18 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/article-content/index.tsx +1 -1
- package/src/templates/next-pagebuilder/components/page-builder/components/content-collection/index.tsx +2 -2
- package/src/templates/next-pagebuilder/components/page-builder/components/description.tsx +3 -7
- package/src/templates/next-pagebuilder/components/page-builder/components/gated-content/index.tsx +2 -2
- package/src/templates/next-pagebuilder/components/page-builder/components/hero.tsx +3 -7
- package/src/templates/next-pagebuilder/components/page-builder/components/rich-text-block.tsx +3 -5
- package/src/templates/next-pagebuilder/components/page-builder/registry.tsx +70 -0
- package/src/templates/next-pagebuilder/components/page-builder/renderer.tsx +33 -49
- package/src/templates/next-pagebuilder/components/page-builder/section.tsx +35 -0
- package/src/templates/next-pagebuilder/components/page-document/index.tsx +1 -1
- package/src/templates/next-pagebuilder/lib/integrations/sanity/sanity.types.ts +92 -4
- package/src/templates/next-pagebuilder-cache/AGENTS.md +136 -0
- package/src/templates/next-pagebuilder-cache/CLAUDE.md +3 -0
- package/src/templates/next-pagebuilder-cache/README.md +75 -0
- package/src/templates/next-pagebuilder-cache/app/(content)/layout.tsx +2 -0
- package/src/templates/next-pagebuilder-cache/components/layout/spacing-scale/index.tsx +46 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/article-content/index.tsx +1 -1
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/content-collection/index.tsx +2 -2
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/description.tsx +3 -7
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/gated-content/index.tsx +2 -2
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/hero.tsx +3 -7
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/rich-text-block.tsx +3 -5
- package/src/templates/next-pagebuilder-cache/components/page-builder/registry.tsx +70 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/renderer.tsx +33 -49
- package/src/templates/next-pagebuilder-cache/components/page-builder/section.tsx +35 -0
- package/src/templates/next-pagebuilder-cache/components/page-document/index.tsx +1 -1
- package/src/templates/next-pagebuilder-cache/lib/integrations/sanity/sanity.types.ts +92 -4
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { defineArrayMember, defineField, defineType } from "sanity";
|
|
2
|
+
import { SpacingStepsInput } from "@/lib/integrations/sanity/components/spacing-steps-input";
|
|
3
|
+
import { spacingSettingsIcon } from "@/lib/integrations/sanity/icons";
|
|
4
|
+
import { defaultSpacingSteps } from "@/lib/integrations/sanity/schemas/shared/spacing";
|
|
5
|
+
|
|
6
|
+
const pxField = (
|
|
7
|
+
name: "mobile" | "tablet" | "desktop",
|
|
8
|
+
title: string,
|
|
9
|
+
description: string,
|
|
10
|
+
isRequired = false,
|
|
11
|
+
) =>
|
|
12
|
+
defineField({
|
|
13
|
+
name,
|
|
14
|
+
title,
|
|
15
|
+
type: "number",
|
|
16
|
+
description,
|
|
17
|
+
validation: (Rule) => {
|
|
18
|
+
const range = Rule.min(0).max(400).integer();
|
|
19
|
+
return isRequired ? range.required() : range;
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const spacingStep = defineType({
|
|
24
|
+
name: "spacingStep",
|
|
25
|
+
title: "Spacing step",
|
|
26
|
+
type: "object",
|
|
27
|
+
fields: [
|
|
28
|
+
defineField({
|
|
29
|
+
name: "name",
|
|
30
|
+
title: "Name",
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "Shown in the Presentation Mode dropdown on every block.",
|
|
33
|
+
validation: (Rule) => Rule.required().max(24),
|
|
34
|
+
}),
|
|
35
|
+
pxField("desktop", "Desktop", "From 1440px up. Required.", true),
|
|
36
|
+
pxField("tablet", "Tablet", "From 768px up. Empty reuses Desktop."),
|
|
37
|
+
pxField("mobile", "Mobile", "Below 768px. Empty reuses Desktop."),
|
|
38
|
+
defineField({
|
|
39
|
+
name: "isDefault",
|
|
40
|
+
title: "Use as default",
|
|
41
|
+
type: "boolean",
|
|
42
|
+
description:
|
|
43
|
+
"Blocks that never touch Presentation Mode use this step. Exactly one step can be the default.",
|
|
44
|
+
initialValue: false,
|
|
45
|
+
}),
|
|
46
|
+
],
|
|
47
|
+
preview: {
|
|
48
|
+
select: {
|
|
49
|
+
name: "name",
|
|
50
|
+
mobile: "mobile",
|
|
51
|
+
tablet: "tablet",
|
|
52
|
+
desktop: "desktop",
|
|
53
|
+
isDefault: "isDefault",
|
|
54
|
+
},
|
|
55
|
+
prepare({ name, mobile, tablet, desktop, isDefault }) {
|
|
56
|
+
const px = [mobile ?? desktop, tablet ?? desktop, desktop]
|
|
57
|
+
.map((value) => (typeof value === "number" ? `${value}px` : "—"))
|
|
58
|
+
.join(" / ");
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
title: `${name || "Untitled"}${isDefault ? " · default" : ""}`,
|
|
62
|
+
subtitle: `${px} (mobile / tablet / desktop)`,
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export const spacingSettings = defineType({
|
|
69
|
+
name: "spacingSettings",
|
|
70
|
+
title: "Spacing Scale",
|
|
71
|
+
type: "document",
|
|
72
|
+
icon: spacingSettingsIcon,
|
|
73
|
+
fields: [
|
|
74
|
+
defineField({
|
|
75
|
+
name: "steps",
|
|
76
|
+
title: "Spacing steps",
|
|
77
|
+
description:
|
|
78
|
+
"The vertical spacing every block can pick from. Add as many as you need and mark one as the default. Left empty, the built-in scale is used.",
|
|
79
|
+
type: "array",
|
|
80
|
+
components: { input: SpacingStepsInput },
|
|
81
|
+
of: [defineArrayMember({ type: "spacingStep" })],
|
|
82
|
+
initialValue: defaultSpacingSteps.map((step) => ({
|
|
83
|
+
_key: step.key,
|
|
84
|
+
_type: "spacingStep",
|
|
85
|
+
name: step.name,
|
|
86
|
+
mobile: step.mobile,
|
|
87
|
+
tablet: step.tablet,
|
|
88
|
+
desktop: step.desktop,
|
|
89
|
+
isDefault: step.isDefault,
|
|
90
|
+
})),
|
|
91
|
+
validation: (Rule) =>
|
|
92
|
+
Rule.custom((steps: unknown) => {
|
|
93
|
+
if (!Array.isArray(steps) || steps.length === 0) return true;
|
|
94
|
+
|
|
95
|
+
const defaults = steps.filter(
|
|
96
|
+
(step) => (step as { isDefault?: boolean })?.isDefault === true,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
if (defaults.length === 0) return "Mark one step as the default";
|
|
100
|
+
if (defaults.length > 1) return "Only one step can be the default";
|
|
101
|
+
|
|
102
|
+
const names = steps.map((step) =>
|
|
103
|
+
(step as { name?: string })?.name?.trim().toLowerCase(),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return new Set(names).size === names.length
|
|
107
|
+
? true
|
|
108
|
+
: "Two steps share the same name";
|
|
109
|
+
}),
|
|
110
|
+
}),
|
|
111
|
+
],
|
|
112
|
+
preview: {
|
|
113
|
+
prepare() {
|
|
114
|
+
return {
|
|
115
|
+
title: "Spacing Scale",
|
|
116
|
+
subtitle: "Section padding steps per breakpoint",
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const anchorSourceFields = [
|
|
2
|
+
"headline",
|
|
3
|
+
"heading",
|
|
4
|
+
"teaserHeading",
|
|
5
|
+
"title",
|
|
6
|
+
"label",
|
|
7
|
+
] as const;
|
|
8
|
+
|
|
9
|
+
const ANCHOR_MAX_LENGTH = 60;
|
|
10
|
+
|
|
11
|
+
export const slugifyAnchor = (raw: string) =>
|
|
12
|
+
raw
|
|
13
|
+
.normalize("NFKD")
|
|
14
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
15
|
+
.toLowerCase()
|
|
16
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
17
|
+
.replace(/^-+|-+$/g, "")
|
|
18
|
+
.slice(0, ANCHOR_MAX_LENGTH)
|
|
19
|
+
.replace(/-+$/g, "");
|
|
20
|
+
|
|
21
|
+
export const deriveAnchorId = (block: Record<string, unknown>) => {
|
|
22
|
+
for (const field of anchorSourceFields) {
|
|
23
|
+
const value = block[field];
|
|
24
|
+
if (typeof value === "string" && value.trim()) {
|
|
25
|
+
const slug = slugifyAnchor(value);
|
|
26
|
+
if (slug) return slug;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return "";
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const explicitAnchorId = (block: Record<string, unknown>) =>
|
|
34
|
+
typeof block.anchorId === "string" ? slugifyAnchor(block.anchorId) : "";
|
|
35
|
+
|
|
36
|
+
const NO_RESERVED_IDS: ReadonlySet<string> = new Set();
|
|
37
|
+
|
|
38
|
+
export const reserveAnchorIds = (
|
|
39
|
+
blocks: readonly Record<string, unknown>[],
|
|
40
|
+
): ReadonlySet<string> => {
|
|
41
|
+
const reserved = new Set<string>();
|
|
42
|
+
|
|
43
|
+
for (const block of blocks) {
|
|
44
|
+
const explicit = explicitAnchorId(block);
|
|
45
|
+
if (explicit) reserved.add(explicit);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return reserved;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const resolveAnchorId = (
|
|
52
|
+
block: Record<string, unknown>,
|
|
53
|
+
taken: Set<string>,
|
|
54
|
+
reserved: ReadonlySet<string> = NO_RESERVED_IDS,
|
|
55
|
+
) => {
|
|
56
|
+
const explicit = explicitAnchorId(block);
|
|
57
|
+
const base = explicit || deriveAnchorId(block);
|
|
58
|
+
|
|
59
|
+
if (!base) return "";
|
|
60
|
+
|
|
61
|
+
const isBlocked = (candidate: string) =>
|
|
62
|
+
taken.has(candidate) || (!explicit && reserved.has(candidate));
|
|
63
|
+
|
|
64
|
+
let candidate = base;
|
|
65
|
+
let suffix = 2;
|
|
66
|
+
|
|
67
|
+
while (isBlocked(candidate)) {
|
|
68
|
+
candidate = `${base}-${suffix}`;
|
|
69
|
+
suffix += 1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
taken.add(candidate);
|
|
73
|
+
|
|
74
|
+
return candidate;
|
|
75
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type DocumentDefinition,
|
|
3
|
+
defineType,
|
|
4
|
+
type FieldDefinition,
|
|
5
|
+
type FieldGroupDefinition,
|
|
6
|
+
} from "sanity";
|
|
7
|
+
import { presentationFields } from "./presentation";
|
|
8
|
+
|
|
9
|
+
const presentationGroup: FieldGroupDefinition = {
|
|
10
|
+
name: "presentation",
|
|
11
|
+
title: "Presentation",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type BlockDefinition = Omit<DocumentDefinition, "type" | "fields" | "groups"> & {
|
|
15
|
+
fields: FieldDefinition[];
|
|
16
|
+
groups?: FieldGroupDefinition[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const withPresentationGroup = (groups: FieldGroupDefinition[] | undefined) => {
|
|
20
|
+
if (!groups?.length) return undefined;
|
|
21
|
+
if (groups.some((group) => group.name === presentationGroup.name)) {
|
|
22
|
+
return groups;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return [...groups, presentationGroup];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Defines a page-builder block — `type: "document"` plus Anchor ID and
|
|
30
|
+
* Presentation Mode. Use instead of `defineType`.
|
|
31
|
+
*/
|
|
32
|
+
export const defineBlock = ({ fields, groups, ...rest }: BlockDefinition) => {
|
|
33
|
+
const blockGroups = withPresentationGroup(groups);
|
|
34
|
+
|
|
35
|
+
return defineType({
|
|
36
|
+
...rest,
|
|
37
|
+
type: "document",
|
|
38
|
+
...(blockGroups ? { groups: blockGroups } : {}),
|
|
39
|
+
fields: [
|
|
40
|
+
...fields,
|
|
41
|
+
...presentationFields(
|
|
42
|
+
blockGroups ? { group: presentationGroup.name } : undefined,
|
|
43
|
+
),
|
|
44
|
+
],
|
|
45
|
+
});
|
|
46
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { defineField, defineType } from "sanity";
|
|
2
|
+
import { AnchorIdInput } from "@/lib/integrations/sanity/components/anchor-id-input";
|
|
3
|
+
import { PresentationInput } from "@/lib/integrations/sanity/components/presentation-input";
|
|
4
|
+
|
|
5
|
+
export const presentation = defineType({
|
|
6
|
+
name: "presentation",
|
|
7
|
+
title: "Presentation Mode",
|
|
8
|
+
type: "object",
|
|
9
|
+
components: { input: PresentationInput },
|
|
10
|
+
fields: [
|
|
11
|
+
defineField({
|
|
12
|
+
name: "paddingTop",
|
|
13
|
+
title: "Padding top",
|
|
14
|
+
type: "string",
|
|
15
|
+
}),
|
|
16
|
+
defineField({
|
|
17
|
+
name: "paddingBottom",
|
|
18
|
+
title: "Padding bottom",
|
|
19
|
+
type: "string",
|
|
20
|
+
}),
|
|
21
|
+
],
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const presentationFields = (options?: { group?: string }) => [
|
|
25
|
+
defineField({
|
|
26
|
+
name: "anchorId",
|
|
27
|
+
title: "Anchor ID",
|
|
28
|
+
type: "string",
|
|
29
|
+
...(options?.group ? { group: options.group } : {}),
|
|
30
|
+
components: { input: AnchorIdInput },
|
|
31
|
+
description:
|
|
32
|
+
"Links to this block as /page#your-anchor-id. Leave empty to generate it from the block title.",
|
|
33
|
+
validation: (Rule) =>
|
|
34
|
+
Rule.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, { name: "anchor id" }).error(
|
|
35
|
+
"Use lowercase letters, numbers and dashes only (e.g. how-it-works)",
|
|
36
|
+
),
|
|
37
|
+
}),
|
|
38
|
+
defineField({
|
|
39
|
+
name: "presentation",
|
|
40
|
+
title: "Presentation Mode",
|
|
41
|
+
type: "presentation",
|
|
42
|
+
...(options?.group ? { group: options.group } : {}),
|
|
43
|
+
}),
|
|
44
|
+
];
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
export const spacingBreakpoints = ["mobile", "tablet", "desktop"] as const;
|
|
2
|
+
|
|
3
|
+
export type SpacingBreakpoint = (typeof spacingBreakpoints)[number];
|
|
4
|
+
|
|
5
|
+
export const spacingBreakpointMinWidth: Record<SpacingBreakpoint, number> = {
|
|
6
|
+
mobile: 0,
|
|
7
|
+
tablet: 768,
|
|
8
|
+
desktop: 1440,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type SpacingStep = {
|
|
12
|
+
key: string;
|
|
13
|
+
name: string;
|
|
14
|
+
isDefault: boolean;
|
|
15
|
+
} & Record<SpacingBreakpoint, number>;
|
|
16
|
+
|
|
17
|
+
const fallbackSpacingStep: SpacingStep = {
|
|
18
|
+
key: "md",
|
|
19
|
+
name: "MD",
|
|
20
|
+
mobile: 24,
|
|
21
|
+
tablet: 28,
|
|
22
|
+
desktop: 32,
|
|
23
|
+
isDefault: true,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const defaultSpacingSteps: SpacingStep[] = [
|
|
27
|
+
{
|
|
28
|
+
key: "xs",
|
|
29
|
+
name: "XS",
|
|
30
|
+
mobile: 12,
|
|
31
|
+
tablet: 14,
|
|
32
|
+
desktop: 16,
|
|
33
|
+
isDefault: false,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
key: "sm",
|
|
37
|
+
name: "SM",
|
|
38
|
+
mobile: 16,
|
|
39
|
+
tablet: 20,
|
|
40
|
+
desktop: 24,
|
|
41
|
+
isDefault: false,
|
|
42
|
+
},
|
|
43
|
+
fallbackSpacingStep,
|
|
44
|
+
{
|
|
45
|
+
key: "lg",
|
|
46
|
+
name: "LG",
|
|
47
|
+
mobile: 32,
|
|
48
|
+
tablet: 40,
|
|
49
|
+
desktop: 48,
|
|
50
|
+
isDefault: false,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
key: "xl",
|
|
54
|
+
name: "XL",
|
|
55
|
+
mobile: 48,
|
|
56
|
+
tablet: 64,
|
|
57
|
+
desktop: 80,
|
|
58
|
+
isDefault: false,
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
export type SpacingStepSource = {
|
|
63
|
+
_key?: string | null;
|
|
64
|
+
name?: string | null;
|
|
65
|
+
mobile?: number | null;
|
|
66
|
+
tablet?: number | null;
|
|
67
|
+
desktop?: number | null;
|
|
68
|
+
isDefault?: boolean | null;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const spacingStepKey = (raw: string) =>
|
|
72
|
+
raw
|
|
73
|
+
.toLowerCase()
|
|
74
|
+
.replace(/[^a-z0-9-]/g, "")
|
|
75
|
+
.slice(0, 40);
|
|
76
|
+
|
|
77
|
+
export const spacingVariable = (key: string) => `--section-space-${key}`;
|
|
78
|
+
|
|
79
|
+
export const defaultSpacingVariable = spacingVariable("default");
|
|
80
|
+
|
|
81
|
+
const FALLBACK_PADDING = "2.5rem";
|
|
82
|
+
|
|
83
|
+
const readPx = (value: number | null | undefined) =>
|
|
84
|
+
typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
85
|
+
? Math.round(value)
|
|
86
|
+
: null;
|
|
87
|
+
|
|
88
|
+
export const resolveSpacingSteps = (
|
|
89
|
+
source?: readonly SpacingStepSource[] | null,
|
|
90
|
+
): SpacingStep[] => {
|
|
91
|
+
const steps = (source ?? []).flatMap<SpacingStep>((entry) => {
|
|
92
|
+
const key = spacingStepKey(entry?._key ?? "");
|
|
93
|
+
const name = entry?.name?.trim();
|
|
94
|
+
const desktop = readPx(entry?.desktop);
|
|
95
|
+
|
|
96
|
+
if (!(key && name && desktop !== null)) return [];
|
|
97
|
+
|
|
98
|
+
return [
|
|
99
|
+
{
|
|
100
|
+
key,
|
|
101
|
+
name,
|
|
102
|
+
desktop,
|
|
103
|
+
tablet: readPx(entry?.tablet) ?? desktop,
|
|
104
|
+
mobile: readPx(entry?.mobile) ?? desktop,
|
|
105
|
+
isDefault: entry?.isDefault === true,
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (steps.length === 0) return defaultSpacingSteps;
|
|
111
|
+
|
|
112
|
+
if (steps.some((step) => step.isDefault)) {
|
|
113
|
+
let seen = false;
|
|
114
|
+
return steps.map((step) => {
|
|
115
|
+
const isDefault = step.isDefault && !seen;
|
|
116
|
+
if (isDefault) seen = true;
|
|
117
|
+
return { ...step, isDefault };
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const middle = Math.floor((steps.length - 1) / 2);
|
|
122
|
+
return steps.map((step, index) => ({ ...step, isDefault: index === middle }));
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export const getDefaultSpacingStep = (steps: SpacingStep[]): SpacingStep =>
|
|
126
|
+
steps.find((step) => step.isDefault) ?? steps[0] ?? fallbackSpacingStep;
|
|
127
|
+
|
|
128
|
+
export const spacingPaddingValue = (key?: string | null) => {
|
|
129
|
+
const safe = key ? spacingStepKey(key) : "";
|
|
130
|
+
const fallback = `var(${defaultSpacingVariable}, ${FALLBACK_PADDING})`;
|
|
131
|
+
|
|
132
|
+
if (!safe || safe === "default") return fallback;
|
|
133
|
+
|
|
134
|
+
return `var(${spacingVariable(safe)}, ${fallback})`;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export const spacingScaleCss = (steps: SpacingStep[]) => {
|
|
138
|
+
const defaultStep = getDefaultSpacingStep(steps);
|
|
139
|
+
|
|
140
|
+
const rule = (breakpoint: SpacingBreakpoint) => {
|
|
141
|
+
const declarations = [
|
|
142
|
+
...steps.map(
|
|
143
|
+
(step) => `${spacingVariable(step.key)}:${step[breakpoint]}px`,
|
|
144
|
+
),
|
|
145
|
+
`${defaultSpacingVariable}:${defaultStep[breakpoint]}px`,
|
|
146
|
+
].join(";");
|
|
147
|
+
|
|
148
|
+
return `:root{${declarations}}`;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return [
|
|
152
|
+
rule("mobile"),
|
|
153
|
+
`@media (min-width:${spacingBreakpointMinWidth.tablet}px){${rule("tablet")}}`,
|
|
154
|
+
`@media (min-width:${spacingBreakpointMinWidth.desktop}px){${rule("desktop")}}`,
|
|
155
|
+
].join("");
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export const spacingStepOptions = (steps: SpacingStep[]) =>
|
|
159
|
+
steps.map((step) => ({
|
|
160
|
+
value: step.key,
|
|
161
|
+
title: `${step.name} (${step.desktop}px · ${step.mobile}px mobile)${
|
|
162
|
+
step.isDefault ? " · default" : ""
|
|
163
|
+
}`,
|
|
164
|
+
}));
|
package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/singletons.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
footerIcon,
|
|
4
4
|
navbarIcon,
|
|
5
5
|
pageListIcon,
|
|
6
|
+
spacingSettingsIcon,
|
|
6
7
|
structuredDataIcon,
|
|
7
8
|
} from "./icons";
|
|
8
9
|
|
|
@@ -42,6 +43,12 @@ export const singletonLayout = [
|
|
|
42
43
|
title: "Structured Data",
|
|
43
44
|
icon: structuredDataIcon,
|
|
44
45
|
},
|
|
46
|
+
{
|
|
47
|
+
schemaType: "spacingSettings",
|
|
48
|
+
documentId: "spacingSettings",
|
|
49
|
+
title: "Spacing Scale",
|
|
50
|
+
icon: spacingSettingsIcon,
|
|
51
|
+
},
|
|
45
52
|
] as const;
|
|
46
53
|
|
|
47
54
|
export const singletonComponentTypes: Set<string> = new Set(
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type StringInputProps, useFormValue } from "sanity";
|
|
2
|
+
import { deriveAnchorId } from "@/lib/integrations/sanity/schemas/shared/anchor";
|
|
3
|
+
|
|
4
|
+
export function AnchorIdInput(props: StringInputProps) {
|
|
5
|
+
const document = useFormValue([]) as Record<string, unknown> | undefined;
|
|
6
|
+
const derived = deriveAnchorId(document ?? {});
|
|
7
|
+
|
|
8
|
+
return props.renderDefault({
|
|
9
|
+
...props,
|
|
10
|
+
elementProps: {
|
|
11
|
+
...props.elementProps,
|
|
12
|
+
placeholder: derived
|
|
13
|
+
? `${derived} — generated from the title`
|
|
14
|
+
: "generated from the title",
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { Box, Card, Flex, Select, Stack, Text } from "@sanity/ui";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { type ObjectInputProps, set, setIfMissing, useClient } from "sanity";
|
|
4
|
+
import { apiVersion } from "@/lib/integrations/sanity/env";
|
|
5
|
+
import {
|
|
6
|
+
paddingBottomIcon,
|
|
7
|
+
paddingTopIcon,
|
|
8
|
+
type SanityStudioIcon,
|
|
9
|
+
} from "@/lib/integrations/sanity/icons";
|
|
10
|
+
import {
|
|
11
|
+
getDefaultSpacingStep,
|
|
12
|
+
resolveSpacingSteps,
|
|
13
|
+
type SpacingStep,
|
|
14
|
+
type SpacingStepSource,
|
|
15
|
+
spacingStepOptions,
|
|
16
|
+
} from "@/lib/integrations/sanity/schemas/shared/spacing";
|
|
17
|
+
|
|
18
|
+
const SPACING_STEPS_QUERY = `coalesce(
|
|
19
|
+
*[_id == "drafts.spacingSettings"][0],
|
|
20
|
+
*[_id == "spacingSettings"][0]
|
|
21
|
+
).steps[]{ _key, name, mobile, tablet, desktop, isDefault }`;
|
|
22
|
+
|
|
23
|
+
type PaddingField = "paddingTop" | "paddingBottom";
|
|
24
|
+
|
|
25
|
+
type PresentationValue = Partial<Record<PaddingField, string>>;
|
|
26
|
+
|
|
27
|
+
function useSpacingSteps() {
|
|
28
|
+
const client = useClient({ apiVersion });
|
|
29
|
+
const [steps, setSteps] = useState<SpacingStep[]>(() =>
|
|
30
|
+
resolveSpacingSteps(null),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
let active = true;
|
|
35
|
+
client
|
|
36
|
+
.fetch<SpacingStepSource[] | null>(SPACING_STEPS_QUERY)
|
|
37
|
+
.then((rows) => {
|
|
38
|
+
if (active) setSteps(resolveSpacingSteps(rows));
|
|
39
|
+
})
|
|
40
|
+
.catch(() => setSteps(resolveSpacingSteps(null)));
|
|
41
|
+
return () => {
|
|
42
|
+
active = false;
|
|
43
|
+
};
|
|
44
|
+
}, [client]);
|
|
45
|
+
|
|
46
|
+
return steps;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type SpacingRowProps = {
|
|
50
|
+
name: PaddingField;
|
|
51
|
+
label: string;
|
|
52
|
+
icon: SanityStudioIcon;
|
|
53
|
+
options: Array<{ value: string; title: string }>;
|
|
54
|
+
value: string;
|
|
55
|
+
onSelect: (name: PaddingField, next: string) => void;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function SpacingRow({
|
|
59
|
+
name,
|
|
60
|
+
label,
|
|
61
|
+
icon,
|
|
62
|
+
options,
|
|
63
|
+
value,
|
|
64
|
+
onSelect,
|
|
65
|
+
}: SpacingRowProps) {
|
|
66
|
+
const Icon = icon;
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<Flex align="center" gap={2}>
|
|
70
|
+
<Card
|
|
71
|
+
padding={2}
|
|
72
|
+
radius={2}
|
|
73
|
+
tone="transparent"
|
|
74
|
+
border
|
|
75
|
+
title={label}
|
|
76
|
+
aria-hidden
|
|
77
|
+
>
|
|
78
|
+
<Text size={2}>
|
|
79
|
+
<Icon />
|
|
80
|
+
</Text>
|
|
81
|
+
</Card>
|
|
82
|
+
<Box flex={1}>
|
|
83
|
+
<Select
|
|
84
|
+
aria-label={label}
|
|
85
|
+
fontSize={1}
|
|
86
|
+
padding={3}
|
|
87
|
+
radius={2}
|
|
88
|
+
value={value}
|
|
89
|
+
onChange={(event) => onSelect(name, event.currentTarget.value)}
|
|
90
|
+
>
|
|
91
|
+
{options.map((option) => (
|
|
92
|
+
<option key={option.value} value={option.value}>
|
|
93
|
+
{option.title}
|
|
94
|
+
</option>
|
|
95
|
+
))}
|
|
96
|
+
</Select>
|
|
97
|
+
</Box>
|
|
98
|
+
</Flex>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function PresentationInput(props: ObjectInputProps) {
|
|
103
|
+
const value = props.value as PresentationValue | undefined;
|
|
104
|
+
const steps = useSpacingSteps();
|
|
105
|
+
const options = spacingStepOptions(steps);
|
|
106
|
+
const fallback = getDefaultSpacingStep(steps).key;
|
|
107
|
+
|
|
108
|
+
const resolve = (key: string | undefined) =>
|
|
109
|
+
steps.some((step) => step.key === key) ? (key as string) : fallback;
|
|
110
|
+
|
|
111
|
+
const handleSelect = (name: PaddingField, next: string) => {
|
|
112
|
+
props.onChange([setIfMissing({}), set(next, [name])]);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<Stack space={3}>
|
|
117
|
+
<Flex gap={3} wrap="wrap">
|
|
118
|
+
<Box flex={1} style={{ minWidth: 200 }}>
|
|
119
|
+
<SpacingRow
|
|
120
|
+
name="paddingTop"
|
|
121
|
+
label="Padding top"
|
|
122
|
+
icon={paddingTopIcon}
|
|
123
|
+
options={options}
|
|
124
|
+
value={resolve(value?.paddingTop)}
|
|
125
|
+
onSelect={handleSelect}
|
|
126
|
+
/>
|
|
127
|
+
</Box>
|
|
128
|
+
<Box flex={1} style={{ minWidth: 200 }}>
|
|
129
|
+
<SpacingRow
|
|
130
|
+
name="paddingBottom"
|
|
131
|
+
label="Padding bottom"
|
|
132
|
+
icon={paddingBottomIcon}
|
|
133
|
+
options={options}
|
|
134
|
+
value={resolve(value?.paddingBottom)}
|
|
135
|
+
onSelect={handleSelect}
|
|
136
|
+
/>
|
|
137
|
+
</Box>
|
|
138
|
+
</Flex>
|
|
139
|
+
<Text size={1} muted>
|
|
140
|
+
Spacing above and below this block, from the shared scale (edit the steps
|
|
141
|
+
in Layout → Spacing Scale). Untouched blocks follow whichever step is
|
|
142
|
+
marked as the default.
|
|
143
|
+
</Text>
|
|
144
|
+
</Stack>
|
|
145
|
+
);
|
|
146
|
+
}
|