@riverbankcms/sdk 0.79.0 → 0.80.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/PreviewEditorSidebar-QZG37QIF.mjs +2655 -0
- package/dist/{PreviewEditorUI-27KJVEET.mjs → PreviewEditorUI-AXIKSMZA.mjs} +27 -4
- package/dist/SdkPreviewModeRuntime-XE5M4UKM.mjs +8 -0
- package/dist/_dts/ai/src/contracts/proposals.d.ts +20 -0
- package/dist/_dts/api/src/endpoints.d.ts +24 -0
- package/dist/_dts/api/src/index.d.ts +1 -1
- package/dist/_dts/api/src/siteInfrastructure.d.ts +124 -0
- package/dist/_dts/api/src/siteOnboarding.d.ts +13 -0
- package/dist/_dts/api/src/sitePlatformEndpoints.d.ts +10 -1
- package/dist/_dts/block-form/src/client/index.d.ts +2 -0
- package/dist/_dts/block-form/src/index.d.ts +2 -0
- package/dist/_dts/block-form/src/utils/evaluateVisibility.d.ts +2 -1
- package/dist/_dts/blocks/src/client/header.d.ts +1 -1
- package/dist/_dts/blocks/src/client.d.ts +12 -12
- package/dist/_dts/blocks/src/index.d.ts +1 -1
- package/dist/_dts/blocks/src/system/blocks/events/event-listing.d.ts +2 -2
- package/dist/_dts/blocks/src/system/blocks/events/event-spotlight.d.ts +2 -1
- package/dist/_dts/blocks/src/system/blocks/events/shared/types.d.ts +4 -1
- package/dist/_dts/blocks/src/system/blocks/testimonials.d.ts +7 -1
- package/dist/_dts/blocks/src/system/customize/backgrounds.d.ts +21 -12
- package/dist/_dts/blocks/src/system/fields/boxStyles.d.ts +4 -2
- package/dist/_dts/blocks/src/system/fragments/library/faqHeading.d.ts +1 -0
- package/dist/_dts/blocks/src/system/fragments/library/testimonialsCarousel.d.ts +5 -0
- package/dist/_dts/blocks/src/system/node/fragments/headingDecoration.d.ts +1 -4
- package/dist/_dts/blocks/src/system/node/validation-constants.d.ts +1 -1
- package/dist/_dts/blocks/src/system/runtime/components/registry.client.d.ts +7 -7
- package/dist/_dts/blocks/src/system/runtime/components/runtime-backed-blocks.d.ts +3 -3
- package/dist/_dts/blocks/src/system/runtime/header/enhanceHeaderSection.d.ts +10 -2
- package/dist/_dts/blocks/src/system/runtime/nodes/events/EventListing.interactive.d.ts +5 -5
- package/dist/_dts/blocks/src/system/runtime/nodes/events/EventListing.server.d.ts +2 -2
- package/dist/_dts/blocks/src/system/runtime/nodes/events/EventSpotlight.d.ts +2 -2
- package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/EventCard.d.ts +2 -2
- package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/EventDetailModal.d.ts +2 -2
- package/dist/_dts/blocks/src/system/runtime/nodes/events/shared/EventModals.d.ts +2 -2
- package/dist/_dts/blocks/src/system/runtime/nodes/header-section.client.d.ts +2 -2
- package/dist/_dts/blocks/src/system/transforms/registry/boxStyles.d.ts +3 -0
- package/dist/_dts/blocks/src/system/transforms/registry/layout.d.ts +9 -0
- package/dist/_dts/blocks/src/system/transforms/typed.d.ts +14 -1
- package/dist/_dts/db/src/generated/supabase/database.types.d.ts +9 -3
- package/dist/_dts/editor-blocks/src/block-editor/BlockCard.d.ts +2 -6
- package/dist/_dts/editor-blocks/src/block-editor/BlockCardBody.d.ts +2 -6
- package/dist/_dts/editor-blocks/src/block-editor/BlockCardHeader.d.ts +2 -6
- package/dist/_dts/editor-blocks/src/block-editor/FeatureCardIconsPanel.d.ts +2 -6
- package/dist/_dts/editor-blocks/src/block-editor/GeneratedIconSetDialog.d.ts +6 -4
- package/dist/_dts/editor-blocks/src/block-editor/SortableBlockItem.d.ts +2 -6
- package/dist/_dts/editor-blocks/src/index.d.ts +2 -0
- package/dist/_dts/preview-next/src/client/blocks/BlockToolbar.d.ts +5 -1
- package/dist/_dts/preview-next/src/client/blocks/previewCustomizeControlPlan.d.ts +24 -0
- package/dist/_dts/preview-next/src/client/blocks/useBlockCustomizeControls.d.ts +34 -0
- package/dist/_dts/preview-next/src/client/preview/PageBlockEditorPanel.d.ts +2 -5
- package/dist/_dts/preview-next/src/client/preview/PageDesignEditorStateContext.d.ts +4 -1
- package/dist/_dts/preview-next/src/client/preview/PreviewBlockCustomizeSelectionContext.d.ts +20 -0
- package/dist/_dts/preview-next/src/client/preview/PreviewOpenEditorButton.d.ts +2 -0
- package/dist/_dts/preview-next/src/client/preview/SelectedBlockCustomizePanel.d.ts +13 -0
- package/dist/_dts/preview-next/src/client/preview/previewCustomizeEditorDraftPatch.d.ts +13 -0
- package/dist/_dts/preview-next/src/client/preview/previewCustomizeRuntimePatch.d.ts +2 -0
- package/dist/_dts/preview-next/src/client/preview/storeTypes.d.ts +4 -0
- package/dist/_dts/preview-next/src/client/widgets/widgetOverrides.d.ts +3 -1
- package/dist/_dts/sdk/src/cli/commands/webhooks.d.ts +44 -0
- package/dist/_dts/sdk/src/client/management/index.d.ts +1 -1
- package/dist/_dts/sdk/src/client/management/types.d.ts +56 -0
- package/dist/_dts/sdk/src/client/management/webhooks.d.ts +3 -0
- package/dist/_dts/sdk/src/contracts/system-block-content.d.ts +6 -3
- package/dist/_dts/sdk/src/version.d.ts +1 -1
- package/dist/_dts/theme-core/src/accordions/siteStyleDefaults.d.ts +220 -0
- package/dist/_dts/theme-core/src/accordions/types.d.ts +45 -2
- package/dist/_dts/theme-core/src/blockOverrides.d.ts +8 -0
- package/dist/_dts/theme-core/src/blocks/types.d.ts +1 -1
- package/dist/_dts/theme-core/src/blocks/vars/testimonials.d.ts +4 -1
- package/dist/_dts/theme-core/src/buttons/personalities/types.d.ts +1 -1
- package/dist/_dts/theme-core/src/cards/core.d.ts +20 -0
- package/dist/_dts/theme-core/src/cards/generateDefaultCardSystem.d.ts +6 -3
- package/dist/_dts/theme-core/src/cards/siteStyleDefaults.d.ts +105 -0
- package/dist/_dts/theme-core/src/cards/types.d.ts +177 -0
- package/dist/_dts/theme-core/src/generated/containerResponsiveThemeCss.d.ts +1 -1
- package/dist/_dts/theme-core/src/mock-themes/all.d.ts +156 -18
- package/dist/_dts/theme-core/src/palette/variants/index.d.ts +15 -0
- package/dist/_dts/theme-core/src/palette/variants/soft-natural.d.ts +16 -0
- package/dist/_dts/theme-core/src/palette/variants/types.d.ts +1 -1
- package/dist/_dts/theme-core/src/schema.d.ts +446 -52
- package/dist/_dts/theme-core/src/site-styles/appearancePresetCatalog.d.ts +86 -17
- package/dist/_dts/theme-core/src/site-styles/appearancePresetTypes.d.ts +2 -3
- package/dist/_dts/theme-core/src/site-styles/blockAppearancePresets.d.ts +86 -17
- package/dist/_dts/theme-core/src/site-styles/curatedSiteStyles.d.ts +110 -1
- package/dist/_dts/theme-core/src/site-styles/footerLooks.d.ts +20 -1
- package/dist/_dts/theme-core/src/site-styles/generatedSchemas.d.ts +4 -0
- package/dist/_dts/theme-core/src/site-styles/headerLooks.d.ts +286 -6
- package/dist/_dts/theme-core/src/site-styles/index.d.ts +2 -0
- package/dist/_dts/theme-core/src/site-styles/logoBranding.d.ts +193 -0
- package/dist/_dts/theme-core/src/site-styles/previewCompiler.d.ts +2 -8
- package/dist/_dts/theme-core/src/site-styles/rawStyleFields.d.ts +2 -0
- package/dist/_dts/theme-core/src/site-styles/runtimeCompiler.d.ts +10 -7
- package/dist/_dts/theme-core/src/site-styles/siteStyleThemeCompiler.d.ts +11 -0
- package/dist/_dts/theme-core/src/site-styles/sourceCatalogs.d.ts +2 -2
- package/dist/_dts/theme-core/src/site-styles/styleTags.d.ts +1 -1
- package/dist/_dts/theme-core/src/site-styles/testimonialQuotePresets.d.ts +15 -0
- package/dist/_dts/theme-core/src/site-styles/tokenRecipes.d.ts +1 -1
- package/dist/_dts/theme-core/src/site-styles/types.d.ts +7 -29
- package/dist/{PreviewEditorSidebar-56NMJMIK.mjs → chunk-5NZGBSJF.mjs} +1423 -2451
- package/dist/{chunk-4HCW7FHJ.mjs → chunk-7PCYG6YV.mjs} +215 -137
- package/dist/{chunk-K2IIF34C.mjs → chunk-BBLZSQEY.mjs} +24 -0
- package/dist/{chunk-V47ZU33I.mjs → chunk-GYXKAUFV.mjs} +2785 -785
- package/dist/{chunk-GP555I66.mjs → chunk-HTB4BMKD.mjs} +94 -46
- package/dist/{chunk-A3HHFKVT.mjs → chunk-MPBNEVPB.mjs} +1 -1
- package/dist/cli/index.mjs +2100 -545
- package/dist/cli/init-docs/content/workflow-isr-revalidation.md +16 -4
- package/dist/client/{accordion-QLA7KBYO.mjs → accordion-QFZMNB7O.mjs} +1 -1
- package/dist/client/{bookingForm-PYZIPA5G.mjs → bookingForm-NCIXFWIG.mjs} +3 -3
- package/dist/client/bookings.mjs +1144 -329
- package/dist/client/{carousel-5EIULWBI.mjs → carousel-EO4BJMWD.mjs} +2 -2
- package/dist/client/{cart-DOP3FNI4.mjs → cart-FTYCLYUW.mjs} +5 -5
- package/dist/client/{checkout-BVMF6FBV.mjs → checkout-NFGLVCOX.mjs} +5 -5
- package/dist/client/{chunk-TYONZMST.mjs → chunk-4W66ALYT.mjs} +1 -1
- package/dist/client/{chunk-44K2U3QI.mjs → chunk-6GVVWSPU.mjs} +4 -4
- package/dist/client/{chunk-UDS3NTWA.mjs → chunk-IWFZSQNZ.mjs} +1 -1
- package/dist/client/{chunk-R5ESWNKX.mjs → chunk-PUOELU4O.mjs} +89 -5
- package/dist/client/{chunk-YLDYVRZL.mjs → chunk-PYRBIURC.mjs} +4 -4
- package/dist/client/{chunk-GG4M2NP6.mjs → chunk-T6UIP6UZ.mjs} +1 -1
- package/dist/client/{chunk-V6M5FCR5.mjs → chunk-VDS5YFLX.mjs} +1 -1
- package/dist/client/{chunk-CCNTDRCF.mjs → chunk-Y37KDO7P.mjs} +1 -1
- package/dist/client/client.mjs +4205 -2324
- package/dist/client/{courseRegistration-E6EAA6NL.mjs → courseRegistration-2EORROPS.mjs} +3 -3
- package/dist/client/{eventCalendar-HBG3HAK6.mjs → eventCalendar-WZI4VL6N.mjs} +5 -5
- package/dist/client/{eventCombined-6K5ZZWAK.mjs → eventCombined-QCB2TCNL.mjs} +5 -5
- package/dist/client/{eventDetails-Q75ICXLT.mjs → eventDetails-TFTATAPH.mjs} +5 -5
- package/dist/client/{eventListing-27P5F7DZ.mjs → eventListing-A2CQEHEQ.mjs} +5 -5
- package/dist/client/{eventRegistration-IQ7AMASR.mjs → eventRegistration-OELPVLUY.mjs} +5 -5
- package/dist/client/{form-JRTD65FN.mjs → form-NTIFD6WQ.mjs} +1 -1
- package/dist/client/{headerSection-7FPSSZHS.mjs → headerSection-7TBNKCLA.mjs} +65 -24
- package/dist/client/hooks.mjs +3354 -2006
- package/dist/client/{locationMap-7NGFNYBH.mjs → locationMap-LRLB5A3H.mjs} +1 -1
- package/dist/client/{newsletterForm-F3BADCI2.mjs → newsletterForm-XI52RZSN.mjs} +1 -1
- package/dist/client/{productDetail-V3HP7ITJ.mjs → productDetail-GSNKBFLY.mjs} +5 -5
- package/dist/client/{productList-L3EGJUF2.mjs → productList-BGCGT4KZ.mjs} +5 -5
- package/dist/client/rendering/client.mjs +2570 -1657
- package/dist/client/rendering/islands.mjs +18 -18
- package/dist/client/rendering.mjs +4275 -2394
- package/dist/client/{shop-DQUB5E2C.mjs → shop-RQHUCLNY.mjs} +5 -5
- package/dist/preview-next/before-render.mjs +24 -0
- package/dist/preview-next/client/runtime.mjs +5 -5
- package/dist/preview-next/middleware.mjs +24 -0
- package/dist/{sdk-runtime-AF6KXJU4.mjs → sdk-runtime-A4LHT2RS.mjs} +6 -6
- package/dist/server/components.mjs +4063 -2293
- package/dist/server/config-validation.mjs +3525 -2176
- package/dist/server/config.mjs +3525 -2176
- package/dist/server/data.mjs +3354 -2006
- package/dist/server/index.mjs +25 -1
- package/dist/server/next.mjs +4006 -2236
- package/dist/server/page-converter.mjs +2747 -1816
- package/dist/server/prebuild.mjs +1 -1
- package/dist/server/rendering/server.mjs +4063 -2293
- package/dist/server/rendering.mjs +4063 -2293
- package/dist/server/routing.mjs +3508 -2111
- package/dist/server/server.mjs +3372 -2024
- package/dist/server/theme-bridge.mjs +1436 -310
- package/dist/server/theme.mjs +409 -219
- package/dist/styles/index.css +300 -33
- package/package.json +1 -1
- package/dist/SdkPreviewModeRuntime-H4BSP6E7.mjs +0 -8
- package/dist/chunk-LCY32PXF.mjs +0 -1175
|
@@ -0,0 +1,2655 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { resolvePreviewDashboardSetupHandoff, buildPreviewDashboardSetupUrl, resolvePreviewSidebarChromeModel, resolvePreviewEditorGuidanceModel, ButtonVariantField, IconPickerField, EntryPickerField, ReferenceField, RichTextField, MediaField, FileField, LinkField, canInspectSiteStyle, mapContentSchemaToFormFields, usePreviewBlockCustomizeSelectionOptional, BlockEditor, usePreviewDesignCoordinator, resolveStyleConfiguratorInitialSnapshot, createStyleConfiguratorState, createOptimisticActionRunner, syncStyleConfiguratorState, isPreviewingChange, planSiteStylePreviewChange, openRow, previewSiteStyle, applyResolvedSelections, previewButtonPersonality, previewHeaderLayout, previewHeaderStyle, planSiteChromeLookSelection, previewFooterLook, previewPaletteVariant, previewTypographyPreset, setTypographyBodyFontOverride, setTypographyHeadingFontOverride, clearTypographyFontOverrides, setPaletteOverrides, toggleRow, cancelPreview, dismissWarnings, dismissNotices, markApplyFailed, markApplySaved, markApplySaving, canApplySiteStyle, buildSiteChromeLookModels, cycleSiteChromeLookId, LinkWidgetProvider, ReferenceWidgetProvider, EntryPickerWidgetProvider, ButtonWidgetProvider, cycleableSiteChromeOptions, BUILT_IN_REFERENCE_KINDS, useRichTextImagePicker, RichTextWidgetProvider, useBlockCustomizeControls, MAX_QUICK_MANIFEST_FIELDS, parseReferenceKind } from './chunk-5NZGBSJF.mjs';
|
|
3
|
+
import { unwrapResponse, useEntryController, useEditorUiStoreInstance, useUISelector, resolvePreviewSidebarTab, BackgroundGradientField, BackgroundColorField, unsafeAsEditorBlockId, Tabs, TabsList, TabsTrigger, Button, X, TabsContent, omitRouteTitleFieldFromManifest, getRouteMetadataManifestForType, createManifestValidation, createStoreBackend, ManifestForm, previewAssistantEnabled, Collapsible, CollapsibleTrigger, ChevronRight, CollapsibleContent, ContentFieldsForm, getCuratedSiteStyle, paletteOverridesSchema, findButtonPersonality, findPaletteVariant, findTypographyPreset, getHeaderLayout, getHeaderStyle, findFooterLook, footerLookCatalog, foldSubroute, SUBROUTE_KINDS, Smartphone, Tablet, Monitor, Maximize2, cn, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuItem, expectParsedBrandedId, parseSiteId, buildThemeRuntime, SdkConfigProvider, PaletteProvider, MediaEditorProvider, curatedSiteStyles, ChevronLeft, requireEntriesForCuratedChoices, buttonPersonalities, typographyPresets, paletteVariants, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, FormRenderer, evaluateTypographyFontSlot, Callout, CalloutDescription, Separator, Surface, ColorPicker, Input } from './chunk-GYXKAUFV.mjs';
|
|
4
|
+
import { usePreviewAuth, getDashboardUrl } from './chunk-BBLZSQEY.mjs';
|
|
5
|
+
import './chunk-R7POPVJR.mjs';
|
|
6
|
+
import { useMemo, useRef, useCallback, useEffect, useState, useTransition, useSyncExternalStore, useId } from 'react';
|
|
7
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
8
|
+
import { useRouter } from 'next/navigation';
|
|
9
|
+
|
|
10
|
+
// ../editor-blocks/src/widgets/entry-picker/loaders.ts
|
|
11
|
+
function getStatusBadgeVariant(status) {
|
|
12
|
+
switch (status) {
|
|
13
|
+
case "published":
|
|
14
|
+
return "default";
|
|
15
|
+
case "draft":
|
|
16
|
+
return "secondary";
|
|
17
|
+
case "scheduled":
|
|
18
|
+
return "outline";
|
|
19
|
+
case "archived":
|
|
20
|
+
return "destructive";
|
|
21
|
+
default:
|
|
22
|
+
return "secondary";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function mapEntryPickerItems(entries, valueFrom) {
|
|
26
|
+
return entries.map((entry) => ({
|
|
27
|
+
value: valueFrom === "identifier" ? entry.identifier ?? entry.id : valueFrom === "slug" ? entry.slug ?? entry.id : entry.id,
|
|
28
|
+
label: entry.draftTitle || entry.title || entry.slug || entry.id,
|
|
29
|
+
id: entry.id,
|
|
30
|
+
identifier: entry.identifier ?? null,
|
|
31
|
+
slug: entry.slug ?? null,
|
|
32
|
+
badge: {
|
|
33
|
+
label: entry.status,
|
|
34
|
+
variant: getStatusBadgeVariant(entry.status)
|
|
35
|
+
}
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
async function loadEntryPickerItems({
|
|
39
|
+
apiClient,
|
|
40
|
+
siteId,
|
|
41
|
+
contentType,
|
|
42
|
+
valueFrom
|
|
43
|
+
}) {
|
|
44
|
+
const result = await apiClient({
|
|
45
|
+
endpoint: "listContentEntries",
|
|
46
|
+
params: { siteId, type: contentType }
|
|
47
|
+
});
|
|
48
|
+
const { entries } = unwrapResponse(result);
|
|
49
|
+
return mapEntryPickerItems(entries, valueFrom);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ../editor-blocks/src/widgets/link/loaders.ts
|
|
53
|
+
async function loadRoutableContentItems({
|
|
54
|
+
apiClient,
|
|
55
|
+
siteId
|
|
56
|
+
}) {
|
|
57
|
+
const result = await apiClient({
|
|
58
|
+
endpoint: "getRoutableContent",
|
|
59
|
+
params: { siteId }
|
|
60
|
+
});
|
|
61
|
+
const payload = unwrapResponse(result);
|
|
62
|
+
return Array.isArray(payload?.items) ? payload.items : [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ../editor-blocks/src/widgets/reference/dynamicAdapters.ts
|
|
66
|
+
function createDynamicReferenceAdapters(staticAdapters, createDynamicContentAdapter) {
|
|
67
|
+
return new Proxy(staticAdapters, {
|
|
68
|
+
get(target, prop) {
|
|
69
|
+
if (typeof prop !== "string") {
|
|
70
|
+
return void 0;
|
|
71
|
+
}
|
|
72
|
+
if (prop in target) {
|
|
73
|
+
return target[prop];
|
|
74
|
+
}
|
|
75
|
+
const parsed = parseReferenceKind(prop);
|
|
76
|
+
if (parsed.kind === "dynamicContent") {
|
|
77
|
+
return createDynamicContentAdapter(parsed.contentTypeKey);
|
|
78
|
+
}
|
|
79
|
+
return void 0;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ../editor-blocks/src/widgets/reference/loaders.ts
|
|
85
|
+
function defineReferenceLoader(referenceKind, loader) {
|
|
86
|
+
return Object.assign(loader, { referenceKind });
|
|
87
|
+
}
|
|
88
|
+
function formatStatusBadge(status) {
|
|
89
|
+
if (status === "published") {
|
|
90
|
+
return { label: "Published", variant: "default" };
|
|
91
|
+
}
|
|
92
|
+
if (status === "scheduled") {
|
|
93
|
+
return { label: "Scheduled", variant: "secondary" };
|
|
94
|
+
}
|
|
95
|
+
if (status === "archived") {
|
|
96
|
+
return { label: "Archived", variant: "outline" };
|
|
97
|
+
}
|
|
98
|
+
return { label: "Draft", variant: "outline" };
|
|
99
|
+
}
|
|
100
|
+
function hasSlug(entry) {
|
|
101
|
+
return entry.slug !== null;
|
|
102
|
+
}
|
|
103
|
+
var loadPostReferenceItems = defineReferenceLoader("post", async ({ apiClient, siteId }) => {
|
|
104
|
+
const result = await apiClient({
|
|
105
|
+
endpoint: "listContentEntries",
|
|
106
|
+
params: { siteId, type: "post" }
|
|
107
|
+
});
|
|
108
|
+
const { entries } = unwrapResponse(result);
|
|
109
|
+
return entries.filter(hasSlug).filter((entry) => entry.status === "published").map((entry) => ({
|
|
110
|
+
value: entry.slug,
|
|
111
|
+
label: entry.title || entry.slug || entry.id,
|
|
112
|
+
badge: formatStatusBadge(entry.status)
|
|
113
|
+
}));
|
|
114
|
+
});
|
|
115
|
+
var loadFormReferenceItems = defineReferenceLoader("form", async ({ apiClient, siteId }) => {
|
|
116
|
+
const result = await apiClient({
|
|
117
|
+
endpoint: "listForms",
|
|
118
|
+
params: { siteId }
|
|
119
|
+
});
|
|
120
|
+
const { forms } = unwrapResponse(result);
|
|
121
|
+
return forms.map((form) => ({
|
|
122
|
+
value: form.slug,
|
|
123
|
+
label: form.name
|
|
124
|
+
}));
|
|
125
|
+
});
|
|
126
|
+
var loadBookingFormReferenceItems = defineReferenceLoader("bookingForm", async ({ apiClient, siteId }) => {
|
|
127
|
+
const result = await apiClient({
|
|
128
|
+
endpoint: "listBookingForms",
|
|
129
|
+
params: { siteId }
|
|
130
|
+
});
|
|
131
|
+
const { forms } = unwrapResponse(result);
|
|
132
|
+
return forms.map((form) => ({
|
|
133
|
+
value: form.id,
|
|
134
|
+
label: form.name
|
|
135
|
+
}));
|
|
136
|
+
});
|
|
137
|
+
var loadAppointmentServiceReferenceItems = defineReferenceLoader(
|
|
138
|
+
"appointmentService",
|
|
139
|
+
async ({ apiClient, siteId }) => {
|
|
140
|
+
const result = await apiClient({
|
|
141
|
+
endpoint: "getAppointmentServicesReference",
|
|
142
|
+
params: { siteId }
|
|
143
|
+
});
|
|
144
|
+
const { options } = unwrapResponse(result);
|
|
145
|
+
return options.map((option) => ({
|
|
146
|
+
value: option.id,
|
|
147
|
+
label: option.label
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
var loadAppointmentResourceReferenceItems = defineReferenceLoader(
|
|
152
|
+
"appointmentResource",
|
|
153
|
+
async ({ apiClient, siteId }) => {
|
|
154
|
+
const result = await apiClient({
|
|
155
|
+
endpoint: "getAppointmentResourcesReference",
|
|
156
|
+
params: { siteId }
|
|
157
|
+
});
|
|
158
|
+
const { options } = unwrapResponse(result);
|
|
159
|
+
return options.map((option) => ({
|
|
160
|
+
value: option.id,
|
|
161
|
+
label: option.label
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
var loadPageReferenceItems = defineReferenceLoader("page", async ({ apiClient, siteId }) => {
|
|
166
|
+
const result = await apiClient({
|
|
167
|
+
endpoint: "getRoutableContent",
|
|
168
|
+
params: { siteId }
|
|
169
|
+
});
|
|
170
|
+
const { items } = unwrapResponse(result);
|
|
171
|
+
return items.filter((item) => item.entityType === "page").map((page) => ({
|
|
172
|
+
value: page.entityId,
|
|
173
|
+
label: page.title || "Untitled"
|
|
174
|
+
}));
|
|
175
|
+
});
|
|
176
|
+
var loadNewsletterListReferenceItems = defineReferenceLoader(
|
|
177
|
+
"newsletterList",
|
|
178
|
+
async ({ apiClient, siteId }) => {
|
|
179
|
+
const result = await apiClient({
|
|
180
|
+
endpoint: "listNewsletterLists",
|
|
181
|
+
params: { siteId }
|
|
182
|
+
});
|
|
183
|
+
const { lists } = unwrapResponse(result);
|
|
184
|
+
return lists.map((list) => ({
|
|
185
|
+
value: list.id,
|
|
186
|
+
label: list.name
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
async function loadDynamicContentReferenceItems({
|
|
191
|
+
apiClient,
|
|
192
|
+
siteId,
|
|
193
|
+
contentTypeKey
|
|
194
|
+
}) {
|
|
195
|
+
const result = await apiClient({
|
|
196
|
+
endpoint: "listContentEntries",
|
|
197
|
+
params: { siteId, type: contentTypeKey }
|
|
198
|
+
});
|
|
199
|
+
const { entries } = unwrapResponse(result);
|
|
200
|
+
return entries.filter((entry) => entry.status === "published").map((entry) => ({
|
|
201
|
+
value: entry.id,
|
|
202
|
+
label: entry.title || entry.slug || entry.id,
|
|
203
|
+
badge: { label: "Published", variant: "default" }
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
var referenceLoaders = {
|
|
207
|
+
post: loadPostReferenceItems,
|
|
208
|
+
form: loadFormReferenceItems,
|
|
209
|
+
bookingForm: loadBookingFormReferenceItems,
|
|
210
|
+
appointmentService: loadAppointmentServiceReferenceItems,
|
|
211
|
+
appointmentResource: loadAppointmentResourceReferenceItems,
|
|
212
|
+
page: loadPageReferenceItems,
|
|
213
|
+
newsletterList: loadNewsletterListReferenceItems
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// ../preview-next/src/client/widgets/widgetAdapters.ts
|
|
217
|
+
var routableContentCache = /* @__PURE__ */ new Map();
|
|
218
|
+
var entryPickerCache = /* @__PURE__ */ new Map();
|
|
219
|
+
var referenceItemsCache = /* @__PURE__ */ new Map();
|
|
220
|
+
var dynamicReferenceItemsCache = /* @__PURE__ */ new Map();
|
|
221
|
+
var MAX_CACHE_ENTRIES = 50;
|
|
222
|
+
function setBoundedCache(map, key, value) {
|
|
223
|
+
if (!map.has(key) && map.size >= MAX_CACHE_ENTRIES) {
|
|
224
|
+
const firstKey = map.keys().next().value;
|
|
225
|
+
if (firstKey !== void 0) {
|
|
226
|
+
map.delete(firstKey);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
map.set(key, value);
|
|
230
|
+
}
|
|
231
|
+
function createCachedDynamicContentReferenceAdapter(contentTypeKey, previewClient, fallbackSiteId) {
|
|
232
|
+
return {
|
|
233
|
+
labels: {
|
|
234
|
+
loading: "Loading entries\u2026",
|
|
235
|
+
select: "Select an entry",
|
|
236
|
+
empty: "No entries found",
|
|
237
|
+
emptySelection: "Select an entry to continue."
|
|
238
|
+
},
|
|
239
|
+
loadItems: async ({ siteId }) => {
|
|
240
|
+
const resolvedSiteId = siteId || fallbackSiteId;
|
|
241
|
+
if (!resolvedSiteId) {
|
|
242
|
+
return [];
|
|
243
|
+
}
|
|
244
|
+
const cacheKey = `${resolvedSiteId}:content:${contentTypeKey}`;
|
|
245
|
+
const cached = dynamicReferenceItemsCache.get(cacheKey);
|
|
246
|
+
if (cached) return cached;
|
|
247
|
+
const promise = (async () => {
|
|
248
|
+
try {
|
|
249
|
+
return await loadDynamicContentReferenceItems({
|
|
250
|
+
apiClient: previewClient,
|
|
251
|
+
siteId: resolvedSiteId,
|
|
252
|
+
contentTypeKey
|
|
253
|
+
});
|
|
254
|
+
} catch (error) {
|
|
255
|
+
dynamicReferenceItemsCache.delete(cacheKey);
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
})();
|
|
259
|
+
setBoundedCache(dynamicReferenceItemsCache, cacheKey, promise);
|
|
260
|
+
return promise;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function createCachedReferenceAdapterLoader(kind, loader, previewClient, fallbackSiteId) {
|
|
265
|
+
return async ({ siteId }) => {
|
|
266
|
+
const resolvedSiteId = siteId || fallbackSiteId;
|
|
267
|
+
if (!resolvedSiteId) {
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
const cacheKey = `${kind}:${resolvedSiteId}`;
|
|
271
|
+
const cached = referenceItemsCache.get(cacheKey);
|
|
272
|
+
if (cached) return cached;
|
|
273
|
+
const promise = (async () => {
|
|
274
|
+
try {
|
|
275
|
+
return await loader({ apiClient: previewClient, siteId: resolvedSiteId });
|
|
276
|
+
} catch (error) {
|
|
277
|
+
referenceItemsCache.delete(cacheKey);
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
280
|
+
})();
|
|
281
|
+
setBoundedCache(referenceItemsCache, cacheKey, promise);
|
|
282
|
+
return promise;
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function createPreviewLinkAdapters(previewClient) {
|
|
286
|
+
return {
|
|
287
|
+
fetchRoutableContent: async (currentSiteId) => {
|
|
288
|
+
const cached = routableContentCache.get(currentSiteId);
|
|
289
|
+
if (cached) return cached;
|
|
290
|
+
const promise = (async () => {
|
|
291
|
+
try {
|
|
292
|
+
return await loadRoutableContentItems({ apiClient: previewClient, siteId: currentSiteId });
|
|
293
|
+
} catch (error) {
|
|
294
|
+
routableContentCache.delete(currentSiteId);
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
297
|
+
})();
|
|
298
|
+
setBoundedCache(routableContentCache, currentSiteId, promise);
|
|
299
|
+
return promise;
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function createPreviewEntryPickerAdapter(previewClient) {
|
|
304
|
+
return {
|
|
305
|
+
loadItems: async ({ siteId, contentType, valueFrom }) => {
|
|
306
|
+
if (!siteId || !contentType) return [];
|
|
307
|
+
const cacheKey = `${siteId}:${contentType}:${valueFrom ?? "id"}`;
|
|
308
|
+
const cached = entryPickerCache.get(cacheKey);
|
|
309
|
+
if (cached) return cached;
|
|
310
|
+
const request = (async () => {
|
|
311
|
+
try {
|
|
312
|
+
return await loadEntryPickerItems({
|
|
313
|
+
apiClient: previewClient,
|
|
314
|
+
siteId,
|
|
315
|
+
contentType,
|
|
316
|
+
valueFrom
|
|
317
|
+
});
|
|
318
|
+
} catch (error) {
|
|
319
|
+
entryPickerCache.delete(cacheKey);
|
|
320
|
+
throw error;
|
|
321
|
+
}
|
|
322
|
+
})();
|
|
323
|
+
setBoundedCache(entryPickerCache, cacheKey, request);
|
|
324
|
+
return request;
|
|
325
|
+
},
|
|
326
|
+
labels: {
|
|
327
|
+
loading: "Loading entries...",
|
|
328
|
+
select: "Select an entry",
|
|
329
|
+
empty: "No entries found",
|
|
330
|
+
emptySelection: "Select an entry to embed.",
|
|
331
|
+
noContentType: "Select a content type first."
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
var previewReferenceChrome = {
|
|
336
|
+
post: {
|
|
337
|
+
labels: {
|
|
338
|
+
loading: "Loading posts\u2026",
|
|
339
|
+
select: "Select a post",
|
|
340
|
+
empty: "No posts found",
|
|
341
|
+
emptySelection: "Select a post to continue."
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
form: {
|
|
345
|
+
labels: {
|
|
346
|
+
loading: "Loading forms\u2026",
|
|
347
|
+
select: "Select a form",
|
|
348
|
+
empty: "No forms found",
|
|
349
|
+
emptySelection: "Select a form to continue."
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
bookingForm: {
|
|
353
|
+
labels: {
|
|
354
|
+
loading: "Loading booking forms\u2026",
|
|
355
|
+
select: "Select a booking form",
|
|
356
|
+
empty: "No booking forms yet",
|
|
357
|
+
emptySelection: "Select a booking form to enable appointment booking."
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
appointmentService: {
|
|
361
|
+
labels: {
|
|
362
|
+
loading: "Loading appointment services\u2026",
|
|
363
|
+
select: "Select a service",
|
|
364
|
+
empty: "No services available",
|
|
365
|
+
emptySelection: "Select a service to enable booking."
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
appointmentResource: {
|
|
369
|
+
labels: {
|
|
370
|
+
loading: "Loading resources\u2026",
|
|
371
|
+
select: "Select a resource",
|
|
372
|
+
empty: "No resources available",
|
|
373
|
+
emptySelection: "Select a resource (optional)."
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
page: {
|
|
377
|
+
labels: {
|
|
378
|
+
loading: "Loading pages\u2026",
|
|
379
|
+
select: "Select a page",
|
|
380
|
+
empty: "No pages available",
|
|
381
|
+
emptySelection: "Select a page."
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
newsletterList: {
|
|
385
|
+
labels: {
|
|
386
|
+
loading: "Loading lists\u2026",
|
|
387
|
+
select: "Select a list",
|
|
388
|
+
empty: "No lists yet",
|
|
389
|
+
emptySelection: "Select a newsletter list to subscribe users to."
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
function createPreviewReferenceAdapters(previewClient, fallbackSiteId) {
|
|
394
|
+
const brandedFallbackSiteId = void 0;
|
|
395
|
+
const adapters = {};
|
|
396
|
+
for (const kind of BUILT_IN_REFERENCE_KINDS) {
|
|
397
|
+
adapters[kind] = {
|
|
398
|
+
loadItems: createCachedReferenceAdapterLoader(
|
|
399
|
+
kind,
|
|
400
|
+
referenceLoaders[kind],
|
|
401
|
+
previewClient,
|
|
402
|
+
brandedFallbackSiteId
|
|
403
|
+
),
|
|
404
|
+
labels: previewReferenceChrome[kind].labels
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
return createDynamicReferenceAdapters(
|
|
408
|
+
adapters,
|
|
409
|
+
(contentTypeKey) => createCachedDynamicContentReferenceAdapter(contentTypeKey, previewClient, brandedFallbackSiteId)
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
function PreviewFormProviderInner({ theme, children }) {
|
|
413
|
+
const { onPickImage, picker } = useRichTextImagePicker();
|
|
414
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
415
|
+
/* @__PURE__ */ jsx(RichTextWidgetProvider, { theme, onPickImage, children }),
|
|
416
|
+
picker
|
|
417
|
+
] });
|
|
418
|
+
}
|
|
419
|
+
function PreviewFormProvider({
|
|
420
|
+
siteId: rawSiteId,
|
|
421
|
+
theme = null,
|
|
422
|
+
previewClient,
|
|
423
|
+
bearerToken,
|
|
424
|
+
supabaseUrl,
|
|
425
|
+
mediaAssets,
|
|
426
|
+
sdkConfig,
|
|
427
|
+
children
|
|
428
|
+
}) {
|
|
429
|
+
const siteId = useMemo(() => expectParsedBrandedId(parseSiteId(rawSiteId)), [rawSiteId]);
|
|
430
|
+
const linkAdapters = useMemo(() => createPreviewLinkAdapters(previewClient), [previewClient]);
|
|
431
|
+
const entryPickerAdapter = useMemo(() => createPreviewEntryPickerAdapter(previewClient), [previewClient]);
|
|
432
|
+
const referenceAdapters = useMemo(
|
|
433
|
+
() => createPreviewReferenceAdapters(previewClient),
|
|
434
|
+
[previewClient]
|
|
435
|
+
);
|
|
436
|
+
const paletteTokens = useMemo(() => {
|
|
437
|
+
if (!theme) return void 0;
|
|
438
|
+
try {
|
|
439
|
+
const runtime = buildThemeRuntime(theme);
|
|
440
|
+
return runtime.paletteTokens;
|
|
441
|
+
} catch {
|
|
442
|
+
return void 0;
|
|
443
|
+
}
|
|
444
|
+
}, [theme]);
|
|
445
|
+
return /* @__PURE__ */ jsx(SdkConfigProvider, { sdkConfig: sdkConfig ?? null, children: /* @__PURE__ */ jsx(PaletteProvider, { tokens: paletteTokens, children: /* @__PURE__ */ jsx(LinkWidgetProvider, { siteId, adapters: linkAdapters, children: /* @__PURE__ */ jsx(ReferenceWidgetProvider, { siteId, adapters: referenceAdapters, children: /* @__PURE__ */ jsx(EntryPickerWidgetProvider, { siteId, adapter: entryPickerAdapter, children: /* @__PURE__ */ jsx(
|
|
446
|
+
MediaEditorProvider,
|
|
447
|
+
{
|
|
448
|
+
siteId,
|
|
449
|
+
apiClient: previewClient,
|
|
450
|
+
bearerToken,
|
|
451
|
+
supabaseUrl,
|
|
452
|
+
mediaAssets,
|
|
453
|
+
children: /* @__PURE__ */ jsx(ButtonWidgetProvider, { buttonSystem: theme?.buttons ?? null, children: /* @__PURE__ */ jsx(PreviewFormProviderInner, { theme, children }) })
|
|
454
|
+
}
|
|
455
|
+
) }) }) }) }) });
|
|
456
|
+
}
|
|
457
|
+
var previewWidgetOverrides = {
|
|
458
|
+
link: LinkField,
|
|
459
|
+
file: FileField,
|
|
460
|
+
media: MediaField,
|
|
461
|
+
richText: RichTextField,
|
|
462
|
+
reference: ReferenceField,
|
|
463
|
+
backgroundColor: BackgroundColorField,
|
|
464
|
+
backgroundGradient: BackgroundGradientField,
|
|
465
|
+
entryPicker: EntryPickerField,
|
|
466
|
+
iconPicker: IconPickerField,
|
|
467
|
+
buttonVariant: ButtonVariantField
|
|
468
|
+
};
|
|
469
|
+
function PreviewZoomControls({ zoom, onZoomChange, disabled }) {
|
|
470
|
+
return /* @__PURE__ */ jsxs(
|
|
471
|
+
Select,
|
|
472
|
+
{
|
|
473
|
+
value: String(zoom),
|
|
474
|
+
onValueChange: (value) => onZoomChange(value === "fit" ? "fit" : Number(value)),
|
|
475
|
+
disabled,
|
|
476
|
+
children: [
|
|
477
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "h-8 w-20", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
478
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
479
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "33", children: "33%" }),
|
|
480
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "50", children: "50%" }),
|
|
481
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "75", children: "75%" }),
|
|
482
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "100", children: "100%" }),
|
|
483
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "fit", children: "Fit" })
|
|
484
|
+
] })
|
|
485
|
+
]
|
|
486
|
+
}
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
var VIEWPORT_PRESETS = [
|
|
490
|
+
{ label: "Mobile", width: 375, icon: Smartphone },
|
|
491
|
+
{ label: "Tablet", width: 768, icon: Tablet },
|
|
492
|
+
{ label: "Desktop", width: 1440, icon: Monitor },
|
|
493
|
+
{ label: "Full Width", width: "full", icon: Maximize2 }
|
|
494
|
+
];
|
|
495
|
+
function PreviewViewportControls({
|
|
496
|
+
mode,
|
|
497
|
+
width,
|
|
498
|
+
onModeChange,
|
|
499
|
+
onWidthChange,
|
|
500
|
+
className,
|
|
501
|
+
zoom,
|
|
502
|
+
onZoomChange
|
|
503
|
+
}) {
|
|
504
|
+
const activePreset = VIEWPORT_PRESETS.find((p) => p.width === width);
|
|
505
|
+
const ActiveIcon = activePreset?.icon ?? Monitor;
|
|
506
|
+
const activeLabel = mode === "squeeze" ? "Auto fit" : activePreset?.label ?? `${width}px`;
|
|
507
|
+
const showZoomControls = mode === "fixed" && zoom !== void 0 && onZoomChange !== void 0;
|
|
508
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-2", className), children: [
|
|
509
|
+
/* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
510
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "gap-2", children: [
|
|
511
|
+
/* @__PURE__ */ jsx(ActiveIcon, { className: "h-4 w-4" }),
|
|
512
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs", children: activeLabel })
|
|
513
|
+
] }) }),
|
|
514
|
+
/* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-56", children: [
|
|
515
|
+
/* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Preview size" }),
|
|
516
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
517
|
+
/* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => onModeChange("squeeze"), className: cn(mode === "squeeze" && "bg-accent"), children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
518
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: "Auto fit" }),
|
|
519
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Matches the space beside the editor" })
|
|
520
|
+
] }) }),
|
|
521
|
+
/* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => onModeChange("fixed"), className: cn(mode === "fixed" && "bg-accent"), children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
522
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: "Choose size" }),
|
|
523
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Preview mobile, tablet, or desktop widths" })
|
|
524
|
+
] }) })
|
|
525
|
+
] })
|
|
526
|
+
] }),
|
|
527
|
+
mode === "fixed" && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
528
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", children: activePreset?.label ?? `${width}px` }) }),
|
|
529
|
+
/* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", children: [
|
|
530
|
+
/* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Screen size" }),
|
|
531
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
532
|
+
VIEWPORT_PRESETS.map((preset) => /* @__PURE__ */ jsxs(
|
|
533
|
+
DropdownMenuItem,
|
|
534
|
+
{
|
|
535
|
+
onClick: () => onWidthChange(preset.width),
|
|
536
|
+
className: cn(width === preset.width && "bg-accent"),
|
|
537
|
+
children: [
|
|
538
|
+
/* @__PURE__ */ jsx(preset.icon, { className: "mr-2 h-4 w-4" }),
|
|
539
|
+
preset.label,
|
|
540
|
+
typeof preset.width === "number" && /* @__PURE__ */ jsxs("span", { className: "ml-auto text-xs text-muted-foreground", children: [
|
|
541
|
+
preset.width,
|
|
542
|
+
"px"
|
|
543
|
+
] })
|
|
544
|
+
]
|
|
545
|
+
},
|
|
546
|
+
preset.label
|
|
547
|
+
))
|
|
548
|
+
] })
|
|
549
|
+
] }),
|
|
550
|
+
showZoomControls ? /* @__PURE__ */ jsx(PreviewZoomControls, { zoom, onZoomChange }) : null
|
|
551
|
+
] });
|
|
552
|
+
}
|
|
553
|
+
function SubrouteNotice({ model }) {
|
|
554
|
+
return /* @__PURE__ */ jsxs("div", { className: "border-b bg-amber-50 px-4 py-3 text-amber-950", children: [
|
|
555
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-semibold", children: model.title }),
|
|
556
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm", children: model.body }),
|
|
557
|
+
model.cta ? /* @__PURE__ */ jsx("div", { className: "mt-3 flex flex-wrap gap-2", children: /* @__PURE__ */ jsx(
|
|
558
|
+
"a",
|
|
559
|
+
{
|
|
560
|
+
className: "inline-flex items-center rounded-md bg-white px-3 py-1.5 text-sm font-medium text-amber-950 shadow-sm ring-1 ring-amber-900/10 hover:bg-amber-100",
|
|
561
|
+
href: model.cta.href,
|
|
562
|
+
target: "_blank",
|
|
563
|
+
rel: "noopener noreferrer",
|
|
564
|
+
children: model.cta.label
|
|
565
|
+
}
|
|
566
|
+
) }) : null,
|
|
567
|
+
model.fallbackMessage ? /* @__PURE__ */ jsx("p", { className: "mt-3 text-sm text-amber-900/90", children: model.fallbackMessage }) : null
|
|
568
|
+
] });
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// ../preview-next/src/client/preview/subrouteNotice.ts
|
|
572
|
+
function buildNoticeFromMeta(meta, payload, siteId) {
|
|
573
|
+
const name = meta.formatInstanceName(payload);
|
|
574
|
+
const title = name ? `You're previewing the ${name} ${meta.instanceNoun} of this ${meta.containerNoun}` : `You're previewing one ${meta.instanceNoun} of this ${meta.containerNoun}`;
|
|
575
|
+
const body = `Edits in this sidebar apply to the whole ${meta.containerNoun}, so they'll show up on every ${meta.instanceNoun}. Need to change just this ${meta.instanceNoun}? You can do that from your ${meta.listLabel}.`;
|
|
576
|
+
const ctaLabel = `Edit just this ${meta.instanceNoun}`;
|
|
577
|
+
let href = null;
|
|
578
|
+
try {
|
|
579
|
+
href = getDashboardUrl(`sites/${siteId}/${meta.subEditorPath(payload)}`);
|
|
580
|
+
} catch {
|
|
581
|
+
href = null;
|
|
582
|
+
}
|
|
583
|
+
return {
|
|
584
|
+
title,
|
|
585
|
+
body,
|
|
586
|
+
cta: href ? { label: ctaLabel, href } : null,
|
|
587
|
+
fallbackMessage: href ? null : "Dashboard links are unavailable. Set `NEXT_PUBLIC_DASHBOARD_URL` so preview can link back to the dashboard."
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
function resolveSubrouteNotice(ctx, args) {
|
|
591
|
+
if (!ctx) return null;
|
|
592
|
+
return foldSubroute(ctx, {
|
|
593
|
+
"event-occurrence": (occurrence) => buildNoticeFromMeta(SUBROUTE_KINDS["event-occurrence"], occurrence, args.siteId),
|
|
594
|
+
"course-run": (run) => buildNoticeFromMeta(SUBROUTE_KINDS["course-run"], run, args.siteId)
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
function EntryEditorPanel({
|
|
598
|
+
siteId,
|
|
599
|
+
entryData,
|
|
600
|
+
contentTypeSchema,
|
|
601
|
+
referenceContentTypeKeys,
|
|
602
|
+
draftContent,
|
|
603
|
+
currentTemplate,
|
|
604
|
+
theme,
|
|
605
|
+
apiClient,
|
|
606
|
+
bearerToken,
|
|
607
|
+
supabaseUrl,
|
|
608
|
+
entrySubrouteContext,
|
|
609
|
+
metadataPanel,
|
|
610
|
+
mediaAssets,
|
|
611
|
+
widgets,
|
|
612
|
+
viewportMode,
|
|
613
|
+
viewportWidth,
|
|
614
|
+
zoom,
|
|
615
|
+
onViewportModeChange,
|
|
616
|
+
onViewportWidthChange,
|
|
617
|
+
onZoomChange
|
|
618
|
+
}) {
|
|
619
|
+
const [contentOpen, setContentOpen] = useState(true);
|
|
620
|
+
const [templateOpen, setTemplateOpen] = useState(false);
|
|
621
|
+
const [detailsOpen, setDetailsOpen] = useState(false);
|
|
622
|
+
const contentFormFields = useMemo(
|
|
623
|
+
// content-model keeps a source-local structural field type so it can typecheck natively
|
|
624
|
+
// without importing @riverbankcms/blocks source. Preview is the integration boundary where
|
|
625
|
+
// those normalized fields are consumed as a block-form manifest.
|
|
626
|
+
() => contentTypeSchema ? mapContentSchemaToFormFields(contentTypeSchema, {
|
|
627
|
+
referenceContentTypeKeys
|
|
628
|
+
}) : [],
|
|
629
|
+
[contentTypeSchema, referenceContentTypeKeys]
|
|
630
|
+
);
|
|
631
|
+
const contentFormSpec = useMemo(
|
|
632
|
+
() => ({
|
|
633
|
+
name: "content-fields",
|
|
634
|
+
version: "1.0",
|
|
635
|
+
title: "Content",
|
|
636
|
+
component: "content-form",
|
|
637
|
+
description: "",
|
|
638
|
+
fields: contentFormFields,
|
|
639
|
+
slots: []
|
|
640
|
+
}),
|
|
641
|
+
[contentFormFields]
|
|
642
|
+
);
|
|
643
|
+
const contentInitialValues = useMemo(() => draftContent ?? {}, [draftContent]);
|
|
644
|
+
const subrouteNotice = useMemo(
|
|
645
|
+
() => resolveSubrouteNotice(entrySubrouteContext ?? null, { siteId }),
|
|
646
|
+
[entrySubrouteContext, siteId]
|
|
647
|
+
);
|
|
648
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-0 flex-col overflow-hidden", children: [
|
|
649
|
+
/* @__PURE__ */ jsx("div", { className: "shrink-0 border-b px-4 py-2", children: /* @__PURE__ */ jsx(
|
|
650
|
+
PreviewViewportControls,
|
|
651
|
+
{
|
|
652
|
+
mode: viewportMode,
|
|
653
|
+
width: viewportWidth,
|
|
654
|
+
onModeChange: onViewportModeChange,
|
|
655
|
+
onWidthChange: onViewportWidthChange,
|
|
656
|
+
zoom,
|
|
657
|
+
onZoomChange
|
|
658
|
+
}
|
|
659
|
+
) }),
|
|
660
|
+
subrouteNotice ? /* @__PURE__ */ jsx(SubrouteNotice, { model: subrouteNotice }) : null,
|
|
661
|
+
/* @__PURE__ */ jsxs("div", { className: "min-h-0 flex-1 space-y-0 overflow-auto", children: [
|
|
662
|
+
contentTypeSchema && entryData.type ? /* @__PURE__ */ jsx(Collapsible, { open: contentOpen, onOpenChange: setContentOpen, children: /* @__PURE__ */ jsxs("div", { className: "border-b", children: [
|
|
663
|
+
/* @__PURE__ */ jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: "flex cursor-pointer items-center gap-2 px-4 py-3 hover:bg-accent/40", children: [
|
|
664
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${contentOpen ? "rotate-90" : ""}` }),
|
|
665
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Content" })
|
|
666
|
+
] }) }),
|
|
667
|
+
/* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-4", children: /* @__PURE__ */ jsx(
|
|
668
|
+
PreviewFormProvider,
|
|
669
|
+
{
|
|
670
|
+
siteId,
|
|
671
|
+
theme: theme ?? null,
|
|
672
|
+
previewClient: apiClient,
|
|
673
|
+
bearerToken,
|
|
674
|
+
supabaseUrl,
|
|
675
|
+
mediaAssets,
|
|
676
|
+
children: /* @__PURE__ */ jsx(
|
|
677
|
+
ContentFieldsForm,
|
|
678
|
+
{
|
|
679
|
+
siteId,
|
|
680
|
+
form: contentFormSpec,
|
|
681
|
+
initialValues: contentInitialValues,
|
|
682
|
+
widgets
|
|
683
|
+
}
|
|
684
|
+
)
|
|
685
|
+
}
|
|
686
|
+
) }) })
|
|
687
|
+
] }) }) : /* @__PURE__ */ jsx("div", { className: "border-b px-4 py-3", children: /* @__PURE__ */ jsx("div", { className: "rounded-md border border-dashed px-4 py-6 text-sm text-muted-foreground", children: "Content schema not available for this entry type." }) }),
|
|
688
|
+
currentTemplate && entryData.type ? /* @__PURE__ */ jsx(Collapsible, { open: templateOpen, onOpenChange: setTemplateOpen, children: /* @__PURE__ */ jsxs("div", { className: "border-b", children: [
|
|
689
|
+
/* @__PURE__ */ jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: "flex cursor-pointer items-center gap-2 px-4 py-3 hover:bg-accent/40", children: [
|
|
690
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${templateOpen ? "rotate-90" : ""}` }),
|
|
691
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Template" })
|
|
692
|
+
] }) }),
|
|
693
|
+
/* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsxs("div", { className: "space-y-3 px-4 pb-4", children: [
|
|
694
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-md border px-4 py-3", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
695
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: currentTemplate.name }),
|
|
696
|
+
currentTemplate.description ? /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: currentTemplate.description }) : null
|
|
697
|
+
] }) }),
|
|
698
|
+
/* @__PURE__ */ jsx(
|
|
699
|
+
"a",
|
|
700
|
+
{
|
|
701
|
+
href: (() => {
|
|
702
|
+
try {
|
|
703
|
+
return getDashboardUrl(`sites/${siteId}/content/${entryData.type}/template`);
|
|
704
|
+
} catch {
|
|
705
|
+
return "#";
|
|
706
|
+
}
|
|
707
|
+
})(),
|
|
708
|
+
target: "_blank",
|
|
709
|
+
rel: "noopener noreferrer",
|
|
710
|
+
className: "inline-flex items-center text-sm text-primary hover:underline",
|
|
711
|
+
children: "Edit template in dashboard \u2192"
|
|
712
|
+
}
|
|
713
|
+
)
|
|
714
|
+
] }) })
|
|
715
|
+
] }) }) : null,
|
|
716
|
+
metadataPanel ? /* @__PURE__ */ jsx(Collapsible, { open: detailsOpen, onOpenChange: setDetailsOpen, children: /* @__PURE__ */ jsxs("div", { className: "border-b", children: [
|
|
717
|
+
/* @__PURE__ */ jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: "flex cursor-pointer items-center gap-2 px-4 py-3 hover:bg-accent/40", children: [
|
|
718
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${detailsOpen ? "rotate-90" : ""}` }),
|
|
719
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Details" })
|
|
720
|
+
] }) }),
|
|
721
|
+
/* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-4", children: metadataPanel }) })
|
|
722
|
+
] }) }) : null
|
|
723
|
+
] })
|
|
724
|
+
] });
|
|
725
|
+
}
|
|
726
|
+
var EMPTY_BLOCK_VALUES = {};
|
|
727
|
+
function SelectedBlockCustomizePanel({
|
|
728
|
+
blockItems,
|
|
729
|
+
uiStoreForBlockEditor,
|
|
730
|
+
theme,
|
|
731
|
+
sdkConfig
|
|
732
|
+
}) {
|
|
733
|
+
const customizeSelection = usePreviewBlockCustomizeSelectionOptional();
|
|
734
|
+
const selection = customizeSelection?.selection ?? { kind: "none" };
|
|
735
|
+
if (selection.kind === "none") {
|
|
736
|
+
return /* @__PURE__ */ jsx("div", { className: "rounded-md border border-dashed px-3 py-4 text-sm text-muted-foreground", children: "Select a section in the preview to customize it." });
|
|
737
|
+
}
|
|
738
|
+
const selectedBlock = blockItems.find((block) => block.id === selection.blockId) ?? null;
|
|
739
|
+
if (!selectedBlock) {
|
|
740
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-md border border-dashed px-3 py-4 text-sm text-muted-foreground", children: [
|
|
741
|
+
/* @__PURE__ */ jsx("p", { children: "This section is no longer on the page." }),
|
|
742
|
+
/* @__PURE__ */ jsx(
|
|
743
|
+
"button",
|
|
744
|
+
{
|
|
745
|
+
type: "button",
|
|
746
|
+
className: "text-xs font-medium text-foreground underline underline-offset-4",
|
|
747
|
+
onClick: customizeSelection?.clearInspection,
|
|
748
|
+
children: "Clear selection"
|
|
749
|
+
}
|
|
750
|
+
)
|
|
751
|
+
] });
|
|
752
|
+
}
|
|
753
|
+
return /* @__PURE__ */ jsx(
|
|
754
|
+
SelectedBlockCustomizeFields,
|
|
755
|
+
{
|
|
756
|
+
block: selectedBlock,
|
|
757
|
+
blockId: selection.blockId,
|
|
758
|
+
uiStoreForBlockEditor,
|
|
759
|
+
theme,
|
|
760
|
+
sdkConfig
|
|
761
|
+
}
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
function SelectedBlockCustomizeFields({
|
|
765
|
+
block,
|
|
766
|
+
blockId,
|
|
767
|
+
uiStoreForBlockEditor,
|
|
768
|
+
theme,
|
|
769
|
+
sdkConfig
|
|
770
|
+
}) {
|
|
771
|
+
const blockContent = useBlockCurrentValues(
|
|
772
|
+
uiStoreForBlockEditor,
|
|
773
|
+
blockId,
|
|
774
|
+
block.content ?? EMPTY_BLOCK_VALUES
|
|
775
|
+
);
|
|
776
|
+
const customizeControls = useBlockCustomizeControls({
|
|
777
|
+
blockId,
|
|
778
|
+
blockKind: block.kind,
|
|
779
|
+
content: blockContent,
|
|
780
|
+
uiStore: uiStoreForBlockEditor,
|
|
781
|
+
theme,
|
|
782
|
+
sdkConfig,
|
|
783
|
+
maxQuickFields: MAX_QUICK_MANIFEST_FIELDS
|
|
784
|
+
});
|
|
785
|
+
const { backend, manifest, panelFields, quickFields } = customizeControls;
|
|
786
|
+
if (!manifest || !backend) {
|
|
787
|
+
return /* @__PURE__ */ jsx("div", { className: "rounded-md border border-dashed px-3 py-4 text-sm text-muted-foreground", children: "No customize controls are available for this section." });
|
|
788
|
+
}
|
|
789
|
+
if (quickFields.length === 0 && panelFields.length === 0) {
|
|
790
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1 rounded-md border border-dashed px-3 py-4 text-sm text-muted-foreground", children: [
|
|
791
|
+
/* @__PURE__ */ jsx("p", { children: "No safe customize controls are available for this section yet." }),
|
|
792
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs", children: "Content fields remain in Page Blocks." })
|
|
793
|
+
] });
|
|
794
|
+
}
|
|
795
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
|
|
796
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
797
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm font-semibold", children: manifest.title }),
|
|
798
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: formatBlockKindLabel(block.kind) })
|
|
799
|
+
] }),
|
|
800
|
+
quickFields.length > 0 ? /* @__PURE__ */ jsxs("section", { className: "space-y-3", children: [
|
|
801
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs font-medium uppercase text-muted-foreground", children: "Quick controls" }),
|
|
802
|
+
/* @__PURE__ */ jsx(
|
|
803
|
+
FormRenderer,
|
|
804
|
+
{
|
|
805
|
+
fields: quickFields,
|
|
806
|
+
backend,
|
|
807
|
+
widgetOverrides: previewWidgetOverrides,
|
|
808
|
+
showSettingsTrigger: false,
|
|
809
|
+
blockKind: block.kind,
|
|
810
|
+
className: "space-y-4"
|
|
811
|
+
}
|
|
812
|
+
)
|
|
813
|
+
] }) : null,
|
|
814
|
+
panelFields.length > 0 ? /* @__PURE__ */ jsxs("section", { className: "space-y-3", children: [
|
|
815
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs font-medium uppercase text-muted-foreground", children: "More settings" }),
|
|
816
|
+
/* @__PURE__ */ jsx(
|
|
817
|
+
FormRenderer,
|
|
818
|
+
{
|
|
819
|
+
fields: panelFields,
|
|
820
|
+
backend,
|
|
821
|
+
widgetOverrides: previewWidgetOverrides,
|
|
822
|
+
showSettingsTrigger: false,
|
|
823
|
+
blockKind: block.kind,
|
|
824
|
+
className: "space-y-4"
|
|
825
|
+
}
|
|
826
|
+
)
|
|
827
|
+
] }) : null
|
|
828
|
+
] });
|
|
829
|
+
}
|
|
830
|
+
function useBlockCurrentValues(store, blockId, fallback) {
|
|
831
|
+
return useSyncExternalStore(
|
|
832
|
+
store.subscribe,
|
|
833
|
+
() => readBlockCurrentValues(store, blockId, fallback),
|
|
834
|
+
() => fallback
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
function readBlockCurrentValues(store, blockId, fallback) {
|
|
838
|
+
const values = store.getState().drafts.getCurrentValues(blockId);
|
|
839
|
+
return values ?? fallback;
|
|
840
|
+
}
|
|
841
|
+
function formatBlockKindLabel(blockKind) {
|
|
842
|
+
return blockKind.replace(/^block\./, "").replace(/^custom\./, "").replace(/[-_]+/g, " ");
|
|
843
|
+
}
|
|
844
|
+
function PageBlockEditorPanel({
|
|
845
|
+
siteId,
|
|
846
|
+
pageId,
|
|
847
|
+
theme,
|
|
848
|
+
apiClient,
|
|
849
|
+
bearerToken,
|
|
850
|
+
supabaseUrl,
|
|
851
|
+
blockItems,
|
|
852
|
+
uiStoreForBlockEditor,
|
|
853
|
+
blockEditorRef,
|
|
854
|
+
onBlocksChange,
|
|
855
|
+
metadataPanel,
|
|
856
|
+
designPanel,
|
|
857
|
+
sdkConfig,
|
|
858
|
+
mediaAssets,
|
|
859
|
+
viewportMode,
|
|
860
|
+
viewportWidth,
|
|
861
|
+
zoom,
|
|
862
|
+
onViewportModeChange,
|
|
863
|
+
onViewportWidthChange,
|
|
864
|
+
onZoomChange
|
|
865
|
+
}) {
|
|
866
|
+
const customizeSelection = usePreviewBlockCustomizeSelectionOptional();
|
|
867
|
+
const [designOpen, setDesignOpen] = useState(false);
|
|
868
|
+
const [customizeOpen, setCustomizeOpen] = useState(false);
|
|
869
|
+
const [contentOpen, setContentOpen] = useState(true);
|
|
870
|
+
const [detailsOpen, setDetailsOpen] = useState(false);
|
|
871
|
+
const selectionKind = customizeSelection?.selection.kind;
|
|
872
|
+
const selectionRequestId = customizeSelection?.selection.kind === "selected" ? customizeSelection.selection.requestId : 0;
|
|
873
|
+
useEffect(() => {
|
|
874
|
+
if (selectionKind === "selected") {
|
|
875
|
+
setCustomizeOpen(true);
|
|
876
|
+
}
|
|
877
|
+
}, [selectionKind, selectionRequestId]);
|
|
878
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-0 flex-col overflow-hidden", children: [
|
|
879
|
+
/* @__PURE__ */ jsx("div", { className: "shrink-0 border-b px-4 py-2", children: /* @__PURE__ */ jsx(
|
|
880
|
+
PreviewViewportControls,
|
|
881
|
+
{
|
|
882
|
+
mode: viewportMode,
|
|
883
|
+
width: viewportWidth,
|
|
884
|
+
onModeChange: onViewportModeChange,
|
|
885
|
+
onWidthChange: onViewportWidthChange,
|
|
886
|
+
zoom,
|
|
887
|
+
onZoomChange
|
|
888
|
+
}
|
|
889
|
+
) }),
|
|
890
|
+
/* @__PURE__ */ jsx(
|
|
891
|
+
PreviewFormProvider,
|
|
892
|
+
{
|
|
893
|
+
siteId,
|
|
894
|
+
theme: theme ?? null,
|
|
895
|
+
previewClient: apiClient,
|
|
896
|
+
bearerToken,
|
|
897
|
+
supabaseUrl,
|
|
898
|
+
mediaAssets,
|
|
899
|
+
sdkConfig: sdkConfig ?? null,
|
|
900
|
+
children: /* @__PURE__ */ jsxs("div", { className: "min-h-0 flex-1 space-y-0 overflow-auto", children: [
|
|
901
|
+
designPanel ? /* @__PURE__ */ jsx(Collapsible, { open: designOpen, onOpenChange: setDesignOpen, children: /* @__PURE__ */ jsxs("div", { className: "border-b", children: [
|
|
902
|
+
/* @__PURE__ */ jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: "flex cursor-pointer items-center gap-2 px-4 py-3 hover:bg-accent/40", children: [
|
|
903
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${designOpen ? "rotate-90" : ""}` }),
|
|
904
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Design" })
|
|
905
|
+
] }) }),
|
|
906
|
+
/* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-4", children: designPanel }) })
|
|
907
|
+
] }) }) : null,
|
|
908
|
+
/* @__PURE__ */ jsx(Collapsible, { open: customizeOpen, onOpenChange: setCustomizeOpen, children: /* @__PURE__ */ jsxs("div", { className: "border-b", children: [
|
|
909
|
+
/* @__PURE__ */ jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: "flex cursor-pointer items-center gap-2 px-4 py-3 hover:bg-accent/40", children: [
|
|
910
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${customizeOpen ? "rotate-90" : ""}` }),
|
|
911
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Customize" })
|
|
912
|
+
] }) }),
|
|
913
|
+
/* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-4", children: /* @__PURE__ */ jsx(
|
|
914
|
+
SelectedBlockCustomizePanel,
|
|
915
|
+
{
|
|
916
|
+
blockItems,
|
|
917
|
+
uiStoreForBlockEditor,
|
|
918
|
+
theme: theme ?? null,
|
|
919
|
+
sdkConfig: sdkConfig ?? null
|
|
920
|
+
}
|
|
921
|
+
) }) })
|
|
922
|
+
] }) }),
|
|
923
|
+
/* @__PURE__ */ jsx(Collapsible, { open: contentOpen, onOpenChange: setContentOpen, children: /* @__PURE__ */ jsxs("div", { className: "border-b", children: [
|
|
924
|
+
/* @__PURE__ */ jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: "flex cursor-pointer items-center gap-2 px-4 py-3 hover:bg-accent/40", children: [
|
|
925
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${contentOpen ? "rotate-90" : ""}` }),
|
|
926
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Page Blocks" })
|
|
927
|
+
] }) }),
|
|
928
|
+
/* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-4", children: /* @__PURE__ */ jsx(
|
|
929
|
+
BlockEditor,
|
|
930
|
+
{
|
|
931
|
+
ref: blockEditorRef,
|
|
932
|
+
siteId,
|
|
933
|
+
pageId,
|
|
934
|
+
blocks: blockItems,
|
|
935
|
+
uiStore: uiStoreForBlockEditor,
|
|
936
|
+
apiClient,
|
|
937
|
+
onBlocksChange,
|
|
938
|
+
widgetOverrides: previewWidgetOverrides,
|
|
939
|
+
sdkConfig: sdkConfig ?? null,
|
|
940
|
+
reorderControlMode: "drag-handle-only",
|
|
941
|
+
actionControlMode: "menu"
|
|
942
|
+
},
|
|
943
|
+
pageId
|
|
944
|
+
) }) })
|
|
945
|
+
] }) }),
|
|
946
|
+
metadataPanel ? /* @__PURE__ */ jsx(Collapsible, { open: detailsOpen, onOpenChange: setDetailsOpen, children: /* @__PURE__ */ jsxs("div", { className: "border-b", children: [
|
|
947
|
+
/* @__PURE__ */ jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: "flex cursor-pointer items-center gap-2 px-4 py-3 hover:bg-accent/40", children: [
|
|
948
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: `h-4 w-4 transition ${detailsOpen ? "rotate-90" : ""}` }),
|
|
949
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Page Details" })
|
|
950
|
+
] }) }),
|
|
951
|
+
/* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-4", children: metadataPanel }) })
|
|
952
|
+
] }) }) : null
|
|
953
|
+
] })
|
|
954
|
+
}
|
|
955
|
+
)
|
|
956
|
+
] });
|
|
957
|
+
}
|
|
958
|
+
function PreviewEditorGuidanceCard({
|
|
959
|
+
guidance,
|
|
960
|
+
completeSetupDisabled,
|
|
961
|
+
onCompleteSetup
|
|
962
|
+
}) {
|
|
963
|
+
return /* @__PURE__ */ jsxs(
|
|
964
|
+
"div",
|
|
965
|
+
{
|
|
966
|
+
className: guidance.cardClassName,
|
|
967
|
+
"data-preview-guidance-tone": guidance.tone,
|
|
968
|
+
children: [
|
|
969
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-semibold tracking-tight text-slate-900", children: guidance.title }),
|
|
970
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs leading-5 text-slate-700", children: guidance.description }),
|
|
971
|
+
/* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
|
|
972
|
+
Button,
|
|
973
|
+
{
|
|
974
|
+
type: "button",
|
|
975
|
+
size: "sm",
|
|
976
|
+
variant: "outline",
|
|
977
|
+
onClick: onCompleteSetup,
|
|
978
|
+
disabled: completeSetupDisabled,
|
|
979
|
+
children: guidance.ctaLabel
|
|
980
|
+
}
|
|
981
|
+
) })
|
|
982
|
+
]
|
|
983
|
+
}
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// ../ui/src/palette/hex.ts
|
|
988
|
+
function normalizeHex(input, fallback = "#000000") {
|
|
989
|
+
if (!input) return fallback;
|
|
990
|
+
const trimmed = input.trim().replace(/^#/, "").toLowerCase();
|
|
991
|
+
const expanded = trimmed.length === 3 ? trimmed.split("").map((char) => char + char).join("") : trimmed;
|
|
992
|
+
if (!/^[0-9a-f]{6}$/i.test(expanded)) {
|
|
993
|
+
return fallback;
|
|
994
|
+
}
|
|
995
|
+
return `#${expanded}`;
|
|
996
|
+
}
|
|
997
|
+
function hexToRgb(hex) {
|
|
998
|
+
const normalized = normalizeHex(hex).replace("#", "");
|
|
999
|
+
const match = normalized.match(/.{2}/g);
|
|
1000
|
+
if (!match || match.length < 3) return null;
|
|
1001
|
+
const [r, g, b] = match.map((value) => parseInt(value, 16));
|
|
1002
|
+
return [r, g, b];
|
|
1003
|
+
}
|
|
1004
|
+
function luminanceChannel(raw) {
|
|
1005
|
+
const channel = raw / 255;
|
|
1006
|
+
return channel <= 0.03928 ? channel / 12.92 : Math.pow((channel + 0.055) / 1.055, 2.4);
|
|
1007
|
+
}
|
|
1008
|
+
function contrastRatio(hexA, hexB) {
|
|
1009
|
+
const rgbA = hexToRgb(hexA);
|
|
1010
|
+
const rgbB = hexToRgb(hexB);
|
|
1011
|
+
if (!rgbA || !rgbB) return 1;
|
|
1012
|
+
const lumA = 0.2126 * luminanceChannel(rgbA[0]) + 0.7152 * luminanceChannel(rgbA[1]) + 0.0722 * luminanceChannel(rgbA[2]);
|
|
1013
|
+
const lumB = 0.2126 * luminanceChannel(rgbB[0]) + 0.7152 * luminanceChannel(rgbB[1]) + 0.0722 * luminanceChannel(rgbB[2]);
|
|
1014
|
+
const brighter = Math.max(lumA, lumB);
|
|
1015
|
+
const darker = Math.min(lumA, lumB);
|
|
1016
|
+
return Number(((brighter + 0.05) / (darker + 0.05)).toFixed(2));
|
|
1017
|
+
}
|
|
1018
|
+
var DEFAULT_BRAND_ROLES = [
|
|
1019
|
+
{ key: "primary", label: "Primary" },
|
|
1020
|
+
{ key: "secondary", label: "Secondary" },
|
|
1021
|
+
{ key: "accent", label: "Accent" }
|
|
1022
|
+
];
|
|
1023
|
+
var DEFAULT_FEEDBACK_ROLES = [
|
|
1024
|
+
{ key: "success", label: "Success" },
|
|
1025
|
+
{ key: "warning", label: "Warning" },
|
|
1026
|
+
{ key: "destructive", label: "Destructive" }
|
|
1027
|
+
];
|
|
1028
|
+
var DEFAULT_SURFACE_ROLES = [
|
|
1029
|
+
{ key: "background", label: "Background" },
|
|
1030
|
+
{ key: "surface", label: "Surface" },
|
|
1031
|
+
{ key: "text", label: "Text" },
|
|
1032
|
+
{ key: "border", label: "Border" }
|
|
1033
|
+
];
|
|
1034
|
+
function PaletteTokenEditor({
|
|
1035
|
+
palette,
|
|
1036
|
+
onChange,
|
|
1037
|
+
hasInvalidValues = false,
|
|
1038
|
+
brandRoles = DEFAULT_BRAND_ROLES,
|
|
1039
|
+
feedbackRoles = DEFAULT_FEEDBACK_ROLES,
|
|
1040
|
+
surfaceRoles = DEFAULT_SURFACE_ROLES,
|
|
1041
|
+
showContrastChecks = true,
|
|
1042
|
+
className
|
|
1043
|
+
}) {
|
|
1044
|
+
const paletteValues = useMemo(() => {
|
|
1045
|
+
const map = /* @__PURE__ */ new Map();
|
|
1046
|
+
for (const entry of palette) {
|
|
1047
|
+
if (entry?.name) map.set(entry.name, normalizeHex(entry.hex));
|
|
1048
|
+
}
|
|
1049
|
+
return map;
|
|
1050
|
+
}, [palette]);
|
|
1051
|
+
const getColor = (role) => paletteValues.get(role) ?? "#000000";
|
|
1052
|
+
const handleRoleChange = (role, hex) => {
|
|
1053
|
+
if (!isPlausibleHexInput(hex)) return;
|
|
1054
|
+
onChange(role, normalizeHex(hex));
|
|
1055
|
+
};
|
|
1056
|
+
const backgroundHex = getColor("background");
|
|
1057
|
+
const textHex = getColor("text");
|
|
1058
|
+
const primaryHex = getColor("primary");
|
|
1059
|
+
const contrast = contrastRatio(backgroundHex, textHex);
|
|
1060
|
+
const actionContrast = contrastRatio(primaryHex, backgroundHex);
|
|
1061
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-6", className), children: [
|
|
1062
|
+
hasInvalidValues ? /* @__PURE__ */ jsx(Callout, { tone: "danger", role: "alert", className: "p-3", children: /* @__PURE__ */ jsx(CalloutDescription, { className: "mt-0", children: "Some palette values are invalid or incomplete." }) }) : null,
|
|
1063
|
+
/* @__PURE__ */ jsx(ColorRoleGroup, { title: "Brand & accents", roles: brandRoles, getColor, onChange: handleRoleChange }),
|
|
1064
|
+
/* @__PURE__ */ jsx(ColorRoleGroup, { title: "Feedback", roles: feedbackRoles, getColor, onChange: handleRoleChange }),
|
|
1065
|
+
/* @__PURE__ */ jsx(ColorRoleGroup, { title: "Surfaces & text", roles: surfaceRoles, getColor, onChange: handleRoleChange }),
|
|
1066
|
+
showContrastChecks ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1067
|
+
/* @__PURE__ */ jsx(Separator, {}),
|
|
1068
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
|
|
1069
|
+
/* @__PURE__ */ jsx(
|
|
1070
|
+
ContrastCard,
|
|
1071
|
+
{
|
|
1072
|
+
title: "Background \u2194 Text contrast",
|
|
1073
|
+
value: contrast,
|
|
1074
|
+
threshold: 4.5,
|
|
1075
|
+
primaryLabel: "Background",
|
|
1076
|
+
primaryHex: backgroundHex,
|
|
1077
|
+
secondaryLabel: "Text",
|
|
1078
|
+
secondaryHex: textHex
|
|
1079
|
+
}
|
|
1080
|
+
),
|
|
1081
|
+
/* @__PURE__ */ jsx(
|
|
1082
|
+
ContrastCard,
|
|
1083
|
+
{
|
|
1084
|
+
title: "Primary action contrast",
|
|
1085
|
+
value: actionContrast,
|
|
1086
|
+
threshold: 3,
|
|
1087
|
+
primaryLabel: "Primary",
|
|
1088
|
+
primaryHex,
|
|
1089
|
+
secondaryLabel: "Background",
|
|
1090
|
+
secondaryHex: backgroundHex
|
|
1091
|
+
}
|
|
1092
|
+
)
|
|
1093
|
+
] })
|
|
1094
|
+
] }) : null
|
|
1095
|
+
] });
|
|
1096
|
+
}
|
|
1097
|
+
function isPlausibleHexInput(value) {
|
|
1098
|
+
const trimmed = value.trim();
|
|
1099
|
+
return /^#?[0-9a-fA-F]{3}$/.test(trimmed) || /^#?[0-9a-fA-F]{6}$/.test(trimmed);
|
|
1100
|
+
}
|
|
1101
|
+
function ColorRoleGroup({ title, roles, getColor, onChange }) {
|
|
1102
|
+
if (roles.length === 0) return null;
|
|
1103
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
1104
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1105
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-foreground", children: title }),
|
|
1106
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Adjust hex values or drag the color picker." })
|
|
1107
|
+
] }),
|
|
1108
|
+
/* @__PURE__ */ jsx("div", { className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3", children: roles.map((role) => /* @__PURE__ */ jsx(ColorRoleField, { role, value: getColor(role.key), onChange }, role.key)) })
|
|
1109
|
+
] });
|
|
1110
|
+
}
|
|
1111
|
+
function ColorRoleField({ role, value, onChange }) {
|
|
1112
|
+
const inputId = useId();
|
|
1113
|
+
const colorPickerLabel = `${role.label} colour picker`;
|
|
1114
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-lg bg-muted p-3 space-y-2", children: [
|
|
1115
|
+
/* @__PURE__ */ jsx(
|
|
1116
|
+
"label",
|
|
1117
|
+
{
|
|
1118
|
+
htmlFor: inputId,
|
|
1119
|
+
className: "text-xs uppercase text-muted-foreground font-medium leading-5 block",
|
|
1120
|
+
children: role.label
|
|
1121
|
+
}
|
|
1122
|
+
),
|
|
1123
|
+
/* @__PURE__ */ jsx(
|
|
1124
|
+
ColorPicker,
|
|
1125
|
+
{
|
|
1126
|
+
value,
|
|
1127
|
+
onChange: ((next) => {
|
|
1128
|
+
if (typeof next === "string") {
|
|
1129
|
+
onChange(role.key, next);
|
|
1130
|
+
}
|
|
1131
|
+
}),
|
|
1132
|
+
className: "w-full",
|
|
1133
|
+
"aria-label": colorPickerLabel
|
|
1134
|
+
}
|
|
1135
|
+
),
|
|
1136
|
+
/* @__PURE__ */ jsx(
|
|
1137
|
+
Input,
|
|
1138
|
+
{
|
|
1139
|
+
id: inputId,
|
|
1140
|
+
value,
|
|
1141
|
+
onChange: (event) => onChange(role.key, event.target.value),
|
|
1142
|
+
className: "font-mono text-xs uppercase",
|
|
1143
|
+
placeholder: "#000000"
|
|
1144
|
+
}
|
|
1145
|
+
)
|
|
1146
|
+
] });
|
|
1147
|
+
}
|
|
1148
|
+
function ContrastCard({ title, value, threshold, primaryLabel, primaryHex, secondaryLabel, secondaryHex }) {
|
|
1149
|
+
const passes = value >= threshold;
|
|
1150
|
+
return /* @__PURE__ */ jsxs(
|
|
1151
|
+
Surface,
|
|
1152
|
+
{
|
|
1153
|
+
variant: "background",
|
|
1154
|
+
radius: "lg",
|
|
1155
|
+
padding: "md",
|
|
1156
|
+
className: cn(
|
|
1157
|
+
"space-y-2 border text-sm",
|
|
1158
|
+
passes ? "border-success/40 bg-success/10 text-success" : "border-warning/40 bg-warning/10 text-warning-foreground"
|
|
1159
|
+
),
|
|
1160
|
+
children: [
|
|
1161
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
1162
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: title }),
|
|
1163
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs font-semibold", children: [
|
|
1164
|
+
value,
|
|
1165
|
+
":1"
|
|
1166
|
+
] })
|
|
1167
|
+
] }),
|
|
1168
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs", children: passes ? "Contrast meets WCAG AA guidance." : `Increase contrast to reach ${threshold}:1 or higher.` }),
|
|
1169
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 pt-2 text-xs", children: [
|
|
1170
|
+
/* @__PURE__ */ jsx(ContrastSwatch, { hex: primaryHex, label: primaryLabel }),
|
|
1171
|
+
/* @__PURE__ */ jsx(ContrastSwatch, { hex: secondaryHex, label: secondaryLabel })
|
|
1172
|
+
] })
|
|
1173
|
+
]
|
|
1174
|
+
}
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1177
|
+
function ContrastSwatch({ hex, label }) {
|
|
1178
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1179
|
+
/* @__PURE__ */ jsx("span", { className: "h-6 w-6 rounded-full border", style: { backgroundColor: hex } }),
|
|
1180
|
+
/* @__PURE__ */ jsxs("span", { className: "font-mono text-xs uppercase tracking-wide text-muted-foreground", children: [
|
|
1181
|
+
label,
|
|
1182
|
+
": ",
|
|
1183
|
+
hex
|
|
1184
|
+
] })
|
|
1185
|
+
] });
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
// ../preview-next/src/client/preview/styleConfiguratorApplyPayload.ts
|
|
1189
|
+
function buildApplySiteStyleSelectionBody(input) {
|
|
1190
|
+
return {
|
|
1191
|
+
siteStyleId: input.previewed.siteStyleId,
|
|
1192
|
+
buttonPersonalityId: input.previewed.buttonPersonalityId,
|
|
1193
|
+
paletteVariantId: input.previewed.paletteVariantId,
|
|
1194
|
+
paletteOverrides: input.previewed.paletteOverrides,
|
|
1195
|
+
typographyPresetId: input.previewed.typographyPresetId,
|
|
1196
|
+
typographyBodyFontOverride: input.previewed.typographyBodyFontOverride,
|
|
1197
|
+
typographyHeadingFontOverride: input.previewed.typographyHeadingFontOverride,
|
|
1198
|
+
headerLookId: input.previewed.headerLookId,
|
|
1199
|
+
headerLayoutId: input.previewed.headerLayoutId,
|
|
1200
|
+
headerStyleId: input.previewed.headerStyleId,
|
|
1201
|
+
footerLookId: input.previewed.footerLookId,
|
|
1202
|
+
baseSelectionVersion: input.base.selectionVersion,
|
|
1203
|
+
selectionId: input.selectionId ?? null
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
var MAX_FONT_RESULTS_WHEN_SEARCHING = 12;
|
|
1207
|
+
var MAX_FONT_RESULTS_WHEN_BROWSING = 8;
|
|
1208
|
+
var PALETTE_BRAND_ROLES = [
|
|
1209
|
+
{ key: "primary", label: "Primary" },
|
|
1210
|
+
{ key: "primaryForeground", label: "Primary text" },
|
|
1211
|
+
{ key: "secondary", label: "Secondary" },
|
|
1212
|
+
{ key: "accent", label: "Accent" }
|
|
1213
|
+
];
|
|
1214
|
+
var PALETTE_FEEDBACK_ROLES = [
|
|
1215
|
+
{ key: "success", label: "Success" },
|
|
1216
|
+
{ key: "warning", label: "Warning" },
|
|
1217
|
+
{ key: "destructive", label: "Destructive" }
|
|
1218
|
+
];
|
|
1219
|
+
var PALETTE_SURFACE_ROLES = [
|
|
1220
|
+
{ key: "background", label: "Background" },
|
|
1221
|
+
{ key: "surface", label: "Surface" },
|
|
1222
|
+
{ key: "surfaceAlt", label: "Alternate surface" },
|
|
1223
|
+
{ key: "muted", label: "Muted surface" },
|
|
1224
|
+
{ key: "text", label: "Text" },
|
|
1225
|
+
{ key: "border", label: "Border" }
|
|
1226
|
+
];
|
|
1227
|
+
function StyleConfigurator({
|
|
1228
|
+
siteId,
|
|
1229
|
+
apiClient,
|
|
1230
|
+
theme,
|
|
1231
|
+
selectionId,
|
|
1232
|
+
selectionVersion = null,
|
|
1233
|
+
selectionUpdatedAt,
|
|
1234
|
+
initialSelections,
|
|
1235
|
+
headerSelectionContext,
|
|
1236
|
+
footerSelectionContext,
|
|
1237
|
+
expandedRowRequest = null,
|
|
1238
|
+
siteStyleAccess
|
|
1239
|
+
}) {
|
|
1240
|
+
const router = useRouter();
|
|
1241
|
+
const previewDesign = usePreviewDesignCoordinator();
|
|
1242
|
+
const {
|
|
1243
|
+
applyFailed,
|
|
1244
|
+
applyStarted,
|
|
1245
|
+
applySucceeded,
|
|
1246
|
+
clearPreview,
|
|
1247
|
+
previewTheme
|
|
1248
|
+
} = previewDesign;
|
|
1249
|
+
const [isRefreshPending, startRefreshTransition] = useTransition();
|
|
1250
|
+
const baseSnapshot = useMemo(
|
|
1251
|
+
() => resolveStyleConfiguratorInitialSnapshot({
|
|
1252
|
+
theme,
|
|
1253
|
+
initialSelections: {
|
|
1254
|
+
...initialSelections,
|
|
1255
|
+
selectionVersion: initialSelections.selectionVersion ?? selectionVersion ?? 0,
|
|
1256
|
+
selectionUpdatedAt: initialSelections.selectionUpdatedAt ?? selectionUpdatedAt ?? null
|
|
1257
|
+
},
|
|
1258
|
+
headerSelectionContext,
|
|
1259
|
+
footerSelectionContext
|
|
1260
|
+
}),
|
|
1261
|
+
[footerSelectionContext, headerSelectionContext, theme, initialSelections, selectionUpdatedAt, selectionVersion]
|
|
1262
|
+
);
|
|
1263
|
+
const [state, setState] = useState(
|
|
1264
|
+
() => baseSnapshot ? createStyleConfiguratorState(baseSnapshot) : null
|
|
1265
|
+
);
|
|
1266
|
+
const hasLoadedState = state !== null;
|
|
1267
|
+
const stateRef = useRef(state);
|
|
1268
|
+
const clearPreviewRef = useRef(clearPreview);
|
|
1269
|
+
const runnerRef = useRef(createOptimisticActionRunner());
|
|
1270
|
+
const appliedExpandedRowRequestIdRef = useRef(null);
|
|
1271
|
+
useEffect(() => {
|
|
1272
|
+
stateRef.current = state;
|
|
1273
|
+
}, [state]);
|
|
1274
|
+
useEffect(() => {
|
|
1275
|
+
clearPreviewRef.current = clearPreview;
|
|
1276
|
+
}, [clearPreview]);
|
|
1277
|
+
useEffect(() => {
|
|
1278
|
+
if (!baseSnapshot) return;
|
|
1279
|
+
setState((current) => current ? syncStyleConfiguratorState(current, baseSnapshot) : createStyleConfiguratorState(baseSnapshot));
|
|
1280
|
+
}, [baseSnapshot]);
|
|
1281
|
+
useEffect(() => {
|
|
1282
|
+
if (!state || !baseSnapshot) {
|
|
1283
|
+
clearPreview("style-configurator");
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
if (!isPreviewingChange(state)) {
|
|
1287
|
+
clearPreview("style-configurator");
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
const siteStyle = getCuratedSiteStyle(state.previewed.siteStyleId);
|
|
1291
|
+
if (!siteStyle) {
|
|
1292
|
+
clearPreview("style-configurator");
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
const result = planSiteStylePreviewChange({
|
|
1296
|
+
siteStyle,
|
|
1297
|
+
selection: state.previewed,
|
|
1298
|
+
baseTheme: theme,
|
|
1299
|
+
headerSelectionContext,
|
|
1300
|
+
footerSelectionContext
|
|
1301
|
+
});
|
|
1302
|
+
previewTheme("style-configurator", result.themeOverlay, "site-style");
|
|
1303
|
+
}, [state, baseSnapshot, clearPreview, previewTheme, headerSelectionContext, footerSelectionContext, theme]);
|
|
1304
|
+
useEffect(() => {
|
|
1305
|
+
const runner = runnerRef.current;
|
|
1306
|
+
return () => {
|
|
1307
|
+
runner.invalidate();
|
|
1308
|
+
clearPreviewRef.current("style-configurator");
|
|
1309
|
+
};
|
|
1310
|
+
}, []);
|
|
1311
|
+
useEffect(() => {
|
|
1312
|
+
if (!expandedRowRequest) return;
|
|
1313
|
+
if (appliedExpandedRowRequestIdRef.current === expandedRowRequest.id) return;
|
|
1314
|
+
setState((current) => {
|
|
1315
|
+
if (!current) return current;
|
|
1316
|
+
appliedExpandedRowRequestIdRef.current = expandedRowRequest.id;
|
|
1317
|
+
return openRow(current, expandedRowRequest.row === "header" ? "header-layout" : expandedRowRequest.row);
|
|
1318
|
+
});
|
|
1319
|
+
}, [expandedRowRequest, hasLoadedState]);
|
|
1320
|
+
const handlePreviewSiteStyle = useCallback((siteStyleId) => {
|
|
1321
|
+
setState((current) => {
|
|
1322
|
+
if (!current) return current;
|
|
1323
|
+
const next = previewSiteStyle(current, siteStyleId);
|
|
1324
|
+
const siteStyle = getCuratedSiteStyle(siteStyleId);
|
|
1325
|
+
if (!siteStyle) return next;
|
|
1326
|
+
const result = planSiteStylePreviewChange({
|
|
1327
|
+
siteStyle,
|
|
1328
|
+
selection: next.previewed,
|
|
1329
|
+
baseTheme: theme,
|
|
1330
|
+
headerSelectionContext,
|
|
1331
|
+
footerSelectionContext
|
|
1332
|
+
});
|
|
1333
|
+
return applyResolvedSelections(next, {
|
|
1334
|
+
resolved: result.resolvedSelections,
|
|
1335
|
+
warnings: result.warnings
|
|
1336
|
+
});
|
|
1337
|
+
});
|
|
1338
|
+
}, [footerSelectionContext, headerSelectionContext, theme]);
|
|
1339
|
+
const handlePreviewPersonality = useCallback((id) => {
|
|
1340
|
+
setState((current) => current ? previewButtonPersonality(current, id) : current);
|
|
1341
|
+
}, []);
|
|
1342
|
+
const handlePreviewHeaderLayout = useCallback((id) => {
|
|
1343
|
+
if (!id) return;
|
|
1344
|
+
setState((current) => current ? previewHeaderLayout(current, id) : current);
|
|
1345
|
+
}, []);
|
|
1346
|
+
const handlePreviewHeaderStyle = useCallback((id) => {
|
|
1347
|
+
if (!id) return;
|
|
1348
|
+
setState((current) => current ? previewHeaderStyle(current, id) : current);
|
|
1349
|
+
}, []);
|
|
1350
|
+
const handlePreviewFooterLook = useCallback((id) => {
|
|
1351
|
+
const plan = planSiteChromeLookSelection({ surface: "footer", lookId: id });
|
|
1352
|
+
if (plan.kind !== "footer") return;
|
|
1353
|
+
setState((current) => current ? previewFooterLook(current, plan.next.footerLookId) : current);
|
|
1354
|
+
}, []);
|
|
1355
|
+
const handlePreviewPalette = useCallback((id) => {
|
|
1356
|
+
setState((current) => current ? previewPaletteVariant(current, id) : current);
|
|
1357
|
+
}, []);
|
|
1358
|
+
const handlePreviewTypographyPreset = useCallback((id) => {
|
|
1359
|
+
setState((current) => current ? previewTypographyPreset(current, id) : current);
|
|
1360
|
+
}, []);
|
|
1361
|
+
const handleBodyFontOverride = useCallback((fontFamily) => {
|
|
1362
|
+
setState((current) => current ? setTypographyBodyFontOverride(current, fontFamily) : current);
|
|
1363
|
+
}, []);
|
|
1364
|
+
const handleHeadingFontOverride = useCallback((fontFamily) => {
|
|
1365
|
+
setState((current) => current ? setTypographyHeadingFontOverride(current, fontFamily) : current);
|
|
1366
|
+
}, []);
|
|
1367
|
+
const handleClearTypographyOverrides = useCallback(() => {
|
|
1368
|
+
setState((current) => current ? clearTypographyFontOverrides(current) : current);
|
|
1369
|
+
}, []);
|
|
1370
|
+
const handleTokenOverride = useCallback((role, hex) => {
|
|
1371
|
+
setState((current) => {
|
|
1372
|
+
if (!current) return current;
|
|
1373
|
+
const next = { ...current.previewed.paletteOverrides ?? {}, [role]: hex };
|
|
1374
|
+
const parsed = paletteOverridesSchema.safeParse(next);
|
|
1375
|
+
if (!parsed.success) return current;
|
|
1376
|
+
return setPaletteOverrides(current, parsed.data);
|
|
1377
|
+
});
|
|
1378
|
+
}, []);
|
|
1379
|
+
const handleResetOverrides = useCallback(() => {
|
|
1380
|
+
setState((current) => current ? setPaletteOverrides(current, null) : current);
|
|
1381
|
+
}, []);
|
|
1382
|
+
const handleToggleRow = useCallback((row) => {
|
|
1383
|
+
setState((current) => current ? toggleRow(current, row) : current);
|
|
1384
|
+
}, []);
|
|
1385
|
+
const handleCancel = useCallback(() => {
|
|
1386
|
+
setState((current) => current ? cancelPreview(current) : current);
|
|
1387
|
+
}, []);
|
|
1388
|
+
const handleDismissWarnings = useCallback(() => {
|
|
1389
|
+
setState((current) => current ? dismissWarnings(current) : current);
|
|
1390
|
+
}, []);
|
|
1391
|
+
const handleDismissNotices = useCallback(() => {
|
|
1392
|
+
setState((current) => current ? dismissNotices(current) : current);
|
|
1393
|
+
}, []);
|
|
1394
|
+
const handleApply = useCallback(async () => {
|
|
1395
|
+
const live = stateRef.current;
|
|
1396
|
+
if (!live || !isPreviewingChange(live)) return;
|
|
1397
|
+
const previewed = live.previewed;
|
|
1398
|
+
const siteStyle = getCuratedSiteStyle(previewed.siteStyleId);
|
|
1399
|
+
if (!siteStyle) return;
|
|
1400
|
+
const compiled = planSiteStylePreviewChange({
|
|
1401
|
+
siteStyle,
|
|
1402
|
+
selection: previewed,
|
|
1403
|
+
baseTheme: theme,
|
|
1404
|
+
headerSelectionContext,
|
|
1405
|
+
footerSelectionContext
|
|
1406
|
+
});
|
|
1407
|
+
const body = buildApplySiteStyleSelectionBody({
|
|
1408
|
+
base: live.base,
|
|
1409
|
+
previewed: {
|
|
1410
|
+
...previewed,
|
|
1411
|
+
buttonPersonalityId: compiled.resolvedSelections.buttonPersonalityId,
|
|
1412
|
+
paletteVariantId: compiled.resolvedSelections.paletteVariantId,
|
|
1413
|
+
headerLayoutId: compiled.resolvedSelections.headerLayoutId,
|
|
1414
|
+
headerStyleId: compiled.resolvedSelections.headerStyleId,
|
|
1415
|
+
headerLookId: compiled.resolvedSelections.headerLookId,
|
|
1416
|
+
typographyPresetId: compiled.resolvedSelections.typographyPresetId,
|
|
1417
|
+
typographyBodyFontOverride: compiled.resolvedSelections.typographyBodyFontOverride,
|
|
1418
|
+
typographyHeadingFontOverride: compiled.resolvedSelections.typographyHeadingFontOverride
|
|
1419
|
+
},
|
|
1420
|
+
selectionId
|
|
1421
|
+
});
|
|
1422
|
+
await runnerRef.current.run({
|
|
1423
|
+
optimistic: () => {
|
|
1424
|
+
applyStarted("style-configurator");
|
|
1425
|
+
setState((current) => current ? markApplySaving(current) : current);
|
|
1426
|
+
},
|
|
1427
|
+
network: async () => {
|
|
1428
|
+
const response = await apiClient({
|
|
1429
|
+
endpoint: "applySiteStyleSelection",
|
|
1430
|
+
params: { siteId },
|
|
1431
|
+
body
|
|
1432
|
+
});
|
|
1433
|
+
return unwrapResponse(response);
|
|
1434
|
+
},
|
|
1435
|
+
onSuccess: (saved) => {
|
|
1436
|
+
setState(
|
|
1437
|
+
(current) => current ? markApplySaved(current, {
|
|
1438
|
+
siteStyleId: previewed.siteStyleId,
|
|
1439
|
+
selectionVersion: saved.selectionVersion,
|
|
1440
|
+
selectionUpdatedAt: saved.selectionUpdatedAt ?? previewed.selectionUpdatedAt ?? null,
|
|
1441
|
+
buttonPersonalityId: saved.buttonPersonalityId,
|
|
1442
|
+
paletteVariantId: saved.paletteVariantId,
|
|
1443
|
+
paletteOverrides: saved.paletteOverrides ?? null,
|
|
1444
|
+
typographyPresetId: saved.typographyPresetId,
|
|
1445
|
+
typographyBodyFontOverride: saved.typographyBodyFontOverride,
|
|
1446
|
+
typographyHeadingFontOverride: saved.typographyHeadingFontOverride,
|
|
1447
|
+
headerLookId: saved.headerLookId,
|
|
1448
|
+
headerLayoutId: saved.headerLayoutId,
|
|
1449
|
+
headerStyleId: saved.headerStyleId,
|
|
1450
|
+
footerLookId: saved.footerLookId
|
|
1451
|
+
}) : current
|
|
1452
|
+
);
|
|
1453
|
+
applySucceeded("style-configurator");
|
|
1454
|
+
startRefreshTransition(() => {
|
|
1455
|
+
router.refresh();
|
|
1456
|
+
});
|
|
1457
|
+
},
|
|
1458
|
+
onError: (error) => {
|
|
1459
|
+
applyFailed("style-configurator");
|
|
1460
|
+
const applyError = classifyStyleApplyError(error);
|
|
1461
|
+
if (applyError.kind === "stale") {
|
|
1462
|
+
startRefreshTransition(() => {
|
|
1463
|
+
router.refresh();
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
const message = applyError.message;
|
|
1467
|
+
setState((current) => current ? markApplyFailed(current, message) : current);
|
|
1468
|
+
}
|
|
1469
|
+
});
|
|
1470
|
+
}, [
|
|
1471
|
+
apiClient,
|
|
1472
|
+
footerSelectionContext,
|
|
1473
|
+
headerSelectionContext,
|
|
1474
|
+
applyFailed,
|
|
1475
|
+
applyStarted,
|
|
1476
|
+
applySucceeded,
|
|
1477
|
+
router,
|
|
1478
|
+
selectionId,
|
|
1479
|
+
siteId,
|
|
1480
|
+
startRefreshTransition,
|
|
1481
|
+
theme
|
|
1482
|
+
]);
|
|
1483
|
+
if (!state || !baseSnapshot) {
|
|
1484
|
+
return /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Loading design controls..." });
|
|
1485
|
+
}
|
|
1486
|
+
const applyAllowed = canApplySiteStyle(siteStyleAccess);
|
|
1487
|
+
const previewingChange = isPreviewingChange(state);
|
|
1488
|
+
const saving = state.saveStatus.kind === "saving" || isRefreshPending;
|
|
1489
|
+
const currentSiteStyle = getCuratedSiteStyle(state.previewed.siteStyleId);
|
|
1490
|
+
if (!currentSiteStyle) {
|
|
1491
|
+
return /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "This site uses a Site Style that cannot be configured here." });
|
|
1492
|
+
}
|
|
1493
|
+
const currentPersonality = findButtonPersonality(state.previewed.buttonPersonalityId);
|
|
1494
|
+
const currentPaletteVariant = findPaletteVariant(state.previewed.paletteVariantId);
|
|
1495
|
+
const currentTypographyPreset = state.previewed.typographyPresetId ? findTypographyPreset(state.previewed.typographyPresetId) : null;
|
|
1496
|
+
const chromeLookModels = buildSiteChromeLookModels({
|
|
1497
|
+
state,
|
|
1498
|
+
siteStyle: currentSiteStyle,
|
|
1499
|
+
headerSelectionContext,
|
|
1500
|
+
footerSelectionContext
|
|
1501
|
+
});
|
|
1502
|
+
const currentHeaderLayout = getHeaderLayout(state.previewed.headerLayoutId);
|
|
1503
|
+
const currentHeaderStyle = getHeaderStyle(state.previewed.headerStyleId);
|
|
1504
|
+
const currentFooterLook = state.previewed.footerLookId ? findFooterLook(state.previewed.footerLookId) : findFooterLook(chromeLookModels.footer.renderedLookId);
|
|
1505
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
1506
|
+
state.warnings.length > 0 ? /* @__PURE__ */ jsx(WarningNotice, { warnings: state.warnings, onDismiss: handleDismissWarnings }) : null,
|
|
1507
|
+
state.notices.length > 0 ? /* @__PURE__ */ jsx(NoticeBanner, { notices: state.notices, onDismiss: handleDismissNotices }) : null,
|
|
1508
|
+
/* @__PURE__ */ jsx(
|
|
1509
|
+
SiteStyleRow,
|
|
1510
|
+
{
|
|
1511
|
+
expanded: state.expandedRow === "style",
|
|
1512
|
+
onToggle: () => handleToggleRow("style"),
|
|
1513
|
+
current: currentSiteStyle,
|
|
1514
|
+
onPreview: handlePreviewSiteStyle,
|
|
1515
|
+
disabled: saving
|
|
1516
|
+
}
|
|
1517
|
+
),
|
|
1518
|
+
/* @__PURE__ */ jsx(
|
|
1519
|
+
SiteChromeLookRow,
|
|
1520
|
+
{
|
|
1521
|
+
label: "Header layout",
|
|
1522
|
+
ariaLabel: "Header layout",
|
|
1523
|
+
defaultDescription: "Choose the header structure.",
|
|
1524
|
+
expanded: state.expandedRow === "header-layout",
|
|
1525
|
+
onToggle: () => handleToggleRow("header-layout"),
|
|
1526
|
+
currentId: state.previewed.headerLayoutId,
|
|
1527
|
+
currentLabel: currentHeaderLayout?.label ?? state.previewed.headerLayoutId,
|
|
1528
|
+
scopeDescription: "Changes the header structure on every page.",
|
|
1529
|
+
options: chromeLookModels.headerLayout.options,
|
|
1530
|
+
diagnostics: [],
|
|
1531
|
+
onCycle: (direction) => {
|
|
1532
|
+
const next = cycleSiteChromeLookId(chromeLookModels.headerLayout.options, state.previewed.headerLayoutId, direction);
|
|
1533
|
+
if (next !== void 0) handlePreviewHeaderLayout(next);
|
|
1534
|
+
},
|
|
1535
|
+
onPreview: handlePreviewHeaderLayout,
|
|
1536
|
+
disabled: saving
|
|
1537
|
+
}
|
|
1538
|
+
),
|
|
1539
|
+
/* @__PURE__ */ jsx(
|
|
1540
|
+
SiteChromeLookRow,
|
|
1541
|
+
{
|
|
1542
|
+
label: "Header look",
|
|
1543
|
+
ariaLabel: "Header look",
|
|
1544
|
+
defaultDescription: "Choose the header visual style.",
|
|
1545
|
+
expanded: state.expandedRow === "header-style",
|
|
1546
|
+
onToggle: () => handleToggleRow("header-style"),
|
|
1547
|
+
currentId: state.previewed.headerStyleId,
|
|
1548
|
+
currentLabel: currentHeaderStyle?.label ?? state.previewed.headerStyleId,
|
|
1549
|
+
scopeDescription: "Changes the header surface and link style.",
|
|
1550
|
+
options: chromeLookModels.headerStyle.options,
|
|
1551
|
+
diagnostics: [],
|
|
1552
|
+
onCycle: (direction) => {
|
|
1553
|
+
const next = cycleSiteChromeLookId(chromeLookModels.headerStyle.options, state.previewed.headerStyleId, direction);
|
|
1554
|
+
if (next !== void 0) handlePreviewHeaderStyle(next);
|
|
1555
|
+
},
|
|
1556
|
+
onPreview: handlePreviewHeaderStyle,
|
|
1557
|
+
disabled: saving
|
|
1558
|
+
}
|
|
1559
|
+
),
|
|
1560
|
+
/* @__PURE__ */ jsx(
|
|
1561
|
+
SiteChromeLookRow,
|
|
1562
|
+
{
|
|
1563
|
+
label: "Footer",
|
|
1564
|
+
ariaLabel: "Footer",
|
|
1565
|
+
defaultDescription: "Use the footer chosen for this site style.",
|
|
1566
|
+
catalog: footerLookCatalog,
|
|
1567
|
+
expanded: state.expandedRow === "footer",
|
|
1568
|
+
onToggle: () => handleToggleRow("footer"),
|
|
1569
|
+
currentId: state.previewed.footerLookId,
|
|
1570
|
+
currentLabel: state.previewed.footerLookId ? currentFooterLook?.label ?? "Unknown footer Look" : `${currentFooterLook?.label ?? "Site style"} (default)`,
|
|
1571
|
+
scopeDescription: "Changes the footer on every page.",
|
|
1572
|
+
options: chromeLookModels.footer.options,
|
|
1573
|
+
diagnostics: chromeLookModels.footer.diagnostics,
|
|
1574
|
+
onCycle: (direction) => {
|
|
1575
|
+
const next = cycleSiteChromeLookId(chromeLookModels.footer.options, state.previewed.footerLookId, direction);
|
|
1576
|
+
if (next !== void 0) handlePreviewFooterLook(next);
|
|
1577
|
+
},
|
|
1578
|
+
onPreview: handlePreviewFooterLook,
|
|
1579
|
+
disabled: saving
|
|
1580
|
+
}
|
|
1581
|
+
),
|
|
1582
|
+
/* @__PURE__ */ jsx(
|
|
1583
|
+
PersonalityRow,
|
|
1584
|
+
{
|
|
1585
|
+
expanded: state.expandedRow === "buttons",
|
|
1586
|
+
onToggle: () => handleToggleRow("buttons"),
|
|
1587
|
+
siteStyle: currentSiteStyle,
|
|
1588
|
+
currentId: state.previewed.buttonPersonalityId,
|
|
1589
|
+
currentLabel: currentPersonality?.name ?? state.previewed.buttonPersonalityId,
|
|
1590
|
+
onPreview: handlePreviewPersonality,
|
|
1591
|
+
disabled: saving
|
|
1592
|
+
}
|
|
1593
|
+
),
|
|
1594
|
+
/* @__PURE__ */ jsx(
|
|
1595
|
+
TypographyRow,
|
|
1596
|
+
{
|
|
1597
|
+
expanded: state.expandedRow === "typography",
|
|
1598
|
+
onToggle: () => handleToggleRow("typography"),
|
|
1599
|
+
apiClient,
|
|
1600
|
+
siteStyle: currentSiteStyle,
|
|
1601
|
+
currentPresetId: state.previewed.typographyPresetId,
|
|
1602
|
+
currentLabel: currentTypographyPreset?.name ?? "Custom typography",
|
|
1603
|
+
bodyFontOverride: state.previewed.typographyBodyFontOverride,
|
|
1604
|
+
headingFontOverride: state.previewed.typographyHeadingFontOverride,
|
|
1605
|
+
onPreviewPreset: handlePreviewTypographyPreset,
|
|
1606
|
+
onBodyFontOverride: handleBodyFontOverride,
|
|
1607
|
+
onHeadingFontOverride: handleHeadingFontOverride,
|
|
1608
|
+
onClearOverrides: handleClearTypographyOverrides,
|
|
1609
|
+
disabled: saving
|
|
1610
|
+
}
|
|
1611
|
+
),
|
|
1612
|
+
/* @__PURE__ */ jsx(
|
|
1613
|
+
PaletteRow,
|
|
1614
|
+
{
|
|
1615
|
+
expanded: state.expandedRow === "palette",
|
|
1616
|
+
onToggle: () => handleToggleRow("palette"),
|
|
1617
|
+
siteStyle: currentSiteStyle,
|
|
1618
|
+
currentId: state.previewed.paletteVariantId,
|
|
1619
|
+
currentLabel: currentPaletteVariant?.name ?? state.previewed.paletteVariantId,
|
|
1620
|
+
currentSwatch: currentPaletteVariant?.colors.find((c) => c.name === "primary")?.hex ?? null,
|
|
1621
|
+
onPreview: handlePreviewPalette,
|
|
1622
|
+
disabled: saving
|
|
1623
|
+
}
|
|
1624
|
+
),
|
|
1625
|
+
currentPaletteVariant ? /* @__PURE__ */ jsx(
|
|
1626
|
+
ColoursRow,
|
|
1627
|
+
{
|
|
1628
|
+
expanded: state.expandedRow === "colours",
|
|
1629
|
+
onToggle: () => handleToggleRow("colours"),
|
|
1630
|
+
variant: currentPaletteVariant,
|
|
1631
|
+
overrides: state.previewed.paletteOverrides,
|
|
1632
|
+
onTokenChange: handleTokenOverride,
|
|
1633
|
+
onReset: handleResetOverrides,
|
|
1634
|
+
disabled: saving
|
|
1635
|
+
}
|
|
1636
|
+
) : null,
|
|
1637
|
+
previewingChange ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pt-2", children: [
|
|
1638
|
+
/* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "outline", onClick: handleCancel, disabled: saving, children: "Cancel" }),
|
|
1639
|
+
applyAllowed ? /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", onClick: handleApply, disabled: saving, children: saving ? "Applying\u2026" : "Apply" }) : /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Preview only \u2014 ask an admin to apply." })
|
|
1640
|
+
] }) : null,
|
|
1641
|
+
state.saveStatus.kind === "saved" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-success", children: "Saved." }) : null,
|
|
1642
|
+
state.saveStatus.kind === "failed" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: state.saveStatus.message }) : null
|
|
1643
|
+
] });
|
|
1644
|
+
}
|
|
1645
|
+
function StyleRowFrame({ label, currentLabel, swatch, expanded, onToggle, children }) {
|
|
1646
|
+
return /* @__PURE__ */ jsx(Collapsible, { open: expanded, onOpenChange: onToggle, children: /* @__PURE__ */ jsxs("div", { className: "rounded-md border bg-background", children: [
|
|
1647
|
+
/* @__PURE__ */ jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
1648
|
+
"button",
|
|
1649
|
+
{
|
|
1650
|
+
type: "button",
|
|
1651
|
+
className: "flex w-full items-center gap-2 px-3 py-2 text-left hover:bg-accent/30",
|
|
1652
|
+
"aria-expanded": expanded,
|
|
1653
|
+
children: [
|
|
1654
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: cn("h-4 w-4 transition", expanded && "rotate-90") }),
|
|
1655
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
1656
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: label }),
|
|
1657
|
+
/* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium", children: currentLabel })
|
|
1658
|
+
] }),
|
|
1659
|
+
swatch ? /* @__PURE__ */ jsx(
|
|
1660
|
+
"span",
|
|
1661
|
+
{
|
|
1662
|
+
className: "h-5 w-5 shrink-0 rounded-full border border-black/10",
|
|
1663
|
+
style: { backgroundColor: swatch },
|
|
1664
|
+
"aria-hidden": "true"
|
|
1665
|
+
}
|
|
1666
|
+
) : null
|
|
1667
|
+
]
|
|
1668
|
+
}
|
|
1669
|
+
) }),
|
|
1670
|
+
/* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "border-t px-3 py-3", children }) })
|
|
1671
|
+
] }) });
|
|
1672
|
+
}
|
|
1673
|
+
function SiteStyleRow({ expanded, onToggle, current, onPreview, disabled }) {
|
|
1674
|
+
return /* @__PURE__ */ jsx(StyleRowFrame, { label: "Site style", currentLabel: current.name, expanded, onToggle, children: /* @__PURE__ */ jsx("ul", { role: "radiogroup", "aria-label": "Site style", className: "list-none space-y-1 p-0", children: curatedSiteStyles.map((style) => /* @__PURE__ */ jsx(
|
|
1675
|
+
PickerOption,
|
|
1676
|
+
{
|
|
1677
|
+
selected: style.id === current.id,
|
|
1678
|
+
label: style.name,
|
|
1679
|
+
description: style.description,
|
|
1680
|
+
onSelect: () => onPreview(style.id),
|
|
1681
|
+
disabled
|
|
1682
|
+
},
|
|
1683
|
+
style.id
|
|
1684
|
+
)) }) });
|
|
1685
|
+
}
|
|
1686
|
+
function PersonalityRow({ expanded, onToggle, siteStyle, currentId, currentLabel, onPreview, disabled }) {
|
|
1687
|
+
const options = useMemo(
|
|
1688
|
+
() => requireEntriesForCuratedChoices(buttonPersonalities, siteStyle.buttonPersonalityChoices),
|
|
1689
|
+
[siteStyle.buttonPersonalityChoices]
|
|
1690
|
+
);
|
|
1691
|
+
return /* @__PURE__ */ jsx(StyleRowFrame, { label: "Button style", currentLabel, expanded, onToggle, children: /* @__PURE__ */ jsx("ul", { role: "radiogroup", "aria-label": "Button style", className: "list-none space-y-1 p-0", children: options.map((personality) => /* @__PURE__ */ jsx(
|
|
1692
|
+
PickerOption,
|
|
1693
|
+
{
|
|
1694
|
+
selected: personality.id === currentId,
|
|
1695
|
+
label: personality.name,
|
|
1696
|
+
description: personality.description,
|
|
1697
|
+
onSelect: () => onPreview(personality.id),
|
|
1698
|
+
disabled
|
|
1699
|
+
},
|
|
1700
|
+
personality.id
|
|
1701
|
+
)) }) });
|
|
1702
|
+
}
|
|
1703
|
+
function TypographyRow({
|
|
1704
|
+
expanded,
|
|
1705
|
+
onToggle,
|
|
1706
|
+
apiClient,
|
|
1707
|
+
siteStyle,
|
|
1708
|
+
currentPresetId,
|
|
1709
|
+
currentLabel,
|
|
1710
|
+
bodyFontOverride,
|
|
1711
|
+
headingFontOverride,
|
|
1712
|
+
onPreviewPreset,
|
|
1713
|
+
onBodyFontOverride,
|
|
1714
|
+
onHeadingFontOverride,
|
|
1715
|
+
onClearOverrides,
|
|
1716
|
+
disabled
|
|
1717
|
+
}) {
|
|
1718
|
+
const options = useMemo(
|
|
1719
|
+
() => requireEntriesForCuratedChoices(typographyPresets, siteStyle.typographyPresetChoices),
|
|
1720
|
+
[siteStyle.typographyPresetChoices]
|
|
1721
|
+
);
|
|
1722
|
+
const fontCatalog = useFontCatalog(apiClient, expanded);
|
|
1723
|
+
const selectedPreset = currentPresetId ? findTypographyPreset(currentPresetId) : null;
|
|
1724
|
+
return /* @__PURE__ */ jsxs(StyleRowFrame, { label: "Typography", currentLabel, expanded, onToggle, children: [
|
|
1725
|
+
currentPresetId === null ? /* @__PURE__ */ jsx("div", { className: "mb-3 rounded-md border border-muted-foreground/20 bg-muted/30 px-3 py-2 text-xs text-muted-foreground", children: "Current custom setup" }) : null,
|
|
1726
|
+
/* @__PURE__ */ jsx("ul", { role: "radiogroup", "aria-label": "Typography preset", className: "space-y-1", children: options.map((preset) => /* @__PURE__ */ jsx(
|
|
1727
|
+
TypographyPresetOption,
|
|
1728
|
+
{
|
|
1729
|
+
preset,
|
|
1730
|
+
selected: preset.id === currentPresetId,
|
|
1731
|
+
onSelect: () => onPreviewPreset(preset.id),
|
|
1732
|
+
disabled
|
|
1733
|
+
},
|
|
1734
|
+
preset.id
|
|
1735
|
+
)) }),
|
|
1736
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-3 space-y-3 border-t pt-3", children: [
|
|
1737
|
+
selectedPreset ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1738
|
+
/* @__PURE__ */ jsx(
|
|
1739
|
+
TypographyFontSlotPicker,
|
|
1740
|
+
{
|
|
1741
|
+
label: "Body font",
|
|
1742
|
+
slot: "body",
|
|
1743
|
+
preset: selectedPreset,
|
|
1744
|
+
catalog: fontCatalog,
|
|
1745
|
+
value: bodyFontOverride,
|
|
1746
|
+
onChange: onBodyFontOverride,
|
|
1747
|
+
disabled
|
|
1748
|
+
}
|
|
1749
|
+
),
|
|
1750
|
+
/* @__PURE__ */ jsx(
|
|
1751
|
+
TypographyFontSlotPicker,
|
|
1752
|
+
{
|
|
1753
|
+
label: "Heading font",
|
|
1754
|
+
slot: "heading",
|
|
1755
|
+
preset: selectedPreset,
|
|
1756
|
+
catalog: fontCatalog,
|
|
1757
|
+
value: headingFontOverride,
|
|
1758
|
+
onChange: onHeadingFontOverride,
|
|
1759
|
+
disabled
|
|
1760
|
+
}
|
|
1761
|
+
)
|
|
1762
|
+
] }) : /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Choose a typography preset to use curated font overrides." }),
|
|
1763
|
+
/* @__PURE__ */ jsx(
|
|
1764
|
+
Button,
|
|
1765
|
+
{
|
|
1766
|
+
type: "button",
|
|
1767
|
+
size: "sm",
|
|
1768
|
+
variant: "outline",
|
|
1769
|
+
onClick: onClearOverrides,
|
|
1770
|
+
disabled: disabled || !bodyFontOverride && !headingFontOverride,
|
|
1771
|
+
children: "Use preset fonts"
|
|
1772
|
+
}
|
|
1773
|
+
)
|
|
1774
|
+
] })
|
|
1775
|
+
] });
|
|
1776
|
+
}
|
|
1777
|
+
function TypographyPresetOption({
|
|
1778
|
+
preset,
|
|
1779
|
+
selected,
|
|
1780
|
+
onSelect,
|
|
1781
|
+
disabled
|
|
1782
|
+
}) {
|
|
1783
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
1784
|
+
"button",
|
|
1785
|
+
{
|
|
1786
|
+
type: "button",
|
|
1787
|
+
role: "radio",
|
|
1788
|
+
"aria-checked": selected,
|
|
1789
|
+
onClick: onSelect,
|
|
1790
|
+
disabled,
|
|
1791
|
+
className: cn(
|
|
1792
|
+
"w-full rounded-md border px-3 py-2 text-left transition disabled:cursor-not-allowed disabled:opacity-50",
|
|
1793
|
+
selected ? "border-primary bg-primary/5" : "border-border hover:bg-muted/60"
|
|
1794
|
+
),
|
|
1795
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
|
|
1796
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
1797
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: preset.name }),
|
|
1798
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: preset.description })
|
|
1799
|
+
] }),
|
|
1800
|
+
/* @__PURE__ */ jsxs("div", { className: "shrink-0 text-right", children: [
|
|
1801
|
+
/* @__PURE__ */ jsx("div", { className: "text-[11px] font-semibold uppercase tracking-wide", children: preset.defaultFonts.heading.split(",")[0] }),
|
|
1802
|
+
/* @__PURE__ */ jsx("div", { className: "text-[11px] text-muted-foreground", children: preset.defaultFonts.body.split(",")[0] })
|
|
1803
|
+
] })
|
|
1804
|
+
] })
|
|
1805
|
+
}
|
|
1806
|
+
) });
|
|
1807
|
+
}
|
|
1808
|
+
function TypographyFontSlotPicker({
|
|
1809
|
+
label,
|
|
1810
|
+
slot,
|
|
1811
|
+
preset,
|
|
1812
|
+
catalog,
|
|
1813
|
+
value,
|
|
1814
|
+
onChange,
|
|
1815
|
+
disabled
|
|
1816
|
+
}) {
|
|
1817
|
+
const [query, setQuery] = useState("");
|
|
1818
|
+
const defaultFont = primaryFontFamily(preset.defaultFonts[slot]);
|
|
1819
|
+
const selectedFamily = value ?? defaultFont;
|
|
1820
|
+
const recordsByFamily = useMemo(() => indexFontsByFamily(catalog.fonts), [catalog.fonts]);
|
|
1821
|
+
const selectedFit = evaluateTypographyFontSlot({
|
|
1822
|
+
slot,
|
|
1823
|
+
policy: preset.slotPolicies[slot],
|
|
1824
|
+
capability: capabilityForFamily(selectedFamily, recordsByFamily)
|
|
1825
|
+
});
|
|
1826
|
+
const recommendedFamilies = preset.recommendedFonts[slot];
|
|
1827
|
+
const recommendedOptions = useMemo(
|
|
1828
|
+
() => uniqueFontFamilies(recommendedFamilies),
|
|
1829
|
+
[recommendedFamilies]
|
|
1830
|
+
);
|
|
1831
|
+
const browseOptions = useMemo(() => {
|
|
1832
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
1833
|
+
const source = normalizedQuery.length > 0 ? catalog.fonts.filter((font) => font.family.toLowerCase().includes(normalizedQuery)) : catalog.fonts;
|
|
1834
|
+
return [...source].sort((a, b) => sortFontForPresetSlot(a, b, slot, preset, recordsByFamily)).slice(0, normalizedQuery.length > 0 ? MAX_FONT_RESULTS_WHEN_SEARCHING : MAX_FONT_RESULTS_WHEN_BROWSING);
|
|
1835
|
+
}, [catalog.fonts, preset, query, recordsByFamily, slot]);
|
|
1836
|
+
return /* @__PURE__ */ jsxs("section", { className: "space-y-2", "aria-label": label, children: [
|
|
1837
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
|
|
1838
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
1839
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs font-medium text-muted-foreground", children: label }),
|
|
1840
|
+
/* @__PURE__ */ jsx("div", { className: "truncate text-sm", children: value ? selectedFamily : `${defaultFont} (preset)` })
|
|
1841
|
+
] }),
|
|
1842
|
+
/* @__PURE__ */ jsx(TypographyFontFitBadge, { fit: selectedFit })
|
|
1843
|
+
] }),
|
|
1844
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-1", children: [
|
|
1845
|
+
/* @__PURE__ */ jsxs(
|
|
1846
|
+
"button",
|
|
1847
|
+
{
|
|
1848
|
+
type: "button",
|
|
1849
|
+
className: cn(
|
|
1850
|
+
"rounded-md border px-3 py-2 text-left text-sm transition disabled:cursor-not-allowed disabled:opacity-50",
|
|
1851
|
+
value === null ? "border-foreground bg-accent/40" : "border-transparent hover:border-foreground/40 hover:bg-accent/20"
|
|
1852
|
+
),
|
|
1853
|
+
onClick: () => onChange(null),
|
|
1854
|
+
disabled,
|
|
1855
|
+
children: [
|
|
1856
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: "Use preset font" }),
|
|
1857
|
+
/* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: defaultFont })
|
|
1858
|
+
]
|
|
1859
|
+
}
|
|
1860
|
+
),
|
|
1861
|
+
recommendedOptions.map((family) => /* @__PURE__ */ jsx(
|
|
1862
|
+
FontFamilyOption,
|
|
1863
|
+
{
|
|
1864
|
+
family,
|
|
1865
|
+
selected: value === family,
|
|
1866
|
+
fit: evaluateTypographyFontSlot({
|
|
1867
|
+
slot,
|
|
1868
|
+
policy: preset.slotPolicies[slot],
|
|
1869
|
+
capability: capabilityForFamily(family, recordsByFamily)
|
|
1870
|
+
}),
|
|
1871
|
+
onSelect: () => onChange(family),
|
|
1872
|
+
disabled
|
|
1873
|
+
},
|
|
1874
|
+
family
|
|
1875
|
+
))
|
|
1876
|
+
] }),
|
|
1877
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-md border bg-muted/20 p-2", children: [
|
|
1878
|
+
/* @__PURE__ */ jsx(
|
|
1879
|
+
"input",
|
|
1880
|
+
{
|
|
1881
|
+
type: "search",
|
|
1882
|
+
value: query,
|
|
1883
|
+
placeholder: `Search ${slot} fonts`,
|
|
1884
|
+
onChange: (event) => setQuery(event.currentTarget.value),
|
|
1885
|
+
disabled: disabled || catalog.kind === "loading",
|
|
1886
|
+
className: "h-8 w-full rounded-md border bg-background px-2 text-sm outline-none focus:border-primary disabled:cursor-not-allowed disabled:opacity-50"
|
|
1887
|
+
}
|
|
1888
|
+
),
|
|
1889
|
+
catalog.kind === "loading" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Loading font catalog..." }) : catalog.kind === "failed" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: catalog.message }) : null,
|
|
1890
|
+
browseOptions.length > 0 ? /* @__PURE__ */ jsx("div", { className: "max-h-56 space-y-1 overflow-y-auto pr-1", children: browseOptions.map((font) => /* @__PURE__ */ jsx(
|
|
1891
|
+
FontFamilyOption,
|
|
1892
|
+
{
|
|
1893
|
+
family: font.family,
|
|
1894
|
+
selected: value === font.family,
|
|
1895
|
+
fit: evaluateTypographyFontSlot({
|
|
1896
|
+
slot,
|
|
1897
|
+
policy: preset.slotPolicies[slot],
|
|
1898
|
+
capability: capabilityFromFontRecord(font)
|
|
1899
|
+
}),
|
|
1900
|
+
onSelect: () => onChange(font.family),
|
|
1901
|
+
disabled
|
|
1902
|
+
},
|
|
1903
|
+
font.family
|
|
1904
|
+
)) }) : catalog.kind === "loaded" ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "No matching Google Fonts." }) : null
|
|
1905
|
+
] }),
|
|
1906
|
+
/* @__PURE__ */ jsxs("label", { className: "block space-y-1", children: [
|
|
1907
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Brand or uploaded font name" }),
|
|
1908
|
+
/* @__PURE__ */ jsx(
|
|
1909
|
+
"input",
|
|
1910
|
+
{
|
|
1911
|
+
type: "text",
|
|
1912
|
+
value: value ?? "",
|
|
1913
|
+
placeholder: "Enter any font family",
|
|
1914
|
+
onChange: (event) => onChange(normalizeFontFamilyOverride(event.currentTarget.value)),
|
|
1915
|
+
disabled,
|
|
1916
|
+
className: "h-8 w-full rounded-md border bg-background px-2 text-sm outline-none focus:border-primary disabled:cursor-not-allowed disabled:opacity-50"
|
|
1917
|
+
}
|
|
1918
|
+
)
|
|
1919
|
+
] })
|
|
1920
|
+
] });
|
|
1921
|
+
}
|
|
1922
|
+
function FontFamilyOption({
|
|
1923
|
+
family,
|
|
1924
|
+
selected,
|
|
1925
|
+
fit,
|
|
1926
|
+
onSelect,
|
|
1927
|
+
disabled
|
|
1928
|
+
}) {
|
|
1929
|
+
return /* @__PURE__ */ jsxs(
|
|
1930
|
+
"button",
|
|
1931
|
+
{
|
|
1932
|
+
type: "button",
|
|
1933
|
+
className: cn(
|
|
1934
|
+
"flex w-full items-center justify-between gap-3 rounded-md border px-3 py-2 text-left text-sm transition disabled:cursor-not-allowed disabled:opacity-50",
|
|
1935
|
+
selected ? "border-foreground bg-accent/40" : "border-transparent bg-background hover:border-foreground/40 hover:bg-accent/20"
|
|
1936
|
+
),
|
|
1937
|
+
onClick: onSelect,
|
|
1938
|
+
disabled,
|
|
1939
|
+
children: [
|
|
1940
|
+
/* @__PURE__ */ jsx("span", { className: "truncate font-medium", children: family }),
|
|
1941
|
+
/* @__PURE__ */ jsx(TypographyFontFitBadge, { fit })
|
|
1942
|
+
]
|
|
1943
|
+
}
|
|
1944
|
+
);
|
|
1945
|
+
}
|
|
1946
|
+
function TypographyFontFitBadge({ fit }) {
|
|
1947
|
+
return /* @__PURE__ */ jsx(
|
|
1948
|
+
"span",
|
|
1949
|
+
{
|
|
1950
|
+
className: cn(
|
|
1951
|
+
"shrink-0 rounded-full px-2 py-0.5 text-[11px] font-medium",
|
|
1952
|
+
fit.kind === "recommended" && "bg-emerald-100 text-emerald-800",
|
|
1953
|
+
fit.kind === "compatible" && "bg-sky-100 text-sky-800",
|
|
1954
|
+
fit.kind === "limited" && "bg-amber-100 text-amber-900",
|
|
1955
|
+
fit.kind === "risky" && "bg-rose-100 text-rose-800",
|
|
1956
|
+
fit.kind === "unknown" && "bg-muted text-muted-foreground"
|
|
1957
|
+
),
|
|
1958
|
+
title: fit.reason,
|
|
1959
|
+
children: typographyFontFitLabel(fit.kind)
|
|
1960
|
+
}
|
|
1961
|
+
);
|
|
1962
|
+
}
|
|
1963
|
+
function useFontCatalog(apiClient, enabled) {
|
|
1964
|
+
const [catalog, setCatalog] = useState({ kind: "idle", fonts: [] });
|
|
1965
|
+
const requestedClientRef = useRef(null);
|
|
1966
|
+
useEffect(() => {
|
|
1967
|
+
if (!enabled) return;
|
|
1968
|
+
if (requestedClientRef.current === apiClient) return;
|
|
1969
|
+
requestedClientRef.current = apiClient;
|
|
1970
|
+
let cancelled = false;
|
|
1971
|
+
setCatalog({ kind: "loading", fonts: [] });
|
|
1972
|
+
async function loadFonts() {
|
|
1973
|
+
try {
|
|
1974
|
+
const response = await apiClient({ endpoint: "listFonts" });
|
|
1975
|
+
const fonts = unwrapResponse(response);
|
|
1976
|
+
if (!cancelled) {
|
|
1977
|
+
setCatalog({
|
|
1978
|
+
kind: "loaded",
|
|
1979
|
+
fonts: Array.isArray(fonts) ? fonts : []
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1982
|
+
} catch (error) {
|
|
1983
|
+
if (!cancelled) {
|
|
1984
|
+
setCatalog({
|
|
1985
|
+
kind: "failed",
|
|
1986
|
+
fonts: [],
|
|
1987
|
+
message: error instanceof Error ? error.message : "Unable to load the font catalog."
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
void loadFonts();
|
|
1993
|
+
return () => {
|
|
1994
|
+
cancelled = true;
|
|
1995
|
+
};
|
|
1996
|
+
}, [apiClient, enabled]);
|
|
1997
|
+
return catalog;
|
|
1998
|
+
}
|
|
1999
|
+
function indexFontsByFamily(fonts) {
|
|
2000
|
+
return new Map(fonts.map((font) => [font.family.toLowerCase(), font]));
|
|
2001
|
+
}
|
|
2002
|
+
function capabilityForFamily(family, recordsByFamily) {
|
|
2003
|
+
const record = recordsByFamily.get(family.toLowerCase());
|
|
2004
|
+
if (record) return capabilityFromFontRecord(record);
|
|
2005
|
+
return {
|
|
2006
|
+
source: "unknown",
|
|
2007
|
+
family,
|
|
2008
|
+
category: "unknown",
|
|
2009
|
+
variants: []
|
|
2010
|
+
};
|
|
2011
|
+
}
|
|
2012
|
+
function capabilityFromFontRecord(font) {
|
|
2013
|
+
return {
|
|
2014
|
+
source: "google",
|
|
2015
|
+
family: font.family,
|
|
2016
|
+
category: normalizeGoogleFontCategory(font.category),
|
|
2017
|
+
variants: font.variants
|
|
2018
|
+
};
|
|
2019
|
+
}
|
|
2020
|
+
function normalizeGoogleFontCategory(category) {
|
|
2021
|
+
switch (category) {
|
|
2022
|
+
case "serif":
|
|
2023
|
+
case "sans-serif":
|
|
2024
|
+
case "display":
|
|
2025
|
+
case "handwriting":
|
|
2026
|
+
case "monospace":
|
|
2027
|
+
return category;
|
|
2028
|
+
default:
|
|
2029
|
+
return "unknown";
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
function sortFontForPresetSlot(a, b, slot, preset, recordsByFamily) {
|
|
2033
|
+
const aFit = evaluateTypographyFontSlot({
|
|
2034
|
+
slot,
|
|
2035
|
+
policy: preset.slotPolicies[slot],
|
|
2036
|
+
capability: capabilityForFamily(a.family, recordsByFamily)
|
|
2037
|
+
});
|
|
2038
|
+
const bFit = evaluateTypographyFontSlot({
|
|
2039
|
+
slot,
|
|
2040
|
+
policy: preset.slotPolicies[slot],
|
|
2041
|
+
capability: capabilityForFamily(b.family, recordsByFamily)
|
|
2042
|
+
});
|
|
2043
|
+
const fitDelta = typographyFontFitRank(aFit.kind) - typographyFontFitRank(bFit.kind);
|
|
2044
|
+
if (fitDelta !== 0) return fitDelta;
|
|
2045
|
+
const popularityDelta = (a.popularity ?? Infinity) - (b.popularity ?? Infinity);
|
|
2046
|
+
if (popularityDelta !== 0) return popularityDelta;
|
|
2047
|
+
return a.family.localeCompare(b.family);
|
|
2048
|
+
}
|
|
2049
|
+
function typographyFontFitRank(kind) {
|
|
2050
|
+
switch (kind) {
|
|
2051
|
+
case "recommended":
|
|
2052
|
+
return 0;
|
|
2053
|
+
case "compatible":
|
|
2054
|
+
return 1;
|
|
2055
|
+
case "limited":
|
|
2056
|
+
return 2;
|
|
2057
|
+
case "risky":
|
|
2058
|
+
return 3;
|
|
2059
|
+
case "unknown":
|
|
2060
|
+
return 4;
|
|
2061
|
+
default: {
|
|
2062
|
+
const exhaustive = kind;
|
|
2063
|
+
return exhaustive;
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
function typographyFontFitLabel(kind) {
|
|
2068
|
+
switch (kind) {
|
|
2069
|
+
case "recommended":
|
|
2070
|
+
return "Recommended";
|
|
2071
|
+
case "compatible":
|
|
2072
|
+
return "Compatible";
|
|
2073
|
+
case "limited":
|
|
2074
|
+
return "Limited";
|
|
2075
|
+
case "risky":
|
|
2076
|
+
return "Risky";
|
|
2077
|
+
case "unknown":
|
|
2078
|
+
return "Unknown";
|
|
2079
|
+
default: {
|
|
2080
|
+
const exhaustive = kind;
|
|
2081
|
+
return exhaustive;
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
function primaryFontFamily(cssFamily) {
|
|
2086
|
+
return cssFamily.split(",")[0]?.trim().replace(/^["']|["']$/gu, "") || cssFamily;
|
|
2087
|
+
}
|
|
2088
|
+
function uniqueFontFamilies(families) {
|
|
2089
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2090
|
+
const result = [];
|
|
2091
|
+
for (const family of families) {
|
|
2092
|
+
const normalized = primaryFontFamily(family);
|
|
2093
|
+
const key = normalized.toLowerCase();
|
|
2094
|
+
if (seen.has(key)) continue;
|
|
2095
|
+
seen.add(key);
|
|
2096
|
+
result.push(normalized);
|
|
2097
|
+
}
|
|
2098
|
+
return result;
|
|
2099
|
+
}
|
|
2100
|
+
function normalizeFontFamilyOverride(value) {
|
|
2101
|
+
const trimmed = value.trim();
|
|
2102
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
2103
|
+
}
|
|
2104
|
+
function SiteChromeLookRow({
|
|
2105
|
+
label,
|
|
2106
|
+
ariaLabel,
|
|
2107
|
+
defaultDescription,
|
|
2108
|
+
catalog,
|
|
2109
|
+
options = [
|
|
2110
|
+
{
|
|
2111
|
+
id: null,
|
|
2112
|
+
label: "Site style default",
|
|
2113
|
+
description: defaultDescription,
|
|
2114
|
+
available: true
|
|
2115
|
+
},
|
|
2116
|
+
...(catalog ?? []).map((look) => ({ ...look, available: true }))
|
|
2117
|
+
],
|
|
2118
|
+
diagnostics,
|
|
2119
|
+
scopeDescription,
|
|
2120
|
+
expanded,
|
|
2121
|
+
onToggle,
|
|
2122
|
+
currentId,
|
|
2123
|
+
currentLabel,
|
|
2124
|
+
onCycle,
|
|
2125
|
+
onPreview,
|
|
2126
|
+
disabled
|
|
2127
|
+
}) {
|
|
2128
|
+
return /* @__PURE__ */ jsxs(StyleRowFrame, { label, currentLabel, expanded, onToggle, children: [
|
|
2129
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-start justify-between gap-3", children: [
|
|
2130
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: scopeDescription }),
|
|
2131
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center overflow-hidden rounded-full border bg-background", children: [
|
|
2132
|
+
/* @__PURE__ */ jsx(
|
|
2133
|
+
"button",
|
|
2134
|
+
{
|
|
2135
|
+
type: "button",
|
|
2136
|
+
onClick: () => onCycle("prev"),
|
|
2137
|
+
className: "grid h-8 w-8 place-items-center hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
|
|
2138
|
+
"aria-label": `Previous ${label.toLowerCase()}`,
|
|
2139
|
+
disabled: disabled || cycleableSiteChromeOptions(options).length <= 1,
|
|
2140
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { size: 14 })
|
|
2141
|
+
}
|
|
2142
|
+
),
|
|
2143
|
+
/* @__PURE__ */ jsx(
|
|
2144
|
+
"button",
|
|
2145
|
+
{
|
|
2146
|
+
type: "button",
|
|
2147
|
+
onClick: () => onCycle("next"),
|
|
2148
|
+
className: "grid h-8 w-8 place-items-center border-l hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50",
|
|
2149
|
+
"aria-label": `Next ${label.toLowerCase()}`,
|
|
2150
|
+
disabled: disabled || cycleableSiteChromeOptions(options).length <= 1,
|
|
2151
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { size: 14 })
|
|
2152
|
+
}
|
|
2153
|
+
)
|
|
2154
|
+
] })
|
|
2155
|
+
] }),
|
|
2156
|
+
diagnostics.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "mb-3 list-none space-y-1 p-0 text-xs text-amber-700", children: diagnostics.map((diagnostic) => /* @__PURE__ */ jsx("li", { children: diagnostic }, diagnostic)) }) : null,
|
|
2157
|
+
/* @__PURE__ */ jsx("ul", { role: "radiogroup", "aria-label": ariaLabel, className: "list-none space-y-1 p-0", children: options.map((look) => /* @__PURE__ */ jsx(
|
|
2158
|
+
PickerOption,
|
|
2159
|
+
{
|
|
2160
|
+
selected: look.id === currentId,
|
|
2161
|
+
label: look.label,
|
|
2162
|
+
description: look.description,
|
|
2163
|
+
onSelect: () => onPreview(look.id),
|
|
2164
|
+
disabled: disabled || !look.available
|
|
2165
|
+
},
|
|
2166
|
+
look.id ?? "site-style-default"
|
|
2167
|
+
)) })
|
|
2168
|
+
] });
|
|
2169
|
+
}
|
|
2170
|
+
function PaletteRow({ expanded, onToggle, siteStyle, currentId, currentLabel, currentSwatch, onPreview, disabled }) {
|
|
2171
|
+
const options = useMemo(
|
|
2172
|
+
() => {
|
|
2173
|
+
const recommended = requireEntriesForCuratedChoices(paletteVariants, siteStyle.paletteVariantChoices);
|
|
2174
|
+
if (recommended.some((variant) => variant.id === currentId)) {
|
|
2175
|
+
return recommended;
|
|
2176
|
+
}
|
|
2177
|
+
const current = findPaletteVariant(currentId);
|
|
2178
|
+
return current ? [current, ...recommended] : recommended;
|
|
2179
|
+
},
|
|
2180
|
+
[currentId, siteStyle.paletteVariantChoices]
|
|
2181
|
+
);
|
|
2182
|
+
return /* @__PURE__ */ jsxs(
|
|
2183
|
+
StyleRowFrame,
|
|
2184
|
+
{
|
|
2185
|
+
label: "Palette",
|
|
2186
|
+
currentLabel,
|
|
2187
|
+
swatch: currentSwatch,
|
|
2188
|
+
expanded,
|
|
2189
|
+
onToggle,
|
|
2190
|
+
children: [
|
|
2191
|
+
/* @__PURE__ */ jsx("p", { className: "mb-3 text-xs text-muted-foreground", children: "These palette bases are recommended for the current Site Style. Choose a base here, then edit exact colours in Palette colours." }),
|
|
2192
|
+
/* @__PURE__ */ jsx("ul", { role: "radiogroup", "aria-label": "Palette", className: "list-none space-y-1 p-0", children: options.map((variant) => {
|
|
2193
|
+
const primaryHex = variant.colors.find((c) => c.name === "primary")?.hex;
|
|
2194
|
+
return /* @__PURE__ */ jsx(
|
|
2195
|
+
PickerOption,
|
|
2196
|
+
{
|
|
2197
|
+
selected: variant.id === currentId,
|
|
2198
|
+
label: variant.name,
|
|
2199
|
+
description: variant.description,
|
|
2200
|
+
swatch: primaryHex,
|
|
2201
|
+
onSelect: () => onPreview(variant.id),
|
|
2202
|
+
disabled
|
|
2203
|
+
},
|
|
2204
|
+
variant.id
|
|
2205
|
+
);
|
|
2206
|
+
}) })
|
|
2207
|
+
]
|
|
2208
|
+
}
|
|
2209
|
+
);
|
|
2210
|
+
}
|
|
2211
|
+
function ColoursRow({ expanded, onToggle, variant, overrides, onTokenChange, onReset, disabled }) {
|
|
2212
|
+
const mergedPalette = useMemo(() => {
|
|
2213
|
+
return variant.colors.map((color) => ({
|
|
2214
|
+
name: color.name,
|
|
2215
|
+
hex: overrides?.[color.name] ?? color.hex
|
|
2216
|
+
}));
|
|
2217
|
+
}, [variant, overrides]);
|
|
2218
|
+
const overrideCount = overrides ? Object.keys(overrides).length : 0;
|
|
2219
|
+
const currentLabel = overrideCount > 0 ? `${overrideCount} colour${overrideCount === 1 ? "" : "s"} customised` : "Using selected palette";
|
|
2220
|
+
return /* @__PURE__ */ jsx(
|
|
2221
|
+
StyleRowFrame,
|
|
2222
|
+
{
|
|
2223
|
+
label: "Palette colours",
|
|
2224
|
+
currentLabel,
|
|
2225
|
+
expanded,
|
|
2226
|
+
onToggle,
|
|
2227
|
+
children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
2228
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Customise the palette tokens used across the site, including backgrounds, surfaces, text and borders." }),
|
|
2229
|
+
/* @__PURE__ */ jsx(
|
|
2230
|
+
PaletteTokenEditor,
|
|
2231
|
+
{
|
|
2232
|
+
palette: mergedPalette,
|
|
2233
|
+
onChange: onTokenChange,
|
|
2234
|
+
brandRoles: PALETTE_BRAND_ROLES,
|
|
2235
|
+
feedbackRoles: PALETTE_FEEDBACK_ROLES,
|
|
2236
|
+
surfaceRoles: PALETTE_SURFACE_ROLES,
|
|
2237
|
+
showContrastChecks: false
|
|
2238
|
+
}
|
|
2239
|
+
),
|
|
2240
|
+
overrideCount > 0 ? /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
|
|
2241
|
+
Button,
|
|
2242
|
+
{
|
|
2243
|
+
type: "button",
|
|
2244
|
+
variant: "outline",
|
|
2245
|
+
size: "sm",
|
|
2246
|
+
onClick: onReset,
|
|
2247
|
+
disabled,
|
|
2248
|
+
children: "Reset to variant"
|
|
2249
|
+
}
|
|
2250
|
+
) }) : null
|
|
2251
|
+
] })
|
|
2252
|
+
}
|
|
2253
|
+
);
|
|
2254
|
+
}
|
|
2255
|
+
function PickerOption({ selected, label, description, swatch, onSelect, disabled }) {
|
|
2256
|
+
return /* @__PURE__ */ jsx("li", { className: "list-none", children: /* @__PURE__ */ jsxs(
|
|
2257
|
+
"button",
|
|
2258
|
+
{
|
|
2259
|
+
type: "button",
|
|
2260
|
+
role: "radio",
|
|
2261
|
+
"aria-checked": selected,
|
|
2262
|
+
onClick: onSelect,
|
|
2263
|
+
disabled,
|
|
2264
|
+
className: cn(
|
|
2265
|
+
"flex w-full items-center gap-3 rounded-md border px-3 py-2 text-left transition",
|
|
2266
|
+
selected ? "border-foreground bg-accent/40 shadow-sm" : "border-transparent bg-background hover:border-foreground/40 hover:bg-accent/20",
|
|
2267
|
+
disabled && "cursor-not-allowed opacity-60"
|
|
2268
|
+
),
|
|
2269
|
+
children: [
|
|
2270
|
+
swatch ? /* @__PURE__ */ jsx(
|
|
2271
|
+
"span",
|
|
2272
|
+
{
|
|
2273
|
+
className: "h-5 w-5 shrink-0 rounded-full border border-black/10",
|
|
2274
|
+
style: { backgroundColor: swatch },
|
|
2275
|
+
"aria-hidden": "true"
|
|
2276
|
+
}
|
|
2277
|
+
) : null,
|
|
2278
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
2279
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: label }),
|
|
2280
|
+
description ? /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: description }) : null
|
|
2281
|
+
] })
|
|
2282
|
+
]
|
|
2283
|
+
}
|
|
2284
|
+
) });
|
|
2285
|
+
}
|
|
2286
|
+
function classifyStyleApplyError(error) {
|
|
2287
|
+
if (isRecord(error)) {
|
|
2288
|
+
const status = error.status;
|
|
2289
|
+
const code = error.code;
|
|
2290
|
+
if (status === 409 || code === "resource:conflict") {
|
|
2291
|
+
return {
|
|
2292
|
+
kind: "stale",
|
|
2293
|
+
message: "This design changed elsewhere. Refreshing the latest version now."
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
return {
|
|
2298
|
+
kind: "other",
|
|
2299
|
+
message: error instanceof Error ? error.message : "Unable to apply your selection."
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2302
|
+
function isRecord(value) {
|
|
2303
|
+
return typeof value === "object" && value !== null;
|
|
2304
|
+
}
|
|
2305
|
+
function WarningNotice({ warnings, onDismiss }) {
|
|
2306
|
+
return /* @__PURE__ */ jsxs("div", { role: "status", className: "rounded-md border border-warning/40 bg-warning/10 px-3 py-2 text-xs text-warning-foreground", children: [
|
|
2307
|
+
/* @__PURE__ */ jsx("ul", { className: "space-y-1", children: warnings.map((warning, index) => /* @__PURE__ */ jsx("li", { children: describeWarning(warning) }, `${warning.kind}-${index}`)) }),
|
|
2308
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onDismiss, className: "mt-2 underline", children: "Dismiss" })
|
|
2309
|
+
] });
|
|
2310
|
+
}
|
|
2311
|
+
function NoticeBanner({ notices, onDismiss }) {
|
|
2312
|
+
return /* @__PURE__ */ jsxs("div", { role: "status", className: "rounded-md border border-muted-foreground/20 bg-muted/40 px-3 py-2 text-xs", children: [
|
|
2313
|
+
/* @__PURE__ */ jsx("ul", { className: "space-y-1", children: notices.map((notice, index) => /* @__PURE__ */ jsx("li", { children: describeNotice(notice) }, `${notice.kind}-${index}`)) }),
|
|
2314
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onDismiss, className: "mt-2 underline", children: "Dismiss" })
|
|
2315
|
+
] });
|
|
2316
|
+
}
|
|
2317
|
+
function describeNotice(notice) {
|
|
2318
|
+
switch (notice.kind) {
|
|
2319
|
+
case "palette-overrides-hidden":
|
|
2320
|
+
return "Custom colours are saved for the previous palette.";
|
|
2321
|
+
case "palette-overrides-restored":
|
|
2322
|
+
return "Custom colours restored for this palette.";
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
function describeWarning(warning) {
|
|
2326
|
+
switch (warning.kind) {
|
|
2327
|
+
case "rejected-personality":
|
|
2328
|
+
return `Button style changed to ${warning.fellBackTo} because ${warning.requested} is not offered for this site style.`;
|
|
2329
|
+
case "rejected-palette":
|
|
2330
|
+
return `Palette changed to ${warning.fellBackTo} because ${warning.requested} is not available.`;
|
|
2331
|
+
case "rejected-typography-preset":
|
|
2332
|
+
return `Typography changed to ${warning.fellBackTo} because ${warning.requested} is not offered for this site style.`;
|
|
2333
|
+
default: {
|
|
2334
|
+
const exhaustive = warning;
|
|
2335
|
+
return exhaustive;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
function PreviewEditorSidebar({
|
|
2340
|
+
surfaceId,
|
|
2341
|
+
siteId,
|
|
2342
|
+
dashboardBaseUrl = null,
|
|
2343
|
+
pageId,
|
|
2344
|
+
entryData,
|
|
2345
|
+
contentTypeSchema,
|
|
2346
|
+
referenceContentTypeKeys = [],
|
|
2347
|
+
draftContent,
|
|
2348
|
+
currentTemplate,
|
|
2349
|
+
theme,
|
|
2350
|
+
themeId = null,
|
|
2351
|
+
themeSelectionId = null,
|
|
2352
|
+
themeSelectionVersion = null,
|
|
2353
|
+
themeSelectionUpdatedAt = null,
|
|
2354
|
+
themeSelectionButtonPersonalityId = null,
|
|
2355
|
+
themeSelectionPaletteVariantId = null,
|
|
2356
|
+
themeSelectionPaletteOverrides = null,
|
|
2357
|
+
themeSelectionHeaderLayoutId = null,
|
|
2358
|
+
themeSelectionHeaderStyleId = null,
|
|
2359
|
+
themeSelectionTypographyPresetId = null,
|
|
2360
|
+
themeSelectionTypographyBodyFontOverride = null,
|
|
2361
|
+
themeSelectionTypographyHeadingFontOverride = null,
|
|
2362
|
+
themeSelectionHeaderLookId = null,
|
|
2363
|
+
themeSelectionFooterLookId = null,
|
|
2364
|
+
themeSelectionHeaderContext,
|
|
2365
|
+
themeSelectionFooterContext,
|
|
2366
|
+
designRowRequest = null,
|
|
2367
|
+
apiClient,
|
|
2368
|
+
bearerToken,
|
|
2369
|
+
supabaseUrl,
|
|
2370
|
+
entrySubrouteContext,
|
|
2371
|
+
assistantPanel,
|
|
2372
|
+
sdkConfig,
|
|
2373
|
+
mediaAssets,
|
|
2374
|
+
siteStyleAccess = { kind: "hidden" },
|
|
2375
|
+
activeTab,
|
|
2376
|
+
onTabChange,
|
|
2377
|
+
onClose,
|
|
2378
|
+
viewportMode,
|
|
2379
|
+
viewportWidth,
|
|
2380
|
+
zoom,
|
|
2381
|
+
onViewportModeChange,
|
|
2382
|
+
onViewportWidthChange,
|
|
2383
|
+
onZoomChange
|
|
2384
|
+
}) {
|
|
2385
|
+
const controller = useEntryController();
|
|
2386
|
+
const controllerBlocks = controller.blocks;
|
|
2387
|
+
const uiStore = useEditorUiStoreInstance();
|
|
2388
|
+
const uiStoreForBlockEditor = useMemo(
|
|
2389
|
+
() => ({
|
|
2390
|
+
getState: uiStore.getState,
|
|
2391
|
+
subscribe: (listener) => uiStore.subscribe(() => listener())
|
|
2392
|
+
}),
|
|
2393
|
+
[uiStore]
|
|
2394
|
+
);
|
|
2395
|
+
const focusRequest = useUISelector(uiStore, (s) => s.focusRequest);
|
|
2396
|
+
const { canEdit } = usePreviewAuth();
|
|
2397
|
+
const dashboardSetupHandoff = useMemo(
|
|
2398
|
+
() => resolvePreviewDashboardSetupHandoff(),
|
|
2399
|
+
[]
|
|
2400
|
+
);
|
|
2401
|
+
const dashboardContinueUrl = useMemo(
|
|
2402
|
+
() => buildPreviewDashboardSetupUrl(dashboardBaseUrl, dashboardSetupHandoff),
|
|
2403
|
+
[dashboardBaseUrl, dashboardSetupHandoff]
|
|
2404
|
+
);
|
|
2405
|
+
const sidebarChrome = useMemo(
|
|
2406
|
+
() => resolvePreviewSidebarChromeModel(surfaceId),
|
|
2407
|
+
[surfaceId]
|
|
2408
|
+
);
|
|
2409
|
+
const editorGuidance = useMemo(
|
|
2410
|
+
() => resolvePreviewEditorGuidanceModel({
|
|
2411
|
+
surfaceId,
|
|
2412
|
+
canEdit,
|
|
2413
|
+
hasDashboardSetupHandoff: Boolean(dashboardSetupHandoff)
|
|
2414
|
+
}),
|
|
2415
|
+
[canEdit, dashboardSetupHandoff, surfaceId]
|
|
2416
|
+
);
|
|
2417
|
+
const showAssistantTab = previewAssistantEnabled && Boolean(assistantPanel);
|
|
2418
|
+
const assistantAvailable = showAssistantTab && Boolean(pageId && bearerToken && canEdit);
|
|
2419
|
+
const effectiveActiveTab = showAssistantTab ? resolvePreviewSidebarTab(activeTab) : "editor";
|
|
2420
|
+
const blockItems = useMemo(
|
|
2421
|
+
() => controllerBlocks.list,
|
|
2422
|
+
[controllerBlocks.list]
|
|
2423
|
+
);
|
|
2424
|
+
const blockEditorRef = useRef(null);
|
|
2425
|
+
const contentFormWidgets = useMemo(() => previewWidgetOverrides, []);
|
|
2426
|
+
const handleBlocksChange = useCallback(
|
|
2427
|
+
(nextItems) => {
|
|
2428
|
+
controllerBlocks.actions.setList(nextItems);
|
|
2429
|
+
const drafts = uiStore.getState().drafts;
|
|
2430
|
+
const extractData = (content) => {
|
|
2431
|
+
if (!content || typeof content !== "object") return null;
|
|
2432
|
+
const obj = content;
|
|
2433
|
+
if ("data" in obj && obj.data && typeof obj.data === "object") {
|
|
2434
|
+
return obj.data;
|
|
2435
|
+
}
|
|
2436
|
+
return obj;
|
|
2437
|
+
};
|
|
2438
|
+
for (const block of nextItems) {
|
|
2439
|
+
if (!block.id) continue;
|
|
2440
|
+
const baseline = extractData(
|
|
2441
|
+
block.content ?? block.draftContent ?? block.publishedContent ?? null
|
|
2442
|
+
);
|
|
2443
|
+
const blockId = unsafeAsEditorBlockId(block.id);
|
|
2444
|
+
if (drafts.getBlockSnapshot(blockId)) {
|
|
2445
|
+
drafts.updateBlockBaseline(blockId, baseline, { kind: block.kind });
|
|
2446
|
+
} else {
|
|
2447
|
+
drafts.initNewBlock(blockId, baseline, { kind: block.kind });
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
},
|
|
2451
|
+
[controllerBlocks.actions, uiStore]
|
|
2452
|
+
);
|
|
2453
|
+
useEffect(() => {
|
|
2454
|
+
const req = focusRequest;
|
|
2455
|
+
if (!req?.blockId) return;
|
|
2456
|
+
blockEditorRef.current?.focusField({
|
|
2457
|
+
blockId: req.blockId,
|
|
2458
|
+
fieldPath: req.fieldPath ?? void 0,
|
|
2459
|
+
itemIndex: req.itemIndex ?? void 0
|
|
2460
|
+
});
|
|
2461
|
+
uiStore.getState().clearFocus();
|
|
2462
|
+
}, [focusRequest, uiStore]);
|
|
2463
|
+
const metadataSectionContent = useMemo(() => {
|
|
2464
|
+
if (!pageId && !entryData) return null;
|
|
2465
|
+
return /* @__PURE__ */ jsx(PreviewMetadataPanel, { contentTypeKey: entryData?.type ?? null });
|
|
2466
|
+
}, [entryData, pageId]);
|
|
2467
|
+
const editorPanel = entryData ? /* @__PURE__ */ jsx(
|
|
2468
|
+
EntryEditorPanel,
|
|
2469
|
+
{
|
|
2470
|
+
siteId,
|
|
2471
|
+
entryData,
|
|
2472
|
+
contentTypeSchema,
|
|
2473
|
+
referenceContentTypeKeys,
|
|
2474
|
+
draftContent,
|
|
2475
|
+
currentTemplate,
|
|
2476
|
+
theme,
|
|
2477
|
+
apiClient,
|
|
2478
|
+
bearerToken,
|
|
2479
|
+
supabaseUrl,
|
|
2480
|
+
entrySubrouteContext,
|
|
2481
|
+
metadataPanel: metadataSectionContent,
|
|
2482
|
+
mediaAssets,
|
|
2483
|
+
widgets: contentFormWidgets,
|
|
2484
|
+
viewportMode,
|
|
2485
|
+
viewportWidth,
|
|
2486
|
+
zoom,
|
|
2487
|
+
onViewportModeChange,
|
|
2488
|
+
onViewportWidthChange,
|
|
2489
|
+
onZoomChange
|
|
2490
|
+
}
|
|
2491
|
+
) : pageId ? /* @__PURE__ */ jsx(
|
|
2492
|
+
PageBlockEditorPanel,
|
|
2493
|
+
{
|
|
2494
|
+
siteId,
|
|
2495
|
+
pageId,
|
|
2496
|
+
theme,
|
|
2497
|
+
apiClient,
|
|
2498
|
+
bearerToken,
|
|
2499
|
+
supabaseUrl,
|
|
2500
|
+
blockItems,
|
|
2501
|
+
uiStoreForBlockEditor,
|
|
2502
|
+
blockEditorRef,
|
|
2503
|
+
onBlocksChange: handleBlocksChange,
|
|
2504
|
+
metadataPanel: metadataSectionContent,
|
|
2505
|
+
designPanel: canInspectSiteStyle(siteStyleAccess) ? /* @__PURE__ */ jsx(
|
|
2506
|
+
StyleConfigurator,
|
|
2507
|
+
{
|
|
2508
|
+
siteId,
|
|
2509
|
+
apiClient,
|
|
2510
|
+
theme,
|
|
2511
|
+
themeId,
|
|
2512
|
+
selectionId: themeSelectionId,
|
|
2513
|
+
selectionVersion: themeSelectionVersion,
|
|
2514
|
+
selectionUpdatedAt: themeSelectionUpdatedAt,
|
|
2515
|
+
initialSelections: {
|
|
2516
|
+
selectionVersion: themeSelectionVersion,
|
|
2517
|
+
selectionUpdatedAt: themeSelectionUpdatedAt,
|
|
2518
|
+
buttonPersonalityId: themeSelectionButtonPersonalityId,
|
|
2519
|
+
paletteVariantId: themeSelectionPaletteVariantId,
|
|
2520
|
+
paletteOverrides: themeSelectionPaletteOverrides,
|
|
2521
|
+
headerLayoutId: themeSelectionHeaderLayoutId,
|
|
2522
|
+
headerStyleId: themeSelectionHeaderStyleId,
|
|
2523
|
+
typographyPresetId: themeSelectionTypographyPresetId,
|
|
2524
|
+
typographyBodyFontOverride: themeSelectionTypographyBodyFontOverride,
|
|
2525
|
+
typographyHeadingFontOverride: themeSelectionTypographyHeadingFontOverride,
|
|
2526
|
+
headerLookId: themeSelectionHeaderLookId,
|
|
2527
|
+
footerLookId: themeSelectionFooterLookId
|
|
2528
|
+
},
|
|
2529
|
+
headerSelectionContext: themeSelectionHeaderContext,
|
|
2530
|
+
footerSelectionContext: themeSelectionFooterContext,
|
|
2531
|
+
expandedRowRequest: designRowRequest,
|
|
2532
|
+
siteStyleAccess
|
|
2533
|
+
}
|
|
2534
|
+
) : null,
|
|
2535
|
+
sdkConfig: sdkConfig ?? null,
|
|
2536
|
+
mediaAssets,
|
|
2537
|
+
viewportMode,
|
|
2538
|
+
viewportWidth,
|
|
2539
|
+
zoom,
|
|
2540
|
+
onViewportModeChange,
|
|
2541
|
+
onViewportWidthChange,
|
|
2542
|
+
onZoomChange
|
|
2543
|
+
}
|
|
2544
|
+
) : /* @__PURE__ */ jsx("div", { className: "flex h-full flex-col", children: /* @__PURE__ */ jsx("div", { className: "flex-1 p-4", children: /* @__PURE__ */ jsx("div", { className: "rounded-md border border-dashed px-4 py-6 text-sm text-muted-foreground", children: "Block editing is only available when a page is selected." }) }) });
|
|
2545
|
+
const assistantPanelDisplay = assistantAvailable ? assistantPanel : /* @__PURE__ */ jsx("div", { className: "flex h-full flex-col items-center justify-center p-6 text-center text-sm text-muted-foreground", children: "AI Assistant is available for pages in preview mode." });
|
|
2546
|
+
return /* @__PURE__ */ jsx(
|
|
2547
|
+
"div",
|
|
2548
|
+
{
|
|
2549
|
+
className: `flex h-full min-h-0 flex-col overflow-hidden pb-[calc(var(--builder-editor-chrome-offset,0px)+var(--builder-preview-banner-offset,0px))] ${sidebarChrome.rootClassName}`,
|
|
2550
|
+
"data-preview-sidebar-tone": sidebarChrome.tone,
|
|
2551
|
+
children: /* @__PURE__ */ jsxs(
|
|
2552
|
+
Tabs,
|
|
2553
|
+
{
|
|
2554
|
+
value: effectiveActiveTab,
|
|
2555
|
+
onValueChange: (value) => onTabChange(
|
|
2556
|
+
resolvePreviewSidebarTab(value)
|
|
2557
|
+
),
|
|
2558
|
+
className: "flex h-full min-h-0 flex-col overflow-hidden",
|
|
2559
|
+
children: [
|
|
2560
|
+
/* @__PURE__ */ jsxs(
|
|
2561
|
+
"div",
|
|
2562
|
+
{
|
|
2563
|
+
className: `shrink-0 flex items-center justify-between px-4 py-3 ${sidebarChrome.headerClassName}`,
|
|
2564
|
+
children: [
|
|
2565
|
+
/* @__PURE__ */ jsxs(TabsList, { className: "h-8", children: [
|
|
2566
|
+
/* @__PURE__ */ jsx(TabsTrigger, { value: "editor", children: "Editor" }),
|
|
2567
|
+
showAssistantTab ? /* @__PURE__ */ jsx(TabsTrigger, { value: "assistant", children: "AI Assistant" }) : null
|
|
2568
|
+
] }),
|
|
2569
|
+
/* @__PURE__ */ jsx(
|
|
2570
|
+
Button,
|
|
2571
|
+
{
|
|
2572
|
+
variant: "ghost",
|
|
2573
|
+
size: "icon",
|
|
2574
|
+
onClick: onClose,
|
|
2575
|
+
"aria-label": "Close sidebar",
|
|
2576
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
2577
|
+
}
|
|
2578
|
+
)
|
|
2579
|
+
]
|
|
2580
|
+
}
|
|
2581
|
+
),
|
|
2582
|
+
editorGuidance ? /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b px-4 py-3", children: /* @__PURE__ */ jsx(
|
|
2583
|
+
PreviewEditorGuidanceCard,
|
|
2584
|
+
{
|
|
2585
|
+
guidance: editorGuidance,
|
|
2586
|
+
completeSetupDisabled: !dashboardContinueUrl,
|
|
2587
|
+
onCompleteSetup: () => {
|
|
2588
|
+
if (!dashboardContinueUrl || typeof window === "undefined")
|
|
2589
|
+
return;
|
|
2590
|
+
window.location.href = dashboardContinueUrl;
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
) }) : null,
|
|
2594
|
+
/* @__PURE__ */ jsx(
|
|
2595
|
+
TabsContent,
|
|
2596
|
+
{
|
|
2597
|
+
value: "editor",
|
|
2598
|
+
forceMount: true,
|
|
2599
|
+
className: "mt-0 min-h-0 flex-1 overflow-hidden data-[state=inactive]:hidden",
|
|
2600
|
+
children: editorPanel
|
|
2601
|
+
}
|
|
2602
|
+
),
|
|
2603
|
+
showAssistantTab ? /* @__PURE__ */ jsx(
|
|
2604
|
+
TabsContent,
|
|
2605
|
+
{
|
|
2606
|
+
value: "assistant",
|
|
2607
|
+
forceMount: true,
|
|
2608
|
+
className: "mt-0 min-h-0 flex-1 overflow-hidden data-[state=inactive]:hidden",
|
|
2609
|
+
children: assistantPanelDisplay
|
|
2610
|
+
}
|
|
2611
|
+
) : null
|
|
2612
|
+
]
|
|
2613
|
+
}
|
|
2614
|
+
)
|
|
2615
|
+
}
|
|
2616
|
+
);
|
|
2617
|
+
}
|
|
2618
|
+
function PreviewMetadataPanel({
|
|
2619
|
+
contentTypeKey
|
|
2620
|
+
}) {
|
|
2621
|
+
const uiStore = useEditorUiStoreInstance();
|
|
2622
|
+
const manifest = useMemo(() => {
|
|
2623
|
+
return omitRouteTitleFieldFromManifest(
|
|
2624
|
+
getRouteMetadataManifestForType(contentTypeKey)
|
|
2625
|
+
);
|
|
2626
|
+
}, [contentTypeKey]);
|
|
2627
|
+
const validation = useMemo(
|
|
2628
|
+
() => createManifestValidation(manifest),
|
|
2629
|
+
[manifest]
|
|
2630
|
+
);
|
|
2631
|
+
const backend = useMemo(
|
|
2632
|
+
() => createStoreBackend({
|
|
2633
|
+
scope: "metadata",
|
|
2634
|
+
store: uiStore,
|
|
2635
|
+
validateFn: (values) => {
|
|
2636
|
+
const result = validation.schema.safeParse(values);
|
|
2637
|
+
if (!result.success) {
|
|
2638
|
+
return result.error.issues;
|
|
2639
|
+
}
|
|
2640
|
+
return [];
|
|
2641
|
+
}
|
|
2642
|
+
}),
|
|
2643
|
+
[uiStore, validation]
|
|
2644
|
+
);
|
|
2645
|
+
return /* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsx(
|
|
2646
|
+
ManifestForm,
|
|
2647
|
+
{
|
|
2648
|
+
manifest,
|
|
2649
|
+
backend,
|
|
2650
|
+
className: "space-y-4"
|
|
2651
|
+
}
|
|
2652
|
+
) });
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
export { PreviewEditorSidebar };
|