@webiny/app-headless-cms 6.5.0-beta.0 → 6.5.0-beta.10
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/ContentEntriesModule.js +14 -2
- package/ContentEntriesModule.js.map +1 -1
- package/admin/components/ContentEntries/BulkActions/ActionDelete.d.ts +2 -2
- package/admin/components/ContentEntries/BulkActions/ActionDelete.js.map +1 -1
- package/admin/components/ContentEntries/BulkActions/ActionMove.d.ts +2 -2
- package/admin/components/ContentEntries/BulkActions/ActionMove.js.map +1 -1
- package/admin/components/ContentEntries/BulkActions/ActionPublish.d.ts +2 -2
- package/admin/components/ContentEntries/BulkActions/ActionPublish.js.map +1 -1
- package/admin/components/ContentEntries/BulkActions/ActionUnpublish.d.ts +2 -2
- package/admin/components/ContentEntries/BulkActions/ActionUnpublish.js.map +1 -1
- package/admin/components/ContentEntries/Filters/RefFieldRenderer.d.ts +2 -0
- package/admin/components/ContentEntries/Filters/RefFieldRenderer.js +84 -0
- package/admin/components/ContentEntries/Filters/RefFieldRenderer.js.map +1 -0
- package/admin/components/ContentEntries/Table/Actions/DuplicateEntry.d.ts +2 -0
- package/admin/components/ContentEntries/Table/Actions/DuplicateEntry.js +34 -0
- package/admin/components/ContentEntries/Table/Actions/DuplicateEntry.js.map +1 -0
- package/admin/components/ContentEntries/Table/Actions/index.d.ts +1 -0
- package/admin/components/ContentEntries/Table/Actions/index.js +1 -0
- package/admin/components/ContentModelEditor/ContentModelEditor.js +1 -1
- package/admin/components/ContentModelEditor/ContentModelEditor.js.map +1 -1
- package/admin/components/Dialogs/DuplicateEntryConfirmDialog.d.ts +2 -0
- package/admin/components/Dialogs/DuplicateEntryConfirmDialog.js +22 -0
- package/admin/components/Dialogs/DuplicateEntryConfirmDialog.js.map +1 -0
- package/admin/components/FieldEditor/EditFieldDialog.d.ts +2 -2
- package/admin/components/FieldEditor/Field.js +4 -4
- package/admin/components/FieldEditor/Field.js.map +1 -1
- package/features/contentEntry/CmsEntryError.d.ts +10 -0
- package/features/contentEntry/CmsEntryError.js +11 -0
- package/features/contentEntry/CmsEntryError.js.map +1 -0
- package/features/contentEntry/createEntry/CreateEntryGateway.d.ts +0 -1
- package/features/contentEntry/createEntry/CreateEntryGateway.js +3 -10
- package/features/contentEntry/createEntry/CreateEntryGateway.js.map +1 -1
- package/features/contentEntry/createRevisionFrom/CreateRevisionFromGateway.d.ts +3 -1
- package/features/contentEntry/createRevisionFrom/CreateRevisionFromGateway.js +8 -3
- package/features/contentEntry/createRevisionFrom/CreateRevisionFromGateway.js.map +1 -1
- package/features/contentEntry/duplicateEntry/DuplicateEntryGateway.d.ts +14 -0
- package/features/contentEntry/duplicateEntry/DuplicateEntryGateway.js +43 -0
- package/features/contentEntry/duplicateEntry/DuplicateEntryGateway.js.map +1 -0
- package/features/contentEntry/duplicateEntry/DuplicateEntryRepository.d.ts +15 -0
- package/features/contentEntry/duplicateEntry/DuplicateEntryRepository.js +34 -0
- package/features/contentEntry/duplicateEntry/DuplicateEntryRepository.js.map +1 -0
- package/features/contentEntry/duplicateEntry/DuplicateEntryUseCase.d.ts +11 -0
- package/features/contentEntry/duplicateEntry/DuplicateEntryUseCase.js +18 -0
- package/features/contentEntry/duplicateEntry/DuplicateEntryUseCase.js.map +1 -0
- package/features/contentEntry/duplicateEntry/abstractions.d.ts +26 -0
- package/features/contentEntry/duplicateEntry/abstractions.js +7 -0
- package/features/contentEntry/duplicateEntry/abstractions.js.map +1 -0
- package/features/contentEntry/duplicateEntry/feature.d.ts +3 -0
- package/features/contentEntry/duplicateEntry/feature.js +21 -0
- package/features/contentEntry/duplicateEntry/feature.js.map +1 -0
- package/features/contentEntry/entryValueGateways.test.d.ts +1 -0
- package/features/contentEntry/entryValueGateways.test.js +106 -0
- package/features/contentEntry/entryValueGateways.test.js.map +1 -0
- package/features/contentEntry/feature.js +2 -0
- package/features/contentEntry/feature.js.map +1 -1
- package/features/contentEntry/singletonEntry/UpdateSingletonEntryGateway.d.ts +0 -1
- package/features/contentEntry/singletonEntry/UpdateSingletonEntryGateway.js +3 -10
- package/features/contentEntry/singletonEntry/UpdateSingletonEntryGateway.js.map +1 -1
- package/features/contentEntry/updateEntry/UpdateEntryGateway.d.ts +0 -1
- package/features/contentEntry/updateEntry/UpdateEntryGateway.js +3 -10
- package/features/contentEntry/updateEntry/UpdateEntryGateway.js.map +1 -1
- package/features/contentEntry/valueTransformers/EntryDataPreparer.d.ts +9 -0
- package/features/contentEntry/valueTransformers/EntryDataPreparer.js +8 -0
- package/features/contentEntry/valueTransformers/EntryDataPreparer.js.map +1 -1
- package/features/contentEntry/valueTransformers/EntryDataPreparer.test.d.ts +1 -0
- package/features/contentEntry/valueTransformers/EntryDataPreparer.test.js +113 -0
- package/features/contentEntry/valueTransformers/EntryDataPreparer.test.js.map +1 -0
- package/features/contentEntry/valueTransformers/entryValueFixtures.d.ts +146 -0
- package/features/contentEntry/valueTransformers/entryValueFixtures.js +171 -0
- package/features/contentEntry/valueTransformers/entryValueFixtures.js.map +1 -0
- package/features/formModel/CmsFormModelBuilder.d.ts +9 -1
- package/features/formModel/CmsFormModelBuilder.js +26 -15
- package/features/formModel/CmsFormModelBuilder.js.map +1 -1
- package/features/formModel/CmsFormModelBuilder.test.d.ts +1 -0
- package/features/formModel/CmsFormModelBuilder.test.js +252 -0
- package/features/formModel/CmsFormModelBuilder.test.js.map +1 -0
- package/features/formModel/CmsLayoutMapper.d.ts +8 -1
- package/features/formModel/CmsLayoutMapper.js +23 -1
- package/features/formModel/CmsLayoutMapper.js.map +1 -1
- package/features/formModel/mappers/DynamicZoneFieldMapper.js +4 -0
- package/features/formModel/mappers/DynamicZoneFieldMapper.js.map +1 -1
- package/features/formModel/mappers/ObjectFieldMapper.js +11 -6
- package/features/formModel/mappers/ObjectFieldMapper.js.map +1 -1
- package/features/model/exportModels/ExportModelsGateway.d.ts +1 -1
- package/features/model/exportModels/ExportModelsGateway.js.map +1 -1
- package/features/model/exportModels/ExportModelsUseCase.js +7 -1
- package/features/model/exportModels/ExportModelsUseCase.js.map +1 -1
- package/features/model/exportModels/abstractions.d.ts +8 -2
- package/features/model/exportModels/abstractions.js.map +1 -1
- package/package.json +26 -26
- package/presentation/cloneContentModel/CloneContentModelPresenter.d.ts +1 -0
- package/presentation/cloneContentModel/CloneContentModelPresenter.js +8 -6
- package/presentation/cloneContentModel/CloneContentModelPresenter.js.map +1 -1
- package/presentation/contentEntries/form/ContentEntryFormPresenter.js +34 -29
- package/presentation/contentEntries/form/ContentEntryFormPresenter.js.map +1 -1
- package/presentation/contentEntries/form/ContentEntryFormPresenter.test.d.ts +1 -0
- package/presentation/contentEntries/form/ContentEntryFormPresenter.test.js +210 -0
- package/presentation/contentEntries/form/ContentEntryFormPresenter.test.js.map +1 -0
- package/presentation/contentEntries/form/toFormErrors.d.ts +7 -0
- package/presentation/contentEntries/form/toFormErrors.js +49 -0
- package/presentation/contentEntries/form/toFormErrors.js.map +1 -0
- package/presentation/contentEntries/form/toFormErrors.test.d.ts +1 -0
- package/presentation/contentEntries/form/toFormErrors.test.js +175 -0
- package/presentation/contentEntries/form/toFormErrors.test.js.map +1 -0
- package/presentation/contentEntries/list/ContentEntriesDataSource.d.ts +5 -0
- package/presentation/contentEntries/list/ContentEntriesDataSource.js +11 -2
- package/presentation/contentEntries/list/ContentEntriesDataSource.js.map +1 -1
- package/presentation/contentEntries/list/ContentEntriesPresenter.d.ts +6 -2
- package/presentation/contentEntries/list/ContentEntriesPresenter.js +27 -2
- package/presentation/contentEntries/list/ContentEntriesPresenter.js.map +1 -1
- package/presentation/contentEntries/list/ContentEntriesPresenter.test.js +130 -5
- package/presentation/contentEntries/list/ContentEntriesPresenter.test.js.map +1 -1
- package/presentation/contentEntries/list/abstractions.d.ts +12 -0
- package/presentation/contentEntries/list/abstractions.js.map +1 -1
- package/presentation/contentEntries/preview/LivePreviewPresenter.d.ts +12 -1
- package/presentation/contentEntries/preview/LivePreviewPresenter.js +48 -5
- package/presentation/contentEntries/preview/LivePreviewPresenter.js.map +1 -1
- package/presentation/contentEntries/preview/LivePreviewPresenter.test.d.ts +1 -0
- package/presentation/contentEntries/preview/LivePreviewPresenter.test.js +81 -0
- package/presentation/contentEntries/preview/LivePreviewPresenter.test.js.map +1 -0
- package/presentation/contentEntries/preview/ModelEditorComponentDiscovery.js +1 -1
- package/presentation/contentEntries/preview/ModelEditorComponentDiscovery.js.map +1 -1
- package/presentation/contentEntries/preview/PreviewDecorator.js +25 -6
- package/presentation/contentEntries/preview/PreviewDecorator.js.map +1 -1
- package/presentation/contentEntries/preview/PreviewDomainStatus.d.ts +10 -0
- package/presentation/contentEntries/preview/PreviewDomainStatus.js +19 -0
- package/presentation/contentEntries/preview/PreviewDomainStatus.js.map +1 -0
- package/presentation/contentEntries/preview/PreviewPane.d.ts +10 -1
- package/presentation/contentEntries/preview/PreviewPane.js +81 -45
- package/presentation/contentEntries/preview/PreviewPane.js.map +1 -1
- package/presentation/contentEntries/preview/abstractions.d.ts +8 -0
- package/presentation/contentEntries/preview/abstractions.js.map +1 -1
- package/presentation/contentEntries/preview/resolvePreviewUrl.d.ts +21 -0
- package/presentation/contentEntries/preview/resolvePreviewUrl.js +50 -1
- package/presentation/contentEntries/preview/resolvePreviewUrl.js.map +1 -1
- package/presentation/contentEntries/preview/resolvePreviewUrl.test.d.ts +1 -0
- package/presentation/contentEntries/preview/resolvePreviewUrl.test.js +127 -0
- package/presentation/contentEntries/preview/resolvePreviewUrl.test.js.map +1 -0
- package/presentation/contentEntries/singleEntry/SingleEntryPresenter.js +9 -4
- package/presentation/contentEntries/singleEntry/SingleEntryPresenter.js.map +1 -1
- package/presentation/contentEntries/singleEntry/SingleEntryPresenter.test.d.ts +1 -0
- package/presentation/contentEntries/singleEntry/SingleEntryPresenter.test.js +122 -0
- package/presentation/contentEntries/singleEntry/SingleEntryPresenter.test.js.map +1 -0
- package/presentation/contentEntries/trashBin/CmsTrashBin.d.ts +2 -2
- package/presentation/contentEntries/views/CmsAdvancedSearch.d.ts +2 -2
- package/presentation/contentEntries/views/ContentEntriesListLayout.d.ts +2 -2
- package/presentation/contentEntries/views/ContentEntryFormView.d.ts +2 -2
- package/presentation/contentEntries/views/OlderRevisionAlert.d.ts +2 -2
- package/presentation/contentEntries/views/RevisionDrawer.d.ts +2 -2
- package/presentation/contentEntries/views/RevisionsList.d.ts +2 -2
- package/presentation/contentEntries/views/SingleEntryLayout.d.ts +2 -2
- package/presentation/contentEntries/views/Table/Table.d.ts +2 -2
- package/presentation/contentEntries/views/actions/CreateNewRevisionButton.d.ts +2 -2
- package/presentation/contentEntries/views/actions/DeleteEntryMenuItem.d.ts +2 -2
- package/presentation/contentEntries/views/actions/PublishOnlyButton.d.ts +2 -2
- package/presentation/contentEntries/views/actions/RevisionSelector.d.ts +2 -2
- package/presentation/contentEntries/views/actions/SaveAndPublishButton.d.ts +2 -2
- package/presentation/contentEntries/views/actions/SaveContentButton.d.ts +2 -2
- package/presentation/contentEntries/views/actions/ShowRevisionListMenuItem.d.ts +2 -2
- package/presentation/contentEntries/views/layout/ContentEntryForm.d.ts +12 -12
- package/presentation/contentModels/ContentModelsPresenter.d.ts +5 -5
- package/presentation/contentModels/ContentModelsPresenter.js +1 -1
- package/presentation/contentModels/ContentModelsPresenter.js.map +1 -1
- package/presentation/contentModels/abstractions.d.ts +7 -2
- package/presentation/contentModels/abstractions.js.map +1 -1
- package/presentation/contentModels/components/CloneContentModelDialog.d.ts +2 -2
- package/presentation/contentModels/components/ContentModels.d.ts +2 -2
- package/presentation/contentModels/components/ContentModelsDataList.d.ts +2 -2
- package/presentation/contentModels/components/ContentModelsDataList.js +6 -5
- package/presentation/contentModels/components/ContentModelsDataList.js.map +1 -1
- package/presentation/contentModels/components/NewContentModelDialog.d.ts +2 -2
- package/presentation/contentModels/components/fullDelete/dialog/Content.js +2 -2
- package/presentation/contentModels/components/fullDelete/dialog/Content.js.map +1 -1
- package/presentation/contentModels/components/fullDelete/types.js.map +1 -1
- package/presentation/fieldRenderers/ref/autocomplete/RefAutocompletePresenters.test.d.ts +1 -0
- package/presentation/fieldRenderers/ref/autocomplete/RefAutocompletePresenters.test.js +133 -0
- package/presentation/fieldRenderers/ref/autocomplete/RefAutocompletePresenters.test.js.map +1 -0
- package/presentation/fieldRenderers/ref/autocomplete/multi/RefMultiAutocompletePresenter.js +1 -1
- package/presentation/fieldRenderers/ref/autocomplete/multi/RefMultiAutocompletePresenter.js.map +1 -1
- package/presentation/fieldRenderers/ref/autocomplete/single/RefSingleAutocompletePresenter.js +1 -1
- package/presentation/fieldRenderers/ref/autocomplete/single/RefSingleAutocompletePresenter.js.map +1 -1
- package/presentation/fieldRenderers/ref/components/RefFieldOptions.d.ts +2 -2
- package/presentation/fieldRenderers/ref/components/RefFieldOptions.js +3 -2
- package/presentation/fieldRenderers/ref/components/RefFieldOptions.js.map +1 -1
- package/presentation/importContentModels/ImportContentModelsDialog.d.ts +2 -2
- package/presentation/importContentModels/ImportContentModelsPresenter.js +1 -1
- package/presentation/importContentModels/ImportContentModelsPresenter.js.map +1 -1
- package/presentation/importContentModels/components/DataList.d.ts +2 -2
- package/presentation/importContentModels/components/FileUpload.d.ts +2 -2
- package/presentation/importContentModels/components/FileUpload.js +1 -1
- package/presentation/importContentModels/components/FileUpload.js.map +1 -1
- package/presentation/importContentModels/components/Model/DataListModelItem.js +3 -3
- package/presentation/importContentModels/components/Model/DataListModelItem.js.map +1 -1
- package/presentation/importContentModels/components/Model/DataListModelItemInfo.js +2 -2
- package/presentation/importContentModels/components/Model/DataListModelItemInfo.js.map +1 -1
- package/presentation/importContentModels/components/Model/DataListModels.d.ts +2 -2
- package/presentation/menus/ContentGroupsMenuItems.d.ts +2 -2
- package/presentation/modelGroup/ModelGroupPresenter.js +1 -2
- package/presentation/modelGroup/ModelGroupPresenter.js.map +1 -1
- package/presentation/modelGroup/components/ContentModelGroups.d.ts +2 -2
- package/presentation/modelGroup/components/ContentModelGroupsDataList.d.ts +6 -2
- package/presentation/modelGroup/components/ContentModelGroupsForm.d.ts +8 -2
- package/presentation/newContentModel/NewContentModelPresenter.d.ts +1 -0
- package/presentation/newContentModel/NewContentModelPresenter.js +8 -6
- package/presentation/newContentModel/NewContentModelPresenter.js.map +1 -1
- package/utils/normalizeIcon.d.ts +4 -0
- package/utils/normalizeIcon.js +9 -6
- package/utils/normalizeIcon.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presentation/contentEntries/preview/PreviewPane.js","sources":["../../../../src/presentation/contentEntries/preview/PreviewPane.tsx"],"sourcesContent":["import React, { useRef, useEffect, useCallback, useState } from \"react\";\nimport { Messenger, MessageOrigin } from \"@webiny/cms-sdk/messenger\";\nimport { jsonPatch } from \"@webiny/cms-sdk\";\nimport { IconButton, Input, OverlayLoader, SegmentedControl } from \"@webiny/admin-ui\";\nimport { ReactComponent as RefreshIcon } from \"@webiny/icons/refresh.svg\";\nimport { ReactComponent as ContentCopyIcon } from \"@webiny/icons/content_copy.svg\";\nimport { ReactComponent as LaptopIcon } from \"@webiny/icons/laptop.svg\";\nimport { ReactComponent as SmartphoneIcon } from \"@webiny/icons/smartphone.svg\";\nimport { ReactComponent as OpenInNewIcon } from \"@webiny/icons/open_in_new.svg\";\nimport { useLivePreviewPresenter } from \"./useLivePreviewPresenter.js\";\nimport { buildEditorUrl, buildDisplayUrl } from \"./resolvePreviewUrl.js\";\n\ninterface PreviewPaneProps {\n domain: string;\n previewPath: string;\n entryId: string;\n entryData: Record<string, unknown> | null;\n}\n\ntype ViewportMode = \"desktop\" | \"mobile\";\n\nexport const PreviewPane = ({ domain, previewPath, entryId, entryData }: PreviewPaneProps) => {\n const iframeRef = useRef<HTMLIFrameElement | null>(null);\n const messengerRef = useRef<Messenger | null>(null);\n const entryDataRef = useRef(entryData);\n const lastSentDataRef = useRef<Record<string, unknown> | null>(null);\n const [ready, setReady] = useState(false);\n const [loading, setLoading] = useState(true);\n const [iframeKey, setIframeKey] = useState(0);\n const [viewport, setViewport] = useState<ViewportMode>(\"desktop\");\n const presenter = useLivePreviewPresenter();\n\n entryDataRef.current = entryData;\n\n const displayUrl = buildDisplayUrl(\n domain,\n previewPath,\n (entryData as Record<string, unknown>) || {}\n );\n\n const [address, setAddress] = useState(displayUrl);\n\n useEffect(() => {\n setAddress(displayUrl);\n }, [displayUrl]);\n\n const iframeSrc = (() => {\n const editorPath = buildEditorUrl(domain, previewPath);\n const url = new URL(editorPath);\n url.searchParams.set(\"wb.editing\", \"true\");\n url.searchParams.set(\"wb.type\", \"entry\");\n url.searchParams.set(\"wb.id\", entryId);\n url.searchParams.set(\"wb.path\", url.pathname);\n url.searchParams.set(\"wb.referrer\", window.location.origin);\n return url.toString();\n })();\n\n const sendEntryData = useCallback(() => {\n const data = entryDataRef.current;\n if (!data || !messengerRef.current) {\n return;\n }\n\n const serialized = JSON.parse(JSON.stringify(data));\n messengerRef.current.send(\"document.set\", serialized);\n lastSentDataRef.current = serialized;\n }, []);\n\n useEffect(() => {\n const iframe = iframeRef.current;\n if (!iframe || !iframe.contentWindow) {\n return;\n }\n\n const targetOrigin = new URL(iframe.src).origin;\n const editorOrigin = new MessageOrigin(() => window, window.location.origin);\n const previewTarget = new MessageOrigin(() => iframe.contentWindow!, targetOrigin);\n\n const messenger = new Messenger(editorOrigin, previewTarget, \"wb.editor.*\");\n messengerRef.current = messenger;\n\n messenger.on(\"preview.ready\", () => {\n setReady(true);\n setLoading(false);\n sendEntryData();\n });\n\n messenger.on(\n \"preview.component.register\",\n (manifest: { name: string; label: string; description: string }) => {\n presenter.addComponent(manifest);\n }\n );\n\n return () => {\n messenger.dispose();\n messengerRef.current = null;\n lastSentDataRef.current = null;\n setReady(false);\n };\n }, [iframeSrc, presenter, iframeKey, sendEntryData]);\n\n useEffect(() => {\n if (!ready || !entryData || !messengerRef.current) {\n return;\n }\n\n const current = JSON.parse(JSON.stringify(entryData));\n const previous = lastSentDataRef.current;\n\n if (previous) {\n const patch = jsonPatch.compare(previous, current);\n if (patch.length > 0) {\n messengerRef.current.send(\"document.patch\", patch);\n }\n } else {\n messengerRef.current.send(\"document.set\", current);\n }\n\n lastSentDataRef.current = current;\n }, [ready, entryData]);\n\n const reload = useCallback(() => {\n setLoading(true);\n setReady(false);\n lastSentDataRef.current = null;\n setIframeKey(prev => prev + 1);\n }, []);\n\n const getDraftUrl = useCallback(() => {\n const url = new URL(displayUrl);\n url.searchParams.set(\"wb.type\", \"entry\");\n url.searchParams.set(\"wb.path\", url.pathname);\n url.searchParams.set(\"wb.preview\", \"true\");\n url.searchParams.set(\"wb.id\", entryId);\n return url.toString();\n }, [displayUrl, entryId]);\n\n const copyUrl = useCallback(() => {\n navigator.clipboard.writeText(getDraftUrl());\n }, [getDraftUrl]);\n\n const openInNewTab = useCallback(() => {\n window.open(getDraftUrl(), \"_blank\");\n }, [getDraftUrl]);\n\n return (\n <div className=\"relative border border-neutral-dimmed rounded-t-lg flex flex-col flex-1 h-full overflow-hidden\">\n <div className=\"flex p-md items-center bg-white border-b border-neutral-dimmed\">\n <div className=\"w-full\">\n <Input\n value={address}\n onChange={setAddress}\n readOnly\n size=\"md\"\n variant={\"secondary\"}\n className=\"bg-gray-100\"\n />\n </div>\n\n <div className=\"flex items-center gap-xxs ml-sm\">\n <IconButton\n onClick={openInNewTab}\n icon={<OpenInNewIcon />}\n variant=\"ghost\"\n size=\"md\"\n />\n <IconButton\n onClick={copyUrl}\n icon={<ContentCopyIcon />}\n variant=\"ghost\"\n size=\"sm\"\n />\n <IconButton onClick={reload} icon={<RefreshIcon />} variant=\"ghost\" size=\"sm\" />\n <SegmentedControl\n value={viewport}\n onChange={(value: string) => setViewport(value as ViewportMode)}\n items={[\n { value: \"desktop\", label: \"\", icon: <LaptopIcon /> },\n { value: \"mobile\", label: \"\", icon: <SmartphoneIcon /> }\n ]}\n />\n </div>\n </div>\n\n <div className=\"block box-border h-full w-full overflow-auto fill-grid\">\n {loading ? <OverlayLoader text=\"Connecting to Live Preview...\" /> : null}\n <div\n className={`mx-auto h-full transition-all duration-300 ${viewport === \"mobile\" ? \"p-md\" : \"\"}`}\n style={{ width: viewport === \"mobile\" ? \"375px\" : \"100%\" }}\n >\n <iframe\n key={iframeKey}\n ref={iframeRef}\n src={iframeSrc}\n width=\"100%\"\n height=\"100%\"\n className=\"h-full\"\n sandbox=\"allow-same-origin allow-scripts allow-forms\"\n />\n </div>\n </div>\n </div>\n );\n};\n"],"names":["PreviewPane","domain","previewPath","entryId","entryData","iframeRef","useRef","messengerRef","entryDataRef","lastSentDataRef","ready","setReady","useState","loading","setLoading","iframeKey","setIframeKey","viewport","setViewport","presenter","useLivePreviewPresenter","displayUrl","buildDisplayUrl","address","setAddress","useEffect","iframeSrc","editorPath","buildEditorUrl","url","URL","window","sendEntryData","useCallback","data","serialized","JSON","iframe","targetOrigin","editorOrigin","MessageOrigin","previewTarget","messenger","Messenger","manifest","current","previous","patch","jsonPatch","reload","prev","getDraftUrl","copyUrl","navigator","openInNewTab","Input","IconButton","OpenInNewIcon","ContentCopyIcon","RefreshIcon","SegmentedControl","value","LaptopIcon","SmartphoneIcon","OverlayLoader"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"presentation/contentEntries/preview/PreviewPane.js","sources":["../../../../src/presentation/contentEntries/preview/PreviewPane.tsx"],"sourcesContent":["import React, { useRef, useEffect, useCallback, useState } from \"react\";\nimport { Messenger, MessageOrigin } from \"@webiny/cms-sdk/messenger\";\nimport { jsonPatch } from \"@webiny/cms-sdk\";\nimport {\n Alert,\n CopyButton,\n IconButton,\n OverlayLoader,\n SegmentedControl,\n Text,\n Tooltip,\n useToast\n} from \"@webiny/admin-ui\";\nimport { ReactComponent as RefreshIcon } from \"@webiny/icons/refresh.svg\";\nimport { ReactComponent as LaptopIcon } from \"@webiny/icons/laptop.svg\";\nimport { ReactComponent as SmartphoneIcon } from \"@webiny/icons/smartphone.svg\";\nimport { ReactComponent as OpenInNewIcon } from \"@webiny/icons/open_in_new.svg\";\nimport { PreviewDomainMenu } from \"@webiny/frontend-settings/exports/admin.js\";\nimport { useLivePreviewPresenter } from \"./useLivePreviewPresenter.js\";\nimport { buildEditorUrl, buildDisplayUrl } from \"./resolvePreviewUrl.js\";\n\n/**\n * How long to wait for the frontend to report it is ready before showing troubleshooting hints.\n */\nconst READY_TIMEOUT_MS = 20_000;\n\ninterface PreviewPaneProps {\n domain: string;\n /**\n * The Frontend Domain setting, passed only when the user overrides it with their own preview domain.\n */\n settingsDomain: string | null;\n onResetDomain: () => void;\n previewPath: string;\n entryId: string;\n entryData: Record<string, unknown> | null;\n /**\n * Entry data used to resolve the preview path, with referenced entries' values filled in.\n */\n urlEntryData: Record<string, unknown> | null;\n}\n\ntype ViewportMode = \"desktop\" | \"mobile\";\n\nexport const PreviewPane = ({\n domain,\n settingsDomain,\n onResetDomain,\n previewPath,\n entryId,\n entryData,\n urlEntryData\n}: PreviewPaneProps) => {\n const iframeRef = useRef<HTMLIFrameElement | null>(null);\n const messengerRef = useRef<Messenger | null>(null);\n const entryDataRef = useRef(entryData);\n const lastSentDataRef = useRef<Record<string, unknown> | null>(null);\n const [ready, setReady] = useState(false);\n const [loading, setLoading] = useState(true);\n const [timedOut, setTimedOut] = useState(false);\n const [iframeKey, setIframeKey] = useState(0);\n const [viewport, setViewport] = useState<ViewportMode>(\"desktop\");\n const presenter = useLivePreviewPresenter();\n const { showToast } = useToast();\n\n entryDataRef.current = entryData;\n\n const displayUrl = buildDisplayUrl(domain, previewPath, urlEntryData || {});\n\n const iframeSrc = (() => {\n const editorPath = buildEditorUrl(domain, previewPath);\n const url = new URL(editorPath);\n url.searchParams.set(\"wb.editing\", \"true\");\n url.searchParams.set(\"wb.type\", \"entry\");\n url.searchParams.set(\"wb.id\", entryId);\n url.searchParams.set(\"wb.path\", url.pathname);\n url.searchParams.set(\"wb.referrer\", window.location.origin);\n return url.toString();\n })();\n\n const sendEntryData = useCallback(() => {\n const data = entryDataRef.current;\n if (!data || !messengerRef.current) {\n return;\n }\n\n const serialized = JSON.parse(JSON.stringify(data));\n messengerRef.current.send(\"document.set\", serialized);\n lastSentDataRef.current = serialized;\n }, []);\n\n useEffect(() => {\n const iframe = iframeRef.current;\n if (!iframe || !iframe.contentWindow) {\n return;\n }\n\n const targetOrigin = new URL(iframe.src).origin;\n const editorOrigin = new MessageOrigin(() => window, window.location.origin);\n const previewTarget = new MessageOrigin(() => iframe.contentWindow!, targetOrigin);\n\n const messenger = new Messenger(editorOrigin, previewTarget, \"wb.editor.*\");\n messengerRef.current = messenger;\n\n setLoading(true);\n setTimedOut(false);\n const timeout = setTimeout(() => setTimedOut(true), READY_TIMEOUT_MS);\n\n messenger.on(\"preview.ready\", () => {\n clearTimeout(timeout);\n setReady(true);\n setLoading(false);\n setTimedOut(false);\n sendEntryData();\n });\n\n messenger.on(\n \"preview.component.register\",\n (manifest: { name: string; label: string; description: string }) => {\n presenter.addComponent(manifest);\n }\n );\n\n return () => {\n clearTimeout(timeout);\n messenger.dispose();\n messengerRef.current = null;\n lastSentDataRef.current = null;\n setReady(false);\n };\n }, [iframeSrc, presenter, iframeKey, sendEntryData]);\n\n useEffect(() => {\n if (!ready || !entryData || !messengerRef.current) {\n return;\n }\n\n const current = JSON.parse(JSON.stringify(entryData));\n const previous = lastSentDataRef.current;\n\n if (previous) {\n const patch = jsonPatch.compare(previous, current);\n if (patch.length > 0) {\n messengerRef.current.send(\"document.patch\", patch);\n }\n } else {\n messengerRef.current.send(\"document.set\", current);\n }\n\n lastSentDataRef.current = current;\n }, [ready, entryData]);\n\n const reload = useCallback(() => {\n setLoading(true);\n setReady(false);\n lastSentDataRef.current = null;\n setIframeKey(prev => prev + 1);\n }, []);\n\n const getDraftUrl = useCallback(() => {\n const url = new URL(displayUrl);\n url.searchParams.set(\"wb.type\", \"entry\");\n url.searchParams.set(\"wb.path\", url.pathname);\n url.searchParams.set(\"wb.preview\", \"true\");\n url.searchParams.set(\"wb.id\", entryId);\n return url.toString();\n }, [displayUrl, entryId]);\n\n const confirmCopy = useCallback(() => {\n showToast({ title: \"Preview link copied to clipboard!\" });\n }, []);\n\n const openInNewTab = useCallback(() => {\n window.open(getDraftUrl(), \"_blank\");\n }, [getDraftUrl]);\n\n return (\n <div className=\"relative border border-neutral-dimmed rounded-t-lg flex flex-col flex-1 h-full overflow-hidden\">\n <div className=\"flex flex-row items-center gap-sm p-sm bg-neutral-base border-b-sm border-neutral-dimmed\">\n <div className=\"relative flex-auto min-w-0 h-[32px]\">\n <div className=\"w-full absolute -top-px py-xs-plus pl-xl pr-[112px] border-sm text-md truncate cursor-not-allowed rounded-md border-neutral-subtle bg-neutral-disabled text-neutral-disabled\">\n <PreviewDomainMenu className={\"absolute left-0 top-0\"} />\n {displayUrl}\n </div>\n <div className=\"absolute right-0 top-0 flex\">\n <Tooltip\n content={<Text size=\"md\">Refresh preview</Text>}\n side=\"bottom\"\n trigger={\n <IconButton\n icon={<RefreshIcon />}\n size=\"md\"\n onClick={reload}\n variant={\"ghost\"}\n />\n }\n />\n <Tooltip\n content={<Text size=\"md\">Copy preview link</Text>}\n side=\"bottom\"\n trigger={\n <CopyButton\n size=\"md\"\n value={getDraftUrl()}\n onCopy={confirmCopy}\n variant={\"ghost\"}\n />\n }\n />\n <Tooltip\n content={<Text size=\"md\">Preview entry in a new tab</Text>}\n side=\"bottom\"\n trigger={\n <IconButton\n icon={<OpenInNewIcon />}\n size=\"md\"\n onClick={openInNewTab}\n variant={\"ghost\"}\n />\n }\n />\n </div>\n </div>\n <div className=\"shrink-0\">\n <SegmentedControl\n value={viewport}\n onChange={(value: string) => setViewport(value as ViewportMode)}\n items={[\n { value: \"desktop\", label: \"\", icon: <LaptopIcon /> },\n { value: \"mobile\", label: \"\", icon: <SmartphoneIcon /> }\n ]}\n />\n </div>\n </div>\n\n {settingsDomain ? (\n <Alert\n type={\"warning\"}\n variant={\"subtle\"}\n className={\"rounded-none\"}\n actions={<Alert.Action text={\"Use Frontend Domain\"} onClick={onResetDomain} />}\n >\n Previewing on your custom domain {domain} instead of the Frontend Domain{\" \"}\n {settingsDomain}.\n </Alert>\n ) : null}\n\n {loading && timedOut ? (\n <Alert\n type={\"warning\"}\n variant={\"subtle\"}\n className={\"rounded-none\"}\n actions={<Alert.Action text={\"Reload\"} onClick={reload} />}\n >\n {new URL(iframeSrc).origin} is not responding to the live preview. Check that\n the frontend app is running at that address, that it initializes the Webiny SDK\n in editing mode, and that it allows this admin app as a referrer.\n </Alert>\n ) : null}\n\n <div className=\"relative block box-border h-full w-full overflow-auto fill-grid\">\n {loading && !timedOut ? (\n <OverlayLoader text=\"Connecting to Live Preview...\" />\n ) : null}\n <div\n className={`mx-auto h-full transition-all duration-300 ${viewport === \"mobile\" ? \"p-md\" : \"\"}`}\n style={{ width: viewport === \"mobile\" ? \"375px\" : \"100%\" }}\n >\n <iframe\n key={iframeKey}\n ref={iframeRef}\n src={iframeSrc}\n width=\"100%\"\n height=\"100%\"\n className=\"h-full\"\n sandbox=\"allow-same-origin allow-scripts allow-forms\"\n />\n </div>\n </div>\n </div>\n );\n};\n"],"names":["READY_TIMEOUT_MS","PreviewPane","domain","settingsDomain","onResetDomain","previewPath","entryId","entryData","urlEntryData","iframeRef","useRef","messengerRef","entryDataRef","lastSentDataRef","ready","setReady","useState","loading","setLoading","timedOut","setTimedOut","iframeKey","setIframeKey","viewport","setViewport","presenter","useLivePreviewPresenter","showToast","useToast","displayUrl","buildDisplayUrl","iframeSrc","editorPath","buildEditorUrl","url","URL","window","sendEntryData","useCallback","data","serialized","JSON","useEffect","iframe","targetOrigin","editorOrigin","MessageOrigin","previewTarget","messenger","Messenger","timeout","setTimeout","clearTimeout","manifest","current","previous","patch","jsonPatch","reload","prev","getDraftUrl","confirmCopy","openInNewTab","PreviewDomainMenu","Tooltip","Text","IconButton","RefreshIcon","CopyButton","OpenInNewIcon","SegmentedControl","value","LaptopIcon","SmartphoneIcon","Alert","OverlayLoader"],"mappings":"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"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { CmsReferenceValue } from "../../../features/contentEntry/refTypes.js";
|
|
2
|
+
import type { RefValuesMap } from "./resolvePreviewUrl.js";
|
|
1
3
|
export interface PreviewComponent {
|
|
2
4
|
name: string;
|
|
3
5
|
label: string;
|
|
@@ -6,9 +8,15 @@ export interface PreviewComponent {
|
|
|
6
8
|
export interface ILivePreviewPresenter {
|
|
7
9
|
vm: {
|
|
8
10
|
components: PreviewComponent[];
|
|
11
|
+
refValues: RefValuesMap;
|
|
9
12
|
};
|
|
10
13
|
addComponent(component: PreviewComponent): void;
|
|
11
14
|
clearComponents(): void;
|
|
15
|
+
/**
|
|
16
|
+
* Loads referenced entries that the preview path reads through, e.g. `{values.location.slug}`.
|
|
17
|
+
* Each ref is requested once; results land in `vm.refValues`.
|
|
18
|
+
*/
|
|
19
|
+
loadRefValues(refs: CmsReferenceValue[]): void;
|
|
12
20
|
}
|
|
13
21
|
export declare const LivePreviewPresenter: import("@webiny/di").Abstraction<ILivePreviewPresenter>;
|
|
14
22
|
export declare namespace LivePreviewPresenter {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presentation/contentEntries/preview/abstractions.js","sources":["../../../../src/presentation/contentEntries/preview/abstractions.ts"],"sourcesContent":["import { createAbstraction } from \"@webiny/feature/admin\";\n\nexport interface PreviewComponent {\n name: string;\n label: string;\n description: string;\n}\n\nexport interface ILivePreviewPresenter {\n vm: {\n components: PreviewComponent[];\n };\n addComponent(component: PreviewComponent): void;\n clearComponents(): void;\n}\n\nexport const LivePreviewPresenter = createAbstraction<ILivePreviewPresenter>(\n \"CmsContentEntries/LivePreviewPresenter\"\n);\n\nexport namespace LivePreviewPresenter {\n export type Interface = ILivePreviewPresenter;\n}\n"],"names":["LivePreviewPresenter","createAbstraction"],"mappings":";
|
|
1
|
+
{"version":3,"file":"presentation/contentEntries/preview/abstractions.js","sources":["../../../../src/presentation/contentEntries/preview/abstractions.ts"],"sourcesContent":["import { createAbstraction } from \"@webiny/feature/admin\";\nimport type { CmsReferenceValue } from \"~/features/contentEntry/refTypes.js\";\nimport type { RefValuesMap } from \"./resolvePreviewUrl.js\";\n\nexport interface PreviewComponent {\n name: string;\n label: string;\n description: string;\n}\n\nexport interface ILivePreviewPresenter {\n vm: {\n components: PreviewComponent[];\n refValues: RefValuesMap;\n };\n addComponent(component: PreviewComponent): void;\n clearComponents(): void;\n /**\n * Loads referenced entries that the preview path reads through, e.g. `{values.location.slug}`.\n * Each ref is requested once; results land in `vm.refValues`.\n */\n loadRefValues(refs: CmsReferenceValue[]): void;\n}\n\nexport const LivePreviewPresenter = createAbstraction<ILivePreviewPresenter>(\n \"CmsContentEntries/LivePreviewPresenter\"\n);\n\nexport namespace LivePreviewPresenter {\n export type Interface = ILivePreviewPresenter;\n}\n"],"names":["LivePreviewPresenter","createAbstraction"],"mappings":";AAwBO,MAAMA,uBAAuBC,kBAChC"}
|
|
@@ -1,4 +1,25 @@
|
|
|
1
|
+
import type { CmsModelField } from "../../../types.js";
|
|
2
|
+
import type { CmsReferenceValue } from "../../../features/contentEntry/refTypes.js";
|
|
1
3
|
type EntryData = Record<string, unknown>;
|
|
4
|
+
/**
|
|
5
|
+
* Values of loaded referenced entries, keyed by the ref `id`.
|
|
6
|
+
* `null` marks a ref that failed to load, so it isn't requested again.
|
|
7
|
+
*/
|
|
8
|
+
export type RefValuesMap = Record<string, Record<string, unknown> | null>;
|
|
9
|
+
/**
|
|
10
|
+
* Returns the ids of ref fields that the pattern reads through,
|
|
11
|
+
* e.g. `location` for `/{values.location.slug}/{values.slug}`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getPatternRefFieldIds(pattern: string, fields: Pick<CmsModelField, "fieldId" | "type">[]): string[];
|
|
14
|
+
/**
|
|
15
|
+
* Collects the ref values held by the given fields, for both single and multiple-value fields.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getRefValues(values: EntryData, refFieldIds: string[]): CmsReferenceValue[];
|
|
18
|
+
/**
|
|
19
|
+
* Spreads loaded referenced entry values into the ref values, so `{values.location.slug}`
|
|
20
|
+
* resolves against the referenced entry. Refs that aren't loaded yet are left as they are.
|
|
21
|
+
*/
|
|
22
|
+
export declare function withRefValues(entry: EntryData, refFieldIds: string[], refValues: RefValuesMap): EntryData;
|
|
2
23
|
export declare function resolveSlugPattern(pattern: string, entry: EntryData): string;
|
|
3
24
|
export declare function buildEditorUrl(domain: string, previewPath: string): string;
|
|
4
25
|
export declare function buildDisplayUrl(domain: string, path: string, entry: EntryData): string;
|
|
@@ -1,3 +1,52 @@
|
|
|
1
|
+
function isRefValue(value) {
|
|
2
|
+
if (!value || "object" != typeof value) return false;
|
|
3
|
+
const ref = value;
|
|
4
|
+
return "string" == typeof ref.id && "string" == typeof ref.modelId;
|
|
5
|
+
}
|
|
6
|
+
function getPatternRefFieldIds(pattern, fields) {
|
|
7
|
+
const refFieldIds = new Set(fields.filter((f)=>"ref" === f.type).map((f)=>f.fieldId));
|
|
8
|
+
const result = new Set();
|
|
9
|
+
for (const [, path] of pattern.matchAll(/\{([^}]+)\}/g)){
|
|
10
|
+
const [root, fieldId, ...rest] = path.split(".");
|
|
11
|
+
if ("values" === root && rest.length > 0 && refFieldIds.has(fieldId)) result.add(fieldId);
|
|
12
|
+
}
|
|
13
|
+
return [
|
|
14
|
+
...result
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
function getRefValues(values, refFieldIds) {
|
|
18
|
+
const refs = [];
|
|
19
|
+
for (const fieldId of refFieldIds){
|
|
20
|
+
const value = values[fieldId];
|
|
21
|
+
const items = Array.isArray(value) ? value : [
|
|
22
|
+
value
|
|
23
|
+
];
|
|
24
|
+
refs.push(...items.filter(isRefValue));
|
|
25
|
+
}
|
|
26
|
+
return refs;
|
|
27
|
+
}
|
|
28
|
+
function withRefValues(entry, refFieldIds, refValues) {
|
|
29
|
+
const values = entry.values;
|
|
30
|
+
if (!values || 0 === refFieldIds.length) return entry;
|
|
31
|
+
const expand = (value)=>{
|
|
32
|
+
if (!isRefValue(value) || !refValues[value.id]) return value;
|
|
33
|
+
return {
|
|
34
|
+
...refValues[value.id],
|
|
35
|
+
...value
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
const expanded = {
|
|
39
|
+
...values
|
|
40
|
+
};
|
|
41
|
+
for (const fieldId of refFieldIds){
|
|
42
|
+
const value = values[fieldId];
|
|
43
|
+
expanded[fieldId] = Array.isArray(value) ? value.map(expand) : expand(value);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
...entry,
|
|
47
|
+
values: expanded
|
|
48
|
+
};
|
|
49
|
+
}
|
|
1
50
|
function getNestedValue(obj, path) {
|
|
2
51
|
const parts = path.split(".");
|
|
3
52
|
let current = obj;
|
|
@@ -27,6 +76,6 @@ function buildDisplayUrl(domain, path, entry) {
|
|
|
27
76
|
const normalizedPath = resolvedPath.startsWith("/") ? resolvedPath : `/${resolvedPath}`;
|
|
28
77
|
return `${base}${normalizedPath}`;
|
|
29
78
|
}
|
|
30
|
-
export { buildDisplayUrl, buildEditorUrl, resolveSlugPattern };
|
|
79
|
+
export { buildDisplayUrl, buildEditorUrl, getPatternRefFieldIds, getRefValues, resolveSlugPattern, withRefValues };
|
|
31
80
|
|
|
32
81
|
//# sourceMappingURL=resolvePreviewUrl.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presentation/contentEntries/preview/resolvePreviewUrl.js","sources":["../../../../src/presentation/contentEntries/preview/resolvePreviewUrl.ts"],"sourcesContent":["type EntryData = Record<string, unknown>;\n\nfunction getNestedValue(obj: EntryData, path: string): unknown {\n const parts = path.split(\".\");\n let current: unknown = obj;\n for (const part of parts) {\n if (current === null || current === undefined || typeof current !== \"object\") {\n return undefined;\n }\n current = (current as Record<string, unknown>)[part];\n }\n return current;\n}\n\nexport function resolveSlugPattern(pattern: string, entry: EntryData): string {\n return pattern.replace(/\\{([^}]+)\\}/g, (_match, path: string) => {\n const value = getNestedValue(entry, path);\n if (value !== null && value !== undefined && value !== \"\") {\n return String(value);\n }\n return \"new\";\n });\n}\n\nexport function buildEditorUrl(domain: string, previewPath: string): string {\n const base = domain.endsWith(\"/\") ? domain.slice(0, -1) : domain;\n const bracketIndex = previewPath.indexOf(\"{\");\n const staticPrefix = bracketIndex >= 0 ? previewPath.substring(0, bracketIndex) : previewPath;\n const trimmed = staticPrefix.endsWith(\"/\") ? staticPrefix.slice(0, -1) : staticPrefix;\n return `${base}${trimmed}/preview`;\n}\n\nexport function buildDisplayUrl(domain: string, path: string, entry: EntryData): string {\n const base = domain.endsWith(\"/\") ? domain.slice(0, -1) : domain;\n const resolvedPath = resolveSlugPattern(path, entry);\n const normalizedPath = resolvedPath.startsWith(\"/\") ? resolvedPath : `/${resolvedPath}`;\n return `${base}${normalizedPath}`;\n}\n"],"names":["
|
|
1
|
+
{"version":3,"file":"presentation/contentEntries/preview/resolvePreviewUrl.js","sources":["../../../../src/presentation/contentEntries/preview/resolvePreviewUrl.ts"],"sourcesContent":["import type { CmsModelField } from \"~/types.js\";\nimport type { CmsReferenceValue } from \"~/features/contentEntry/refTypes.js\";\n\ntype EntryData = Record<string, unknown>;\n\n/**\n * Values of loaded referenced entries, keyed by the ref `id`.\n * `null` marks a ref that failed to load, so it isn't requested again.\n */\nexport type RefValuesMap = Record<string, Record<string, unknown> | null>;\n\nfunction isRefValue(value: unknown): value is CmsReferenceValue {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const ref = value as Partial<CmsReferenceValue>;\n return typeof ref.id === \"string\" && typeof ref.modelId === \"string\";\n}\n\n/**\n * Returns the ids of ref fields that the pattern reads through,\n * e.g. `location` for `/{values.location.slug}/{values.slug}`.\n */\nexport function getPatternRefFieldIds(\n pattern: string,\n fields: Pick<CmsModelField, \"fieldId\" | \"type\">[]\n): string[] {\n const refFieldIds = new Set(fields.filter(f => f.type === \"ref\").map(f => f.fieldId));\n const result = new Set<string>();\n for (const [, path] of pattern.matchAll(/\\{([^}]+)\\}/g)) {\n const [root, fieldId, ...rest] = path.split(\".\");\n if (root === \"values\" && rest.length > 0 && refFieldIds.has(fieldId)) {\n result.add(fieldId);\n }\n }\n return [...result];\n}\n\n/**\n * Collects the ref values held by the given fields, for both single and multiple-value fields.\n */\nexport function getRefValues(values: EntryData, refFieldIds: string[]): CmsReferenceValue[] {\n const refs: CmsReferenceValue[] = [];\n for (const fieldId of refFieldIds) {\n const value = values[fieldId];\n const items = Array.isArray(value) ? value : [value];\n refs.push(...items.filter(isRefValue));\n }\n return refs;\n}\n\n/**\n * Spreads loaded referenced entry values into the ref values, so `{values.location.slug}`\n * resolves against the referenced entry. Refs that aren't loaded yet are left as they are.\n */\nexport function withRefValues(\n entry: EntryData,\n refFieldIds: string[],\n refValues: RefValuesMap\n): EntryData {\n const values = entry.values as EntryData | undefined;\n if (!values || refFieldIds.length === 0) {\n return entry;\n }\n\n const expand = (value: unknown) => {\n if (!isRefValue(value) || !refValues[value.id]) {\n return value;\n }\n return { ...refValues[value.id], ...value };\n };\n\n const expanded = { ...values };\n for (const fieldId of refFieldIds) {\n const value = values[fieldId];\n expanded[fieldId] = Array.isArray(value) ? value.map(expand) : expand(value);\n }\n return { ...entry, values: expanded };\n}\n\nfunction getNestedValue(obj: EntryData, path: string): unknown {\n const parts = path.split(\".\");\n let current: unknown = obj;\n for (const part of parts) {\n if (current === null || current === undefined || typeof current !== \"object\") {\n return undefined;\n }\n current = (current as Record<string, unknown>)[part];\n }\n return current;\n}\n\nexport function resolveSlugPattern(pattern: string, entry: EntryData): string {\n return pattern.replace(/\\{([^}]+)\\}/g, (_match, path: string) => {\n const value = getNestedValue(entry, path);\n if (value !== null && value !== undefined && value !== \"\") {\n return String(value);\n }\n return \"new\";\n });\n}\n\nexport function buildEditorUrl(domain: string, previewPath: string): string {\n const base = domain.endsWith(\"/\") ? domain.slice(0, -1) : domain;\n const bracketIndex = previewPath.indexOf(\"{\");\n const staticPrefix = bracketIndex >= 0 ? previewPath.substring(0, bracketIndex) : previewPath;\n const trimmed = staticPrefix.endsWith(\"/\") ? staticPrefix.slice(0, -1) : staticPrefix;\n return `${base}${trimmed}/preview`;\n}\n\nexport function buildDisplayUrl(domain: string, path: string, entry: EntryData): string {\n const base = domain.endsWith(\"/\") ? domain.slice(0, -1) : domain;\n const resolvedPath = resolveSlugPattern(path, entry);\n const normalizedPath = resolvedPath.startsWith(\"/\") ? resolvedPath : `/${resolvedPath}`;\n return `${base}${normalizedPath}`;\n}\n"],"names":["isRefValue","value","ref","getPatternRefFieldIds","pattern","fields","refFieldIds","Set","f","result","path","root","fieldId","rest","getRefValues","values","refs","items","Array","withRefValues","entry","refValues","expand","expanded","getNestedValue","obj","parts","current","part","resolveSlugPattern","_match","String","buildEditorUrl","domain","previewPath","base","bracketIndex","staticPrefix","trimmed","buildDisplayUrl","resolvedPath","normalizedPath"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildDisplayUrl, buildEditorUrl, getPatternRefFieldIds, getRefValues, resolveSlugPattern, withRefValues } from "./resolvePreviewUrl.js";
|
|
3
|
+
const fields = [
|
|
4
|
+
{
|
|
5
|
+
fieldId: "slug",
|
|
6
|
+
type: "text"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
fieldId: "location",
|
|
10
|
+
type: "ref"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
fieldId: "tags",
|
|
14
|
+
type: "ref"
|
|
15
|
+
}
|
|
16
|
+
];
|
|
17
|
+
const resolvePreviewUrl_test_location = {
|
|
18
|
+
id: "loc1#0001",
|
|
19
|
+
modelId: "location"
|
|
20
|
+
};
|
|
21
|
+
describe("resolvePreviewUrl", ()=>{
|
|
22
|
+
it("should resolve plain values and fall back to `new` for missing ones", ()=>{
|
|
23
|
+
expect(resolveSlugPattern("/articles/{values.slug}", {
|
|
24
|
+
values: {
|
|
25
|
+
slug: "hello"
|
|
26
|
+
}
|
|
27
|
+
})).toBe("/articles/hello");
|
|
28
|
+
expect(resolveSlugPattern("/articles/{values.slug}", {
|
|
29
|
+
values: {}
|
|
30
|
+
})).toBe("/articles/new");
|
|
31
|
+
});
|
|
32
|
+
it("should build the iframe url from the static prefix only", ()=>{
|
|
33
|
+
expect(buildEditorUrl("https://site.com/", "/articles/{values.slug}")).toBe("https://site.com/articles/preview");
|
|
34
|
+
expect(buildEditorUrl("https://site.com", "/{values.location.slug}/{values.slug}")).toBe("https://site.com/preview");
|
|
35
|
+
});
|
|
36
|
+
it("should find ref fields the pattern reads through", ()=>{
|
|
37
|
+
expect(getPatternRefFieldIds("/{values.location.slug}/{values.slug}/{values.tags}", fields)).toEqual([
|
|
38
|
+
"location"
|
|
39
|
+
]);
|
|
40
|
+
expect(getPatternRefFieldIds("/articles/{values.slug}", fields)).toEqual([]);
|
|
41
|
+
});
|
|
42
|
+
it("should collect ref values from single and multiple-value fields", ()=>{
|
|
43
|
+
const tag = {
|
|
44
|
+
id: "tag1#0001",
|
|
45
|
+
modelId: "tag"
|
|
46
|
+
};
|
|
47
|
+
expect(getRefValues({
|
|
48
|
+
location: resolvePreviewUrl_test_location,
|
|
49
|
+
tags: [
|
|
50
|
+
tag,
|
|
51
|
+
null
|
|
52
|
+
],
|
|
53
|
+
slug: "x"
|
|
54
|
+
}, [
|
|
55
|
+
"location",
|
|
56
|
+
"tags"
|
|
57
|
+
])).toEqual([
|
|
58
|
+
resolvePreviewUrl_test_location,
|
|
59
|
+
tag
|
|
60
|
+
]);
|
|
61
|
+
expect(getRefValues({
|
|
62
|
+
location: null
|
|
63
|
+
}, [
|
|
64
|
+
"location"
|
|
65
|
+
])).toEqual([]);
|
|
66
|
+
});
|
|
67
|
+
it("should resolve a ref field value once the referenced entry is loaded", ()=>{
|
|
68
|
+
const entry = {
|
|
69
|
+
id: "page1#0001",
|
|
70
|
+
values: {
|
|
71
|
+
slug: "climbing",
|
|
72
|
+
location: resolvePreviewUrl_test_location
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const pattern = "/{values.location.slug}/{values.slug}";
|
|
76
|
+
const notLoaded = withRefValues(entry, [
|
|
77
|
+
"location"
|
|
78
|
+
], {});
|
|
79
|
+
expect(buildDisplayUrl("https://site.com", pattern, notLoaded)).toBe("https://site.com/new/climbing");
|
|
80
|
+
const loaded = withRefValues(entry, [
|
|
81
|
+
"location"
|
|
82
|
+
], {
|
|
83
|
+
[resolvePreviewUrl_test_location.id]: {
|
|
84
|
+
slug: "baker",
|
|
85
|
+
id: "should-not-win"
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
expect(buildDisplayUrl("https://site.com", pattern, loaded)).toBe("https://site.com/baker/climbing");
|
|
89
|
+
expect(loaded.values.location.id).toBe(resolvePreviewUrl_test_location.id);
|
|
90
|
+
});
|
|
91
|
+
it("should support indexing into multiple-value ref fields", ()=>{
|
|
92
|
+
const tag = {
|
|
93
|
+
id: "tag1#0001",
|
|
94
|
+
modelId: "tag"
|
|
95
|
+
};
|
|
96
|
+
const entry = {
|
|
97
|
+
values: {
|
|
98
|
+
tags: [
|
|
99
|
+
tag
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const resolved = withRefValues(entry, [
|
|
104
|
+
"tags"
|
|
105
|
+
], {
|
|
106
|
+
[tag.id]: {
|
|
107
|
+
slug: "news"
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
expect(resolveSlugPattern("/{values.tags.0.slug}", resolved)).toBe("/news");
|
|
111
|
+
});
|
|
112
|
+
it("should leave the entry untouched when there are no ref fields to fill in", ()=>{
|
|
113
|
+
const entry = {
|
|
114
|
+
values: {
|
|
115
|
+
slug: "x",
|
|
116
|
+
location: resolvePreviewUrl_test_location
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
expect(withRefValues(entry, [], {
|
|
120
|
+
[resolvePreviewUrl_test_location.id]: {
|
|
121
|
+
slug: "baker"
|
|
122
|
+
}
|
|
123
|
+
})).toBe(entry);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
//# sourceMappingURL=resolvePreviewUrl.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presentation/contentEntries/preview/resolvePreviewUrl.test.js","sources":["../../../../src/presentation/contentEntries/preview/resolvePreviewUrl.test.ts"],"sourcesContent":["import { describe, it, expect } from \"vitest\";\nimport {\n buildDisplayUrl,\n buildEditorUrl,\n getPatternRefFieldIds,\n getRefValues,\n resolveSlugPattern,\n withRefValues\n} from \"./resolvePreviewUrl.js\";\n\nconst fields = [\n { fieldId: \"slug\", type: \"text\" },\n { fieldId: \"location\", type: \"ref\" },\n { fieldId: \"tags\", type: \"ref\" }\n];\n\nconst location = { id: \"loc1#0001\", modelId: \"location\" };\n\ndescribe(\"resolvePreviewUrl\", () => {\n it(\"should resolve plain values and fall back to `new` for missing ones\", () => {\n expect(resolveSlugPattern(\"/articles/{values.slug}\", { values: { slug: \"hello\" } })).toBe(\n \"/articles/hello\"\n );\n expect(resolveSlugPattern(\"/articles/{values.slug}\", { values: {} })).toBe(\"/articles/new\");\n });\n\n it(\"should build the iframe url from the static prefix only\", () => {\n expect(buildEditorUrl(\"https://site.com/\", \"/articles/{values.slug}\")).toBe(\n \"https://site.com/articles/preview\"\n );\n expect(buildEditorUrl(\"https://site.com\", \"/{values.location.slug}/{values.slug}\")).toBe(\n \"https://site.com/preview\"\n );\n });\n\n it(\"should find ref fields the pattern reads through\", () => {\n expect(\n getPatternRefFieldIds(\"/{values.location.slug}/{values.slug}/{values.tags}\", fields)\n ).toEqual([\"location\"]);\n expect(getPatternRefFieldIds(\"/articles/{values.slug}\", fields)).toEqual([]);\n });\n\n it(\"should collect ref values from single and multiple-value fields\", () => {\n const tag = { id: \"tag1#0001\", modelId: \"tag\" };\n expect(\n getRefValues({ location, tags: [tag, null], slug: \"x\" }, [\"location\", \"tags\"])\n ).toEqual([location, tag]);\n expect(getRefValues({ location: null }, [\"location\"])).toEqual([]);\n });\n\n it(\"should resolve a ref field value once the referenced entry is loaded\", () => {\n const entry = { id: \"page1#0001\", values: { slug: \"climbing\", location } };\n const pattern = \"/{values.location.slug}/{values.slug}\";\n\n const notLoaded = withRefValues(entry, [\"location\"], {});\n expect(buildDisplayUrl(\"https://site.com\", pattern, notLoaded)).toBe(\n \"https://site.com/new/climbing\"\n );\n\n const loaded = withRefValues(entry, [\"location\"], {\n [location.id]: { slug: \"baker\", id: \"should-not-win\" }\n });\n expect(buildDisplayUrl(\"https://site.com\", pattern, loaded)).toBe(\n \"https://site.com/baker/climbing\"\n );\n expect((loaded.values as any).location.id).toBe(location.id);\n });\n\n it(\"should support indexing into multiple-value ref fields\", () => {\n const tag = { id: \"tag1#0001\", modelId: \"tag\" };\n const entry = { values: { tags: [tag] } };\n const resolved = withRefValues(entry, [\"tags\"], { [tag.id]: { slug: \"news\" } });\n expect(resolveSlugPattern(\"/{values.tags.0.slug}\", resolved)).toBe(\"/news\");\n });\n\n it(\"should leave the entry untouched when there are no ref fields to fill in\", () => {\n const entry = { values: { slug: \"x\", location } };\n expect(withRefValues(entry, [], { [location.id]: { slug: \"baker\" } })).toBe(entry);\n });\n});\n"],"names":["fields","location","describe","it","expect","resolveSlugPattern","buildEditorUrl","getPatternRefFieldIds","tag","getRefValues","entry","pattern","notLoaded","withRefValues","buildDisplayUrl","loaded","resolved"],"mappings":";;AAUA,MAAMA,SAAS;IACX;QAAE,SAAS;QAAQ,MAAM;IAAO;IAChC;QAAE,SAAS;QAAY,MAAM;IAAM;IACnC;QAAE,SAAS;QAAQ,MAAM;IAAM;CAClC;AAED,MAAMC,kCAAW;IAAE,IAAI;IAAa,SAAS;AAAW;AAExDC,SAAS,qBAAqB;IAC1BC,GAAG,uEAAuE;QACtEC,OAAOC,mBAAmB,2BAA2B;YAAE,QAAQ;gBAAE,MAAM;YAAQ;QAAE,IAAI,IAAI,CACrF;QAEJD,OAAOC,mBAAmB,2BAA2B;YAAE,QAAQ,CAAC;QAAE,IAAI,IAAI,CAAC;IAC/E;IAEAF,GAAG,2DAA2D;QAC1DC,OAAOE,eAAe,qBAAqB,4BAA4B,IAAI,CACvE;QAEJF,OAAOE,eAAe,oBAAoB,0CAA0C,IAAI,CACpF;IAER;IAEAH,GAAG,oDAAoD;QACnDC,OACIG,sBAAsB,uDAAuDP,SAC/E,OAAO,CAAC;YAAC;SAAW;QACtBI,OAAOG,sBAAsB,2BAA2BP,SAAS,OAAO,CAAC,EAAE;IAC/E;IAEAG,GAAG,mEAAmE;QAClE,MAAMK,MAAM;YAAE,IAAI;YAAa,SAAS;QAAM;QAC9CJ,OACIK,aAAa;YAAER,UAAAA;YAAU,MAAM;gBAACO;gBAAK;aAAK;YAAE,MAAM;QAAI,GAAG;YAAC;YAAY;SAAO,GAC/E,OAAO,CAAC;YAACP;YAAUO;SAAI;QACzBJ,OAAOK,aAAa;YAAE,UAAU;QAAK,GAAG;YAAC;SAAW,GAAG,OAAO,CAAC,EAAE;IACrE;IAEAN,GAAG,wEAAwE;QACvE,MAAMO,QAAQ;YAAE,IAAI;YAAc,QAAQ;gBAAE,MAAM;gBAAYT,UAAAA;YAAS;QAAE;QACzE,MAAMU,UAAU;QAEhB,MAAMC,YAAYC,cAAcH,OAAO;YAAC;SAAW,EAAE,CAAC;QACtDN,OAAOU,gBAAgB,oBAAoBH,SAASC,YAAY,IAAI,CAChE;QAGJ,MAAMG,SAASF,cAAcH,OAAO;YAAC;SAAW,EAAE;YAC9C,CAACT,gCAAS,EAAE,CAAC,EAAE;gBAAE,MAAM;gBAAS,IAAI;YAAiB;QACzD;QACAG,OAAOU,gBAAgB,oBAAoBH,SAASI,SAAS,IAAI,CAC7D;QAEJX,OAAQW,OAAO,MAAM,CAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAACd,gCAAS,EAAE;IAC/D;IAEAE,GAAG,0DAA0D;QACzD,MAAMK,MAAM;YAAE,IAAI;YAAa,SAAS;QAAM;QAC9C,MAAME,QAAQ;YAAE,QAAQ;gBAAE,MAAM;oBAACF;iBAAI;YAAC;QAAE;QACxC,MAAMQ,WAAWH,cAAcH,OAAO;YAAC;SAAO,EAAE;YAAE,CAACF,IAAI,EAAE,CAAC,EAAE;gBAAE,MAAM;YAAO;QAAE;QAC7EJ,OAAOC,mBAAmB,yBAAyBW,WAAW,IAAI,CAAC;IACvE;IAEAb,GAAG,4EAA4E;QAC3E,MAAMO,QAAQ;YAAE,QAAQ;gBAAE,MAAM;gBAAKT,UAAAA;YAAS;QAAE;QAChDG,OAAOS,cAAcH,OAAO,EAAE,EAAE;YAAE,CAACT,gCAAS,EAAE,CAAC,EAAE;gBAAE,MAAM;YAAQ;QAAE,IAAI,IAAI,CAACS;IAChF;AACJ"}
|
|
@@ -4,6 +4,7 @@ import { GetSingletonEntryUseCase, UpdateSingletonEntryUseCase } from "../../../
|
|
|
4
4
|
import { CmsFormModelBuilder } from "../../../features/formModel/abstractions.js";
|
|
5
5
|
import { CmsModelContext } from "../../../features/contentEntry/abstractions.js";
|
|
6
6
|
import { SingleEntryPresenter } from "./abstractions.js";
|
|
7
|
+
import { toFormErrors } from "../form/toFormErrors.js";
|
|
7
8
|
class SingleEntryPresenterImpl {
|
|
8
9
|
constructor(formModelFactory, cmsFormModelBuilder, modelAccessor, getSingletonEntryUseCase, updateSingletonEntryUseCase){
|
|
9
10
|
this.formModelFactory = formModelFactory;
|
|
@@ -37,7 +38,9 @@ class SingleEntryPresenterImpl {
|
|
|
37
38
|
}
|
|
38
39
|
async save() {
|
|
39
40
|
if (!this.form) return false;
|
|
40
|
-
const
|
|
41
|
+
const form = this.form;
|
|
42
|
+
const model = this.model;
|
|
43
|
+
const data = await form.submit();
|
|
41
44
|
if (!data) return false;
|
|
42
45
|
this.loading = "Saving...";
|
|
43
46
|
try {
|
|
@@ -48,12 +51,14 @@ class SingleEntryPresenterImpl {
|
|
|
48
51
|
}
|
|
49
52
|
});
|
|
50
53
|
runInAction(()=>{
|
|
54
|
+
if (this.form !== form) return;
|
|
51
55
|
this.entry = entry;
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
form.setData(entry.values);
|
|
57
|
+
form.reset();
|
|
54
58
|
});
|
|
55
59
|
return true;
|
|
56
|
-
} catch
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (this.form === form) form.setErrors(toFormErrors(error, model));
|
|
57
62
|
return false;
|
|
58
63
|
} finally{
|
|
59
64
|
runInAction(()=>{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presentation/contentEntries/singleEntry/SingleEntryPresenter.js","sources":["../../../../src/presentation/contentEntries/singleEntry/SingleEntryPresenter.ts"],"sourcesContent":["import { computed, makeAutoObservable, runInAction } from \"mobx\";\nimport type { IFormModel } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport type { CmsContentEntry } from \"~/types.js\";\nimport { GetSingletonEntryUseCase } from \"~/features/contentEntry/singletonEntry/abstractions.js\";\nimport { UpdateSingletonEntryUseCase } from \"~/features/contentEntry/singletonEntry/abstractions.js\";\nimport { CmsFormModelBuilder } from \"~/features/formModel/abstractions.js\";\nimport { CmsModelContext } from \"~/features/contentEntry/abstractions.js\";\nimport {\n SingleEntryPresenter as Abstraction,\n type ISingleEntryPresenter,\n type ISingleEntryViewModel\n} from \"./abstractions.js\";\n\nclass SingleEntryPresenterImpl implements ISingleEntryPresenter {\n private entry: CmsContentEntry | null = null;\n private form: IFormModel | null = null;\n private loading: string | null = null;\n\n constructor(\n private formModelFactory: FormModelFactory.Interface,\n private cmsFormModelBuilder: CmsFormModelBuilder.Interface,\n private modelAccessor: CmsModelContext.Interface,\n private getSingletonEntryUseCase: GetSingletonEntryUseCase.Interface,\n private updateSingletonEntryUseCase: UpdateSingletonEntryUseCase.Interface\n ) {\n makeAutoObservable<\n SingleEntryPresenterImpl,\n | \"formModelFactory\"\n | \"cmsFormModelBuilder\"\n | \"modelAccessor\"\n | \"getSingletonEntryUseCase\"\n | \"updateSingletonEntryUseCase\"\n >(this, {\n formModelFactory: false,\n cmsFormModelBuilder: false,\n modelAccessor: false,\n getSingletonEntryUseCase: false,\n updateSingletonEntryUseCase: false,\n vm: computed\n });\n }\n\n private get model() {\n return this.modelAccessor.getModel();\n }\n\n get vm(): ISingleEntryViewModel {\n return {\n loading: this.loading,\n entry: this.entry,\n form: this.form?.vm ?? null,\n canSave: this.form !== null,\n isDirty: this.form?.isDirty ?? false\n };\n }\n\n async save(): Promise<boolean> {\n if (!this.form) {\n return false;\n }\n\n const data = await
|
|
1
|
+
{"version":3,"file":"presentation/contentEntries/singleEntry/SingleEntryPresenter.js","sources":["../../../../src/presentation/contentEntries/singleEntry/SingleEntryPresenter.ts"],"sourcesContent":["import { computed, makeAutoObservable, runInAction } from \"mobx\";\nimport type { IFormModel } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport type { CmsContentEntry } from \"~/types.js\";\nimport { GetSingletonEntryUseCase } from \"~/features/contentEntry/singletonEntry/abstractions.js\";\nimport { UpdateSingletonEntryUseCase } from \"~/features/contentEntry/singletonEntry/abstractions.js\";\nimport { CmsFormModelBuilder } from \"~/features/formModel/abstractions.js\";\nimport { CmsModelContext } from \"~/features/contentEntry/abstractions.js\";\nimport {\n SingleEntryPresenter as Abstraction,\n type ISingleEntryPresenter,\n type ISingleEntryViewModel\n} from \"./abstractions.js\";\nimport { toFormErrors } from \"~/presentation/contentEntries/form/toFormErrors.js\";\n\nclass SingleEntryPresenterImpl implements ISingleEntryPresenter {\n private entry: CmsContentEntry | null = null;\n private form: IFormModel | null = null;\n private loading: string | null = null;\n\n constructor(\n private formModelFactory: FormModelFactory.Interface,\n private cmsFormModelBuilder: CmsFormModelBuilder.Interface,\n private modelAccessor: CmsModelContext.Interface,\n private getSingletonEntryUseCase: GetSingletonEntryUseCase.Interface,\n private updateSingletonEntryUseCase: UpdateSingletonEntryUseCase.Interface\n ) {\n makeAutoObservable<\n SingleEntryPresenterImpl,\n | \"formModelFactory\"\n | \"cmsFormModelBuilder\"\n | \"modelAccessor\"\n | \"getSingletonEntryUseCase\"\n | \"updateSingletonEntryUseCase\"\n >(this, {\n formModelFactory: false,\n cmsFormModelBuilder: false,\n modelAccessor: false,\n getSingletonEntryUseCase: false,\n updateSingletonEntryUseCase: false,\n vm: computed\n });\n }\n\n private get model() {\n return this.modelAccessor.getModel();\n }\n\n get vm(): ISingleEntryViewModel {\n return {\n loading: this.loading,\n entry: this.entry,\n form: this.form?.vm ?? null,\n canSave: this.form !== null,\n isDirty: this.form?.isDirty ?? false\n };\n }\n\n async save(): Promise<boolean> {\n if (!this.form) {\n return false;\n }\n\n // The form can be disposed or rebuilt while saving; only the form being saved gets the result.\n const form = this.form;\n const model = this.model;\n\n const data = await form.submit();\n if (!data) {\n return false;\n }\n\n this.loading = \"Saving...\";\n\n try {\n const entry = await this.updateSingletonEntryUseCase.execute({\n model: this.model,\n data: {\n values: data\n }\n });\n\n runInAction(() => {\n if (this.form !== form) {\n return;\n }\n this.entry = entry;\n form.setData(entry.values);\n form.reset();\n });\n\n return true;\n } catch (error) {\n if (this.form === form) {\n form.setErrors(toFormErrors(error, model));\n }\n return false;\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n async init(): Promise<void> {\n this.loading = \"Loading...\";\n\n try {\n const entry = await this.getSingletonEntryUseCase.execute({\n model: this.model\n });\n\n runInAction(() => {\n this.entry = entry;\n const formConfig = this.cmsFormModelBuilder.build(this.model);\n this.form = this.formModelFactory.create(formConfig);\n this.form.setData(entry.values);\n this.form.reset();\n });\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n dispose(): void {\n this.form = null;\n this.entry = null;\n }\n}\n\nexport const SingleEntryPresenter = Abstraction.createImplementation({\n implementation: SingleEntryPresenterImpl,\n dependencies: [\n FormModelFactory,\n CmsFormModelBuilder,\n CmsModelContext,\n GetSingletonEntryUseCase,\n UpdateSingletonEntryUseCase\n ]\n});\n"],"names":["SingleEntryPresenterImpl","formModelFactory","cmsFormModelBuilder","modelAccessor","getSingletonEntryUseCase","updateSingletonEntryUseCase","makeAutoObservable","computed","form","model","data","entry","runInAction","error","toFormErrors","formConfig","SingleEntryPresenter","Abstraction","FormModelFactory","CmsFormModelBuilder","CmsModelContext","GetSingletonEntryUseCase","UpdateSingletonEntryUseCase"],"mappings":";;;;;;;AAeA,MAAMA;IAKF,YACYC,gBAA4C,EAC5CC,mBAAkD,EAClDC,aAAwC,EACxCC,wBAA4D,EAC5DC,2BAAkE,CAC5E;aALUJ,gBAAgB,GAAhBA;aACAC,mBAAmB,GAAnBA;aACAC,aAAa,GAAbA;aACAC,wBAAwB,GAAxBA;aACAC,2BAA2B,GAA3BA;aATJ,KAAK,GAA2B;aAChC,IAAI,GAAsB;aAC1B,OAAO,GAAkB;QAS7BC,mBAOE,IAAI,EAAE;YACJ,kBAAkB;YAClB,qBAAqB;YACrB,eAAe;YACf,0BAA0B;YAC1B,6BAA6B;YAC7B,IAAIC;QACR;IACJ;IAEA,IAAY,QAAQ;QAChB,OAAO,IAAI,CAAC,aAAa,CAAC,QAAQ;IACtC;IAEA,IAAI,KAA4B;QAC5B,OAAO;YACH,SAAS,IAAI,CAAC,OAAO;YACrB,OAAO,IAAI,CAAC,KAAK;YACjB,MAAM,IAAI,CAAC,IAAI,EAAE,MAAM;YACvB,SAAS,AAAc,SAAd,IAAI,CAAC,IAAI;YAClB,SAAS,IAAI,CAAC,IAAI,EAAE,WAAW;QACnC;IACJ;IAEA,MAAM,OAAyB;QAC3B,IAAI,CAAC,IAAI,CAAC,IAAI,EACV,OAAO;QAIX,MAAMC,OAAO,IAAI,CAAC,IAAI;QACtB,MAAMC,QAAQ,IAAI,CAAC,KAAK;QAExB,MAAMC,OAAO,MAAMF,KAAK,MAAM;QAC9B,IAAI,CAACE,MACD,OAAO;QAGX,IAAI,CAAC,OAAO,GAAG;QAEf,IAAI;YACA,MAAMC,QAAQ,MAAM,IAAI,CAAC,2BAA2B,CAAC,OAAO,CAAC;gBACzD,OAAO,IAAI,CAAC,KAAK;gBACjB,MAAM;oBACF,QAAQD;gBACZ;YACJ;YAEAE,YAAY;gBACR,IAAI,IAAI,CAAC,IAAI,KAAKJ,MACd;gBAEJ,IAAI,CAAC,KAAK,GAAGG;gBACbH,KAAK,OAAO,CAACG,MAAM,MAAM;gBACzBH,KAAK,KAAK;YACd;YAEA,OAAO;QACX,EAAE,OAAOK,OAAO;YACZ,IAAI,IAAI,CAAC,IAAI,KAAKL,MACdA,KAAK,SAAS,CAACM,aAAaD,OAAOJ;YAEvC,OAAO;QACX,SAAU;YACNG,YAAY;gBACR,IAAI,CAAC,OAAO,GAAG;YACnB;QACJ;IACJ;IAEA,MAAM,OAAsB;QACxB,IAAI,CAAC,OAAO,GAAG;QAEf,IAAI;YACA,MAAMD,QAAQ,MAAM,IAAI,CAAC,wBAAwB,CAAC,OAAO,CAAC;gBACtD,OAAO,IAAI,CAAC,KAAK;YACrB;YAEAC,YAAY;gBACR,IAAI,CAAC,KAAK,GAAGD;gBACb,MAAMI,aAAa,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK;gBAC5D,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAACA;gBACzC,IAAI,CAAC,IAAI,CAAC,OAAO,CAACJ,MAAM,MAAM;gBAC9B,IAAI,CAAC,IAAI,CAAC,KAAK;YACnB;QACJ,SAAU;YACNC,YAAY;gBACR,IAAI,CAAC,OAAO,GAAG;YACnB;QACJ;IACJ;IAEA,UAAgB;QACZ,IAAI,CAAC,IAAI,GAAG;QACZ,IAAI,CAAC,KAAK,GAAG;IACjB;AACJ;AAEO,MAAMI,4CAAuBC,qBAAAA,oBAAgC,CAAC;IACjE,gBAAgBjB;IAChB,cAAc;QACVkB;QACAC;QACAC;QACAC;QACAC;KACH;AACL"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { Container } from "@webiny/di";
|
|
3
|
+
import { FormModelFactory } from "@webiny/app-admin/features/formModel/abstractions.js";
|
|
4
|
+
import { CmsFormModelBuilder } from "../../../features/formModel/abstractions.js";
|
|
5
|
+
import { CmsModelContext } from "../../../features/contentEntry/abstractions.js";
|
|
6
|
+
import { GetSingletonEntryUseCase, UpdateSingletonEntryUseCase } from "../../../features/contentEntry/singletonEntry/abstractions.js";
|
|
7
|
+
import { SingleEntryPresenter } from "./abstractions.js";
|
|
8
|
+
import { SingleEntryPresenter as external_SingleEntryPresenter_js_SingleEntryPresenter } from "./SingleEntryPresenter.js";
|
|
9
|
+
const createFakeForm = ()=>({
|
|
10
|
+
vm: {},
|
|
11
|
+
isDirty: false,
|
|
12
|
+
submit: vi.fn(async ()=>({
|
|
13
|
+
title: "Draft"
|
|
14
|
+
})),
|
|
15
|
+
setData: vi.fn(),
|
|
16
|
+
reset: vi.fn(),
|
|
17
|
+
setErrors: vi.fn()
|
|
18
|
+
});
|
|
19
|
+
const deferred = ()=>{
|
|
20
|
+
let resolve;
|
|
21
|
+
let reject;
|
|
22
|
+
const promise = new Promise((res, rej)=>{
|
|
23
|
+
resolve = res;
|
|
24
|
+
reject = rej;
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
promise,
|
|
28
|
+
resolve,
|
|
29
|
+
reject
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
const setup = ()=>{
|
|
33
|
+
const forms = [];
|
|
34
|
+
let pendingUpdate = deferred();
|
|
35
|
+
const container = new Container();
|
|
36
|
+
container.registerInstance(FormModelFactory, {
|
|
37
|
+
create: ()=>{
|
|
38
|
+
const form = createFakeForm();
|
|
39
|
+
forms.push(form);
|
|
40
|
+
return form;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
container.registerInstance(CmsFormModelBuilder, {
|
|
44
|
+
build: ()=>({})
|
|
45
|
+
});
|
|
46
|
+
container.registerInstance(CmsModelContext, {
|
|
47
|
+
getModel: ()=>({
|
|
48
|
+
modelId: "settings",
|
|
49
|
+
fields: []
|
|
50
|
+
})
|
|
51
|
+
});
|
|
52
|
+
container.registerInstance(GetSingletonEntryUseCase, {
|
|
53
|
+
execute: async ()=>({
|
|
54
|
+
id: "settings#0001",
|
|
55
|
+
values: {
|
|
56
|
+
title: "Saved"
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
});
|
|
60
|
+
container.registerInstance(UpdateSingletonEntryUseCase, {
|
|
61
|
+
execute: ()=>pendingUpdate.promise
|
|
62
|
+
});
|
|
63
|
+
container.register(external_SingleEntryPresenter_js_SingleEntryPresenter);
|
|
64
|
+
return {
|
|
65
|
+
presenter: container.resolve(SingleEntryPresenter),
|
|
66
|
+
forms,
|
|
67
|
+
nextUpdate: ()=>{
|
|
68
|
+
pendingUpdate = deferred();
|
|
69
|
+
return pendingUpdate;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
describe("SingleEntryPresenter.save", ()=>{
|
|
74
|
+
it("puts a rejected save's errors on the form", async ()=>{
|
|
75
|
+
const { presenter, forms, nextUpdate } = setup();
|
|
76
|
+
await presenter.init();
|
|
77
|
+
const update = nextUpdate();
|
|
78
|
+
const saving = presenter.save();
|
|
79
|
+
update.reject(new Error("Value must be unique."));
|
|
80
|
+
await expect(saving).resolves.toBe(false);
|
|
81
|
+
expect(forms[0].setErrors).toHaveBeenCalledTimes(1);
|
|
82
|
+
});
|
|
83
|
+
it("returns false instead of throwing when the form was disposed mid-save", async ()=>{
|
|
84
|
+
const { presenter, nextUpdate } = setup();
|
|
85
|
+
await presenter.init();
|
|
86
|
+
const update = nextUpdate();
|
|
87
|
+
const saving = presenter.save();
|
|
88
|
+
presenter.dispose();
|
|
89
|
+
update.reject(new Error("Value must be unique."));
|
|
90
|
+
await expect(saving).resolves.toBe(false);
|
|
91
|
+
});
|
|
92
|
+
it("doesn't put a rejected save's errors on a form built after it started", async ()=>{
|
|
93
|
+
const { presenter, forms, nextUpdate } = setup();
|
|
94
|
+
await presenter.init();
|
|
95
|
+
const update = nextUpdate();
|
|
96
|
+
const saving = presenter.save();
|
|
97
|
+
await presenter.init();
|
|
98
|
+
update.reject(new Error("Value must be unique."));
|
|
99
|
+
await expect(saving).resolves.toBe(false);
|
|
100
|
+
expect(forms).toHaveLength(2);
|
|
101
|
+
expect(forms[0].setErrors).not.toHaveBeenCalled();
|
|
102
|
+
expect(forms[1].setErrors).not.toHaveBeenCalled();
|
|
103
|
+
});
|
|
104
|
+
it("doesn't overwrite a form built after a successful save started", async ()=>{
|
|
105
|
+
const { presenter, forms, nextUpdate } = setup();
|
|
106
|
+
await presenter.init();
|
|
107
|
+
const update = nextUpdate();
|
|
108
|
+
const saving = presenter.save();
|
|
109
|
+
await presenter.init();
|
|
110
|
+
const setDataCallsBefore = forms[1].setData.mock.calls.length;
|
|
111
|
+
update.resolve({
|
|
112
|
+
id: "settings#0002",
|
|
113
|
+
values: {
|
|
114
|
+
title: "From the old save"
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
await expect(saving).resolves.toBe(true);
|
|
118
|
+
expect(forms[1].setData).toHaveBeenCalledTimes(setDataCallsBefore);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
//# sourceMappingURL=SingleEntryPresenter.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presentation/contentEntries/singleEntry/SingleEntryPresenter.test.js","sources":["../../../../src/presentation/contentEntries/singleEntry/SingleEntryPresenter.test.ts"],"sourcesContent":["import { describe, it, expect, vi } from \"vitest\";\nimport { Container } from \"@webiny/di\";\nimport { FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { CmsFormModelBuilder } from \"~/features/formModel/abstractions.js\";\nimport { CmsModelContext } from \"~/features/contentEntry/abstractions.js\";\nimport {\n GetSingletonEntryUseCase,\n UpdateSingletonEntryUseCase\n} from \"~/features/contentEntry/singletonEntry/abstractions.js\";\nimport { SingleEntryPresenter as Abstraction } from \"./abstractions.js\";\nimport { SingleEntryPresenter } from \"./SingleEntryPresenter.js\";\n\nconst createFakeForm = () => ({\n vm: {},\n isDirty: false,\n submit: vi.fn(async () => ({ title: \"Draft\" })),\n setData: vi.fn(),\n reset: vi.fn(),\n setErrors: vi.fn()\n});\n\n/** A promise the test settles by hand, so it can act while a save is still in flight. */\nconst deferred = <T>() => {\n let resolve!: (value: T) => void;\n let reject!: (reason: unknown) => void;\n const promise = new Promise<T>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n return { promise, resolve, reject };\n};\n\nconst setup = () => {\n const forms: ReturnType<typeof createFakeForm>[] = [];\n let pendingUpdate = deferred<any>();\n\n const container = new Container();\n container.registerInstance(FormModelFactory, {\n create: () => {\n const form = createFakeForm();\n forms.push(form);\n return form;\n }\n } as unknown as FormModelFactory.Interface);\n container.registerInstance(CmsFormModelBuilder, {\n build: () => ({})\n } as unknown as CmsFormModelBuilder.Interface);\n container.registerInstance(CmsModelContext, {\n getModel: () => ({ modelId: \"settings\", fields: [] })\n } as unknown as CmsModelContext.Interface);\n container.registerInstance(GetSingletonEntryUseCase, {\n execute: async () => ({ id: \"settings#0001\", values: { title: \"Saved\" } })\n } as unknown as GetSingletonEntryUseCase.Interface);\n container.registerInstance(UpdateSingletonEntryUseCase, {\n execute: () => pendingUpdate.promise\n } as unknown as UpdateSingletonEntryUseCase.Interface);\n container.register(SingleEntryPresenter);\n\n return {\n presenter: container.resolve(Abstraction),\n forms,\n nextUpdate: () => {\n pendingUpdate = deferred<any>();\n return pendingUpdate;\n }\n };\n};\n\ndescribe(\"SingleEntryPresenter.save\", () => {\n it(\"puts a rejected save's errors on the form\", async () => {\n const { presenter, forms, nextUpdate } = setup();\n await presenter.init();\n const update = nextUpdate();\n\n const saving = presenter.save();\n update.reject(new Error(\"Value must be unique.\"));\n\n await expect(saving).resolves.toBe(false);\n expect(forms[0].setErrors).toHaveBeenCalledTimes(1);\n });\n\n it(\"returns false instead of throwing when the form was disposed mid-save\", async () => {\n const { presenter, nextUpdate } = setup();\n await presenter.init();\n const update = nextUpdate();\n\n const saving = presenter.save();\n presenter.dispose();\n update.reject(new Error(\"Value must be unique.\"));\n\n await expect(saving).resolves.toBe(false);\n });\n\n it(\"doesn't put a rejected save's errors on a form built after it started\", async () => {\n const { presenter, forms, nextUpdate } = setup();\n await presenter.init();\n const update = nextUpdate();\n\n const saving = presenter.save();\n await presenter.init();\n update.reject(new Error(\"Value must be unique.\"));\n\n await expect(saving).resolves.toBe(false);\n expect(forms).toHaveLength(2);\n expect(forms[0].setErrors).not.toHaveBeenCalled();\n expect(forms[1].setErrors).not.toHaveBeenCalled();\n });\n\n it(\"doesn't overwrite a form built after a successful save started\", async () => {\n const { presenter, forms, nextUpdate } = setup();\n await presenter.init();\n const update = nextUpdate();\n\n const saving = presenter.save();\n await presenter.init();\n const setDataCallsBefore = forms[1].setData.mock.calls.length;\n update.resolve({ id: \"settings#0002\", values: { title: \"From the old save\" } });\n\n await expect(saving).resolves.toBe(true);\n expect(forms[1].setData).toHaveBeenCalledTimes(setDataCallsBefore);\n });\n});\n"],"names":["createFakeForm","vi","deferred","resolve","reject","promise","Promise","res","rej","setup","forms","pendingUpdate","container","Container","FormModelFactory","form","CmsFormModelBuilder","CmsModelContext","GetSingletonEntryUseCase","UpdateSingletonEntryUseCase","SingleEntryPresenter","Abstraction","describe","it","presenter","nextUpdate","update","saving","Error","expect","setDataCallsBefore"],"mappings":";;;;;;;;AAYA,MAAMA,iBAAiB,IAAO;QAC1B,IAAI,CAAC;QACL,SAAS;QACT,QAAQC,GAAG,EAAE,CAAC,UAAa;gBAAE,OAAO;YAAQ;QAC5C,SAASA,GAAG,EAAE;QACd,OAAOA,GAAG,EAAE;QACZ,WAAWA,GAAG,EAAE;IACpB;AAGA,MAAMC,WAAW;IACb,IAAIC;IACJ,IAAIC;IACJ,MAAMC,UAAU,IAAIC,QAAW,CAACC,KAAKC;QACjCL,UAAUI;QACVH,SAASI;IACb;IACA,OAAO;QAAEH;QAASF;QAASC;IAAO;AACtC;AAEA,MAAMK,QAAQ;IACV,MAAMC,QAA6C,EAAE;IACrD,IAAIC,gBAAgBT;IAEpB,MAAMU,YAAY,IAAIC;IACtBD,UAAU,gBAAgB,CAACE,kBAAkB;QACzC,QAAQ;YACJ,MAAMC,OAAOf;YACbU,MAAM,IAAI,CAACK;YACX,OAAOA;QACX;IACJ;IACAH,UAAU,gBAAgB,CAACI,qBAAqB;QAC5C,OAAO,IAAO,EAAC;IACnB;IACAJ,UAAU,gBAAgB,CAACK,iBAAiB;QACxC,UAAU,IAAO;gBAAE,SAAS;gBAAY,QAAQ,EAAE;YAAC;IACvD;IACAL,UAAU,gBAAgB,CAACM,0BAA0B;QACjD,SAAS,UAAa;gBAAE,IAAI;gBAAiB,QAAQ;oBAAE,OAAO;gBAAQ;YAAE;IAC5E;IACAN,UAAU,gBAAgB,CAACO,6BAA6B;QACpD,SAAS,IAAMR,cAAc,OAAO;IACxC;IACAC,UAAU,QAAQ,CAACQ;IAEnB,OAAO;QACH,WAAWR,UAAU,OAAO,CAACS;QAC7BX;QACA,YAAY;YACRC,gBAAgBT;YAChB,OAAOS;QACX;IACJ;AACJ;AAEAW,SAAS,6BAA6B;IAClCC,GAAG,6CAA6C;QAC5C,MAAM,EAAEC,SAAS,EAAEd,KAAK,EAAEe,UAAU,EAAE,GAAGhB;QACzC,MAAMe,UAAU,IAAI;QACpB,MAAME,SAASD;QAEf,MAAME,SAASH,UAAU,IAAI;QAC7BE,OAAO,MAAM,CAAC,IAAIE,MAAM;QAExB,MAAMC,OAAOF,QAAQ,QAAQ,CAAC,IAAI,CAAC;QACnCE,OAAOnB,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,qBAAqB,CAAC;IACrD;IAEAa,GAAG,yEAAyE;QACxE,MAAM,EAAEC,SAAS,EAAEC,UAAU,EAAE,GAAGhB;QAClC,MAAMe,UAAU,IAAI;QACpB,MAAME,SAASD;QAEf,MAAME,SAASH,UAAU,IAAI;QAC7BA,UAAU,OAAO;QACjBE,OAAO,MAAM,CAAC,IAAIE,MAAM;QAExB,MAAMC,OAAOF,QAAQ,QAAQ,CAAC,IAAI,CAAC;IACvC;IAEAJ,GAAG,yEAAyE;QACxE,MAAM,EAAEC,SAAS,EAAEd,KAAK,EAAEe,UAAU,EAAE,GAAGhB;QACzC,MAAMe,UAAU,IAAI;QACpB,MAAME,SAASD;QAEf,MAAME,SAASH,UAAU,IAAI;QAC7B,MAAMA,UAAU,IAAI;QACpBE,OAAO,MAAM,CAAC,IAAIE,MAAM;QAExB,MAAMC,OAAOF,QAAQ,QAAQ,CAAC,IAAI,CAAC;QACnCE,OAAOnB,OAAO,YAAY,CAAC;QAC3BmB,OAAOnB,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,CAAC,gBAAgB;QAC/CmB,OAAOnB,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,CAAC,gBAAgB;IACnD;IAEAa,GAAG,kEAAkE;QACjE,MAAM,EAAEC,SAAS,EAAEd,KAAK,EAAEe,UAAU,EAAE,GAAGhB;QACzC,MAAMe,UAAU,IAAI;QACpB,MAAME,SAASD;QAEf,MAAME,SAASH,UAAU,IAAI;QAC7B,MAAMA,UAAU,IAAI;QACpB,MAAMM,qBAAqBpB,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM;QAC7DgB,OAAO,OAAO,CAAC;YAAE,IAAI;YAAiB,QAAQ;gBAAE,OAAO;YAAoB;QAAE;QAE7E,MAAMG,OAAOF,QAAQ,QAAQ,CAAC,IAAI,CAAC;QACnCE,OAAOnB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,qBAAqB,CAACoB;IACnD;AACJ"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
export declare const CmsAdvancedSearch: (() => React.JSX.Element) & {
|
|
3
|
-
|
|
2
|
+
export declare const CmsAdvancedSearch: (() => React.JSX.Element) & React.NamedExoticComponent<unknown> & {
|
|
3
|
+
readonly type: () => React.JSX.Element;
|
|
4
4
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
export declare const ContentEntriesListLayout: (() => React.JSX.Element) & {
|
|
3
|
-
|
|
2
|
+
export declare const ContentEntriesListLayout: (() => React.JSX.Element) & React.NamedExoticComponent<unknown> & {
|
|
3
|
+
readonly type: () => React.JSX.Element;
|
|
4
4
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
export declare const ContentEntryFormView: (() => React.JSX.Element) & {
|
|
3
|
-
|
|
2
|
+
export declare const ContentEntryFormView: (() => React.JSX.Element) & React.NamedExoticComponent<unknown> & {
|
|
3
|
+
readonly type: () => React.JSX.Element;
|
|
4
4
|
};
|