create-nextblock 0.16.3 → 0.17.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/CLAUDE.md +22 -0
- package/docker-template/.env.docker.example +69 -69
- package/docker-template/docker/db/init/99-jwt.sql +6 -6
- package/docker-template/docker/db/init/99-roles.sql +25 -25
- package/docker-template/docker/kong/kong.yml +112 -112
- package/docker-template/scripts/docker-setup.mjs +310 -310
- package/package.json +1 -1
- package/templates/nextblock-template/.browserslistrc +11 -11
- package/templates/nextblock-template/.swcrc +30 -30
- package/templates/nextblock-template/CLAUDE.md +26 -0
- package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +46 -46
- package/templates/nextblock-template/app/(auth-pages)/two-factor/page.tsx +51 -51
- package/templates/nextblock-template/app/.well-known/ucp/route.ts +16 -16
- package/templates/nextblock-template/app/actions/consent.ts +57 -57
- package/templates/nextblock-template/app/actions/twoFactorEmail.ts +22 -22
- package/templates/nextblock-template/app/api/ai/cortex/build-widget/route.ts +153 -153
- package/templates/nextblock-template/app/api/ai/generate-blocks/route.ts +96 -96
- package/templates/nextblock-template/app/api/brand/email-logo/route.ts +48 -48
- package/templates/nextblock-template/app/api/checkout/freemius/sync/route.ts +29 -29
- package/templates/nextblock-template/app/api/cms/check-updates/route.ts +44 -44
- package/templates/nextblock-template/app/api/cms/full-backup/export/route.ts +33 -33
- package/templates/nextblock-template/app/api/cms/full-backup/restore/route.ts +63 -63
- package/templates/nextblock-template/app/api/cron/reset-sandbox/route.ts +270 -205
- package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +10388 -9315
- package/templates/nextblock-template/app/api/cron/sync-currencies/route.ts +39 -39
- package/templates/nextblock-template/app/api/custom-blocks/db-relations/route.ts +92 -92
- package/templates/nextblock-template/app/api/custom-blocks/editor-definitions/route.ts +43 -43
- package/templates/nextblock-template/app/api/media/library/route.ts +69 -69
- package/templates/nextblock-template/app/api/media/r2-presigned/route.ts +53 -53
- package/templates/nextblock-template/app/api/visual-editing/block-draft/route.ts +47 -47
- package/templates/nextblock-template/app/api/visual-editing/product-draft/route.ts +47 -47
- package/templates/nextblock-template/app/article/[slug]/PostClientContent.tsx +442 -441
- package/templates/nextblock-template/app/article/[slug]/page.utils.ts +4 -1
- package/templates/nextblock-template/app/checkout/UcpCartHydrator.tsx +20 -20
- package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +241 -241
- package/templates/nextblock-template/app/cms/blocks/components/CustomBlockEditorPreview.tsx +160 -160
- package/templates/nextblock-template/app/cms/blocks/editors/DynamicCustomBlockEditor.tsx +167 -167
- package/templates/nextblock-template/app/cms/components/ConnectGitHubButton.tsx +122 -122
- package/templates/nextblock-template/app/cms/components/CortexAiActiveContext.tsx +23 -23
- package/templates/nextblock-template/app/cms/components/SeoScoreBadge.tsx +3 -40
- package/templates/nextblock-template/app/cms/components/SystemAlertsBanner.tsx +112 -112
- package/templates/nextblock-template/app/cms/components/TablePagination.tsx +136 -136
- package/templates/nextblock-template/app/cms/components/TwoFactorReminderBanner.tsx +45 -45
- package/templates/nextblock-template/app/cms/components/github-connect-actions.ts +102 -102
- package/templates/nextblock-template/app/cms/components/system-alerts-actions.ts +31 -31
- package/templates/nextblock-template/app/cms/coupons/[id]/edit/page.tsx +16 -16
- package/templates/nextblock-template/app/cms/coupons/page.tsx +16 -16
- package/templates/nextblock-template/app/cms/custom-blocks/[id]/edit/page.tsx +66 -66
- package/templates/nextblock-template/app/cms/custom-blocks/actions.ts +519 -519
- package/templates/nextblock-template/app/cms/custom-blocks/components/BlocksLibraryTransferControls.tsx +256 -256
- package/templates/nextblock-template/app/cms/custom-blocks/components/DBRelationSelect.tsx +384 -384
- package/templates/nextblock-template/app/cms/custom-blocks/components/ImageR2Picker.tsx +221 -221
- package/templates/nextblock-template/app/cms/custom-blocks/new/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/custom-blocks/page.tsx +438 -438
- package/templates/nextblock-template/app/cms/dashboard/components/DashboardComponents.tsx +200 -200
- package/templates/nextblock-template/app/cms/dashboard/components/DashboardOnboarding.tsx +130 -130
- package/templates/nextblock-template/app/cms/import-export/actions.ts +226 -226
- package/templates/nextblock-template/app/cms/pages/page.tsx +273 -273
- package/templates/nextblock-template/app/cms/posts/page.tsx +251 -251
- package/templates/nextblock-template/app/cms/products/ProductFormClientShell.tsx +19 -0
- package/templates/nextblock-template/app/cms/products/[id]/edit/page.tsx +14 -2
- package/templates/nextblock-template/app/cms/products/categories/page.tsx +12 -12
- package/templates/nextblock-template/app/cms/products/new/page.tsx +135 -135
- package/templates/nextblock-template/app/cms/promotions/PromotionsWorkspace.tsx +456 -456
- package/templates/nextblock-template/app/cms/promotions/actions.ts +115 -115
- package/templates/nextblock-template/app/cms/promotions/page.tsx +31 -31
- package/templates/nextblock-template/app/cms/settings/backup-restore/BackupRestoreWorkspace.tsx +1004 -1004
- package/templates/nextblock-template/app/cms/settings/backup-restore/page.tsx +29 -29
- package/templates/nextblock-template/app/cms/settings/bot-protection/page.tsx +24 -24
- package/templates/nextblock-template/app/cms/settings/email/page.tsx +28 -28
- package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +276 -276
- package/templates/nextblock-template/app/cms/settings/google-analytics/page.tsx +26 -26
- package/templates/nextblock-template/app/cms/settings/logos/components/DeleteLogoButton.tsx +21 -21
- package/templates/nextblock-template/app/cms/settings/logos/components/SetActiveLogoButton.tsx +42 -42
- package/templates/nextblock-template/app/cms/settings/logos/components/SiteSeoSettingsForm.tsx +133 -133
- package/templates/nextblock-template/app/cms/settings/privacy/page.tsx +27 -27
- package/templates/nextblock-template/app/cms/settings/registration/page.tsx +27 -27
- package/templates/nextblock-template/app/cms/settings/security/page.tsx +33 -33
- package/templates/nextblock-template/app/lib/site-settings.ts +105 -105
- package/templates/nextblock-template/app/lib/ucp/protocol.ts +190 -190
- package/templates/nextblock-template/app/lib/ucp/server.test.ts +56 -56
- package/templates/nextblock-template/app/setup/SetupWizard.tsx +678 -678
- package/templates/nextblock-template/app/setup/layout.tsx +13 -13
- package/templates/nextblock-template/app/setup/page.tsx +111 -111
- package/templates/nextblock-template/app/ucp/v1/carts/[id]/cancel/route.ts +38 -38
- package/templates/nextblock-template/app/ucp/v1/carts/[id]/route.ts +68 -68
- package/templates/nextblock-template/app/ucp/v1/carts/route.ts +35 -35
- package/templates/nextblock-template/app/ucp/v1/catalog/lookup/route.ts +35 -35
- package/templates/nextblock-template/app/ucp/v1/catalog/product/route.ts +35 -35
- package/templates/nextblock-template/app/ucp/v1/catalog/search/route.ts +34 -34
- package/templates/nextblock-template/components/CartTranslator.tsx +210 -210
- package/templates/nextblock-template/components/DeferredCartTranslator.tsx +51 -51
- package/templates/nextblock-template/components/DeferredGlobalSearch.tsx +68 -68
- package/templates/nextblock-template/components/DeferredGoogleAnalytics.tsx +70 -70
- package/templates/nextblock-template/components/FeatureImageHero.tsx +47 -47
- package/templates/nextblock-template/components/GlobalSearch.tsx +557 -557
- package/templates/nextblock-template/components/Header.tsx +38 -38
- package/templates/nextblock-template/components/PublicEnvBootstrap.tsx +30 -30
- package/templates/nextblock-template/components/ResponsiveNav.tsx +14 -14
- package/templates/nextblock-template/components/auth/AuthBotProtection.tsx +182 -182
- package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +48 -48
- package/templates/nextblock-template/components/blocks/TestimonialBlock.tsx +9 -9
- package/templates/nextblock-template/components/blocks/publicRendererLoaders.ts +25 -25
- package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -92
- package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +11 -0
- package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +24 -24
- package/templates/nextblock-template/components/blocks/renderers/TestimonialBlockRenderer.tsx +57 -57
- package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +2 -2
- package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +2 -2
- package/templates/nextblock-template/components/media/YouTubeFacade.tsx +105 -105
- package/templates/nextblock-template/components/media/youtube-embed-replace.tsx +32 -32
- package/templates/nextblock-template/components/privacy/ConsentBanner.tsx +170 -170
- package/templates/nextblock-template/components/privacy/ConsentGatedAnalytics.tsx +70 -70
- package/templates/nextblock-template/components/renderers/CachedDynamicLayoutEngine.tsx +28 -28
- package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.test.tsx +166 -166
- package/templates/nextblock-template/components/renderers/DynamicLayoutEngine.tsx +471 -471
- package/templates/nextblock-template/components/visual-editing/DeferredVisualEditing.tsx +21 -21
- package/templates/nextblock-template/context/language-rest-client.ts +32 -32
- package/templates/nextblock-template/docker/db/init/99-jwt.sql +6 -6
- package/templates/nextblock-template/docker/db/init/99-roles.sql +25 -25
- package/templates/nextblock-template/docker/kong/kong.yml +112 -112
- package/templates/nextblock-template/docs/02-ECOMMERCE-CAPABILITIES.md +364 -364
- package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +11 -0
- package/templates/nextblock-template/docs/07-BLOCK-SDK-AND-EXTENSIBILITY.md +146 -146
- package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +19 -0
- package/templates/nextblock-template/docs/10-CUSTOM-BLOCKS.md +222 -222
- package/templates/nextblock-template/lib/app-secrets.ts +39 -39
- package/templates/nextblock-template/lib/auth/cookies.ts +47 -47
- package/templates/nextblock-template/lib/auth/crypto.ts +45 -45
- package/templates/nextblock-template/lib/auth/trustedDevices.ts +92 -92
- package/templates/nextblock-template/lib/blocks/README.md +13 -13
- package/templates/nextblock-template/lib/botProtection/verify.ts +134 -134
- package/templates/nextblock-template/lib/cms/payments-reminder.test.ts +135 -135
- package/templates/nextblock-template/lib/cms/payments-reminder.ts +105 -105
- package/templates/nextblock-template/lib/cms-transfer/types.ts +145 -145
- package/templates/nextblock-template/lib/custom-block-definitions.ts +87 -87
- package/templates/nextblock-template/lib/custom-block-r2-upload-shared.ts +178 -178
- package/templates/nextblock-template/lib/custom-block-r2-upload.test.ts +140 -140
- package/templates/nextblock-template/lib/custom-block-r2-upload.ts +88 -88
- package/templates/nextblock-template/lib/custom-block-relations.test.ts +227 -227
- package/templates/nextblock-template/lib/custom-block-relations.ts +279 -279
- package/templates/nextblock-template/lib/custom-block-safelist.ts +14 -14
- package/templates/nextblock-template/lib/editor/dynamic-extension-core.test.ts +172 -172
- package/templates/nextblock-template/lib/editor/dynamic-extension-core.ts +213 -213
- package/templates/nextblock-template/lib/editor/dynamic-extension-loader.ts +22 -22
- package/templates/nextblock-template/lib/editor/dynamic-extensions.tsx +193 -193
- package/templates/nextblock-template/lib/email/branding-format.test.ts +133 -133
- package/templates/nextblock-template/lib/email/branding-format.ts +123 -123
- package/templates/nextblock-template/lib/email/branding.ts +76 -76
- package/templates/nextblock-template/lib/full-backup/manifest.test.ts +121 -121
- package/templates/nextblock-template/lib/full-backup/manifest.ts +206 -206
- package/templates/nextblock-template/lib/logos/active-logo.ts +53 -53
- package/templates/nextblock-template/lib/media/resolveMediaUrl.ts +56 -54
- package/templates/nextblock-template/lib/media/youtube.ts +99 -99
- package/templates/nextblock-template/lib/onboarding/actions.ts +31 -31
- package/templates/nextblock-template/lib/privacy/consent-client.ts +57 -57
- package/templates/nextblock-template/lib/privacy/contact-emails.ts +64 -64
- package/templates/nextblock-template/lib/privacy/settings.ts +115 -115
- package/templates/nextblock-template/lib/privacy/types.ts +69 -69
- package/templates/nextblock-template/lib/promotions/server.test.ts +74 -74
- package/templates/nextblock-template/lib/promotions/server.ts +741 -741
- package/templates/nextblock-template/lib/resolve-block-relations.test.ts +142 -142
- package/templates/nextblock-template/lib/resolve-block-relations.ts +255 -255
- package/templates/nextblock-template/lib/seo/page-audit-context.tsx +3 -3
- package/templates/nextblock-template/lib/seo/page-document.test.ts +34 -0
- package/templates/nextblock-template/lib/seo/page-document.ts +3 -462
- package/templates/nextblock-template/lib/seo/robots-txt.ts +5 -4
- package/templates/nextblock-template/lib/setup/actions.ts +460 -460
- package/templates/nextblock-template/lib/setup/env-status.ts +125 -125
- package/templates/nextblock-template/lib/setup/env-write.ts +111 -111
- package/templates/nextblock-template/lib/setup/migrations-bundle.ts +50 -10
- package/templates/nextblock-template/lib/setup/provisioning.ts +59 -59
- package/templates/nextblock-template/lib/setup/schema-apply.ts +408 -408
- package/templates/nextblock-template/lib/setup/system-config.ts +105 -105
- package/templates/nextblock-template/lib/setup/types.ts +18 -18
- package/templates/nextblock-template/lib/storage/provider.ts +66 -66
- package/templates/nextblock-template/lib/storage/supabase-storage.ts +103 -103
- package/templates/nextblock-template/lib/updates/github-device.ts +206 -206
- package/templates/nextblock-template/lib/updates/repo-identity.ts +56 -56
- package/templates/nextblock-template/lib/visual-editing/draft-content.test.ts +105 -105
- package/templates/nextblock-template/lib/visual-editing/draft-route.test.ts +42 -42
- package/templates/nextblock-template/lib/visual-editing/edit-info.test.ts +143 -143
- package/templates/nextblock-template/lib/visual-editing/edit-info.ts +94 -94
- package/templates/nextblock-template/lib/visual-editing/product-drafts.test.ts +81 -81
- package/templates/nextblock-template/lib/zod-config.ts +5 -5
- package/templates/nextblock-template/next-env.d.ts +1 -0
- package/templates/nextblock-template/package.json +1 -1
- package/templates/nextblock-template/public/images/cortex_post.webp +0 -0
- package/templates/nextblock-template/public/images/update_nextblock.webp +0 -0
- package/templates/nextblock-template/scripts/docker-setup.mjs +310 -310
- package/templates/nextblock-template/scripts/validate-editor-block-schema.ts +112 -112
- package/templates/nextblock-template/scripts/verify-cortex-ai-build-widget.tsx +98 -98
- package/templates/nextblock-template/scripts/verify-cortex-ai-generate-blocks.ts +62 -62
- package/templates/nextblock-template/scripts/verify-cortex-ai-global-tools.ts +537 -537
- package/templates/nextblock-template/scripts/verify-cortex-ai-routing.ts +58 -58
- package/templates/nextblock-template/scripts/verify-custom-block-definitions.ts +188 -188
- package/templates/nextblock-template/scripts/verify-dynamic-custom-block-extensions.ts +123 -123
- package/templates/nextblock-template/scripts/verify-dynamic-layout-engine.tsx +133 -133
- package/templates/nextblock-template/scripts/verify-milestone-2-custom-blocks.ts +65 -65
- package/templates/nextblock-template/tools/deploy-supabase.js +159 -159
- package/templates/nextblock-template/types/jsdom.d.ts +6 -6
|
@@ -14,6 +14,8 @@ type PublicPostData = PostType & {
|
|
|
14
14
|
language_id: number;
|
|
15
15
|
translation_group_id: string;
|
|
16
16
|
feature_image_url?: string | null;
|
|
17
|
+
/** The media row's description, used as the hero image alt text when present. */
|
|
18
|
+
feature_image_alt?: string | null;
|
|
17
19
|
feature_image_blur_data_url?: string | null;
|
|
18
20
|
feature_image_width?: number | null;
|
|
19
21
|
feature_image_height?: number | null;
|
|
@@ -249,7 +251,7 @@ export async function getPostDataBySlug(slug: string): Promise<PublicPostData |
|
|
|
249
251
|
*,
|
|
250
252
|
languages!inner (id, code),
|
|
251
253
|
blocks (*),
|
|
252
|
-
media ( object_key, blur_data_url, width, height )
|
|
254
|
+
media ( object_key, blur_data_url, width, height, description )
|
|
253
255
|
`)
|
|
254
256
|
.eq("slug", slug) // Find the post by its unique slug for this language
|
|
255
257
|
.order('order', { foreignTable: 'blocks', ascending: true });
|
|
@@ -320,6 +322,7 @@ export async function getPostDataBySlug(slug: string): Promise<PublicPostData |
|
|
|
320
322
|
language_id: langInfo.id,
|
|
321
323
|
translation_group_id: postData.translation_group_id,
|
|
322
324
|
feature_image_url: resolveMediaUrl(postData.media?.object_key),
|
|
325
|
+
feature_image_alt: postData.media?.description ?? null,
|
|
323
326
|
feature_image_blur_data_url: postData.media?.blur_data_url,
|
|
324
327
|
feature_image_width: postData.media?.width ?? null,
|
|
325
328
|
feature_image_height: postData.media?.height ?? null,
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useEffect } from 'react';
|
|
4
|
-
import { useCartStore, type CartItem } from '@nextblock-cms/ecommerce';
|
|
5
|
-
|
|
6
|
-
interface UcpCartHydratorProps {
|
|
7
|
-
items: CartItem[];
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function UcpCartHydrator({ items }: UcpCartHydratorProps) {
|
|
11
|
-
useEffect(() => {
|
|
12
|
-
if (items.length === 0) {
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
useCartStore.getState().setItems(items);
|
|
17
|
-
}, [items]);
|
|
18
|
-
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
import { useCartStore, type CartItem } from '@nextblock-cms/ecommerce';
|
|
5
|
+
|
|
6
|
+
interface UcpCartHydratorProps {
|
|
7
|
+
items: CartItem[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function UcpCartHydrator({ items }: UcpCartHydratorProps) {
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (items.length === 0) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
useCartStore.getState().setItems(items);
|
|
17
|
+
}, [items]);
|
|
18
|
+
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useState, useEffect, type ComponentType, Suspense, LazyExoticComponent, useCallback, type CSSProperties } from "react";
|
|
2
|
-
import { cn } from "@nextblock-cms/utils";
|
|
1
|
+
import { useState, useEffect, type ComponentType, Suspense, LazyExoticComponent, useCallback, type CSSProperties } from "react";
|
|
2
|
+
import { cn } from "@nextblock-cms/utils";
|
|
3
3
|
import {
|
|
4
4
|
Dialog,
|
|
5
5
|
DialogContent,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
DialogDescription,
|
|
10
10
|
} from "@nextblock-cms/ui";
|
|
11
11
|
import { Button } from "@nextblock-cms/ui";
|
|
12
|
-
import { blockRegistry, BlockType } from '../../../../lib/blocks/blockRegistry';
|
|
12
|
+
import { blockRegistry, BlockType } from '../../../../lib/blocks/blockRegistry';
|
|
13
13
|
|
|
14
14
|
// A generic representation of a block object.
|
|
15
15
|
// The modal primarily needs `type` to get the label and `content` for editing.
|
|
@@ -20,171 +20,171 @@ export type Block<T = unknown> = {
|
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
// Props that every block editor component must accept.
|
|
23
|
-
export type BlockEditorProps<T = unknown> = {
|
|
24
|
-
block: Block<T>;
|
|
25
|
-
content: T;
|
|
26
|
-
onChange: (newContent: T) => void;
|
|
27
|
-
className?: string; // Added for editor component styling
|
|
28
|
-
sectionBackground?: import("@/lib/blocks/blockRegistry").SectionBlockContent['background'];
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export type EditorSurfaceContext = {
|
|
32
|
-
isDark: boolean;
|
|
33
|
-
style: CSSProperties;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export type BlockEditorSaveMode = "manual" | "autosave";
|
|
37
|
-
export type BlockEditorSaveStatus = "idle" | "dirty" | "saving" | "saved" | "error";
|
|
38
|
-
|
|
39
|
-
type BlockEditorModalProps = {
|
|
40
|
-
block: Block;
|
|
41
|
-
isOpen: boolean;
|
|
42
|
-
onClose: () => void;
|
|
43
|
-
onSave: (updatedContent: unknown) => void;
|
|
44
|
-
EditorComponent: LazyExoticComponent<ComponentType<BlockEditorProps<unknown>>> | ComponentType<BlockEditorProps<unknown>>;
|
|
45
|
-
sectionBackground?: import("@/lib/blocks/blockRegistry").SectionBlockContent['background'];
|
|
46
|
-
editorSurfaceContext?: EditorSurfaceContext | null;
|
|
47
|
-
titleOverride?: string;
|
|
48
|
-
useContextualSurface?: boolean;
|
|
49
|
-
saveMode?: BlockEditorSaveMode;
|
|
50
|
-
saveStatus?: BlockEditorSaveStatus;
|
|
51
|
-
saveStatusText?: string;
|
|
52
|
-
onAutoChange?: (content: unknown) => void;
|
|
53
|
-
onFlushBeforeClose?: () => Promise<boolean>;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const autosaveStatusLabels: Record<BlockEditorSaveStatus, string> = {
|
|
57
|
-
idle: "Saved",
|
|
58
|
-
dirty: "Unsaved",
|
|
59
|
-
saving: "Saving...",
|
|
60
|
-
saved: "Saved",
|
|
61
|
-
error: "Save failed",
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
export function BlockEditorModal({
|
|
65
|
-
block,
|
|
66
|
-
isOpen,
|
|
67
|
-
onClose,
|
|
68
|
-
onSave,
|
|
69
|
-
EditorComponent,
|
|
70
|
-
sectionBackground,
|
|
71
|
-
editorSurfaceContext,
|
|
72
|
-
titleOverride,
|
|
73
|
-
useContextualSurface,
|
|
74
|
-
saveMode = "manual",
|
|
75
|
-
saveStatus = "idle",
|
|
76
|
-
saveStatusText,
|
|
77
|
-
onAutoChange,
|
|
78
|
-
onFlushBeforeClose,
|
|
79
|
-
}: BlockEditorModalProps) {
|
|
80
|
-
const [tempContent, setTempContent] = useState(block.content);
|
|
81
|
-
const [showConfirmClose, setShowConfirmClose] = useState(false);
|
|
82
|
-
const [isFlushingBeforeClose, setIsFlushingBeforeClose] = useState(false);
|
|
83
|
-
const isValid = true; // Placeholder for future validation logic
|
|
84
|
-
const isAutosaveMode = saveMode === "autosave";
|
|
85
|
-
const [saveShortcutLabel, setSaveShortcutLabel] = useState("CMD+S");
|
|
86
|
-
|
|
87
|
-
useEffect(() => {
|
|
88
|
-
if (typeof window !== "undefined") {
|
|
89
|
-
const isMac = navigator.userAgent.indexOf("Mac") !== -1;
|
|
90
|
-
setSaveShortcutLabel(isMac ? "CMD+S" : "Ctrl+S");
|
|
91
|
-
}
|
|
92
|
-
}, []);
|
|
93
|
-
|
|
94
|
-
useEffect(() => {
|
|
95
|
-
// When the modal is opened with a new block, reset the temp content
|
|
96
|
-
if (isOpen) {
|
|
97
|
-
setTempContent(block.content);
|
|
98
|
-
setShowConfirmClose(false);
|
|
99
|
-
setIsFlushingBeforeClose(false);
|
|
100
|
-
}
|
|
101
|
-
}, [isOpen, block.content]);
|
|
102
|
-
|
|
103
|
-
const handleSave = useCallback(() => {
|
|
104
|
-
onSave(tempContent);
|
|
105
|
-
}, [onSave, tempContent]);
|
|
106
|
-
|
|
107
|
-
const hasUnsavedChanges = useCallback(() => {
|
|
108
|
-
return JSON.stringify(block.content) !== JSON.stringify(tempContent);
|
|
109
|
-
}, [block.content, tempContent]);
|
|
110
|
-
|
|
111
|
-
const flushAutosave = useCallback(async () => {
|
|
112
|
-
if (!onFlushBeforeClose) {
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
setIsFlushingBeforeClose(true);
|
|
117
|
-
try {
|
|
118
|
-
return await onFlushBeforeClose();
|
|
119
|
-
} finally {
|
|
120
|
-
setIsFlushingBeforeClose(false);
|
|
121
|
-
}
|
|
122
|
-
}, [onFlushBeforeClose]);
|
|
123
|
-
|
|
124
|
-
const handleCloseAttempt = useCallback(() => {
|
|
125
|
-
if (isAutosaveMode) {
|
|
126
|
-
void (async () => {
|
|
127
|
-
const didFlush = await flushAutosave();
|
|
128
|
-
if (didFlush) {
|
|
129
|
-
onClose();
|
|
130
|
-
}
|
|
131
|
-
})();
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (hasUnsavedChanges()) {
|
|
136
|
-
setShowConfirmClose(true);
|
|
137
|
-
} else {
|
|
138
|
-
onClose();
|
|
139
|
-
}
|
|
140
|
-
}, [flushAutosave, hasUnsavedChanges, isAutosaveMode, onClose]);
|
|
141
|
-
|
|
142
|
-
useEffect(() => {
|
|
143
|
-
if (!isOpen) {
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const handleKeyDown = (event: KeyboardEvent) => {
|
|
148
|
-
if (!(event.metaKey || event.ctrlKey) || event.key.toLowerCase() !== "s") {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
event.preventDefault();
|
|
153
|
-
if (isAutosaveMode) {
|
|
154
|
-
handleCloseAttempt();
|
|
155
|
-
} else {
|
|
156
|
-
handleSave();
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
window.addEventListener("keydown", handleKeyDown, true);
|
|
161
|
-
return () => {
|
|
162
|
-
window.removeEventListener("keydown", handleKeyDown, true);
|
|
163
|
-
};
|
|
164
|
-
}, [handleCloseAttempt, handleSave, isAutosaveMode, isOpen]);
|
|
165
|
-
|
|
166
|
-
const handleContentChange = (newContent: unknown) => {
|
|
167
|
-
setTempContent(newContent);
|
|
168
|
-
if (isAutosaveMode) {
|
|
169
|
-
onAutoChange?.(newContent);
|
|
170
|
-
}
|
|
171
|
-
// Potentially add validation here and set isValid
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
const blockInfo = blockRegistry[block.type];
|
|
175
|
-
const displayText = blockInfo?.label || "Block";
|
|
176
|
-
const shouldUseContextualSurface =
|
|
177
|
-
(useContextualSurface ?? true) && block.type === 'text';
|
|
178
|
-
const contextualSurfaceStyle = shouldUseContextualSurface ? editorSurfaceContext?.style : undefined;
|
|
179
|
-
const editorClassName = cn(
|
|
180
|
-
"bg-transparent text-foreground border-none shadow-none focus-within:ring-0 min-h-[60vh]",
|
|
181
|
-
shouldUseContextualSurface &&
|
|
182
|
-
editorSurfaceContext?.isDark &&
|
|
183
|
-
"[&_.ProseMirror]:text-white [&_.ProseMirror.prose]:prose-invert"
|
|
184
|
-
);
|
|
185
|
-
const resolvedSaveStatusText = saveStatusText ?? autosaveStatusLabels[saveStatus];
|
|
186
|
-
|
|
187
|
-
return (
|
|
23
|
+
export type BlockEditorProps<T = unknown> = {
|
|
24
|
+
block: Block<T>;
|
|
25
|
+
content: T;
|
|
26
|
+
onChange: (newContent: T) => void;
|
|
27
|
+
className?: string; // Added for editor component styling
|
|
28
|
+
sectionBackground?: import("@/lib/blocks/blockRegistry").SectionBlockContent['background'];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type EditorSurfaceContext = {
|
|
32
|
+
isDark: boolean;
|
|
33
|
+
style: CSSProperties;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type BlockEditorSaveMode = "manual" | "autosave";
|
|
37
|
+
export type BlockEditorSaveStatus = "idle" | "dirty" | "saving" | "saved" | "error";
|
|
38
|
+
|
|
39
|
+
type BlockEditorModalProps = {
|
|
40
|
+
block: Block;
|
|
41
|
+
isOpen: boolean;
|
|
42
|
+
onClose: () => void;
|
|
43
|
+
onSave: (updatedContent: unknown) => void;
|
|
44
|
+
EditorComponent: LazyExoticComponent<ComponentType<BlockEditorProps<unknown>>> | ComponentType<BlockEditorProps<unknown>>;
|
|
45
|
+
sectionBackground?: import("@/lib/blocks/blockRegistry").SectionBlockContent['background'];
|
|
46
|
+
editorSurfaceContext?: EditorSurfaceContext | null;
|
|
47
|
+
titleOverride?: string;
|
|
48
|
+
useContextualSurface?: boolean;
|
|
49
|
+
saveMode?: BlockEditorSaveMode;
|
|
50
|
+
saveStatus?: BlockEditorSaveStatus;
|
|
51
|
+
saveStatusText?: string;
|
|
52
|
+
onAutoChange?: (content: unknown) => void;
|
|
53
|
+
onFlushBeforeClose?: () => Promise<boolean>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const autosaveStatusLabels: Record<BlockEditorSaveStatus, string> = {
|
|
57
|
+
idle: "Saved",
|
|
58
|
+
dirty: "Unsaved",
|
|
59
|
+
saving: "Saving...",
|
|
60
|
+
saved: "Saved",
|
|
61
|
+
error: "Save failed",
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export function BlockEditorModal({
|
|
65
|
+
block,
|
|
66
|
+
isOpen,
|
|
67
|
+
onClose,
|
|
68
|
+
onSave,
|
|
69
|
+
EditorComponent,
|
|
70
|
+
sectionBackground,
|
|
71
|
+
editorSurfaceContext,
|
|
72
|
+
titleOverride,
|
|
73
|
+
useContextualSurface,
|
|
74
|
+
saveMode = "manual",
|
|
75
|
+
saveStatus = "idle",
|
|
76
|
+
saveStatusText,
|
|
77
|
+
onAutoChange,
|
|
78
|
+
onFlushBeforeClose,
|
|
79
|
+
}: BlockEditorModalProps) {
|
|
80
|
+
const [tempContent, setTempContent] = useState(block.content);
|
|
81
|
+
const [showConfirmClose, setShowConfirmClose] = useState(false);
|
|
82
|
+
const [isFlushingBeforeClose, setIsFlushingBeforeClose] = useState(false);
|
|
83
|
+
const isValid = true; // Placeholder for future validation logic
|
|
84
|
+
const isAutosaveMode = saveMode === "autosave";
|
|
85
|
+
const [saveShortcutLabel, setSaveShortcutLabel] = useState("CMD+S");
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (typeof window !== "undefined") {
|
|
89
|
+
const isMac = navigator.userAgent.indexOf("Mac") !== -1;
|
|
90
|
+
setSaveShortcutLabel(isMac ? "CMD+S" : "Ctrl+S");
|
|
91
|
+
}
|
|
92
|
+
}, []);
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
// When the modal is opened with a new block, reset the temp content
|
|
96
|
+
if (isOpen) {
|
|
97
|
+
setTempContent(block.content);
|
|
98
|
+
setShowConfirmClose(false);
|
|
99
|
+
setIsFlushingBeforeClose(false);
|
|
100
|
+
}
|
|
101
|
+
}, [isOpen, block.content]);
|
|
102
|
+
|
|
103
|
+
const handleSave = useCallback(() => {
|
|
104
|
+
onSave(tempContent);
|
|
105
|
+
}, [onSave, tempContent]);
|
|
106
|
+
|
|
107
|
+
const hasUnsavedChanges = useCallback(() => {
|
|
108
|
+
return JSON.stringify(block.content) !== JSON.stringify(tempContent);
|
|
109
|
+
}, [block.content, tempContent]);
|
|
110
|
+
|
|
111
|
+
const flushAutosave = useCallback(async () => {
|
|
112
|
+
if (!onFlushBeforeClose) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
setIsFlushingBeforeClose(true);
|
|
117
|
+
try {
|
|
118
|
+
return await onFlushBeforeClose();
|
|
119
|
+
} finally {
|
|
120
|
+
setIsFlushingBeforeClose(false);
|
|
121
|
+
}
|
|
122
|
+
}, [onFlushBeforeClose]);
|
|
123
|
+
|
|
124
|
+
const handleCloseAttempt = useCallback(() => {
|
|
125
|
+
if (isAutosaveMode) {
|
|
126
|
+
void (async () => {
|
|
127
|
+
const didFlush = await flushAutosave();
|
|
128
|
+
if (didFlush) {
|
|
129
|
+
onClose();
|
|
130
|
+
}
|
|
131
|
+
})();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (hasUnsavedChanges()) {
|
|
136
|
+
setShowConfirmClose(true);
|
|
137
|
+
} else {
|
|
138
|
+
onClose();
|
|
139
|
+
}
|
|
140
|
+
}, [flushAutosave, hasUnsavedChanges, isAutosaveMode, onClose]);
|
|
141
|
+
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
if (!isOpen) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
148
|
+
if (!(event.metaKey || event.ctrlKey) || event.key.toLowerCase() !== "s") {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
event.preventDefault();
|
|
153
|
+
if (isAutosaveMode) {
|
|
154
|
+
handleCloseAttempt();
|
|
155
|
+
} else {
|
|
156
|
+
handleSave();
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
window.addEventListener("keydown", handleKeyDown, true);
|
|
161
|
+
return () => {
|
|
162
|
+
window.removeEventListener("keydown", handleKeyDown, true);
|
|
163
|
+
};
|
|
164
|
+
}, [handleCloseAttempt, handleSave, isAutosaveMode, isOpen]);
|
|
165
|
+
|
|
166
|
+
const handleContentChange = (newContent: unknown) => {
|
|
167
|
+
setTempContent(newContent);
|
|
168
|
+
if (isAutosaveMode) {
|
|
169
|
+
onAutoChange?.(newContent);
|
|
170
|
+
}
|
|
171
|
+
// Potentially add validation here and set isValid
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const blockInfo = blockRegistry[block.type];
|
|
175
|
+
const displayText = blockInfo?.label || "Block";
|
|
176
|
+
const shouldUseContextualSurface =
|
|
177
|
+
(useContextualSurface ?? true) && block.type === 'text';
|
|
178
|
+
const contextualSurfaceStyle = shouldUseContextualSurface ? editorSurfaceContext?.style : undefined;
|
|
179
|
+
const editorClassName = cn(
|
|
180
|
+
"bg-transparent text-foreground border-none shadow-none focus-within:ring-0 min-h-[60vh]",
|
|
181
|
+
shouldUseContextualSurface &&
|
|
182
|
+
editorSurfaceContext?.isDark &&
|
|
183
|
+
"[&_.ProseMirror]:text-white [&_.ProseMirror.prose]:prose-invert"
|
|
184
|
+
);
|
|
185
|
+
const resolvedSaveStatusText = saveStatusText ?? autosaveStatusLabels[saveStatus];
|
|
186
|
+
|
|
187
|
+
return (
|
|
188
188
|
<>
|
|
189
189
|
<Dialog open={isOpen} onOpenChange={(open) => {
|
|
190
190
|
if (!open) {
|
|
@@ -192,56 +192,56 @@ export function BlockEditorModal({
|
|
|
192
192
|
}
|
|
193
193
|
}}>
|
|
194
194
|
<DialogContent
|
|
195
|
-
className="max-w-6xl h-[90vh] flex flex-col p-0 gap-0 overflow-hidden"
|
|
196
|
-
onInteractOutside={(e) => {
|
|
197
|
-
e.preventDefault();
|
|
198
|
-
handleCloseAttempt();
|
|
199
|
-
}}
|
|
195
|
+
className="max-w-6xl h-[90vh] flex flex-col p-0 gap-0 overflow-hidden"
|
|
196
|
+
onInteractOutside={(e) => {
|
|
197
|
+
e.preventDefault();
|
|
198
|
+
handleCloseAttempt();
|
|
199
|
+
}}
|
|
200
200
|
onEscapeKeyDown={(e) => {
|
|
201
201
|
e.preventDefault();
|
|
202
202
|
handleCloseAttempt();
|
|
203
203
|
}}
|
|
204
204
|
>
|
|
205
205
|
{/* Header */}
|
|
206
|
-
<div className="flex items-center justify-between p-4 border-b bg-background/95 text-foreground backdrop-blur z-10">
|
|
207
|
-
<div className="flex items-center gap-2">
|
|
208
|
-
<DialogTitle className="text-lg font-semibold">
|
|
209
|
-
{titleOverride ?? `Edit ${displayText}`}
|
|
210
|
-
</DialogTitle>
|
|
211
|
-
</div>
|
|
212
|
-
<div className="flex items-center gap-2">
|
|
213
|
-
{isAutosaveMode && (
|
|
214
|
-
<span
|
|
215
|
-
aria-live="polite"
|
|
216
|
-
className={cn(
|
|
217
|
-
"rounded-md px-2 py-1 text-xs font-medium",
|
|
218
|
-
saveStatus === "error"
|
|
219
|
-
? "bg-destructive/10 text-destructive"
|
|
220
|
-
: saveStatus === "dirty"
|
|
221
|
-
? "bg-amber-500/10 text-amber-700 dark:text-amber-300"
|
|
222
|
-
: "bg-muted text-muted-foreground",
|
|
223
|
-
"max-w-[360px] truncate"
|
|
224
|
-
)}
|
|
225
|
-
title={resolvedSaveStatusText}
|
|
226
|
-
>
|
|
227
|
-
{resolvedSaveStatusText}
|
|
228
|
-
</span>
|
|
229
|
-
)}
|
|
230
|
-
<Button
|
|
231
|
-
variant={isAutosaveMode ? "default" : "ghost"}
|
|
232
|
-
size="sm"
|
|
233
|
-
onClick={handleCloseAttempt}
|
|
234
|
-
disabled={isAutosaveMode && isFlushingBeforeClose}
|
|
235
|
-
>
|
|
236
|
-
{isAutosaveMode ? "Done" : "Cancel"}
|
|
237
|
-
</Button>
|
|
238
|
-
{!isAutosaveMode && (
|
|
239
|
-
<Button onClick={handleSave} disabled={!isValid} size="sm">
|
|
240
|
-
Save ({saveShortcutLabel})
|
|
241
|
-
</Button>
|
|
242
|
-
)}
|
|
243
|
-
</div>
|
|
244
|
-
</div>
|
|
206
|
+
<div className="flex items-center justify-between p-4 border-b bg-background/95 text-foreground backdrop-blur z-10">
|
|
207
|
+
<div className="flex items-center gap-2">
|
|
208
|
+
<DialogTitle className="text-lg font-semibold">
|
|
209
|
+
{titleOverride ?? `Edit ${displayText}`}
|
|
210
|
+
</DialogTitle>
|
|
211
|
+
</div>
|
|
212
|
+
<div className="flex items-center gap-2">
|
|
213
|
+
{isAutosaveMode && (
|
|
214
|
+
<span
|
|
215
|
+
aria-live="polite"
|
|
216
|
+
className={cn(
|
|
217
|
+
"rounded-md px-2 py-1 text-xs font-medium",
|
|
218
|
+
saveStatus === "error"
|
|
219
|
+
? "bg-destructive/10 text-destructive"
|
|
220
|
+
: saveStatus === "dirty"
|
|
221
|
+
? "bg-amber-500/10 text-amber-700 dark:text-amber-300"
|
|
222
|
+
: "bg-muted text-muted-foreground",
|
|
223
|
+
"max-w-[360px] truncate"
|
|
224
|
+
)}
|
|
225
|
+
title={resolvedSaveStatusText}
|
|
226
|
+
>
|
|
227
|
+
{resolvedSaveStatusText}
|
|
228
|
+
</span>
|
|
229
|
+
)}
|
|
230
|
+
<Button
|
|
231
|
+
variant={isAutosaveMode ? "default" : "ghost"}
|
|
232
|
+
size="sm"
|
|
233
|
+
onClick={handleCloseAttempt}
|
|
234
|
+
disabled={isAutosaveMode && isFlushingBeforeClose}
|
|
235
|
+
>
|
|
236
|
+
{isAutosaveMode ? "Done" : "Cancel"}
|
|
237
|
+
</Button>
|
|
238
|
+
{!isAutosaveMode && (
|
|
239
|
+
<Button onClick={handleSave} disabled={!isValid} size="sm">
|
|
240
|
+
Save ({saveShortcutLabel})
|
|
241
|
+
</Button>
|
|
242
|
+
)}
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
245
|
|
|
246
246
|
{/* Editor Area with Contextual Background */}
|
|
247
247
|
<div
|
|
@@ -251,38 +251,38 @@ export function BlockEditorModal({
|
|
|
251
251
|
// Conditional Background Logic:
|
|
252
252
|
// Only apply specific section background to 'text' and 'heading' blocks to allow "Live Preview" of copy.
|
|
253
253
|
// For complex blocks like Forms, Buttons, etc., keep a neutral background to ensure input field contrast.
|
|
254
|
-
shouldUseContextualSurface ? (
|
|
255
|
-
// If no specific background, use white/dark default
|
|
256
|
-
(!sectionBackground || sectionBackground.type === 'none') && "bg-muted/10"
|
|
257
|
-
) : "bg-muted/10", // Default for non-text blocks
|
|
258
|
-
|
|
259
|
-
// Apply theme classes if present (ONLY for text/heading)
|
|
260
|
-
shouldUseContextualSurface && sectionBackground?.type === 'theme' && sectionBackground.theme === 'primary' && 'bg-primary text-primary-foreground',
|
|
261
|
-
shouldUseContextualSurface && sectionBackground?.type === 'theme' && sectionBackground.theme === 'secondary' && 'bg-secondary text-secondary-foreground',
|
|
262
|
-
shouldUseContextualSurface && sectionBackground?.type === 'theme' && sectionBackground.theme === 'muted' && 'bg-muted text-muted-foreground',
|
|
263
|
-
|
|
264
|
-
// Dark mode prose invert if dark background (approximate check for solid color)
|
|
265
|
-
shouldUseContextualSurface && (sectionBackground?.type === 'solid' && sectionBackground.solid_color && ['#000', '#111', '#0f172a', 'black'].some(c => sectionBackground.solid_color?.includes(c))) && "[&_.prose]:prose-invert",
|
|
266
|
-
shouldUseContextualSurface && editorSurfaceContext?.isDark && "[&_.prose]:prose-invert [&_.ProseMirror]:text-white"
|
|
267
|
-
)}
|
|
268
|
-
style={{
|
|
269
|
-
// Only apply custom color/gradient styles for text/heading
|
|
270
|
-
backgroundColor: shouldUseContextualSurface && sectionBackground?.type === 'solid' ? sectionBackground.solid_color : undefined,
|
|
271
|
-
backgroundImage: shouldUseContextualSurface && sectionBackground?.type === 'gradient' && sectionBackground.gradient ?
|
|
272
|
-
`${sectionBackground.gradient.type}-gradient(${sectionBackground.gradient.direction}, ${sectionBackground.gradient.stops.map(s => `${s.color} ${s.position}%`).join(', ')})`
|
|
273
|
-
: undefined,
|
|
274
|
-
...contextualSurfaceStyle,
|
|
275
|
-
}}
|
|
276
|
-
>
|
|
254
|
+
shouldUseContextualSurface ? (
|
|
255
|
+
// If no specific background, use white/dark default
|
|
256
|
+
(!sectionBackground || sectionBackground.type === 'none') && "bg-muted/10"
|
|
257
|
+
) : "bg-muted/10", // Default for non-text blocks
|
|
258
|
+
|
|
259
|
+
// Apply theme classes if present (ONLY for text/heading)
|
|
260
|
+
shouldUseContextualSurface && sectionBackground?.type === 'theme' && sectionBackground.theme === 'primary' && 'bg-primary text-primary-foreground',
|
|
261
|
+
shouldUseContextualSurface && sectionBackground?.type === 'theme' && sectionBackground.theme === 'secondary' && 'bg-secondary text-secondary-foreground',
|
|
262
|
+
shouldUseContextualSurface && sectionBackground?.type === 'theme' && sectionBackground.theme === 'muted' && 'bg-muted text-muted-foreground',
|
|
263
|
+
|
|
264
|
+
// Dark mode prose invert if dark background (approximate check for solid color)
|
|
265
|
+
shouldUseContextualSurface && (sectionBackground?.type === 'solid' && sectionBackground.solid_color && ['#000', '#111', '#0f172a', 'black'].some(c => sectionBackground.solid_color?.includes(c))) && "[&_.prose]:prose-invert",
|
|
266
|
+
shouldUseContextualSurface && editorSurfaceContext?.isDark && "[&_.prose]:prose-invert [&_.ProseMirror]:text-white"
|
|
267
|
+
)}
|
|
268
|
+
style={{
|
|
269
|
+
// Only apply custom color/gradient styles for text/heading
|
|
270
|
+
backgroundColor: shouldUseContextualSurface && sectionBackground?.type === 'solid' ? sectionBackground.solid_color : undefined,
|
|
271
|
+
backgroundImage: shouldUseContextualSurface && sectionBackground?.type === 'gradient' && sectionBackground.gradient ?
|
|
272
|
+
`${sectionBackground.gradient.type}-gradient(${sectionBackground.gradient.direction}, ${sectionBackground.gradient.stops.map(s => `${s.color} ${s.position}%`).join(', ')})`
|
|
273
|
+
: undefined,
|
|
274
|
+
...contextualSurfaceStyle,
|
|
275
|
+
}}
|
|
276
|
+
>
|
|
277
277
|
<div className="max-w-6xl mx-auto">
|
|
278
278
|
<Suspense fallback={<div className="flex justify-center items-center h-32">Loading editor...</div>}>
|
|
279
279
|
<EditorComponent
|
|
280
|
-
block={block}
|
|
281
|
-
content={tempContent}
|
|
282
|
-
onChange={handleContentChange}
|
|
283
|
-
className={editorClassName}
|
|
284
|
-
sectionBackground={sectionBackground} // Pass down if editor supports it
|
|
285
|
-
/>
|
|
280
|
+
block={block}
|
|
281
|
+
content={tempContent}
|
|
282
|
+
onChange={handleContentChange}
|
|
283
|
+
className={editorClassName}
|
|
284
|
+
sectionBackground={sectionBackground} // Pass down if editor supports it
|
|
285
|
+
/>
|
|
286
286
|
</Suspense>
|
|
287
287
|
</div>
|
|
288
288
|
</div>
|