@endora-commerce/mod-cms 0.0.0-stage → 0.100.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/LICENSE +21 -0
- package/README.md +65 -2
- package/dist/admin/api/cms-client.d.ts +77 -0
- package/dist/admin/api/cms-client.d.ts.map +1 -0
- package/dist/admin/api/cms-client.js +162 -0
- package/dist/admin/api/cms-client.js.map +1 -0
- package/dist/admin/components/AdminCatalogPreviewProvider.d.ts +6 -0
- package/dist/admin/components/AdminCatalogPreviewProvider.d.ts.map +1 -0
- package/dist/admin/components/AdminCatalogPreviewProvider.js +74 -0
- package/dist/admin/components/AdminCatalogPreviewProvider.js.map +1 -0
- package/dist/admin/components/AdminCmsAssetProvider.d.ts +9 -0
- package/dist/admin/components/AdminCmsAssetProvider.d.ts.map +1 -0
- package/dist/admin/components/AdminCmsAssetProvider.js +50 -0
- package/dist/admin/components/AdminCmsAssetProvider.js.map +1 -0
- package/dist/admin/components/BackgroundFields.d.ts +4 -0
- package/dist/admin/components/BackgroundFields.d.ts.map +1 -0
- package/dist/admin/components/BackgroundFields.js +40 -0
- package/dist/admin/components/BackgroundFields.js.map +1 -0
- package/dist/admin/components/ButtonLinkFields.d.ts +6 -0
- package/dist/admin/components/ButtonLinkFields.d.ts.map +1 -0
- package/dist/admin/components/ButtonLinkFields.js +127 -0
- package/dist/admin/components/ButtonLinkFields.js.map +1 -0
- package/dist/admin/components/CarouselPreviewNav.d.ts +12 -0
- package/dist/admin/components/CarouselPreviewNav.d.ts.map +1 -0
- package/dist/admin/components/CarouselPreviewNav.js +31 -0
- package/dist/admin/components/CarouselPreviewNav.js.map +1 -0
- package/dist/admin/components/CmsContentEditorLayout.d.ts +12 -0
- package/dist/admin/components/CmsContentEditorLayout.d.ts.map +1 -0
- package/dist/admin/components/CmsContentEditorLayout.js +9 -0
- package/dist/admin/components/CmsContentEditorLayout.js.map +1 -0
- package/dist/admin/components/ComponentDragHandle.d.ts +6 -0
- package/dist/admin/components/ComponentDragHandle.d.ts.map +1 -0
- package/dist/admin/components/ComponentDragHandle.js +119 -0
- package/dist/admin/components/ComponentDragHandle.js.map +1 -0
- package/dist/admin/components/ContentSliderActionBarExtras.d.ts +6 -0
- package/dist/admin/components/ContentSliderActionBarExtras.d.ts.map +1 -0
- package/dist/admin/components/ContentSliderActionBarExtras.js +109 -0
- package/dist/admin/components/ContentSliderActionBarExtras.js.map +1 -0
- package/dist/admin/components/HookBlockAttachmentsPanel.d.ts +9 -0
- package/dist/admin/components/HookBlockAttachmentsPanel.d.ts.map +1 -0
- package/dist/admin/components/HookBlockAttachmentsPanel.js +137 -0
- package/dist/admin/components/HookBlockAttachmentsPanel.js.map +1 -0
- package/dist/admin/components/PageBuilderActionBar.d.ts +10 -0
- package/dist/admin/components/PageBuilderActionBar.d.ts.map +1 -0
- package/dist/admin/components/PageBuilderActionBar.js +118 -0
- package/dist/admin/components/PageBuilderActionBar.js.map +1 -0
- package/dist/admin/components/PageBuilderDrawer.d.ts +16 -0
- package/dist/admin/components/PageBuilderDrawer.d.ts.map +1 -0
- package/dist/admin/components/PageBuilderDrawer.js +53 -0
- package/dist/admin/components/PageBuilderDrawer.js.map +1 -0
- package/dist/admin/components/PageBuilderEditor.d.ts +45 -0
- package/dist/admin/components/PageBuilderEditor.d.ts.map +1 -0
- package/dist/admin/components/PageBuilderEditor.js +632 -0
- package/dist/admin/components/PageBuilderEditor.js.map +1 -0
- package/dist/admin/components/PuckActionGuard.d.ts +12 -0
- package/dist/admin/components/PuckActionGuard.d.ts.map +1 -0
- package/dist/admin/components/PuckActionGuard.js +30 -0
- package/dist/admin/components/PuckActionGuard.js.map +1 -0
- package/dist/admin/components/RowLayoutPicker.d.ts +8 -0
- package/dist/admin/components/RowLayoutPicker.d.ts.map +1 -0
- package/dist/admin/components/RowLayoutPicker.js +17 -0
- package/dist/admin/components/RowLayoutPicker.js.map +1 -0
- package/dist/admin/components/SliderPreviewActionBarExtras.d.ts +6 -0
- package/dist/admin/components/SliderPreviewActionBarExtras.d.ts.map +1 -0
- package/dist/admin/components/SliderPreviewActionBarExtras.js +32 -0
- package/dist/admin/components/SliderPreviewActionBarExtras.js.map +1 -0
- package/dist/admin/components/admin-catalog-preview-map.d.ts +17 -0
- package/dist/admin/components/admin-catalog-preview-map.d.ts.map +1 -0
- package/dist/admin/components/admin-catalog-preview-map.js +18 -0
- package/dist/admin/components/admin-catalog-preview-map.js.map +1 -0
- package/dist/admin/components/build-viewports.d.ts +23 -0
- package/dist/admin/components/build-viewports.d.ts.map +1 -0
- package/dist/admin/components/build-viewports.js +23 -0
- package/dist/admin/components/build-viewports.js.map +1 -0
- package/dist/admin/components/cms-template-layout.d.ts +16 -0
- package/dist/admin/components/cms-template-layout.d.ts.map +1 -0
- package/dist/admin/components/cms-template-layout.js +53 -0
- package/dist/admin/components/cms-template-layout.js.map +1 -0
- package/dist/admin/components/page-builder-i18n.d.ts +27 -0
- package/dist/admin/components/page-builder-i18n.d.ts.map +1 -0
- package/dist/admin/components/page-builder-i18n.js +34 -0
- package/dist/admin/components/page-builder-i18n.js.map +1 -0
- package/dist/admin/components/puck-safe.d.ts +10 -0
- package/dist/admin/components/puck-safe.d.ts.map +1 -0
- package/dist/admin/components/puck-safe.js +22 -0
- package/dist/admin/components/puck-safe.js.map +1 -0
- package/dist/admin/components/scope-utils.d.ts +4 -0
- package/dist/admin/components/scope-utils.d.ts.map +1 -0
- package/dist/admin/components/scope-utils.js +7 -0
- package/dist/admin/components/scope-utils.js.map +1 -0
- package/dist/admin/editors/BlockEditor.d.ts +9 -0
- package/dist/admin/editors/BlockEditor.d.ts.map +1 -0
- package/dist/admin/editors/BlockEditor.js +217 -0
- package/dist/admin/editors/BlockEditor.js.map +1 -0
- package/dist/admin/editors/PageEditor.d.ts +9 -0
- package/dist/admin/editors/PageEditor.d.ts.map +1 -0
- package/dist/admin/editors/PageEditor.js +316 -0
- package/dist/admin/editors/PageEditor.js.map +1 -0
- package/dist/admin/editors/TemplateEditor.d.ts +9 -0
- package/dist/admin/editors/TemplateEditor.d.ts.map +1 -0
- package/dist/admin/editors/TemplateEditor.js +180 -0
- package/dist/admin/editors/TemplateEditor.js.map +1 -0
- package/dist/admin/index.d.ts +38 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +144 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/BlocksListPage.d.ts +9 -0
- package/dist/admin/pages/BlocksListPage.d.ts.map +1 -0
- package/dist/admin/pages/BlocksListPage.js +47 -0
- package/dist/admin/pages/BlocksListPage.js.map +1 -0
- package/dist/admin/pages/HooksPage.d.ts +9 -0
- package/dist/admin/pages/HooksPage.d.ts.map +1 -0
- package/dist/admin/pages/HooksPage.js +41 -0
- package/dist/admin/pages/HooksPage.js.map +1 -0
- package/dist/admin/pages/PagesListPage.d.ts +9 -0
- package/dist/admin/pages/PagesListPage.d.ts.map +1 -0
- package/dist/admin/pages/PagesListPage.js +51 -0
- package/dist/admin/pages/PagesListPage.js.map +1 -0
- package/dist/admin/pages/TemplatesListPage.d.ts +9 -0
- package/dist/admin/pages/TemplatesListPage.d.ts.map +1 -0
- package/dist/admin/pages/TemplatesListPage.js +47 -0
- package/dist/admin/pages/TemplatesListPage.js.map +1 -0
- package/dist/admin-ui/index.d.ts +90 -0
- package/dist/admin-ui/index.d.ts.map +1 -0
- package/dist/admin-ui/index.js +89 -0
- package/dist/admin-ui/index.js.map +1 -0
- package/dist/backend/cli/block-names.d.ts +92 -0
- package/dist/backend/cli/block-names.d.ts.map +1 -0
- package/dist/backend/cli/block-names.js +132 -0
- package/dist/backend/cli/block-names.js.map +1 -0
- package/dist/backend/entities/cms-block.entity.d.ts +20 -0
- package/dist/backend/entities/cms-block.entity.d.ts.map +1 -0
- package/dist/backend/entities/cms-block.entity.js +77 -0
- package/dist/backend/entities/cms-block.entity.js.map +1 -0
- package/dist/backend/entities/cms-hook-block-attachment.entity.d.ts +15 -0
- package/dist/backend/entities/cms-hook-block-attachment.entity.d.ts.map +1 -0
- package/dist/backend/entities/cms-hook-block-attachment.entity.js +46 -0
- package/dist/backend/entities/cms-hook-block-attachment.entity.js.map +1 -0
- package/dist/backend/entities/cms-hook.entity.d.ts +20 -0
- package/dist/backend/entities/cms-hook.entity.d.ts.map +1 -0
- package/dist/backend/entities/cms-hook.entity.js +73 -0
- package/dist/backend/entities/cms-hook.entity.js.map +1 -0
- package/dist/backend/entities/cms-page.entity.d.ts +43 -0
- package/dist/backend/entities/cms-page.entity.d.ts.map +1 -0
- package/dist/backend/entities/cms-page.entity.js +140 -0
- package/dist/backend/entities/cms-page.entity.js.map +1 -0
- package/dist/backend/entities/cms-template.entity.d.ts +20 -0
- package/dist/backend/entities/cms-template.entity.d.ts.map +1 -0
- package/dist/backend/entities/cms-template.entity.js +73 -0
- package/dist/backend/entities/cms-template.entity.js.map +1 -0
- package/dist/backend/index.d.ts +157 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +317 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/plugin.d.ts +81 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +81 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +19 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +164 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.storefront.d.ts +6 -0
- package/dist/backend/routes.storefront.d.ts.map +1 -0
- package/dist/backend/routes.storefront.js +89 -0
- package/dist/backend/routes.storefront.js.map +1 -0
- package/dist/backend/services/asset-embed-resolver.d.ts +17 -0
- package/dist/backend/services/asset-embed-resolver.d.ts.map +1 -0
- package/dist/backend/services/asset-embed-resolver.js +40 -0
- package/dist/backend/services/asset-embed-resolver.js.map +1 -0
- package/dist/backend/services/asset-references.d.ts +4 -0
- package/dist/backend/services/asset-references.d.ts.map +1 -0
- package/dist/backend/services/asset-references.js +55 -0
- package/dist/backend/services/asset-references.js.map +1 -0
- package/dist/backend/services/cms-block-read-port.d.ts +27 -0
- package/dist/backend/services/cms-block-read-port.d.ts.map +1 -0
- package/dist/backend/services/cms-block-read-port.js +50 -0
- package/dist/backend/services/cms-block-read-port.js.map +1 -0
- package/dist/backend/services/cms-block-seed-port.d.ts +31 -0
- package/dist/backend/services/cms-block-seed-port.d.ts.map +1 -0
- package/dist/backend/services/cms-block-seed-port.js +68 -0
- package/dist/backend/services/cms-block-seed-port.js.map +1 -0
- package/dist/backend/services/cms-block-service.d.ts +38 -0
- package/dist/backend/services/cms-block-service.d.ts.map +1 -0
- package/dist/backend/services/cms-block-service.js +218 -0
- package/dist/backend/services/cms-block-service.js.map +1 -0
- package/dist/backend/services/cms-cache.d.ts +54 -0
- package/dist/backend/services/cms-cache.d.ts.map +1 -0
- package/dist/backend/services/cms-cache.js +113 -0
- package/dist/backend/services/cms-cache.js.map +1 -0
- package/dist/backend/services/cms-hook-service.d.ts +35 -0
- package/dist/backend/services/cms-hook-service.d.ts.map +1 -0
- package/dist/backend/services/cms-hook-service.js +216 -0
- package/dist/backend/services/cms-hook-service.js.map +1 -0
- package/dist/backend/services/cms-language-reference.d.ts +11 -0
- package/dist/backend/services/cms-language-reference.d.ts.map +1 -0
- package/dist/backend/services/cms-language-reference.js +26 -0
- package/dist/backend/services/cms-language-reference.js.map +1 -0
- package/dist/backend/services/cms-page-read-port.d.ts +23 -0
- package/dist/backend/services/cms-page-read-port.d.ts.map +1 -0
- package/dist/backend/services/cms-page-read-port.js +54 -0
- package/dist/backend/services/cms-page-read-port.js.map +1 -0
- package/dist/backend/services/cms-page-service.d.ts +104 -0
- package/dist/backend/services/cms-page-service.d.ts.map +1 -0
- package/dist/backend/services/cms-page-service.js +468 -0
- package/dist/backend/services/cms-page-service.js.map +1 -0
- package/dist/backend/services/cms-reference-registry.d.ts +30 -0
- package/dist/backend/services/cms-reference-registry.d.ts.map +1 -0
- package/dist/backend/services/cms-reference-registry.js +98 -0
- package/dist/backend/services/cms-reference-registry.js.map +1 -0
- package/dist/backend/services/cms-template-service.d.ts +37 -0
- package/dist/backend/services/cms-template-service.d.ts.map +1 -0
- package/dist/backend/services/cms-template-service.js +205 -0
- package/dist/backend/services/cms-template-service.js.map +1 -0
- package/dist/backend/services/content-tree-walker.d.ts +7 -0
- package/dist/backend/services/content-tree-walker.d.ts.map +1 -0
- package/dist/backend/services/content-tree-walker.js +89 -0
- package/dist/backend/services/content-tree-walker.js.map +1 -0
- package/dist/backend/services/page-builder-registry.d.ts +120 -0
- package/dist/backend/services/page-builder-registry.d.ts.map +1 -0
- package/dist/backend/services/page-builder-registry.js +220 -0
- package/dist/backend/services/page-builder-registry.js.map +1 -0
- package/dist/backend/services/seed-hooks.d.ts +22 -0
- package/dist/backend/services/seed-hooks.d.ts.map +1 -0
- package/dist/backend/services/seed-hooks.js +94 -0
- package/dist/backend/services/seed-hooks.js.map +1 -0
- package/dist/backend/services/storefront-resolver.d.ts +71 -0
- package/dist/backend/services/storefront-resolver.d.ts.map +1 -0
- package/dist/backend/services/storefront-resolver.js +329 -0
- package/dist/backend/services/storefront-resolver.js.map +1 -0
- package/dist/manifest.d.ts +225 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +695 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260425T162418_cms_pages_init.d.ts +10 -0
- package/dist/migrations/20260425T162418_cms_pages_init.d.ts.map +1 -0
- package/dist/migrations/20260425T162418_cms_pages_init.js +30 -0
- package/dist/migrations/20260425T162418_cms_pages_init.js.map +1 -0
- package/dist/migrations/20260505T130214_cms_init.d.ts +28 -0
- package/dist/migrations/20260505T130214_cms_init.d.ts.map +1 -0
- package/dist/migrations/20260505T130214_cms_init.js +332 -0
- package/dist/migrations/20260505T130214_cms_init.js.map +1 -0
- package/dist/migrations/20260903T101741_cms_namespace_block_names.d.ts +6 -0
- package/dist/migrations/20260903T101741_cms_namespace_block_names.d.ts.map +1 -0
- package/dist/migrations/20260903T101741_cms_namespace_block_names.js +60 -0
- package/dist/migrations/20260903T101741_cms_namespace_block_names.js.map +1 -0
- package/dist/migrations/20260912T094733_cms_sales_channel_cms_pages.d.ts +27 -0
- package/dist/migrations/20260912T094733_cms_sales_channel_cms_pages.d.ts.map +1 -0
- package/dist/migrations/20260912T094733_cms_sales_channel_cms_pages.js +44 -0
- package/dist/migrations/20260912T094733_cms_sales_channel_cms_pages.js.map +1 -0
- package/dist/migrations/20260912T125709_cms_page_body_asset_ref_index.d.ts +32 -0
- package/dist/migrations/20260912T125709_cms_page_body_asset_ref_index.d.ts.map +1 -0
- package/dist/migrations/20260912T125709_cms_page_body_asset_ref_index.js +36 -0
- package/dist/migrations/20260912T125709_cms_page_body_asset_ref_index.js.map +1 -0
- package/dist/migrations/index.d.ts +31 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +37 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/cms/extending-page-builder.md +256 -0
- package/docs/cms/index.md +233 -0
- package/i18n/en.json +231 -0
- package/i18n/pl.json +231 -0
- package/package.json +125 -3
- package/tailwind.css +16 -0
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Component, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { Puck, Render } from '@measured/puck';
|
|
4
|
+
import '@measured/puck/puck.css';
|
|
5
|
+
// Self-contained, prefix-isolated (`cmsc:`) stylesheet for the shared CMS components
|
|
6
|
+
// (feature 041, FR-012b). This is the admin's ONLY change; it carries its own token
|
|
7
|
+
// values + no preflight, so it cannot restyle admin chrome.
|
|
8
|
+
import '@endora-commerce/cms-components/styles.css';
|
|
9
|
+
import { defaultPageBuilderConfig, makeMissingComponentConfig, withCmsPageRoot, withMissingBlockPlaceholders, } from '@endora-commerce/cms-components';
|
|
10
|
+
import { buildPaletteCategories, contextAdmits, countBlockNames, filterConfigByContext, } from '@endora-commerce/page-builder-core';
|
|
11
|
+
import { createPageBuilderEditorPlugin } from '@endora-commerce/page-builder-core/editor';
|
|
12
|
+
import { AdminCmsAssetProvider } from './AdminCmsAssetProvider.js';
|
|
13
|
+
import { Alert, AlertDescription } from '@endora-commerce/admin-kit/ui';
|
|
14
|
+
import { cn } from '@endora-commerce/admin-kit/lib';
|
|
15
|
+
import { useTranslation, useTranslationContext } from '@endora-commerce/admin-kit/i18n';
|
|
16
|
+
import { cmsClient } from '../api/cms-client.js';
|
|
17
|
+
import { PageBuilderColorPaletteProvider, createCategorySlugField, createCategorySlugsField, createProductSlugField, createProductSlugsField, createImageAssetField, createImageSourceField, createImageUrlField, createSlideImageField, PageBuilderHeaderActions, PageBuilderHeaderShell, PageBuilderTemplateActions, emptyPageBuilderData, isEmptyPageBuilderData, PageBuilderOverlayBridge, } from '@endora-commerce/page-builder-admin';
|
|
18
|
+
import { AdminCatalogPreviewProvider } from './AdminCatalogPreviewProvider.js';
|
|
19
|
+
import { createButtonLinkSlugField } from './ButtonLinkFields.js';
|
|
20
|
+
import { createAdminBackgroundField } from './BackgroundFields.js';
|
|
21
|
+
import { buildViewports } from './build-viewports.js';
|
|
22
|
+
import { applyRowLayoutPreset, findRowById, replaceRowInData, wrapRowInContentSliderSlide, } from '@endora-commerce/cms-components/editor/row-layout-presets';
|
|
23
|
+
import { isContentSliderSlidesZone, toPuckItemArray } from '@endora-commerce/page-builder-core/editor';
|
|
24
|
+
import { createPuckActionHandler, PuckDispatchBridgeSlot } from './PuckActionGuard.js';
|
|
25
|
+
import { RowLayoutPicker } from './RowLayoutPicker.js';
|
|
26
|
+
import { PageBuilderActionBar } from './PageBuilderActionBar.js';
|
|
27
|
+
import { PageBuilderDrawer } from './PageBuilderDrawer.js';
|
|
28
|
+
import { applyPageBuilderTranslations } from './page-builder-i18n.js';
|
|
29
|
+
import { hasInvalidColumnPlacement } from '@endora-commerce/page-builder-core/editor';
|
|
30
|
+
const emptyData = { root: { props: {} }, content: [] };
|
|
31
|
+
function insertedItem(action, data) {
|
|
32
|
+
const items = action.destinationZone.startsWith('root:')
|
|
33
|
+
? toPuckItemArray(data.content)
|
|
34
|
+
: toPuckItemArray(data.zones?.[action.destinationZone]);
|
|
35
|
+
return items[action.destinationIndex] ?? null;
|
|
36
|
+
}
|
|
37
|
+
function resolveInsertedRowId(action, data) {
|
|
38
|
+
if (typeof action.id === 'string' && action.id.length > 0)
|
|
39
|
+
return action.id;
|
|
40
|
+
const inserted = insertedItem(action, data);
|
|
41
|
+
return typeof inserted?.props.id === 'string' ? inserted.props.id : null;
|
|
42
|
+
}
|
|
43
|
+
function collectRowIds(data) {
|
|
44
|
+
const ids = new Set();
|
|
45
|
+
const visit = (items) => {
|
|
46
|
+
for (const item of items) {
|
|
47
|
+
if (item.type === 'cms.Row' && typeof item.props.id === 'string') {
|
|
48
|
+
ids.add(item.props.id);
|
|
49
|
+
}
|
|
50
|
+
for (const value of Object.values(item.props)) {
|
|
51
|
+
const nested = toPuckItemArray(value);
|
|
52
|
+
if (nested.length > 0)
|
|
53
|
+
visit(nested);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
visit(toPuckItemArray(data.content));
|
|
58
|
+
for (const zoneItems of Object.values(data.zones ?? {})) {
|
|
59
|
+
visit(toPuckItemArray(zoneItems));
|
|
60
|
+
}
|
|
61
|
+
return ids;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Guards a best-effort on-canvas embed preview: if rendering a referenced
|
|
65
|
+
* block/template tree throws (e.g. an unknown extension component or a
|
|
66
|
+
* malformed tree), the placeholder simply disappears instead of taking the
|
|
67
|
+
* whole editor down with it.
|
|
68
|
+
*/
|
|
69
|
+
class PreviewBoundary extends Component {
|
|
70
|
+
constructor(props) {
|
|
71
|
+
super(props);
|
|
72
|
+
this.state = { failed: false };
|
|
73
|
+
}
|
|
74
|
+
static getDerivedStateFromError() {
|
|
75
|
+
return { failed: true };
|
|
76
|
+
}
|
|
77
|
+
render() {
|
|
78
|
+
return this.state.failed ? null : this.props.children;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Renders a block/template's stored content (per-language Puck trees) into an
|
|
83
|
+
* on-canvas preview so the InsertBlock/InsertTemplate embeds show their actual
|
|
84
|
+
* content instead of just printing the referenced code. Prefers Polish, then
|
|
85
|
+
* English, then any available language.
|
|
86
|
+
*/
|
|
87
|
+
function previewNode(content) {
|
|
88
|
+
const langs = content.languages ?? {};
|
|
89
|
+
const tree = langs['pl-PL'] ?? langs['en-US'] ?? Object.values(langs)[0] ?? null;
|
|
90
|
+
if (!tree)
|
|
91
|
+
return null;
|
|
92
|
+
return (_jsx(PreviewBoundary, { children: _jsx(Render, { config: defaultPageBuilderConfig, data: tree }) }));
|
|
93
|
+
}
|
|
94
|
+
function mergeConfig(descriptor, extensionTitle, blockOptions, context, sectionTitle) {
|
|
95
|
+
const base = defaultPageBuilderConfig;
|
|
96
|
+
const components = {
|
|
97
|
+
...(base.components ?? {}),
|
|
98
|
+
};
|
|
99
|
+
// Replace InsertBlock's free-text "Block code" field with a dropdown of the
|
|
100
|
+
// available CMS blocks, so authors pick from a list instead of having to know
|
|
101
|
+
// and type a code by hand.
|
|
102
|
+
const insertBlock = components['cms.InsertBlock'];
|
|
103
|
+
if (insertBlock) {
|
|
104
|
+
components['cms.InsertBlock'] = {
|
|
105
|
+
...insertBlock,
|
|
106
|
+
fields: {
|
|
107
|
+
...insertBlock.fields,
|
|
108
|
+
code: {
|
|
109
|
+
type: 'select',
|
|
110
|
+
label: 'Block',
|
|
111
|
+
options: [{ label: '—', value: '' }, ...blockOptions],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const button = components['cms.Button'];
|
|
117
|
+
if (button) {
|
|
118
|
+
components['cms.Button'] = {
|
|
119
|
+
...button,
|
|
120
|
+
fields: {
|
|
121
|
+
...button.fields,
|
|
122
|
+
linkSlug: createButtonLinkSlugField(),
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const hero = components['cms.Hero'];
|
|
127
|
+
if (hero) {
|
|
128
|
+
components['cms.Hero'] = {
|
|
129
|
+
...hero,
|
|
130
|
+
fields: {
|
|
131
|
+
...hero.fields,
|
|
132
|
+
buttonLinkSlug: createButtonLinkSlugField({
|
|
133
|
+
label: 'Button link target',
|
|
134
|
+
linkTypeProp: 'buttonLinkType',
|
|
135
|
+
}),
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const productCard = components['catalog.ProductCard'];
|
|
140
|
+
if (productCard) {
|
|
141
|
+
components['catalog.ProductCard'] = {
|
|
142
|
+
...productCard,
|
|
143
|
+
fields: { ...productCard.fields, productSlug: createProductSlugField() },
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
for (const name of ['catalog.ProductGrid', 'catalog.ProductSlider']) {
|
|
147
|
+
const cfg = components[name];
|
|
148
|
+
if (cfg) {
|
|
149
|
+
components[name] = {
|
|
150
|
+
...cfg,
|
|
151
|
+
fields: {
|
|
152
|
+
...cfg.fields,
|
|
153
|
+
productSlugs: createProductSlugsField(),
|
|
154
|
+
categorySlug: createCategorySlugField(),
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
for (const name of ['catalog.CategoryList', 'catalog.CategoryGrid']) {
|
|
160
|
+
const cfg = components[name];
|
|
161
|
+
if (cfg) {
|
|
162
|
+
components[name] = {
|
|
163
|
+
...cfg,
|
|
164
|
+
fields: {
|
|
165
|
+
...cfg.fields,
|
|
166
|
+
categorySlugs: createCategorySlugsField(),
|
|
167
|
+
parentSlug: createCategorySlugField(),
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const image = components['cms.Image'];
|
|
173
|
+
if (image) {
|
|
174
|
+
components['cms.Image'] = {
|
|
175
|
+
...image,
|
|
176
|
+
fields: {
|
|
177
|
+
...image.fields,
|
|
178
|
+
imageSource: createImageSourceField(),
|
|
179
|
+
src: createImageUrlField('Image URL'),
|
|
180
|
+
assetId: createImageAssetField('Image'),
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const imageSlider = components['cms.ImageSlider'];
|
|
185
|
+
if (imageSlider) {
|
|
186
|
+
const itemsField = imageSlider.fields?.items;
|
|
187
|
+
components['cms.ImageSlider'] = {
|
|
188
|
+
...imageSlider,
|
|
189
|
+
fields: {
|
|
190
|
+
...imageSlider.fields,
|
|
191
|
+
items: itemsField && itemsField.type === 'array'
|
|
192
|
+
? {
|
|
193
|
+
...itemsField,
|
|
194
|
+
arrayFields: {
|
|
195
|
+
image: createSlideImageField(),
|
|
196
|
+
title: { type: 'text', label: 'Title' },
|
|
197
|
+
titlePlacement: itemsField.arrayFields?.titlePlacement ?? {
|
|
198
|
+
type: 'select',
|
|
199
|
+
label: 'Title placement',
|
|
200
|
+
options: [
|
|
201
|
+
{ label: 'Hidden', value: 'none' },
|
|
202
|
+
{ label: 'Top left', value: 'top-left' },
|
|
203
|
+
{ label: 'Top center', value: 'top-center' },
|
|
204
|
+
{ label: 'Top right', value: 'top-right' },
|
|
205
|
+
{ label: 'Center', value: 'center' },
|
|
206
|
+
{ label: 'Bottom left', value: 'bottom-left' },
|
|
207
|
+
{ label: 'Bottom center', value: 'bottom-center' },
|
|
208
|
+
{ label: 'Bottom right', value: 'bottom-right' },
|
|
209
|
+
],
|
|
210
|
+
},
|
|
211
|
+
...(itemsField.arrayFields?.titleBackground
|
|
212
|
+
? { titleBackground: itemsField.arrayFields.titleBackground }
|
|
213
|
+
: {}),
|
|
214
|
+
...(itemsField.arrayFields?.titleColor
|
|
215
|
+
? { titleColor: itemsField.arrayFields.titleColor }
|
|
216
|
+
: {}),
|
|
217
|
+
...(itemsField.arrayFields?.titleBorderColor
|
|
218
|
+
? { titleBorderColor: itemsField.arrayFields.titleBorderColor }
|
|
219
|
+
: {}),
|
|
220
|
+
...(itemsField.arrayFields?.titleBorderWidth
|
|
221
|
+
? { titleBorderWidth: itemsField.arrayFields.titleBorderWidth }
|
|
222
|
+
: {}),
|
|
223
|
+
...(itemsField.arrayFields?.titleBorderRadius
|
|
224
|
+
? { titleBorderRadius: itemsField.arrayFields.titleBorderRadius }
|
|
225
|
+
: {}),
|
|
226
|
+
...(itemsField.arrayFields?.titlePaddingPx
|
|
227
|
+
? { titlePaddingPx: itemsField.arrayFields.titlePaddingPx }
|
|
228
|
+
: {}),
|
|
229
|
+
},
|
|
230
|
+
}
|
|
231
|
+
: itemsField,
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
for (const name of ['cms.Row', 'cms.Column', 'cms.Hero', 'cms.Testimonial', 'cms.NewsletterSignup']) {
|
|
236
|
+
const cfg = components[name];
|
|
237
|
+
if (cfg) {
|
|
238
|
+
components[name] = {
|
|
239
|
+
...cfg,
|
|
240
|
+
fields: {
|
|
241
|
+
...cfg.fields,
|
|
242
|
+
background: createAdminBackgroundField(),
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const testimonial = components['cms.Testimonial'];
|
|
248
|
+
if (testimonial) {
|
|
249
|
+
components['cms.Testimonial'] = {
|
|
250
|
+
...testimonial,
|
|
251
|
+
fields: {
|
|
252
|
+
...testimonial.fields,
|
|
253
|
+
avatarSource: createImageSourceField(),
|
|
254
|
+
avatarUrl: createImageUrlField('Avatar URL'),
|
|
255
|
+
avatarAssetId: createImageAssetField('Avatar'),
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
const logoStrip = components['cms.LogoStrip'];
|
|
260
|
+
if (logoStrip) {
|
|
261
|
+
const itemsField = logoStrip.fields?.items;
|
|
262
|
+
components['cms.LogoStrip'] = {
|
|
263
|
+
...logoStrip,
|
|
264
|
+
fields: {
|
|
265
|
+
...logoStrip.fields,
|
|
266
|
+
items: itemsField && itemsField.type === 'array'
|
|
267
|
+
? {
|
|
268
|
+
...itemsField,
|
|
269
|
+
arrayFields: {
|
|
270
|
+
image: createSlideImageField(),
|
|
271
|
+
alt: itemsField.arrayFields?.alt ?? { type: 'text', label: 'Alt text' },
|
|
272
|
+
href: itemsField.arrayFields?.href ?? {
|
|
273
|
+
type: 'text',
|
|
274
|
+
label: 'Link URL (optional)',
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
}
|
|
278
|
+
: itemsField,
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
// Feature 096, T306 — **the descriptor is filtered by context here.**
|
|
283
|
+
// Until Phase 3 it carried only the 35 CMS names, so every entry belonged in
|
|
284
|
+
// this palette by construction; it now carries all 74 a platform composes,
|
|
285
|
+
// and an unfiltered sweep would put 39 e-mail and invoice blocks into this
|
|
286
|
+
// editor as missing-renderer placeholders.
|
|
287
|
+
const declared = (descriptor?.components ?? []).filter((entry) => contextAdmits(entry.contexts ?? ['cms'], context));
|
|
288
|
+
for (const entry of declared) {
|
|
289
|
+
if (components[entry.name])
|
|
290
|
+
continue;
|
|
291
|
+
// `visible: true` because this is an editing surface: the operator has to be
|
|
292
|
+
// told which module a block is waiting on. The parameter had no caller until
|
|
293
|
+
// feature 096's T602 and the placeholder read the `?cms_admin=1` preview
|
|
294
|
+
// parameter instead, which nothing in this repository sets — so every
|
|
295
|
+
// placeholder the editor merged rendered an empty span.
|
|
296
|
+
components[entry.name] = makeMissingComponentConfig(entry.name, entry.ownerModule, {
|
|
297
|
+
visible: true,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
// The sections come from the modules that declared them, resolved and merged
|
|
301
|
+
// by the registry (`contracts/block-definition.md` §1.1 and §4.1.1). The
|
|
302
|
+
// hand-written `categories` map this replaced lived in `cms-components` and
|
|
303
|
+
// listed five of `catalog`'s blocks.
|
|
304
|
+
const categories = buildPaletteCategories(declared, descriptor?.categories ?? [], context, {
|
|
305
|
+
title: (section) => sectionTitle(section.ownerModule ?? 'cms', section.titleKey, section.key),
|
|
306
|
+
renderable: new Set(Object.keys(components)),
|
|
307
|
+
});
|
|
308
|
+
// Anything the descriptor declares for this context but no declared section
|
|
309
|
+
// holds still has to be reachable, or it is a block an operator can never
|
|
310
|
+
// insert. That is the *Extensions* drawer's job and it stays.
|
|
311
|
+
const sectioned = new Set(Object.values(categories).flatMap((c) => c.components ?? []));
|
|
312
|
+
const unsectioned = declared.map((entry) => entry.name).filter((name) => !sectioned.has(name));
|
|
313
|
+
return {
|
|
314
|
+
...base,
|
|
315
|
+
components,
|
|
316
|
+
categories: unsectioned.length > 0
|
|
317
|
+
? { ...categories, extensions: { title: extensionTitle, components: unsectioned } }
|
|
318
|
+
: categories,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
export function PageBuilderEditor({ data, onChange, contentKey, pageContainer = false, context = 'cms', languages = [], activeLanguage = null, onResolveLanguageContent, onSaveAsTemplate, onListTemplatesForApply, onResolveTemplateLayout, }) {
|
|
322
|
+
const t = useTranslation('cms');
|
|
323
|
+
// A section's `titleKey` is **module-relative** and belongs to the module
|
|
324
|
+
// whose declaration won the merge, so it is resolved in that module's scope
|
|
325
|
+
// and not in `cms`' (`contracts/block-definition.md` §2). The key itself is
|
|
326
|
+
// the fallback the resolver already renders for a key it cannot find; the
|
|
327
|
+
// section key is a last resort so a drawer is never headed with nothing.
|
|
328
|
+
const { t: translateInScope } = useTranslationContext();
|
|
329
|
+
const sectionTitle = useCallback((ownerModule, titleKey, fallback) => {
|
|
330
|
+
const resolved = translateInScope(ownerModule, titleKey);
|
|
331
|
+
return resolved === `${ownerModule}.${titleKey}` ? fallback : resolved;
|
|
332
|
+
}, [translateInScope]);
|
|
333
|
+
// The shared page-builder chrome (`PageBuilderHeaderShell`,
|
|
334
|
+
// `PageBuilderTemplateActions`, `PageBuilderHeaderActions`) owns no module
|
|
335
|
+
// knowledge and reads `core`; this screen's own strings stay `cms`' (feature
|
|
336
|
+
// 091 P5a, R-1).
|
|
337
|
+
const tChrome = useTranslation('core');
|
|
338
|
+
const [descriptor, setDescriptor] = useState(null);
|
|
339
|
+
/**
|
|
340
|
+
* A block's palette label, from the declaring module's own bundle.
|
|
341
|
+
*
|
|
342
|
+
* Same rule as the section title and for the same reason: `labelKey` is
|
|
343
|
+
* module-relative, and after Phase 3 five of the CMS palette's entries are
|
|
344
|
+
* `catalog`'s. Falls through to `applyPageBuilderTranslations`' legacy
|
|
345
|
+
* `pageBuilder.components.<name>` key when the descriptor has not been
|
|
346
|
+
* fetched yet, which is the first render.
|
|
347
|
+
*/
|
|
348
|
+
const blockLabel = useCallback((name) => {
|
|
349
|
+
const entry = descriptor?.components.find((component) => component.name === name);
|
|
350
|
+
if (!entry?.labelKey)
|
|
351
|
+
return undefined;
|
|
352
|
+
const resolved = translateInScope(entry.ownerModule, entry.labelKey);
|
|
353
|
+
return resolved === `${entry.ownerModule}.${entry.labelKey}` ? undefined : resolved;
|
|
354
|
+
}, [descriptor, translateInScope]);
|
|
355
|
+
const [blockOptions, setBlockOptions] = useState([]);
|
|
356
|
+
const [embeds, setEmbeds] = useState({ blocks: {}, templates: {} });
|
|
357
|
+
const [error, setError] = useState(null);
|
|
358
|
+
const [fullscreen, setFullscreen] = useState(false);
|
|
359
|
+
const [rowLayoutPickerForId, setRowLayoutPickerForId] = useState(null);
|
|
360
|
+
/** Bumped on clear / copy-from so Puck remounts (data prop is mount-only). */
|
|
361
|
+
const [canvasEpoch, setCanvasEpoch] = useState(0);
|
|
362
|
+
const dispatchRef = useRef(null);
|
|
363
|
+
// Allow exiting fullscreen with Escape.
|
|
364
|
+
useEffect(() => {
|
|
365
|
+
if (!fullscreen)
|
|
366
|
+
return undefined;
|
|
367
|
+
const onKey = (e) => {
|
|
368
|
+
if (e.key === 'Escape')
|
|
369
|
+
setFullscreen(false);
|
|
370
|
+
};
|
|
371
|
+
window.addEventListener('keydown', onKey);
|
|
372
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
373
|
+
}, [fullscreen]);
|
|
374
|
+
useEffect(() => {
|
|
375
|
+
let live = true;
|
|
376
|
+
cmsClient
|
|
377
|
+
.getPageBuilderConfig()
|
|
378
|
+
.then((result) => {
|
|
379
|
+
if (live)
|
|
380
|
+
setDescriptor(result);
|
|
381
|
+
})
|
|
382
|
+
.catch((err) => {
|
|
383
|
+
if (live)
|
|
384
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
385
|
+
});
|
|
386
|
+
return () => {
|
|
387
|
+
live = false;
|
|
388
|
+
};
|
|
389
|
+
}, []);
|
|
390
|
+
// Available CMS blocks power the InsertBlock dropdown; their resolved content
|
|
391
|
+
// (and templates') power the on-canvas embed previews. Best-effort: any
|
|
392
|
+
// failure degrades to an empty dropdown / code-only placeholder rather than
|
|
393
|
+
// breaking the editor.
|
|
394
|
+
useEffect(() => {
|
|
395
|
+
let live = true;
|
|
396
|
+
void (async () => {
|
|
397
|
+
const [blockRes, templateRes] = await Promise.all([
|
|
398
|
+
cmsClient.listBlocks().catch(() => ({ data: [] })),
|
|
399
|
+
cmsClient.listTemplates().catch(() => ({ data: [] })),
|
|
400
|
+
]);
|
|
401
|
+
if (!live)
|
|
402
|
+
return;
|
|
403
|
+
setBlockOptions(blockRes.data.map((b) => ({
|
|
404
|
+
label: b.name ? `${b.name} (${b.code})` : b.code,
|
|
405
|
+
value: b.code,
|
|
406
|
+
})));
|
|
407
|
+
const blockPreviews = {};
|
|
408
|
+
await Promise.all(blockRes.data.map(async (b) => {
|
|
409
|
+
try {
|
|
410
|
+
const detail = await cmsClient.getBlock(b.id);
|
|
411
|
+
blockPreviews[b.code] = previewNode(detail.content);
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
/* leave the code-only fallback */
|
|
415
|
+
}
|
|
416
|
+
}));
|
|
417
|
+
const templatePreviews = {};
|
|
418
|
+
await Promise.all(templateRes.data.map(async (tpl) => {
|
|
419
|
+
try {
|
|
420
|
+
const detail = await cmsClient.getTemplate(tpl.id);
|
|
421
|
+
templatePreviews[tpl.code] = previewNode(detail.content);
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
/* leave the code-only fallback */
|
|
425
|
+
}
|
|
426
|
+
}));
|
|
427
|
+
if (live)
|
|
428
|
+
setEmbeds({ blocks: blockPreviews, templates: templatePreviews });
|
|
429
|
+
})();
|
|
430
|
+
return () => {
|
|
431
|
+
live = false;
|
|
432
|
+
};
|
|
433
|
+
}, []);
|
|
434
|
+
const viewports = useMemo(() => buildViewports(descriptor), [descriptor]);
|
|
435
|
+
const plugins = useMemo(() => [createPageBuilderEditorPlugin()], []);
|
|
436
|
+
/** Holds canvas data across remount until parent `data` catches up (clear / copy-from). */
|
|
437
|
+
const pendingSeedRef = useRef(null);
|
|
438
|
+
const editorData = pendingSeedRef.current ?? data ?? emptyData;
|
|
439
|
+
/**
|
|
440
|
+
* The block names the loaded document carries, as a stable key.
|
|
441
|
+
*
|
|
442
|
+
* `editorData` changes identity on every keystroke and the set of names it
|
|
443
|
+
* holds almost never does, so keying the config on the document would rebuild
|
|
444
|
+
* the whole Puck config while an author types. The key is what the
|
|
445
|
+
* degradation merge below actually depends on (feature 096, T602).
|
|
446
|
+
*/
|
|
447
|
+
const storedBlockNamesKey = useMemo(() => [...countBlockNames(editorData).keys()].sort().join('\n'), [editorData]);
|
|
448
|
+
const config = useMemo(() => {
|
|
449
|
+
const merged = mergeConfig(descriptor, t('pageBuilder.extensions'), blockOptions, context, sectionTitle);
|
|
450
|
+
const filtered = filterConfigByContext(merged, context);
|
|
451
|
+
// FR-019/FR-020 — a stored block nothing here can render degrades to the
|
|
452
|
+
// placeholder rather than vanishing from the canvas. **After** the context
|
|
453
|
+
// filter and after the categories, deliberately: the block stays visible and
|
|
454
|
+
// editable where it already is, and is not something an operator may insert.
|
|
455
|
+
const degraded = withMissingBlockPlaceholders(filtered, storedBlockNamesKey === '' ? [] : storedBlockNamesKey.split('\n'));
|
|
456
|
+
const rooted = pageContainer ? withCmsPageRoot(degraded) : degraded;
|
|
457
|
+
return applyPageBuilderTranslations(rooted, t, blockLabel);
|
|
458
|
+
}, [
|
|
459
|
+
descriptor,
|
|
460
|
+
t,
|
|
461
|
+
sectionTitle,
|
|
462
|
+
blockLabel,
|
|
463
|
+
blockOptions,
|
|
464
|
+
pageContainer,
|
|
465
|
+
context,
|
|
466
|
+
storedBlockNamesKey,
|
|
467
|
+
]);
|
|
468
|
+
const lastValidDataRef = useRef(editorData);
|
|
469
|
+
const knownRowIdsRef = useRef(collectRowIds(editorData));
|
|
470
|
+
useEffect(() => {
|
|
471
|
+
pendingSeedRef.current = null;
|
|
472
|
+
lastValidDataRef.current = data ?? emptyData;
|
|
473
|
+
knownRowIdsRef.current = collectRowIds(data ?? emptyData);
|
|
474
|
+
setRowLayoutPickerForId(null);
|
|
475
|
+
setCanvasEpoch(0);
|
|
476
|
+
// Only reseed when switching page/language — not on every draft edit.
|
|
477
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- data intentionally omitted
|
|
478
|
+
}, [contentKey]);
|
|
479
|
+
useEffect(() => {
|
|
480
|
+
lastValidDataRef.current = editorData;
|
|
481
|
+
}, [editorData]);
|
|
482
|
+
const handleEditorChange = useCallback((next) => {
|
|
483
|
+
if (hasInvalidColumnPlacement(next)) {
|
|
484
|
+
dispatchRef.current?.({
|
|
485
|
+
type: 'setData',
|
|
486
|
+
data: lastValidDataRef.current,
|
|
487
|
+
recordHistory: false,
|
|
488
|
+
});
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
pendingSeedRef.current = null;
|
|
492
|
+
const nextRowIds = collectRowIds(next);
|
|
493
|
+
let newRowId = null;
|
|
494
|
+
for (const id of nextRowIds) {
|
|
495
|
+
if (!knownRowIdsRef.current.has(id)) {
|
|
496
|
+
newRowId = id;
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
knownRowIdsRef.current = nextRowIds;
|
|
501
|
+
lastValidDataRef.current = next;
|
|
502
|
+
onChange(next);
|
|
503
|
+
if (newRowId) {
|
|
504
|
+
const rowId = newRowId;
|
|
505
|
+
window.setTimeout(() => setRowLayoutPickerForId(rowId), 0);
|
|
506
|
+
}
|
|
507
|
+
}, [onChange]);
|
|
508
|
+
const applyCanvasData = useCallback((next) => {
|
|
509
|
+
pendingSeedRef.current = next;
|
|
510
|
+
lastValidDataRef.current = next;
|
|
511
|
+
knownRowIdsRef.current = collectRowIds(next);
|
|
512
|
+
onChange(next);
|
|
513
|
+
// Puck treats `data` as initial only — remount so the canvas reseeds.
|
|
514
|
+
setCanvasEpoch((epoch) => epoch + 1);
|
|
515
|
+
}, [onChange]);
|
|
516
|
+
const headerActionsStateRef = useRef({
|
|
517
|
+
fullscreen,
|
|
518
|
+
setFullscreen,
|
|
519
|
+
tChrome,
|
|
520
|
+
languages,
|
|
521
|
+
activeLanguage,
|
|
522
|
+
editorData: data ?? emptyPageBuilderData(),
|
|
523
|
+
onCopyFromLanguage: null,
|
|
524
|
+
onClearCanvas: null,
|
|
525
|
+
onSaveAsTemplate: null,
|
|
526
|
+
onListTemplatesForApply: null,
|
|
527
|
+
onApplyTemplate: null,
|
|
528
|
+
drawerSearchPlaceholder: t('pageBuilder.drawer.searchPlaceholder'),
|
|
529
|
+
drawerSearchEmpty: t('pageBuilder.drawer.searchEmpty'),
|
|
530
|
+
});
|
|
531
|
+
headerActionsStateRef.current = {
|
|
532
|
+
fullscreen,
|
|
533
|
+
setFullscreen,
|
|
534
|
+
tChrome,
|
|
535
|
+
languages,
|
|
536
|
+
activeLanguage,
|
|
537
|
+
editorData: editorData,
|
|
538
|
+
onCopyFromLanguage: onResolveLanguageContent
|
|
539
|
+
? async (sourceLanguage) => {
|
|
540
|
+
const resolved = await onResolveLanguageContent(sourceLanguage);
|
|
541
|
+
if (isEmptyPageBuilderData(resolved)) {
|
|
542
|
+
throw new Error(t('pageBuilder.copyLanguage.emptySource'));
|
|
543
|
+
}
|
|
544
|
+
applyCanvasData(structuredClone(resolved));
|
|
545
|
+
}
|
|
546
|
+
: null,
|
|
547
|
+
onClearCanvas: () => {
|
|
548
|
+
applyCanvasData(emptyPageBuilderData());
|
|
549
|
+
},
|
|
550
|
+
onSaveAsTemplate: onSaveAsTemplate
|
|
551
|
+
? async (meta, canvasData) => {
|
|
552
|
+
await onSaveAsTemplate(meta, canvasData);
|
|
553
|
+
}
|
|
554
|
+
: null,
|
|
555
|
+
onListTemplatesForApply: onListTemplatesForApply ?? null,
|
|
556
|
+
onApplyTemplate: onResolveTemplateLayout
|
|
557
|
+
? async (templateId) => {
|
|
558
|
+
const next = await onResolveTemplateLayout(templateId);
|
|
559
|
+
applyCanvasData(structuredClone(next));
|
|
560
|
+
}
|
|
561
|
+
: null,
|
|
562
|
+
drawerSearchPlaceholder: t('pageBuilder.drawer.searchPlaceholder'),
|
|
563
|
+
drawerSearchEmpty: t('pageBuilder.drawer.searchEmpty'),
|
|
564
|
+
};
|
|
565
|
+
const applyRowPreset = useCallback((rowId, presetId) => {
|
|
566
|
+
dispatchRef.current?.({
|
|
567
|
+
type: 'setData',
|
|
568
|
+
data: (previous) => {
|
|
569
|
+
const row = findRowById(previous, rowId);
|
|
570
|
+
if (!row)
|
|
571
|
+
return previous;
|
|
572
|
+
const nextProps = applyRowLayoutPreset(row.props, presetId);
|
|
573
|
+
return replaceRowInData(previous, rowId, { type: 'cms.Row', props: { ...nextProps, id: rowId } });
|
|
574
|
+
},
|
|
575
|
+
});
|
|
576
|
+
}, []);
|
|
577
|
+
const handlePuckAction = useMemo(() => createPuckActionHandler(dispatchRef, (action, appState) => {
|
|
578
|
+
if (action.type !== 'insert' || action.componentType !== 'cms.Row')
|
|
579
|
+
return;
|
|
580
|
+
const rowId = resolveInsertedRowId(action, appState.data);
|
|
581
|
+
if (!rowId)
|
|
582
|
+
return;
|
|
583
|
+
if (!isContentSliderSlidesZone(action.destinationZone, appState.data))
|
|
584
|
+
return;
|
|
585
|
+
const inserted = insertedItem(action, appState.data) ??
|
|
586
|
+
findRowById(appState.data, rowId) ?? {
|
|
587
|
+
type: 'cms.Row',
|
|
588
|
+
props: { id: rowId, content: [] },
|
|
589
|
+
};
|
|
590
|
+
dispatchRef.current?.({
|
|
591
|
+
type: 'setData',
|
|
592
|
+
data: (previous) => wrapRowInContentSliderSlide(previous, action.destinationZone, action.destinationIndex, inserted),
|
|
593
|
+
recordHistory: false,
|
|
594
|
+
});
|
|
595
|
+
}), []);
|
|
596
|
+
const puckOverrides = useMemo(() => ({
|
|
597
|
+
puck: ({ children }) => (_jsx(PuckDispatchBridgeSlot, { dispatchRef: dispatchRef, children: children })),
|
|
598
|
+
actionBar: (props) => _jsx(PageBuilderActionBar, { ...props }),
|
|
599
|
+
componentOverlay: (props) => _jsx(PageBuilderOverlayBridge, { ...props }),
|
|
600
|
+
drawerItem: ({ name, children, }) => (name === 'cms.Column' || name === 'cms.Slide' ? _jsx(_Fragment, {}) : _jsx(_Fragment, { children: children })),
|
|
601
|
+
drawer: ({ children }) => {
|
|
602
|
+
const state = headerActionsStateRef.current;
|
|
603
|
+
return (_jsx(PageBuilderDrawer, { searchPlaceholder: state.drawerSearchPlaceholder, emptyLabel: state.drawerSearchEmpty, children: children }));
|
|
604
|
+
},
|
|
605
|
+
header: ({ children }) => {
|
|
606
|
+
const state = headerActionsStateRef.current;
|
|
607
|
+
const hasTemplates = Boolean(state.onSaveAsTemplate || state.onApplyTemplate);
|
|
608
|
+
return (_jsx(PageBuilderHeaderShell, { leading: hasTemplates ? (_jsx(PageBuilderTemplateActions, { currentData: state.editorData, ...(state.onSaveAsTemplate ? { onSaveAsTemplate: state.onSaveAsTemplate } : {}), ...(state.onListTemplatesForApply
|
|
609
|
+
? { onListTemplatesForApply: state.onListTemplatesForApply }
|
|
610
|
+
: {}), ...(state.onApplyTemplate
|
|
611
|
+
? {
|
|
612
|
+
onApplyTemplate: async (templateId) => {
|
|
613
|
+
await state.onApplyTemplate?.(templateId);
|
|
614
|
+
},
|
|
615
|
+
}
|
|
616
|
+
: {}), t: state.tChrome })) : null, children: children }));
|
|
617
|
+
},
|
|
618
|
+
headerActions: () => {
|
|
619
|
+
const state = headerActionsStateRef.current;
|
|
620
|
+
return (_jsx(PageBuilderHeaderActions, { fullscreen: state.fullscreen, onToggleFullscreen: () => state.setFullscreen((f) => !f), languages: state.languages, activeLanguage: state.activeLanguage, currentData: state.editorData, ...(state.onCopyFromLanguage ? { onCopyFromLanguage: state.onCopyFromLanguage } : {}), ...(state.onClearCanvas ? { onClearCanvas: state.onClearCanvas } : {}), t: state.tChrome }));
|
|
621
|
+
},
|
|
622
|
+
}), []);
|
|
623
|
+
return (_jsxs("div", { className: cn('cms-page-builder space-y-3', fullscreen && 'fixed inset-0 z-50 flex flex-col overflow-auto bg-background p-4'), children: [error ? (_jsx(Alert, { variant: "destructive", children: _jsx(AlertDescription, { children: error }) })) : null, !descriptor && !error ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('pageBuilder.loadingConfig') })) : null, _jsx("div", { className: cn('cms-page-builder__canvas overflow-x-auto overflow-y-auto border', fullscreen ? 'min-h-0 flex-1 rounded-md' : 'min-h-[640px] rounded-md'), children: _jsx(AdminCmsAssetProvider, { embeds: embeds, data: editorData, children: _jsx(AdminCatalogPreviewProvider, { children: _jsx(PageBuilderColorPaletteProvider, { initialEntries: descriptor?.colorPalette ?? [], children: _jsx(Puck, { config: config, data: editorData, onChange: handleEditorChange, onAction: handlePuckAction, viewports: viewports, iframe: { enabled: true, waitForStyles: true }, plugins: plugins, overrides: puckOverrides }, `${contentKey ?? 'pb'}:${canvasEpoch}`) }) }) }) }), _jsx(RowLayoutPicker, { open: rowLayoutPickerForId !== null, onOpenChange: (open) => {
|
|
624
|
+
if (!open)
|
|
625
|
+
setRowLayoutPickerForId(null);
|
|
626
|
+
}, onSelect: (presetId) => {
|
|
627
|
+
if (rowLayoutPickerForId)
|
|
628
|
+
applyRowPreset(rowLayoutPickerForId, presetId);
|
|
629
|
+
setRowLayoutPickerForId(null);
|
|
630
|
+
} })] }));
|
|
631
|
+
}
|
|
632
|
+
//# sourceMappingURL=PageBuilderEditor.js.map
|