meno-core 1.0.53 → 1.0.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +1 -3
- package/bin/cli.ts +48 -57
- package/build-astro.ts +131 -113
- package/build-next.ts +109 -96
- package/build-static.test.ts +39 -10
- package/build-static.ts +120 -122
- package/dist/bin/cli.js +34 -38
- package/dist/bin/cli.js.map +2 -2
- package/dist/build-static.js +12 -11
- package/dist/chunks/chunk-2AR55GYH.js +42 -0
- package/dist/chunks/chunk-2AR55GYH.js.map +7 -0
- package/dist/chunks/{chunk-IGYR22T6.js → chunk-2FN4UOVO.js} +512 -239
- package/dist/chunks/chunk-2FN4UOVO.js.map +7 -0
- package/dist/chunks/chunk-3XER4E5W.js +168 -0
- package/dist/chunks/chunk-3XER4E5W.js.map +7 -0
- package/dist/chunks/{chunk-X754AHS5.js → chunk-5ETZFREW.js} +9 -12
- package/dist/chunks/chunk-5ETZFREW.js.map +7 -0
- package/dist/chunks/{chunk-2MHDV5BF.js → chunk-7E4IF5L7.js} +15 -21
- package/dist/chunks/chunk-7E4IF5L7.js.map +7 -0
- package/dist/chunks/{chunk-JGWFTO6P.js → chunk-7HWQUVTU.js} +1578 -1349
- package/dist/chunks/chunk-7HWQUVTU.js.map +7 -0
- package/dist/chunks/{chunk-O3NAGJP4.js → chunk-AE3QK5QW.js} +110 -21
- package/dist/chunks/chunk-AE3QK5QW.js.map +7 -0
- package/dist/chunks/{chunk-GZHGVVW3.js → chunk-F6KTJYGV.js} +7 -7
- package/dist/chunks/chunk-F6KTJYGV.js.map +7 -0
- package/dist/chunks/{chunk-WQFG7PAH.js → chunk-FZITJSSS.js} +2 -6
- package/dist/chunks/chunk-FZITJSSS.js.map +7 -0
- package/dist/chunks/{chunk-QB2LNO4W.js → chunk-GSYYA5GX.js} +2 -2
- package/dist/chunks/chunk-GSYYA5GX.js.map +7 -0
- package/dist/chunks/{chunk-YBLHKYFF.js → chunk-HIZMY3EP.js} +1 -1
- package/dist/chunks/chunk-HIZMY3EP.js.map +7 -0
- package/dist/chunks/{chunk-AZQYF6KE.js → chunk-I2WEGYA7.js} +41 -176
- package/dist/chunks/chunk-I2WEGYA7.js.map +7 -0
- package/dist/chunks/{chunk-I7YIGZXT.js → chunk-JNO3CNLJ.js} +6 -9
- package/dist/chunks/chunk-JNO3CNLJ.js.map +7 -0
- package/dist/chunks/{chunk-UB44F4Z2.js → chunk-NVRBTSQG.js} +2 -4
- package/dist/chunks/chunk-NVRBTSQG.js.map +7 -0
- package/dist/chunks/{chunk-JGP5A3Y5.js → chunk-Q4OBWKXG.js} +40 -33
- package/dist/chunks/chunk-Q4OBWKXG.js.map +7 -0
- package/dist/chunks/{chunk-R6XHAFBF.js → chunk-QTE32Y53.js} +250 -242
- package/dist/chunks/chunk-QTE32Y53.js.map +7 -0
- package/dist/chunks/{chunk-H3GJ4H2U.js → chunk-STDY3OVM.js} +214 -85
- package/dist/chunks/chunk-STDY3OVM.js.map +7 -0
- package/dist/chunks/configService-PRJZF7Y6.js +14 -0
- package/dist/chunks/{constants-STK2YBIW.js → constants-KIQEYMAM.js} +2 -2
- package/dist/chunks/{fs-JGINUXGL.js → fs-ZI5JEU7V.js} +2 -2
- package/dist/entries/server-router.js +14 -19
- package/dist/entries/server-router.js.map +2 -2
- package/dist/lib/client/index.js +638 -332
- package/dist/lib/client/index.js.map +2 -2
- package/dist/lib/server/index.js +177 -235
- package/dist/lib/server/index.js.map +2 -2
- package/dist/lib/shared/index.js +80 -44
- package/dist/lib/shared/index.js.map +2 -2
- package/dist/lib/shared/richtext/index.js +1 -1
- package/dist/lib/test-utils/index.js +38 -60
- package/dist/lib/test-utils/index.js.map +2 -2
- package/entries/client-router.tsx +10 -8
- package/entries/server-router.tsx +1 -7
- package/lib/client/ClientInitializer.ts +8 -8
- package/lib/client/ErrorBoundary.test.tsx +146 -143
- package/lib/client/ErrorBoundary.tsx +175 -127
- package/lib/client/componentRegistry.test.ts +96 -108
- package/lib/client/componentRegistry.ts +1 -2
- package/lib/client/contexts/ThemeContext.tsx +3 -2
- package/lib/client/core/ComponentBuilder.test.ts +513 -560
- package/lib/client/core/ComponentBuilder.ts +318 -146
- package/lib/client/core/ComponentRenderer.test.tsx +1 -2
- package/lib/client/core/ComponentRenderer.tsx +46 -33
- package/lib/client/core/builders/embedBuilder.ts +241 -53
- package/lib/client/core/builders/linkBuilder.ts +71 -44
- package/lib/client/core/builders/linkNodeBuilder.ts +78 -53
- package/lib/client/core/builders/listBuilder.ts +115 -86
- package/lib/client/core/builders/localeListBuilder.ts +95 -60
- package/lib/client/core/builders/types.ts +5 -5
- package/lib/client/core/cmsTemplateProcessor.ts +7 -7
- package/lib/client/elementRegistry.ts +3 -3
- package/lib/client/fontFamiliesService.test.ts +2 -10
- package/lib/client/fontFamiliesService.ts +3 -3
- package/lib/client/hmr/HMRManager.tsx +8 -0
- package/lib/client/hmrCssReload.ts +14 -8
- package/lib/client/hmrWebSocket.ts +9 -14
- package/lib/client/hooks/useColorVariables.test.ts +21 -21
- package/lib/client/hooks/useColorVariables.ts +12 -10
- package/lib/client/hooks/usePropertyAutocomplete.ts +3 -5
- package/lib/client/hooks/useVariables.ts +4 -4
- package/lib/client/hydration/HydrationUtils.test.ts +24 -25
- package/lib/client/hydration/HydrationUtils.ts +3 -4
- package/lib/client/i18nConfigService.test.ts +2 -7
- package/lib/client/i18nConfigService.ts +2 -2
- package/lib/client/meno-filter/MenoFilter.test.ts +19 -21
- package/lib/client/meno-filter/MenoFilter.ts +5 -9
- package/lib/client/meno-filter/bindings.ts +15 -40
- package/lib/client/meno-filter/init.ts +1 -1
- package/lib/client/meno-filter/renderer.ts +23 -29
- package/lib/client/meno-filter/script.generated.ts +1 -3
- package/lib/client/meno-filter/ui.ts +3 -5
- package/lib/client/meno-filter/updates.ts +15 -21
- package/lib/client/navigation.test.ts +159 -159
- package/lib/client/navigation.ts +0 -1
- package/lib/client/responsiveStyleResolver.test.ts +230 -228
- package/lib/client/responsiveStyleResolver.ts +13 -16
- package/lib/client/routing/RouteLoader.test.ts +23 -24
- package/lib/client/routing/RouteLoader.ts +22 -35
- package/lib/client/routing/Router.tsx +31 -3
- package/lib/client/scripts/ScriptExecutor.test.ts +157 -158
- package/lib/client/scripts/ScriptExecutor.ts +15 -33
- package/lib/client/services/PrefetchService.test.ts +2 -2
- package/lib/client/services/PrefetchService.ts +10 -24
- package/lib/client/styleProcessor.test.ts +9 -9
- package/lib/client/styleProcessor.ts +18 -15
- package/lib/client/styles/StyleInjector.test.ts +122 -115
- package/lib/client/styles/StyleInjector.ts +9 -6
- package/lib/client/styles/UtilityClassCollector.ts +19 -26
- package/lib/client/styles/cspNonce.test.ts +2 -5
- package/lib/client/templateEngine.test.ts +554 -482
- package/lib/client/templateEngine.ts +200 -64
- package/lib/client/theme.ts +0 -1
- package/lib/client/utils/toast.ts +0 -1
- package/lib/server/__integration__/api-routes.test.ts +8 -4
- package/lib/server/__integration__/cms-integration.test.ts +1 -4
- package/lib/server/__integration__/server-lifecycle.test.ts +2 -5
- package/lib/server/__integration__/ssr-rendering.test.ts +47 -37
- package/lib/server/__integration__/static-assets.test.ts +1 -1
- package/lib/server/__integration__/test-helpers.ts +84 -70
- package/lib/server/ab/generateFunctions.ts +12 -10
- package/lib/server/astro/cmsPageEmitter.ts +23 -38
- package/lib/server/astro/componentEmitter.ts +25 -36
- package/lib/server/astro/cssCollector.ts +10 -26
- package/lib/server/astro/nodeToAstro.test.ts +1750 -30
- package/lib/server/astro/nodeToAstro.ts +198 -187
- package/lib/server/astro/normalizeOrphanTemplateProps.test.ts +105 -109
- package/lib/server/astro/normalizeOrphanTemplateProps.ts +4 -12
- package/lib/server/astro/pageEmitter.ts +9 -13
- package/lib/server/astro/tailwindMapper.test.ts +10 -37
- package/lib/server/astro/tailwindMapper.ts +33 -40
- package/lib/server/astro/templateTransformer.ts +14 -17
- package/lib/server/createServer.ts +5 -17
- package/lib/server/cssGenerator.test.ts +35 -44
- package/lib/server/cssGenerator.ts +6 -17
- package/lib/server/fileWatcher.test.ts +124 -10
- package/lib/server/fileWatcher.ts +124 -118
- package/lib/server/index.ts +7 -1
- package/lib/server/jsonLoader.test.ts +39 -2
- package/lib/server/jsonLoader.ts +33 -31
- package/lib/server/middleware/cors.test.ts +20 -20
- package/lib/server/middleware/cors.ts +28 -4
- package/lib/server/middleware/errorHandler.test.ts +5 -3
- package/lib/server/middleware/errorHandler.ts +3 -8
- package/lib/server/middleware/index.ts +0 -1
- package/lib/server/middleware/logger.test.ts +7 -5
- package/lib/server/middleware/logger.ts +10 -22
- package/lib/server/pageCache.test.ts +76 -77
- package/lib/server/pageCache.ts +0 -1
- package/lib/server/projectContext.ts +4 -3
- package/lib/server/providers/fileSystemCMSProvider.test.ts +124 -95
- package/lib/server/providers/fileSystemCMSProvider.ts +35 -20
- package/lib/server/providers/fileSystemPageProvider.test.ts +84 -0
- package/lib/server/providers/fileSystemPageProvider.ts +31 -12
- package/lib/server/routes/api/cms.test.ts +26 -14
- package/lib/server/routes/api/cms.ts +9 -14
- package/lib/server/routes/api/components.ts +30 -34
- package/lib/server/routes/api/config.ts +0 -1
- package/lib/server/routes/api/core-routes.ts +47 -74
- package/lib/server/routes/api/functions.ts +8 -16
- package/lib/server/routes/api/index.ts +3 -6
- package/lib/server/routes/api/pages.ts +8 -11
- package/lib/server/routes/api/shared.test.ts +1 -1
- package/lib/server/routes/api/shared.ts +12 -9
- package/lib/server/routes/api/variables.test.ts +1 -3
- package/lib/server/routes/api/variables.ts +1 -1
- package/lib/server/routes/index.ts +23 -26
- package/lib/server/routes/pages.ts +34 -29
- package/lib/server/routes/static.ts +16 -4
- package/lib/server/runtime/bundler.ts +47 -32
- package/lib/server/runtime/fs.ts +3 -13
- package/lib/server/runtime/httpServer.ts +5 -9
- package/lib/server/services/ColorService.ts +32 -27
- package/lib/server/services/EnumService.test.ts +2 -6
- package/lib/server/services/EnumService.ts +7 -2
- package/lib/server/services/VariableService.test.ts +1 -5
- package/lib/server/services/VariableService.ts +6 -1
- package/lib/server/services/cmsService.test.ts +116 -78
- package/lib/server/services/cmsService.ts +24 -54
- package/lib/server/services/componentService.test.ts +122 -36
- package/lib/server/services/componentService.ts +54 -38
- package/lib/server/services/configService.test.ts +9 -31
- package/lib/server/services/configService.ts +20 -27
- package/lib/server/services/fileWatcherService.ts +40 -6
- package/lib/server/services/index.ts +0 -1
- package/lib/server/services/pageService.test.ts +1 -3
- package/lib/server/services/pageService.ts +14 -13
- package/lib/server/ssr/attributeBuilder.ts +16 -6
- package/lib/server/ssr/buildErrorOverlay.ts +11 -12
- package/lib/server/ssr/clientDataInjector.ts +7 -21
- package/lib/server/ssr/cmsSSRProcessor.ts +3 -6
- package/lib/server/ssr/cssCollector.ts +1 -1
- package/lib/server/ssr/errorOverlay.test.ts +1 -1
- package/lib/server/ssr/errorOverlay.ts +3 -9
- package/lib/server/ssr/htmlGenerator.nonce.test.ts +3 -9
- package/lib/server/ssr/htmlGenerator.test.ts +120 -43
- package/lib/server/ssr/htmlGenerator.ts +120 -85
- package/lib/server/ssr/imageMetadata.test.ts +3 -1
- package/lib/server/ssr/imageMetadata.ts +25 -19
- package/lib/server/ssr/jsCollector.test.ts +3 -13
- package/lib/server/ssr/jsCollector.ts +3 -8
- package/lib/server/ssr/liveReloadIntegration.test.ts +69 -28
- package/lib/server/ssr/metaTagGenerator.ts +2 -2
- package/lib/server/ssr/ssrRenderer.branches.test.ts +1103 -0
- package/lib/server/ssr/ssrRenderer.test.ts +196 -246
- package/lib/server/ssr/ssrRenderer.ts +609 -225
- package/lib/server/ssrRenderer.test.ts +1044 -950
- package/lib/server/utils/jsonLineMapper.test.ts +28 -28
- package/lib/server/utils/jsonLineMapper.ts +1 -1
- package/lib/server/validateStyleCoverage.ts +18 -20
- package/lib/server/webflow/buildWebflow.ts +41 -53
- package/lib/server/webflow/nodeToWebflow.test.ts +150 -218
- package/lib/server/webflow/nodeToWebflow.ts +195 -258
- package/lib/server/webflow/styleMapper.test.ts +15 -56
- package/lib/server/webflow/styleMapper.ts +33 -41
- package/lib/server/webflow/types.ts +1 -8
- package/lib/server/websocketManager.ts +16 -19
- package/lib/shared/attributeNodeUtils.test.ts +15 -15
- package/lib/shared/attributeNodeUtils.ts +5 -12
- package/lib/shared/breakpoints.ts +4 -11
- package/lib/shared/cmsQueryParser.test.ts +50 -42
- package/lib/shared/cmsQueryParser.ts +49 -31
- package/lib/shared/colorVariableUtils.test.ts +5 -5
- package/lib/shared/colorVariableUtils.ts +3 -8
- package/lib/shared/componentRefs.ts +1 -5
- package/lib/shared/constants.test.ts +3 -3
- package/lib/shared/constants.ts +4 -8
- package/lib/shared/cssGeneration.test.ts +262 -144
- package/lib/shared/cssGeneration.ts +189 -516
- package/lib/shared/cssNamedColors.ts +152 -30
- package/lib/shared/cssProperties.test.ts +5 -6
- package/lib/shared/cssProperties.ts +296 -112
- package/lib/shared/elementClassName.test.ts +109 -109
- package/lib/shared/elementClassName.ts +1 -1
- package/lib/shared/elementUtils.ts +12 -16
- package/lib/shared/errorLogger.ts +2 -10
- package/lib/shared/errors.test.ts +2 -13
- package/lib/shared/errors.ts +2 -8
- package/lib/shared/expressionEvaluator.test.ts +119 -0
- package/lib/shared/expressionEvaluator.ts +43 -24
- package/lib/shared/fontLoader.test.ts +19 -5
- package/lib/shared/friendlyError.ts +2 -3
- package/lib/shared/gradientUtils.test.ts +1 -5
- package/lib/shared/gradientUtils.ts +2 -6
- package/lib/shared/hrefRefs.ts +2 -10
- package/lib/shared/i18n.test.ts +1 -1
- package/lib/shared/i18n.ts +13 -34
- package/lib/shared/index.ts +4 -0
- package/lib/shared/inlineSvgStyleRules.ts +2 -4
- package/lib/shared/interactiveStyleMappings.test.ts +11 -33
- package/lib/shared/interactiveStyleMappings.ts +9 -16
- package/lib/shared/interactiveStyles.test.ts +165 -188
- package/lib/shared/interfaces/contentProvider.ts +1 -1
- package/lib/shared/itemTemplateUtils.test.ts +6 -12
- package/lib/shared/itemTemplateUtils.ts +19 -35
- package/lib/shared/jsonRepair.ts +8 -2
- package/lib/shared/libraryLoader.test.ts +15 -49
- package/lib/shared/libraryLoader.ts +7 -22
- package/lib/shared/netlifyLocale404.test.ts +179 -0
- package/lib/shared/netlifyLocale404.ts +110 -0
- package/lib/shared/nodeUtils.test.ts +24 -16
- package/lib/shared/nodeUtils.ts +49 -19
- package/lib/shared/pathArrayUtils.test.ts +1 -2
- package/lib/shared/pathArrayUtils.ts +1 -1
- package/lib/shared/pathSecurity.ts +1 -1
- package/lib/shared/pathUtils.test.ts +4 -6
- package/lib/shared/pathUtils.ts +42 -48
- package/lib/shared/paths/Path.test.ts +2 -2
- package/lib/shared/paths/Path.ts +0 -1
- package/lib/shared/paths/PathConverter.test.ts +1 -1
- package/lib/shared/paths/PathConverter.ts +14 -17
- package/lib/shared/paths/PathUtils.ts +9 -10
- package/lib/shared/paths/PathValidator.test.ts +2 -15
- package/lib/shared/paths/PathValidator.ts +11 -9
- package/lib/shared/paths/index.ts +1 -2
- package/lib/shared/propResolver.test.ts +240 -244
- package/lib/shared/propResolver.ts +14 -25
- package/lib/shared/pxToRem.test.ts +7 -6
- package/lib/shared/pxToRem.ts +2 -5
- package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +9 -5
- package/lib/shared/registry/ClientRegistry.ts +0 -1
- package/lib/shared/registry/ComponentRegistry.test.ts +43 -29
- package/lib/shared/registry/ComponentRegistry.ts +9 -11
- package/lib/shared/registry/NodeTypeDefinition.ts +15 -8
- package/lib/shared/registry/RegistryManager.ts +1 -2
- package/lib/shared/registry/SSRRegistry.ts +0 -1
- package/lib/shared/registry/createNodeType.ts +7 -9
- package/lib/shared/registry/defineNodeType.ts +2 -6
- package/lib/shared/registry/index.ts +0 -1
- package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +14 -15
- package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +18 -11
- package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +47 -18
- package/lib/shared/registry/nodeTypes/LinkNodeType.ts +21 -19
- package/lib/shared/registry/nodeTypes/ListNodeType.ts +78 -74
- package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +27 -21
- package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +6 -7
- package/lib/shared/registry/nodeTypes/index.ts +10 -2
- package/lib/shared/responsiveScaling.test.ts +15 -31
- package/lib/shared/responsiveScaling.ts +55 -37
- package/lib/shared/responsiveStyleUtils.ts +11 -13
- package/lib/shared/richtext/htmlToTiptap.test.ts +23 -14
- package/lib/shared/richtext/htmlToTiptap.ts +1 -3
- package/lib/shared/richtext/tiptapToHtml.test.ts +5 -6
- package/lib/shared/richtext/types.ts +1 -8
- package/lib/shared/slugTranslator.test.ts +13 -13
- package/lib/shared/slugTranslator.ts +12 -16
- package/lib/shared/slugify.ts +9 -15
- package/lib/shared/styleNodeUtils.test.ts +8 -8
- package/lib/shared/styleNodeUtils.ts +5 -10
- package/lib/shared/styleUtils.test.ts +87 -61
- package/lib/shared/styleUtils.ts +5 -6
- package/lib/shared/themeDefaults.test.ts +11 -11
- package/lib/shared/themeDefaults.ts +3 -4
- package/lib/shared/tree/PathBuilder.test.ts +62 -138
- package/lib/shared/tree/PathBuilder.ts +49 -39
- package/lib/shared/treePathUtils.test.ts +2 -10
- package/lib/shared/treePathUtils.ts +54 -59
- package/lib/shared/types/api.ts +1 -2
- package/lib/shared/types/cms.ts +25 -21
- package/lib/shared/types/comment.ts +49 -16
- package/lib/shared/types/components.ts +27 -25
- package/lib/shared/types/errors.test.ts +1 -6
- package/lib/shared/types/errors.ts +3 -7
- package/lib/shared/types/experiments.ts +28 -28
- package/lib/shared/types/index.ts +2 -2
- package/lib/shared/types/styles.ts +0 -1
- package/lib/shared/types/variables.test.ts +4 -13
- package/lib/shared/types/variables.ts +48 -27
- package/lib/shared/types.ts +1 -2
- package/lib/shared/utilityClassConfig.ts +648 -321
- package/lib/shared/utilityClassMapper.test.ts +203 -92
- package/lib/shared/utilityClassMapper.ts +188 -246
- package/lib/shared/utilityClassNames.ts +326 -0
- package/lib/shared/utils.test.ts +2 -10
- package/lib/shared/utils.ts +19 -10
- package/lib/shared/validation/cmsValidators.ts +2 -1
- package/lib/shared/validation/commentValidators.test.ts +53 -0
- package/lib/shared/validation/commentValidators.ts +12 -1
- package/lib/shared/validation/propValidator.test.ts +18 -20
- package/lib/shared/validation/propValidator.ts +12 -17
- package/lib/shared/validation/schemas.test.ts +24 -33
- package/lib/shared/validation/schemas.ts +469 -344
- package/lib/shared/validation/validators.test.ts +1 -6
- package/lib/shared/validation/validators.ts +89 -68
- package/lib/shared/viewportUnits.integration.test.ts +14 -10
- package/lib/shared/viewportUnits.test.ts +11 -23
- package/lib/test-utils/dom-setup.ts +1 -1
- package/lib/test-utils/factories/ConsoleMockFactory.ts +3 -7
- package/lib/test-utils/factories/DomMockFactory.ts +7 -19
- package/lib/test-utils/factories/EventMockFactory.ts +7 -13
- package/lib/test-utils/factories/FetchMockFactory.ts +39 -57
- package/lib/test-utils/factories/ServerMockFactory.ts +5 -9
- package/lib/test-utils/factories/StoreMockFactory.ts +14 -25
- package/lib/test-utils/fixtures.ts +45 -45
- package/lib/test-utils/helpers/asyncHelpers.test.ts +15 -18
- package/lib/test-utils/helpers/asyncHelpers.ts +11 -20
- package/lib/test-utils/helpers.ts +1 -5
- package/lib/test-utils/index.ts +0 -4
- package/lib/test-utils/mockFactories.ts +12 -18
- package/lib/test-utils/mocks.ts +4 -2
- package/package.json +1 -1
- package/scripts/build-meno-filter.ts +1 -4
- package/vite.config.ts +4 -4
- package/dist/chunks/chunk-2MHDV5BF.js.map +0 -7
- package/dist/chunks/chunk-AZQYF6KE.js.map +0 -7
- package/dist/chunks/chunk-GZHGVVW3.js.map +0 -7
- package/dist/chunks/chunk-H3GJ4H2U.js.map +0 -7
- package/dist/chunks/chunk-I7YIGZXT.js.map +0 -7
- package/dist/chunks/chunk-IGYR22T6.js.map +0 -7
- package/dist/chunks/chunk-JGP5A3Y5.js.map +0 -7
- package/dist/chunks/chunk-JGWFTO6P.js.map +0 -7
- package/dist/chunks/chunk-O3NAGJP4.js.map +0 -7
- package/dist/chunks/chunk-QB2LNO4W.js.map +0 -7
- package/dist/chunks/chunk-R6XHAFBF.js.map +0 -7
- package/dist/chunks/chunk-UB44F4Z2.js.map +0 -7
- package/dist/chunks/chunk-WQFG7PAH.js.map +0 -7
- package/dist/chunks/chunk-X754AHS5.js.map +0 -7
- package/dist/chunks/chunk-YBLHKYFF.js.map +0 -7
- package/dist/chunks/configService-R3OGU2UD.js +0 -13
- /package/dist/chunks/{configService-R3OGU2UD.js.map → configService-PRJZF7Y6.js.map} +0 -0
- /package/dist/chunks/{constants-STK2YBIW.js.map → constants-KIQEYMAM.js.map} +0 -0
- /package/dist/chunks/{fs-JGINUXGL.js.map → fs-ZI5JEU7V.js.map} +0 -0
|
@@ -3,21 +3,64 @@
|
|
|
3
3
|
* Converts JSON component structures to HTML strings for SEO-friendly initial page loads
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
ComponentNode,
|
|
8
|
+
ComponentDefinition,
|
|
9
|
+
JSONPage,
|
|
10
|
+
CMSItem,
|
|
11
|
+
EmbedNode,
|
|
12
|
+
LinkNode,
|
|
13
|
+
LocaleListNode,
|
|
14
|
+
CMSFilterCondition,
|
|
15
|
+
} from '../../shared/types';
|
|
7
16
|
import type { TemplateContext, NestedCMSListConfig } from '../../shared/types/cms';
|
|
8
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
processItemTemplate,
|
|
19
|
+
processItemPropsTemplate,
|
|
20
|
+
hasItemTemplates,
|
|
21
|
+
buildTemplateContext,
|
|
22
|
+
resolveItemsTemplate,
|
|
23
|
+
resolveTemplateRawValue,
|
|
24
|
+
addItemUrl,
|
|
25
|
+
getNestedValue,
|
|
26
|
+
type ValueResolver,
|
|
27
|
+
} from '../../shared/itemTemplateUtils';
|
|
9
28
|
import { singularize, isItemDraftForLocale } from '../../shared/types/cms';
|
|
10
29
|
import type { ResponsiveStyleObject, StyleObject } from '../../shared/types';
|
|
11
30
|
import type { BreakpointConfig } from '../../shared/breakpoints';
|
|
12
31
|
import type { ResponsiveScales } from '../../shared/responsiveScaling';
|
|
13
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
evaluateTemplate,
|
|
34
|
+
processStructure,
|
|
35
|
+
isResponsiveStyle,
|
|
36
|
+
isHtmlMapping,
|
|
37
|
+
resolveHtmlMapping,
|
|
38
|
+
} from '../../client/templateEngine';
|
|
14
39
|
import { resolvePropsFromDefinition, isRichTextMarker, richTextMarkerToHtml } from '../../shared/propResolver';
|
|
15
40
|
import { loadBreakpointConfig, loadI18nConfig } from '../jsonLoader';
|
|
16
41
|
import { configService } from '../services/configService';
|
|
17
42
|
import type { I18nConfig } from '../../shared/types/components';
|
|
18
|
-
import {
|
|
43
|
+
import {
|
|
44
|
+
extractLocaleFromPath,
|
|
45
|
+
DEFAULT_I18N_CONFIG,
|
|
46
|
+
resolveI18nValue,
|
|
47
|
+
buildLocalizedPath,
|
|
48
|
+
isI18nValue,
|
|
49
|
+
} from '../../shared/i18n';
|
|
19
50
|
import { NODE_TYPE } from '../../shared/constants';
|
|
20
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
isComponentNode,
|
|
53
|
+
isHtmlNode,
|
|
54
|
+
isLinkNode,
|
|
55
|
+
isEmbedNode,
|
|
56
|
+
isLocaleListNode,
|
|
57
|
+
isListNode,
|
|
58
|
+
markAsSlotContent,
|
|
59
|
+
isBooleanMapping,
|
|
60
|
+
hasIf,
|
|
61
|
+
isSlotContent,
|
|
62
|
+
hasChildren,
|
|
63
|
+
} from '../../shared/nodeUtils';
|
|
21
64
|
import type { ListNode } from '../../shared/registry/nodeTypes/ListNodeType';
|
|
22
65
|
import type { CMSService } from '../services/cmsService';
|
|
23
66
|
import { extractAttributesFromNode, skipEmptyTemplateAttributes } from '../../shared/attributeNodeUtils';
|
|
@@ -46,20 +89,31 @@ import { responsiveStylesToClasses } from '../../shared/utilityClassMapper';
|
|
|
46
89
|
import { validateStyleCoverage } from '../validateStyleCoverage';
|
|
47
90
|
import { generateElementClassName, type ElementClassContext } from '../../shared/elementClassName';
|
|
48
91
|
import type { InteractiveStyles } from '../../shared/types/styles';
|
|
49
|
-
import {
|
|
92
|
+
import {
|
|
93
|
+
extractInteractiveStyleMappings,
|
|
94
|
+
resolveExtractedMappings,
|
|
95
|
+
hasInteractiveStyleMappings,
|
|
96
|
+
} from '../../shared/interactiveStyleMappings';
|
|
50
97
|
import { isCurrentLink } from '../../shared/linkUtils';
|
|
51
98
|
import type { SlugMap } from '../../shared/slugTranslator';
|
|
52
99
|
import { buildSlugIndex, getLocaleLinks, translatePath } from '../../shared/slugTranslator';
|
|
53
100
|
|
|
54
101
|
// Import from modularized files
|
|
55
102
|
import { escapeHtml, buildAttributes, buildEditorAttrs, styleToString } from './attributeBuilder';
|
|
103
|
+
import { logRuntimeError } from '../../shared/errorLogger';
|
|
56
104
|
import { toFriendlyError } from '../../shared/friendlyError';
|
|
57
105
|
import { rewriteViewportUnits } from '../../shared/viewportUnits';
|
|
58
106
|
import { extractPageMeta, generateMetaTags } from './metaTagGenerator';
|
|
59
107
|
import { collectComponentCSS } from './cssCollector';
|
|
60
108
|
import { collectComponentJavaScript } from './jsCollector';
|
|
61
|
-
import {
|
|
62
|
-
|
|
109
|
+
import {
|
|
110
|
+
type CMSContext,
|
|
111
|
+
processCMSTemplate,
|
|
112
|
+
processCMSPropsTemplate,
|
|
113
|
+
createI18nResolver,
|
|
114
|
+
RAW_HTML_PREFIX,
|
|
115
|
+
} from './cmsSSRProcessor';
|
|
116
|
+
import { type ImageMetadataMap, DEFAULT_SIZES, buildImageMetadataMap, rewriteRichTextImages } from './imageMetadata';
|
|
63
117
|
|
|
64
118
|
/**
|
|
65
119
|
* Image preload info for generating <link rel="preload"> tags in head
|
|
@@ -86,7 +140,7 @@ export type { CMSContext } from './cmsSSRProcessor';
|
|
|
86
140
|
function resolveI18nAttrs<T extends Record<string, unknown>>(
|
|
87
141
|
attrs: T,
|
|
88
142
|
locale: string | undefined,
|
|
89
|
-
i18nConfig: I18nConfig | undefined
|
|
143
|
+
i18nConfig: I18nConfig | undefined,
|
|
90
144
|
): T {
|
|
91
145
|
let mutated: Record<string, unknown> | null = null;
|
|
92
146
|
const config = i18nConfig ?? DEFAULT_I18N_CONFIG;
|
|
@@ -243,47 +297,37 @@ function localizeHref(href: string, ctx: SSRContext): string {
|
|
|
243
297
|
*/
|
|
244
298
|
function localizeRichTextLinks(html: string, ctx: SSRContext): string {
|
|
245
299
|
if (ctx.templateMode || !ctx.locale || !ctx.i18nConfig) return html;
|
|
246
|
-
return html.replace(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
return localized === href ? match : `<a${before}href=${quote}${localized}${quote}${after}>`;
|
|
252
|
-
}
|
|
253
|
-
);
|
|
300
|
+
return html.replace(/<a\b([^>]*?)href=(["'])([^"']*?)\2([^>]*?)>/gi, (match, before, quote, href, after) => {
|
|
301
|
+
if (!href.startsWith('/') || href.startsWith('//')) return match;
|
|
302
|
+
const localized = localizeHref(href, ctx);
|
|
303
|
+
return localized === href ? match : `<a${before}href=${quote}${localized}${quote}${after}>`;
|
|
304
|
+
});
|
|
254
305
|
}
|
|
255
306
|
|
|
256
307
|
/**
|
|
257
308
|
* Process style templates and convert to utility classes.
|
|
258
309
|
* Handles {{item.field}} patterns within list contexts.
|
|
259
310
|
*/
|
|
260
|
-
function processStyleToClasses(
|
|
261
|
-
style: StyleObject | ResponsiveStyleObject | undefined,
|
|
262
|
-
ctx: SSRContext
|
|
263
|
-
): string[] {
|
|
311
|
+
function processStyleToClasses(style: StyleObject | ResponsiveStyleObject | undefined, ctx: SSRContext): string[] {
|
|
264
312
|
if (!style) return [];
|
|
265
313
|
|
|
266
314
|
let processedStyle = style;
|
|
267
315
|
const templateCtx = getTemplateContext(ctx);
|
|
268
316
|
if (templateCtx && !ctx.templateMode) {
|
|
269
|
-
processedStyle = processItemPropsTemplate(
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
getI18nResolver(ctx)
|
|
273
|
-
) as StyleObject | ResponsiveStyleObject;
|
|
317
|
+
processedStyle = processItemPropsTemplate(style as Record<string, unknown>, templateCtx, getI18nResolver(ctx)) as
|
|
318
|
+
| StyleObject
|
|
319
|
+
| ResponsiveStyleObject;
|
|
274
320
|
}
|
|
275
321
|
|
|
276
322
|
// Fluid container pattern: in fluid mode `responsiveStylesToClasses` rewrites
|
|
277
323
|
// any tier whose `width === maxWidth` to use `calc(100% - var(--site-margin)*2)`
|
|
278
324
|
// with auto margins.
|
|
279
|
-
const fluidActive =
|
|
280
|
-
ctx.responsiveScales?.enabled === true &&
|
|
281
|
-
ctx.responsiveScales?.mode === 'fluid';
|
|
325
|
+
const fluidActive = ctx.responsiveScales?.enabled === true && ctx.responsiveScales?.mode === 'fluid';
|
|
282
326
|
|
|
283
|
-
return responsiveStylesToClasses(
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
);
|
|
327
|
+
return responsiveStylesToClasses(processedStyle as ResponsiveStyleObject, {
|
|
328
|
+
fluidActive,
|
|
329
|
+
responsiveScales: ctx.responsiveScales,
|
|
330
|
+
});
|
|
287
331
|
}
|
|
288
332
|
|
|
289
333
|
/**
|
|
@@ -344,50 +388,88 @@ function evaluateIfCondition(node: ComponentNode, ctx: SSRContext): boolean {
|
|
|
344
388
|
return true;
|
|
345
389
|
}
|
|
346
390
|
|
|
391
|
+
/**
|
|
392
|
+
* Sentinel returned by resolveFilterValue when a {{...}} template value can't
|
|
393
|
+
* be resolved from the available scope. Lets resolveFilterTemplates tell
|
|
394
|
+
* "resolved to a real value" apart from "unresolvable" so the condition can be
|
|
395
|
+
* dropped from the CMS query instead of being sent as a literal template
|
|
396
|
+
* string — which would never match a real value and silently return nothing.
|
|
397
|
+
*/
|
|
398
|
+
const UNRESOLVED_FILTER_VALUE = Symbol('unresolved-filter-value');
|
|
399
|
+
|
|
347
400
|
/**
|
|
348
401
|
* Resolve a template value like "{{category.slug}}" from a flat scope.
|
|
349
|
-
* Returns the original value
|
|
402
|
+
* Returns the original value for non-template values, and
|
|
403
|
+
* UNRESOLVED_FILTER_VALUE when a template can't be resolved.
|
|
350
404
|
*/
|
|
351
405
|
function resolveFilterValue(value: unknown, scope: Record<string, unknown> | undefined): unknown {
|
|
352
|
-
if (
|
|
406
|
+
if (typeof value !== 'string' || !value.startsWith('{{') || !value.endsWith('}}')) {
|
|
353
407
|
return value;
|
|
354
408
|
}
|
|
355
409
|
const path = value.slice(2, -2).trim();
|
|
356
|
-
const resolved = getNestedValue(scope, path);
|
|
357
|
-
return resolved !== undefined ? resolved :
|
|
410
|
+
const resolved = scope ? getNestedValue(scope, path) : undefined;
|
|
411
|
+
return resolved !== undefined ? resolved : UNRESOLVED_FILTER_VALUE;
|
|
358
412
|
}
|
|
359
413
|
|
|
360
414
|
/**
|
|
361
415
|
* Resolve template values in CMS filter for nested cms-list support.
|
|
362
416
|
* Handles filter values like "{{category.slug}}" against a flat scope built from
|
|
363
417
|
* parent template context overlaid on host-component resolved props.
|
|
418
|
+
*
|
|
419
|
+
* Conditions whose template value can't be resolved are dropped from the query
|
|
420
|
+
* (a verbatim "{{x.y}}" string would match nothing) and surfaced via
|
|
421
|
+
* logRuntimeError — dropping a condition changes results, so users must be
|
|
422
|
+
* able to see why.
|
|
364
423
|
*/
|
|
365
424
|
function resolveFilterTemplates(
|
|
366
425
|
filter: CMSFilterCondition | CMSFilterCondition[] | Record<string, unknown> | undefined,
|
|
367
|
-
scope: Record<string, unknown> | undefined
|
|
426
|
+
scope: Record<string, unknown> | undefined,
|
|
427
|
+
collection: string,
|
|
368
428
|
): CMSFilterCondition | CMSFilterCondition[] | Record<string, unknown> | undefined {
|
|
369
|
-
if (!filter
|
|
429
|
+
if (!filter) return filter;
|
|
430
|
+
|
|
431
|
+
const reportDropped = (field: unknown, template: unknown): void => {
|
|
432
|
+
logRuntimeError(
|
|
433
|
+
'ssrRenderer.resolveFilterTemplates',
|
|
434
|
+
new Error(`Unresolved filter template ${String(template)} — condition dropped from "${collection}" query`),
|
|
435
|
+
{ collection, field: String(field), template: String(template) },
|
|
436
|
+
);
|
|
437
|
+
};
|
|
370
438
|
|
|
371
439
|
// Handle array of conditions
|
|
372
440
|
if (Array.isArray(filter)) {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
value
|
|
376
|
-
|
|
441
|
+
const conditions: CMSFilterCondition[] = [];
|
|
442
|
+
for (const cond of filter) {
|
|
443
|
+
const value = resolveFilterValue(cond.value, scope);
|
|
444
|
+
if (value === UNRESOLVED_FILTER_VALUE) {
|
|
445
|
+
reportDropped(cond.field, cond.value);
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
conditions.push({ ...cond, value } as CMSFilterCondition);
|
|
449
|
+
}
|
|
450
|
+
return conditions;
|
|
377
451
|
}
|
|
378
452
|
|
|
379
453
|
// Handle single condition object with field/value
|
|
380
454
|
if ('field' in filter && 'value' in filter) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
455
|
+
const cond = filter as CMSFilterCondition;
|
|
456
|
+
const value = resolveFilterValue(cond.value, scope);
|
|
457
|
+
if (value === UNRESOLVED_FILTER_VALUE) {
|
|
458
|
+
reportDropped(cond.field, cond.value);
|
|
459
|
+
return undefined;
|
|
460
|
+
}
|
|
461
|
+
return { ...cond, value } as CMSFilterCondition;
|
|
385
462
|
}
|
|
386
463
|
|
|
387
464
|
// Handle simple object filter: { category: "{{category.slug}}" }
|
|
388
465
|
const resolved: Record<string, unknown> = {};
|
|
389
466
|
for (const [key, value] of Object.entries(filter)) {
|
|
390
|
-
|
|
467
|
+
const resolvedValue = resolveFilterValue(value, scope);
|
|
468
|
+
if (resolvedValue === UNRESOLVED_FILTER_VALUE) {
|
|
469
|
+
reportDropped(key, value);
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
resolved[key] = resolvedValue;
|
|
391
473
|
}
|
|
392
474
|
return resolved;
|
|
393
475
|
}
|
|
@@ -422,15 +504,16 @@ async function expandRichTextComponents(html: string, ctx: SSRContext): Promise<
|
|
|
422
504
|
.replace(/'/g, "'")
|
|
423
505
|
.replace(/&/g, '&');
|
|
424
506
|
props = JSON.parse(propsStr);
|
|
425
|
-
} catch {
|
|
426
|
-
//
|
|
507
|
+
} catch (error) {
|
|
508
|
+
// Malformed data-meno-props JSON: fall back to interface defaults so the
|
|
509
|
+
// marker never crashes the page, but surface the parse failure — the
|
|
510
|
+
// component silently losing its instance props is otherwise invisible.
|
|
511
|
+
logRuntimeError('ssrRenderer.expandRichTextComponents', error, { component: componentName });
|
|
427
512
|
}
|
|
428
513
|
|
|
429
514
|
// Render the component using SSR registry
|
|
430
515
|
if (ssrComponentRegistry.has(componentName)) {
|
|
431
|
-
parts.push(
|
|
432
|
-
renderComponent(componentName, props, [], {}, ctx)
|
|
433
|
-
);
|
|
516
|
+
parts.push(renderComponent(componentName, props, [], {}, ctx));
|
|
434
517
|
} else {
|
|
435
518
|
// Keep marker for unknown components
|
|
436
519
|
parts.push(match[0]);
|
|
@@ -470,8 +553,15 @@ export async function buildComponentHTML(
|
|
|
470
553
|
cmsContext?: CMSContext,
|
|
471
554
|
cmsService?: CMSService,
|
|
472
555
|
isProductionBuild?: boolean,
|
|
473
|
-
injectEditorAttrs?: boolean
|
|
474
|
-
): Promise<{
|
|
556
|
+
injectEditorAttrs?: boolean,
|
|
557
|
+
): Promise<{
|
|
558
|
+
html: string;
|
|
559
|
+
interactiveStylesMap: Map<string, InteractiveStyles>;
|
|
560
|
+
preloadImages: PreloadImage[];
|
|
561
|
+
neededCollections: Set<string>;
|
|
562
|
+
ssrFallbackCollector: Map<string, string>;
|
|
563
|
+
processedRawHtmlCollector: Map<string, string>;
|
|
564
|
+
}> {
|
|
475
565
|
// Create map to collect interactive styles during render
|
|
476
566
|
const interactiveStylesMap = new Map<string, InteractiveStyles>();
|
|
477
567
|
// Create array to collect high-priority images for preloading
|
|
@@ -483,7 +573,15 @@ export async function buildComponentHTML(
|
|
|
483
573
|
// Create map to collect raw-HTML → processed-HTML for Astro exporter parity
|
|
484
574
|
const processedRawHtmlCollector = new Map<string, string>();
|
|
485
575
|
|
|
486
|
-
if (!node)
|
|
576
|
+
if (!node)
|
|
577
|
+
return {
|
|
578
|
+
html: '',
|
|
579
|
+
interactiveStylesMap,
|
|
580
|
+
preloadImages,
|
|
581
|
+
neededCollections,
|
|
582
|
+
ssrFallbackCollector,
|
|
583
|
+
processedRawHtmlCollector,
|
|
584
|
+
};
|
|
487
585
|
|
|
488
586
|
// Register components for this render
|
|
489
587
|
ssrComponentRegistry.merge(globalComponents);
|
|
@@ -523,7 +621,14 @@ export async function buildComponentHTML(
|
|
|
523
621
|
|
|
524
622
|
const html = await renderNode(node, ctx);
|
|
525
623
|
|
|
526
|
-
return {
|
|
624
|
+
return {
|
|
625
|
+
html,
|
|
626
|
+
interactiveStylesMap,
|
|
627
|
+
preloadImages,
|
|
628
|
+
neededCollections,
|
|
629
|
+
ssrFallbackCollector,
|
|
630
|
+
processedRawHtmlCollector,
|
|
631
|
+
};
|
|
527
632
|
}
|
|
528
633
|
|
|
529
634
|
/**
|
|
@@ -531,10 +636,7 @@ export async function buildComponentHTML(
|
|
|
531
636
|
* Used when parent list has emitTemplate: true.
|
|
532
637
|
* The placeholder contains the configuration and template for MenoFilter to hydrate.
|
|
533
638
|
*/
|
|
534
|
-
async function renderNestedListPlaceholder(
|
|
535
|
-
node: ListNode,
|
|
536
|
-
ctx: SSRContext
|
|
537
|
-
): Promise<string> {
|
|
639
|
+
async function renderNestedListPlaceholder(node: ListNode, ctx: SSRContext): Promise<string> {
|
|
538
640
|
// Get source - handle both string and pre-resolved array sources
|
|
539
641
|
const sourceValue = node.source || node.collection;
|
|
540
642
|
const sourceStr = typeof sourceValue === 'string' ? sourceValue : '';
|
|
@@ -563,17 +665,17 @@ async function renderNestedListPlaceholder(
|
|
|
563
665
|
templateContext: undefined, // Clear context for inner item templates
|
|
564
666
|
nestedCMSListMode: false, // Reset for deeper nesting levels
|
|
565
667
|
};
|
|
566
|
-
const templateContent = node.children
|
|
567
|
-
? await renderChildrenAsync(node.children, childTemplateCtx)
|
|
568
|
-
: '';
|
|
668
|
+
const templateContent = node.children ? await renderChildrenAsync(node.children, childTemplateCtx) : '';
|
|
569
669
|
|
|
570
670
|
// Escape config for attribute
|
|
571
671
|
const configJson = escapeHtml(JSON.stringify(config));
|
|
572
672
|
|
|
573
673
|
// Emit placeholder with embedded config and template
|
|
574
|
-
return
|
|
674
|
+
return (
|
|
675
|
+
`<div data-cms-list-nested="true" data-collection="${escapeHtml(sourceStr)}" data-cms-config="${configJson}"${editorAttrs(ctx, { isCMSListContainer: true })}>` +
|
|
575
676
|
`<template data-nested-template>${templateContent}</template>` +
|
|
576
|
-
`</div
|
|
677
|
+
`</div>`
|
|
678
|
+
);
|
|
577
679
|
}
|
|
578
680
|
|
|
579
681
|
/**
|
|
@@ -606,7 +708,7 @@ async function processList(node: ListNode, ctx: SSRContext): Promise<string> {
|
|
|
606
708
|
// Determine source type (default to 'prop', but handle legacy 'cms-list' type)
|
|
607
709
|
const nodeType = node.type as string;
|
|
608
710
|
const isLegacyCMSList = nodeType === 'cms-list';
|
|
609
|
-
const sourceType = isLegacyCMSList ? 'collection' :
|
|
711
|
+
const sourceType = isLegacyCMSList ? 'collection' : node.sourceType || 'prop';
|
|
610
712
|
|
|
611
713
|
// For collection mode, check CMS service availability
|
|
612
714
|
if (sourceType === 'collection' && !ctx.cmsService) {
|
|
@@ -676,26 +778,16 @@ async function processList(node: ListNode, ctx: SSRContext): Promise<string> {
|
|
|
676
778
|
}
|
|
677
779
|
|
|
678
780
|
// Get schema for URL computation (collection mode only)
|
|
679
|
-
const schema = sourceType === 'collection' && ctx.cmsService
|
|
680
|
-
? ctx.cmsService.getSchema(source)
|
|
681
|
-
: undefined;
|
|
781
|
+
const schema = sourceType === 'collection' && ctx.cmsService ? ctx.cmsService.getSchema(source) : undefined;
|
|
682
782
|
|
|
683
783
|
// Render children for each item
|
|
684
784
|
const renderedItems: string[] = [];
|
|
685
785
|
|
|
686
786
|
for (let i = 0; i < items.length; i++) {
|
|
687
787
|
const rawItem = items[i] as Record<string, unknown>;
|
|
688
|
-
const item = schema
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
const templateContext = buildTemplateContext(
|
|
693
|
-
variableName,
|
|
694
|
-
item as CMSItem,
|
|
695
|
-
i,
|
|
696
|
-
items.length,
|
|
697
|
-
ctx.templateContext
|
|
698
|
-
);
|
|
788
|
+
const item = schema ? addItemUrl(rawItem as CMSItem, schema, ctx.locale, ctx.i18nConfig) : rawItem;
|
|
789
|
+
|
|
790
|
+
const templateContext = buildTemplateContext(variableName, item as CMSItem, i, items.length, ctx.templateContext);
|
|
699
791
|
|
|
700
792
|
const itemCtx: SSRContext = {
|
|
701
793
|
...ctx,
|
|
@@ -762,7 +854,7 @@ async function getCollectionItems(node: ListNode, source: string, ctx: SSRContex
|
|
|
762
854
|
}
|
|
763
855
|
}
|
|
764
856
|
if (value !== null && value !== undefined) {
|
|
765
|
-
resolvedIds = Array.isArray(value) ? value.map(v => String(v)) : String(value);
|
|
857
|
+
resolvedIds = Array.isArray(value) ? value.map((v) => String(v)) : String(value);
|
|
766
858
|
}
|
|
767
859
|
} else {
|
|
768
860
|
// Otherwise resolve from a merged scope: parent list loop variables (highest precedence)
|
|
@@ -786,14 +878,18 @@ async function getCollectionItems(node: ListNode, source: string, ctx: SSRContex
|
|
|
786
878
|
}
|
|
787
879
|
// Filter draft items from ID-based queries
|
|
788
880
|
if (ctx.locale) {
|
|
789
|
-
items = items.filter(item => !isItemDraftForLocale(item, ctx.locale!));
|
|
881
|
+
items = items.filter((item) => !isItemDraftForLocale(item, ctx.locale!));
|
|
790
882
|
}
|
|
791
883
|
} else {
|
|
792
884
|
// Build query from node props (resolve filter templates against parent loop variables
|
|
793
885
|
// overlaid on host-component resolved props, with parent loop variables winning).
|
|
794
886
|
const query = {
|
|
795
887
|
collection: source,
|
|
796
|
-
filter: resolveFilterTemplates(
|
|
888
|
+
filter: resolveFilterTemplates(
|
|
889
|
+
node.filter as CMSFilterCondition | CMSFilterCondition[] | Record<string, unknown> | undefined,
|
|
890
|
+
buildListResolutionScope(ctx),
|
|
891
|
+
source,
|
|
892
|
+
),
|
|
797
893
|
sort: node.sort,
|
|
798
894
|
limit: node.limit,
|
|
799
895
|
offset: node.offset,
|
|
@@ -805,7 +901,7 @@ async function getCollectionItems(node: ListNode, source: string, ctx: SSRContex
|
|
|
805
901
|
// Exclude current item if option is set and we have a current CMS context
|
|
806
902
|
if (node.excludeCurrentItem && ctx.cmsContext?.cms?._id) {
|
|
807
903
|
const currentId = ctx.cmsContext.cms._id as string;
|
|
808
|
-
items = items.filter(item => item._id !== currentId);
|
|
904
|
+
items = items.filter((item) => item._id !== currentId);
|
|
809
905
|
}
|
|
810
906
|
|
|
811
907
|
return items;
|
|
@@ -866,25 +962,21 @@ async function renderChildrenAsync(children: unknown[], ctx: SSRContext): Promis
|
|
|
866
962
|
* Build element class name for a node based on its SSR context.
|
|
867
963
|
* Used by embed, link, locale-list, and regular HTML nodes.
|
|
868
964
|
*/
|
|
869
|
-
function buildNodeElementClass(
|
|
870
|
-
ctx: SSRContext,
|
|
871
|
-
label: string | undefined,
|
|
872
|
-
isSlotContent?: boolean
|
|
873
|
-
): string {
|
|
965
|
+
function buildNodeElementClass(ctx: SSRContext, label: string | undefined, isSlotContent?: boolean): string {
|
|
874
966
|
const { pagePath } = ctx;
|
|
875
967
|
// Slot content uses page context (defined in page, not component)
|
|
876
968
|
const useComponentContext = !isSlotContent && Boolean(ctx.componentContext);
|
|
877
969
|
const effectiveFileType = useComponentContext ? 'component' : 'page';
|
|
878
|
-
const effectiveFileName = useComponentContext
|
|
879
|
-
?
|
|
880
|
-
:
|
|
970
|
+
const effectiveFileName = useComponentContext
|
|
971
|
+
? ctx.componentContext
|
|
972
|
+
: pagePath
|
|
973
|
+
? pagePath.replace(/^\//, '').replace(/\//g, '_') || 'index'
|
|
974
|
+
: 'page';
|
|
881
975
|
|
|
882
976
|
// Slice off the component-instance prefix so class hashes stay stable across
|
|
883
977
|
// instances of the same component (mirrors ComponentBuilder.ts:678-680).
|
|
884
978
|
const rawPath = ctx.elementPath || [];
|
|
885
|
-
const path = useComponentContext && ctx.componentRootPath
|
|
886
|
-
? rawPath.slice(ctx.componentRootPath.length)
|
|
887
|
-
: rawPath;
|
|
979
|
+
const path = useComponentContext && ctx.componentRootPath ? rawPath.slice(ctx.componentRootPath.length) : rawPath;
|
|
888
980
|
|
|
889
981
|
const elementClassCtx: ElementClassContext = {
|
|
890
982
|
fileType: effectiveFileType,
|
|
@@ -902,7 +994,7 @@ function buildNodeElementClass(
|
|
|
902
994
|
function registerInteractiveStyles(
|
|
903
995
|
ctx: SSRContext,
|
|
904
996
|
elementClass: string,
|
|
905
|
-
interactiveStyles: InteractiveStyles
|
|
997
|
+
interactiveStyles: InteractiveStyles,
|
|
906
998
|
): Record<string, string> {
|
|
907
999
|
if (!ctx.interactiveStylesMap) return {};
|
|
908
1000
|
|
|
@@ -939,20 +1031,18 @@ function arraysEqual(a: number[] | undefined, b: number[] | undefined): boolean
|
|
|
939
1031
|
* Compute editor-only attributes for the current context.
|
|
940
1032
|
* Returns '' when injectEditorAttrs is off — preview-only feature.
|
|
941
1033
|
*/
|
|
942
|
-
function editorAttrs(
|
|
943
|
-
ctx: SSRContext,
|
|
944
|
-
opts: { isSlotContent?: boolean; isCMSListContainer?: boolean } = {}
|
|
945
|
-
): string {
|
|
1034
|
+
function editorAttrs(ctx: SSRContext, opts: { isSlotContent?: boolean; isCMSListContainer?: boolean } = {}): string {
|
|
946
1035
|
if (!ctx.injectEditorAttrs) return '';
|
|
947
1036
|
// Component root: inside a component instance and at the path the instance was reset to.
|
|
948
|
-
const isComponentRoot =
|
|
949
|
-
&& !opts.isSlotContent
|
|
950
|
-
&& arraysEqual(ctx.elementPath, ctx.componentRootPath);
|
|
1037
|
+
const isComponentRoot =
|
|
1038
|
+
!!ctx.componentContext && !opts.isSlotContent && arraysEqual(ctx.elementPath, ctx.componentRootPath);
|
|
951
1039
|
// Mirror client semantics: at component root, parent is the outer component;
|
|
952
1040
|
// for descendants of a component, parent is the current component itself.
|
|
953
1041
|
const effectiveParent = opts.isSlotContent
|
|
954
1042
|
? ctx.parentComponentName
|
|
955
|
-
:
|
|
1043
|
+
: isComponentRoot
|
|
1044
|
+
? ctx.parentComponentName
|
|
1045
|
+
: (ctx.componentContext ?? ctx.parentComponentName);
|
|
956
1046
|
return buildEditorAttrs({
|
|
957
1047
|
elementPath: ctx.elementPath,
|
|
958
1048
|
cmsItemIndexPath: ctx.cmsItemIndexPath,
|
|
@@ -970,7 +1060,7 @@ function editorAttrs(
|
|
|
970
1060
|
*/
|
|
971
1061
|
async function renderNode(
|
|
972
1062
|
node: ComponentNode | ComponentNode[] | string | number | null | undefined,
|
|
973
|
-
ctx: SSRContext
|
|
1063
|
+
ctx: SSRContext,
|
|
974
1064
|
): Promise<string> {
|
|
975
1065
|
const { breakpoints, viewportWidth, locale, i18nConfig, slugMappings, pagePath } = ctx;
|
|
976
1066
|
|
|
@@ -1028,10 +1118,14 @@ async function renderNode(
|
|
|
1028
1118
|
}
|
|
1029
1119
|
|
|
1030
1120
|
if (Array.isArray(node)) {
|
|
1031
|
-
return (
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1121
|
+
return (
|
|
1122
|
+
await Promise.all(
|
|
1123
|
+
node.map((child, index) => {
|
|
1124
|
+
const childPath = ctx.elementPath ? [...ctx.elementPath.slice(0, -1), index] : [index];
|
|
1125
|
+
return renderNode(child, { ...ctx, elementPath: childPath });
|
|
1126
|
+
}),
|
|
1127
|
+
)
|
|
1128
|
+
).join('');
|
|
1035
1129
|
}
|
|
1036
1130
|
|
|
1037
1131
|
if (typeof node !== 'object') return '';
|
|
@@ -1045,10 +1139,7 @@ async function renderNode(
|
|
|
1045
1139
|
const i18nResolveConfig = i18nConfig ?? DEFAULT_I18N_CONFIG;
|
|
1046
1140
|
const i18nEffectiveLocale = locale || i18nResolveConfig.defaultLocale;
|
|
1047
1141
|
const resolved = resolveI18nValue(node, i18nEffectiveLocale, i18nResolveConfig);
|
|
1048
|
-
return renderNode(
|
|
1049
|
-
resolved as ComponentNode | string | number | null | undefined,
|
|
1050
|
-
ctx
|
|
1051
|
-
);
|
|
1142
|
+
return renderNode(resolved as ComponentNode | string | number | null | undefined, ctx);
|
|
1052
1143
|
}
|
|
1053
1144
|
|
|
1054
1145
|
// Check if condition - skip rendering if false
|
|
@@ -1069,8 +1160,8 @@ async function renderNode(
|
|
|
1069
1160
|
}
|
|
1070
1161
|
|
|
1071
1162
|
const nodeType = 'type' in node ? node.type : undefined;
|
|
1072
|
-
const nodeStyle =
|
|
1073
|
-
const children =
|
|
1163
|
+
const nodeStyle = 'style' in node ? (node.style as StyleObject | ResponsiveStyleObject | undefined) : undefined;
|
|
1164
|
+
const children = 'children' in node ? node.children || [] : [];
|
|
1074
1165
|
|
|
1075
1166
|
// Handle embed nodes - render custom HTML content
|
|
1076
1167
|
if (isEmbedNode(node)) {
|
|
@@ -1106,11 +1197,198 @@ async function renderNode(
|
|
|
1106
1197
|
|
|
1107
1198
|
// Sanitize HTML with allowlist for SVG, rich-text formatting, and common elements (same as client)
|
|
1108
1199
|
const purify = getDOMPurify();
|
|
1109
|
-
const purifiedHtml = purify
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1200
|
+
const purifiedHtml = purify
|
|
1201
|
+
? purify.sanitize(htmlContent, {
|
|
1202
|
+
ALLOWED_TAGS: [
|
|
1203
|
+
'svg',
|
|
1204
|
+
'path',
|
|
1205
|
+
'circle',
|
|
1206
|
+
'rect',
|
|
1207
|
+
'line',
|
|
1208
|
+
'polyline',
|
|
1209
|
+
'polygon',
|
|
1210
|
+
'g',
|
|
1211
|
+
'text',
|
|
1212
|
+
'tspan',
|
|
1213
|
+
'image',
|
|
1214
|
+
'defs',
|
|
1215
|
+
'use',
|
|
1216
|
+
'linearGradient',
|
|
1217
|
+
'radialGradient',
|
|
1218
|
+
'stop',
|
|
1219
|
+
'clipPath',
|
|
1220
|
+
'mask',
|
|
1221
|
+
'pattern',
|
|
1222
|
+
'marker',
|
|
1223
|
+
'symbol',
|
|
1224
|
+
'a',
|
|
1225
|
+
'div',
|
|
1226
|
+
'span',
|
|
1227
|
+
'p',
|
|
1228
|
+
'br',
|
|
1229
|
+
'button',
|
|
1230
|
+
'img',
|
|
1231
|
+
'iframe',
|
|
1232
|
+
'video',
|
|
1233
|
+
'audio',
|
|
1234
|
+
'source',
|
|
1235
|
+
'canvas',
|
|
1236
|
+
'b',
|
|
1237
|
+
'i',
|
|
1238
|
+
'u',
|
|
1239
|
+
'strong',
|
|
1240
|
+
'em',
|
|
1241
|
+
'sub',
|
|
1242
|
+
'sup',
|
|
1243
|
+
'mark',
|
|
1244
|
+
's',
|
|
1245
|
+
'small',
|
|
1246
|
+
'del',
|
|
1247
|
+
'ins',
|
|
1248
|
+
'q',
|
|
1249
|
+
'abbr',
|
|
1250
|
+
'code',
|
|
1251
|
+
'pre',
|
|
1252
|
+
'blockquote',
|
|
1253
|
+
'ul',
|
|
1254
|
+
'ol',
|
|
1255
|
+
'li',
|
|
1256
|
+
'h1',
|
|
1257
|
+
'h2',
|
|
1258
|
+
'h3',
|
|
1259
|
+
'h4',
|
|
1260
|
+
'h5',
|
|
1261
|
+
'h6',
|
|
1262
|
+
'style',
|
|
1263
|
+
'animate',
|
|
1264
|
+
'animateTransform',
|
|
1265
|
+
'animateMotion',
|
|
1266
|
+
'set',
|
|
1267
|
+
'filter',
|
|
1268
|
+
'feGaussianBlur',
|
|
1269
|
+
'feOffset',
|
|
1270
|
+
'feMerge',
|
|
1271
|
+
'feMergeNode',
|
|
1272
|
+
'feColorMatrix',
|
|
1273
|
+
'feComposite',
|
|
1274
|
+
'feFlood',
|
|
1275
|
+
'feMorphology',
|
|
1276
|
+
'feBlend',
|
|
1277
|
+
'feDropShadow',
|
|
1278
|
+
'feTurbulence',
|
|
1279
|
+
'feDisplacementMap',
|
|
1280
|
+
'foreignObject',
|
|
1281
|
+
],
|
|
1282
|
+
ALLOWED_ATTR: [
|
|
1283
|
+
'class',
|
|
1284
|
+
'id',
|
|
1285
|
+
'style',
|
|
1286
|
+
'width',
|
|
1287
|
+
'height',
|
|
1288
|
+
'viewBox',
|
|
1289
|
+
'xmlns',
|
|
1290
|
+
'xmlns:xlink',
|
|
1291
|
+
'xlink:href',
|
|
1292
|
+
'fill',
|
|
1293
|
+
'stroke',
|
|
1294
|
+
'stroke-width',
|
|
1295
|
+
'stroke-linecap',
|
|
1296
|
+
'stroke-linejoin',
|
|
1297
|
+
'stroke-dasharray',
|
|
1298
|
+
'stroke-dashoffset',
|
|
1299
|
+
'd',
|
|
1300
|
+
'cx',
|
|
1301
|
+
'cy',
|
|
1302
|
+
'r',
|
|
1303
|
+
'x',
|
|
1304
|
+
'y',
|
|
1305
|
+
'x1',
|
|
1306
|
+
'y1',
|
|
1307
|
+
'x2',
|
|
1308
|
+
'y2',
|
|
1309
|
+
'points',
|
|
1310
|
+
'href',
|
|
1311
|
+
'src',
|
|
1312
|
+
'alt',
|
|
1313
|
+
'target',
|
|
1314
|
+
'rel',
|
|
1315
|
+
'data-*',
|
|
1316
|
+
'aria-*',
|
|
1317
|
+
'transform',
|
|
1318
|
+
'opacity',
|
|
1319
|
+
'fill-opacity',
|
|
1320
|
+
'fill-rule',
|
|
1321
|
+
'clip-rule',
|
|
1322
|
+
'clip-path',
|
|
1323
|
+
'clipPathUnits',
|
|
1324
|
+
'mask',
|
|
1325
|
+
'mask-type',
|
|
1326
|
+
'maskUnits',
|
|
1327
|
+
'maskContentUnits',
|
|
1328
|
+
'patternUnits',
|
|
1329
|
+
'patternContentUnits',
|
|
1330
|
+
'patternTransform',
|
|
1331
|
+
'gradientUnits',
|
|
1332
|
+
'gradientTransform',
|
|
1333
|
+
'spreadMethod',
|
|
1334
|
+
'preserveAspectRatio',
|
|
1335
|
+
'marker-start',
|
|
1336
|
+
'marker-mid',
|
|
1337
|
+
'marker-end',
|
|
1338
|
+
'markerUnits',
|
|
1339
|
+
'markerWidth',
|
|
1340
|
+
'markerHeight',
|
|
1341
|
+
'refX',
|
|
1342
|
+
'refY',
|
|
1343
|
+
'orient',
|
|
1344
|
+
'paint-order',
|
|
1345
|
+
'vector-effect',
|
|
1346
|
+
'filter',
|
|
1347
|
+
'filterUnits',
|
|
1348
|
+
'primitiveUnits',
|
|
1349
|
+
'in',
|
|
1350
|
+
'in2',
|
|
1351
|
+
'result',
|
|
1352
|
+
'stdDeviation',
|
|
1353
|
+
'flood-color',
|
|
1354
|
+
'flood-opacity',
|
|
1355
|
+
'stroke-opacity',
|
|
1356
|
+
'font-size',
|
|
1357
|
+
'font-family',
|
|
1358
|
+
'font-weight',
|
|
1359
|
+
'font-style',
|
|
1360
|
+
'text-anchor',
|
|
1361
|
+
'dominant-baseline',
|
|
1362
|
+
'offset',
|
|
1363
|
+
'stop-color',
|
|
1364
|
+
'stop-opacity',
|
|
1365
|
+
'frameborder',
|
|
1366
|
+
'allowfullscreen',
|
|
1367
|
+
'allow',
|
|
1368
|
+
'title',
|
|
1369
|
+
'attributeName',
|
|
1370
|
+
'values',
|
|
1371
|
+
'dur',
|
|
1372
|
+
'begin',
|
|
1373
|
+
'end',
|
|
1374
|
+
'repeatCount',
|
|
1375
|
+
'repeatDur',
|
|
1376
|
+
'keyTimes',
|
|
1377
|
+
'keySplines',
|
|
1378
|
+
'calcMode',
|
|
1379
|
+
'from',
|
|
1380
|
+
'to',
|
|
1381
|
+
'by',
|
|
1382
|
+
'additive',
|
|
1383
|
+
'accumulate',
|
|
1384
|
+
'type',
|
|
1385
|
+
'rotate',
|
|
1386
|
+
'keyPoints',
|
|
1387
|
+
'path',
|
|
1388
|
+
],
|
|
1389
|
+
KEEP_CONTENT: true,
|
|
1390
|
+
})
|
|
1391
|
+
: htmlContent;
|
|
1114
1392
|
// Inline simple SVG <style> rules as style="" attributes — class-scoped
|
|
1115
1393
|
// declarations don't survive the editor preview's HMR `innerHTML` reparse
|
|
1116
1394
|
// path (`htmlGenerator.ts:574`), but inline attributes always render.
|
|
@@ -1144,7 +1422,7 @@ async function renderNode(
|
|
|
1144
1422
|
embedCssVariables = registerInteractiveStyles(ctx, elementClass, embedInteractiveStyles);
|
|
1145
1423
|
}
|
|
1146
1424
|
|
|
1147
|
-
|
|
1425
|
+
const embedStyleAttr = buildCssVariableStyleAttr(embedCssVariables);
|
|
1148
1426
|
|
|
1149
1427
|
// Add attribute className if present
|
|
1150
1428
|
const attrClassName = (nodeAttributes.className || nodeAttributes.class || '') as string;
|
|
@@ -1191,7 +1469,11 @@ async function renderNode(
|
|
|
1191
1469
|
// Link object - extract href and target
|
|
1192
1470
|
// Unwrap nested link objects (e.g., {href: {href: "/path"}} from double-wrapped list item templates)
|
|
1193
1471
|
let linkObj = rawValue as { href: unknown; target?: string };
|
|
1194
|
-
while (
|
|
1472
|
+
while (
|
|
1473
|
+
typeof linkObj.href === 'object' &&
|
|
1474
|
+
linkObj.href !== null &&
|
|
1475
|
+
'href' in (linkObj.href as Record<string, unknown>)
|
|
1476
|
+
) {
|
|
1195
1477
|
const nested = linkObj.href as { href: unknown; target?: string };
|
|
1196
1478
|
if (!linkObj.target && nested.target) linkObj = { ...linkObj, target: nested.target };
|
|
1197
1479
|
linkObj = { ...linkObj, href: nested.href };
|
|
@@ -1262,11 +1544,18 @@ async function renderNode(
|
|
|
1262
1544
|
const classAttr = ` class="${escapeHtml(classNames.filter(Boolean).join(' '))}"`;
|
|
1263
1545
|
|
|
1264
1546
|
const childrenHTML = Array.isArray(children)
|
|
1265
|
-
? (
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1547
|
+
? (
|
|
1548
|
+
await Promise.all(
|
|
1549
|
+
children.map((child, index) => {
|
|
1550
|
+
const childPath = ctx.elementPath ? [...ctx.elementPath, index] : [index];
|
|
1551
|
+
return renderNode(child, { ...ctx, elementPath: childPath });
|
|
1552
|
+
}),
|
|
1553
|
+
)
|
|
1554
|
+
).join('')
|
|
1555
|
+
: await renderNode(children as ComponentNode | string | number | null | undefined, {
|
|
1556
|
+
...ctx,
|
|
1557
|
+
elementPath: ctx.elementPath ? [...ctx.elementPath, 0] : [0],
|
|
1558
|
+
});
|
|
1270
1559
|
|
|
1271
1560
|
return `<a href="${escapeHtml(String(href))}"${classAttr}${olinkStyleAttr}${attrs}${editorAttrs(ctx, { isSlotContent: isSlotContent(node) })}>${childrenHTML}</a>`;
|
|
1272
1561
|
}
|
|
@@ -1277,9 +1566,9 @@ async function renderNode(
|
|
|
1277
1566
|
}
|
|
1278
1567
|
|
|
1279
1568
|
// Extract tag or component name based on node type
|
|
1280
|
-
|
|
1569
|
+
const tag = isHtmlNode(node) ? node.tag : undefined;
|
|
1281
1570
|
const componentName = isComponentNode(node) ? node.component : undefined;
|
|
1282
|
-
let nodeProps = isComponentNode(node) ?
|
|
1571
|
+
let nodeProps = isComponentNode(node) ? node.props || {} : {};
|
|
1283
1572
|
|
|
1284
1573
|
// Process CMS templates in props
|
|
1285
1574
|
if (ctx.cmsContext?.cms && Object.keys(nodeProps).length > 0) {
|
|
@@ -1302,18 +1591,29 @@ async function renderNode(
|
|
|
1302
1591
|
|
|
1303
1592
|
// Process CMS templates in attributes (e.g., href="{{cms.link}}")
|
|
1304
1593
|
if (ctx.cmsContext?.cms && Object.keys(nodeAttributes).length > 0) {
|
|
1305
|
-
nodeAttributes = processCMSPropsTemplate(
|
|
1594
|
+
nodeAttributes = processCMSPropsTemplate(
|
|
1595
|
+
nodeAttributes as Record<string, unknown>,
|
|
1596
|
+
ctx.cmsContext.cms,
|
|
1597
|
+
locale,
|
|
1598
|
+
i18nConfig,
|
|
1599
|
+
) as Record<string, string | number | boolean>;
|
|
1306
1600
|
}
|
|
1307
1601
|
|
|
1308
1602
|
// Process item templates in attributes (for CMSList context)
|
|
1309
1603
|
// Skip when in templateMode to preserve {{item.field}} placeholders
|
|
1310
1604
|
if (!ctx.templateMode && templateCtx && Object.keys(nodeAttributes).length > 0) {
|
|
1311
|
-
nodeAttributes = processItemPropsTemplate(nodeAttributes, templateCtx, i18nResolver) as Record<
|
|
1605
|
+
nodeAttributes = processItemPropsTemplate(nodeAttributes, templateCtx, i18nResolver) as Record<
|
|
1606
|
+
string,
|
|
1607
|
+
string | number | boolean
|
|
1608
|
+
>;
|
|
1312
1609
|
}
|
|
1313
1610
|
|
|
1314
1611
|
// Auto-skip attributes that were entirely template expressions and resolved to ""
|
|
1315
1612
|
if (Object.keys(nodeAttributes).length > 0) {
|
|
1316
|
-
nodeAttributes = skipEmptyTemplateAttributes(originalAttributes, nodeAttributes) as Record<
|
|
1613
|
+
nodeAttributes = skipEmptyTemplateAttributes(originalAttributes, nodeAttributes) as Record<
|
|
1614
|
+
string,
|
|
1615
|
+
string | number | boolean
|
|
1616
|
+
>;
|
|
1317
1617
|
}
|
|
1318
1618
|
|
|
1319
1619
|
if (!tag && !componentName) return '';
|
|
@@ -1327,7 +1627,8 @@ async function renderNode(
|
|
|
1327
1627
|
// result. This matches the client (ComponentBuilder.mergeNodeStyles) so that
|
|
1328
1628
|
// an instance override of e.g. marginBottom actually replaces the structure's
|
|
1329
1629
|
// marginBottom instead of fighting it as a same-specificity utility class.
|
|
1330
|
-
const isCustomComponentNode =
|
|
1630
|
+
const isCustomComponentNode =
|
|
1631
|
+
nodeType === NODE_TYPE.COMPONENT && isComponentNode(node) && ssrComponentRegistry.has(node.component);
|
|
1331
1632
|
let deferredComponentStyle: StyleObject | ResponsiveStyleObject | undefined;
|
|
1332
1633
|
|
|
1333
1634
|
if (nodeStyle) {
|
|
@@ -1343,16 +1644,23 @@ async function renderNode(
|
|
|
1343
1644
|
} else if (nodeProps.style) {
|
|
1344
1645
|
// If no node.style but props have style, keep it for backward compatibility
|
|
1345
1646
|
if (isResponsiveStyle(nodeProps.style) && breakpoints && viewportWidth) {
|
|
1346
|
-
resolvedStyle = mergeResponsiveStyles(
|
|
1647
|
+
resolvedStyle = mergeResponsiveStyles(
|
|
1648
|
+
nodeProps.style as ResponsiveStyleObject,
|
|
1649
|
+
'viewport',
|
|
1650
|
+
viewportWidth,
|
|
1651
|
+
breakpoints,
|
|
1652
|
+
);
|
|
1347
1653
|
} else {
|
|
1348
1654
|
resolvedStyle = nodeProps.style as StyleObject;
|
|
1349
1655
|
}
|
|
1350
1656
|
}
|
|
1351
1657
|
|
|
1352
1658
|
// Generate element-specific class if node has interactive styles or generateElementClass flag
|
|
1353
|
-
const nodeInteractiveStyles =
|
|
1354
|
-
|
|
1355
|
-
const
|
|
1659
|
+
const nodeInteractiveStyles =
|
|
1660
|
+
'interactiveStyles' in node ? (node.interactiveStyles as InteractiveStyles | undefined) : undefined;
|
|
1661
|
+
const nodeGenerateElementClass =
|
|
1662
|
+
'generateElementClass' in node ? (node.generateElementClass as boolean | undefined) : undefined;
|
|
1663
|
+
const nodeLabel = 'label' in node ? (node.label as string | undefined) : undefined;
|
|
1356
1664
|
let elementClass = '';
|
|
1357
1665
|
|
|
1358
1666
|
if ((nodeInteractiveStyles && nodeInteractiveStyles.length > 0) || nodeGenerateElementClass) {
|
|
@@ -1387,7 +1695,9 @@ async function renderNode(
|
|
|
1387
1695
|
...(mergedClassName ? { className: mergedClassName } : {}),
|
|
1388
1696
|
...(deferredComponentStyle
|
|
1389
1697
|
? { style: deferredComponentStyle }
|
|
1390
|
-
: Object.keys(resolvedStyle).length > 0
|
|
1698
|
+
: Object.keys(resolvedStyle).length > 0
|
|
1699
|
+
? { style: resolvedStyle }
|
|
1700
|
+
: {}),
|
|
1391
1701
|
};
|
|
1392
1702
|
|
|
1393
1703
|
// Check if this is a custom component
|
|
@@ -1406,13 +1716,11 @@ async function renderNode(
|
|
|
1406
1716
|
return renderLinkNode(propsWithStyleAndAttrs, children, ctx);
|
|
1407
1717
|
}
|
|
1408
1718
|
|
|
1409
|
-
// Regular HTML element
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
return renderHtmlElement(tag, propsWithStyleAndAttrs, children, ctx);
|
|
1719
|
+
// Regular HTML element. `tag` is necessarily set here: the `!tag &&
|
|
1720
|
+
// !componentName` case returned at the top of this section, and every
|
|
1721
|
+
// `componentName` path has already returned (isComponentNode ⇒ type ===
|
|
1722
|
+
// 'component', which the two component branches above handle exhaustively).
|
|
1723
|
+
return renderHtmlElement(tag as string, propsWithStyleAndAttrs, children, ctx);
|
|
1416
1724
|
}
|
|
1417
1725
|
|
|
1418
1726
|
/**
|
|
@@ -1423,7 +1731,7 @@ async function renderComponent(
|
|
|
1423
1731
|
propsWithStyleAndAttrs: Record<string, unknown>,
|
|
1424
1732
|
children: unknown,
|
|
1425
1733
|
nodeAttributes: Record<string, unknown>,
|
|
1426
|
-
ctx: SSRContext
|
|
1734
|
+
ctx: SSRContext,
|
|
1427
1735
|
): Promise<string> {
|
|
1428
1736
|
const { locale, i18nConfig, pagePath } = ctx;
|
|
1429
1737
|
const componentDef = ssrComponentRegistry.get(componentName);
|
|
@@ -1439,7 +1747,7 @@ async function renderComponent(
|
|
|
1439
1747
|
propsWithStyleAndAttrs,
|
|
1440
1748
|
children as Array<ComponentNode | string> | string | ComponentNode | null | undefined,
|
|
1441
1749
|
locale,
|
|
1442
|
-
i18nConfig
|
|
1750
|
+
i18nConfig,
|
|
1443
1751
|
);
|
|
1444
1752
|
|
|
1445
1753
|
// Process the structure with resolved props
|
|
@@ -1447,7 +1755,9 @@ async function renderComponent(
|
|
|
1447
1755
|
// Use preserveResponsiveStyles: true to keep responsive styles intact for SSR rendering
|
|
1448
1756
|
// Mark children as slot content so they use page context for element class generation
|
|
1449
1757
|
// Pass parent template context (itemContext) for nested cms-list template resolution
|
|
1450
|
-
const markedChildren = children
|
|
1758
|
+
const markedChildren = children
|
|
1759
|
+
? markAsSlotContent(children as ComponentNode | ComponentNode[] | string | number | null | undefined)
|
|
1760
|
+
: undefined;
|
|
1451
1761
|
|
|
1452
1762
|
const processedStructure = processStructure(
|
|
1453
1763
|
structuredComponentDef.structure,
|
|
@@ -1464,20 +1774,28 @@ async function renderComponent(
|
|
|
1464
1774
|
},
|
|
1465
1775
|
undefined,
|
|
1466
1776
|
markedChildren,
|
|
1467
|
-
true
|
|
1777
|
+
true,
|
|
1468
1778
|
);
|
|
1469
1779
|
|
|
1470
1780
|
if (!processedStructure) return '';
|
|
1471
1781
|
|
|
1472
1782
|
// Type guard: ensure processedStructure is a ComponentNode
|
|
1473
|
-
if (
|
|
1783
|
+
if (
|
|
1784
|
+
typeof processedStructure === 'string' ||
|
|
1785
|
+
typeof processedStructure === 'number' ||
|
|
1786
|
+
Array.isArray(processedStructure)
|
|
1787
|
+
) {
|
|
1474
1788
|
return await renderNode(processedStructure as string | number | ComponentNode[], ctx);
|
|
1475
1789
|
}
|
|
1476
1790
|
|
|
1477
1791
|
// Merge instance style overrides, className, and attributes
|
|
1478
1792
|
// processedStructure is typically an HTML node (the component's root element)
|
|
1479
1793
|
// Handle component nodes, HTML nodes, and Link nodes (Link and HTML share top-level style/attributes)
|
|
1480
|
-
const rootNode = processedStructure as ComponentNode & {
|
|
1794
|
+
const rootNode = processedStructure as ComponentNode & {
|
|
1795
|
+
props?: Record<string, unknown>;
|
|
1796
|
+
style?: any;
|
|
1797
|
+
attributes?: Record<string, string | number | boolean>;
|
|
1798
|
+
};
|
|
1481
1799
|
if (isComponentNode(rootNode) || isHtmlNode(rootNode) || isLinkNode(rootNode)) {
|
|
1482
1800
|
if (!rootNode.props) {
|
|
1483
1801
|
rootNode.props = {};
|
|
@@ -1499,7 +1817,7 @@ async function renderComponent(
|
|
|
1499
1817
|
const existingClass = (rootNode.attributes.class || '') as string;
|
|
1500
1818
|
rootNode.attributes.class = existingClass
|
|
1501
1819
|
? `${existingClass} ${propsWithStyleAndAttrs.className}`
|
|
1502
|
-
: propsWithStyleAndAttrs.className as string;
|
|
1820
|
+
: (propsWithStyleAndAttrs.className as string);
|
|
1503
1821
|
} else {
|
|
1504
1822
|
const existingClassName = rootNode.props.className || '';
|
|
1505
1823
|
rootNode.props.className = existingClassName
|
|
@@ -1527,9 +1845,7 @@ async function renderComponent(
|
|
|
1527
1845
|
// Add defineVars data attributes for JS prop injection
|
|
1528
1846
|
const defineVars = structuredComponentDef.defineVars;
|
|
1529
1847
|
if (defineVars && componentName) {
|
|
1530
|
-
const varsToExpose = defineVars === true
|
|
1531
|
-
? Object.keys(structuredComponentDef.interface || {})
|
|
1532
|
-
: defineVars;
|
|
1848
|
+
const varsToExpose = defineVars === true ? Object.keys(structuredComponentDef.interface || {}) : defineVars;
|
|
1533
1849
|
|
|
1534
1850
|
const propsForJS: Record<string, unknown> = {};
|
|
1535
1851
|
for (const varName of varsToExpose) {
|
|
@@ -1543,18 +1859,21 @@ async function renderComponent(
|
|
|
1543
1859
|
if (!processedRoot.attributes) {
|
|
1544
1860
|
processedRoot.attributes = {};
|
|
1545
1861
|
}
|
|
1546
|
-
const existingComponent =
|
|
1547
|
-
|
|
1548
|
-
|
|
1862
|
+
const existingComponent =
|
|
1863
|
+
(processedRoot.attributes?.['data-component'] as string) ||
|
|
1864
|
+
(processedRoot.props?.['data-component'] as string) ||
|
|
1865
|
+
'';
|
|
1549
1866
|
processedRoot.attributes['data-component'] = existingComponent
|
|
1550
1867
|
? `${existingComponent} ${componentName}`
|
|
1551
1868
|
: componentName;
|
|
1552
1869
|
|
|
1553
1870
|
let existingPropsMap: Record<string, unknown> = {};
|
|
1554
|
-
const existingPropsStr =
|
|
1555
|
-
|| (processedRoot.props?.['data-props'] as string);
|
|
1871
|
+
const existingPropsStr =
|
|
1872
|
+
(processedRoot.attributes?.['data-props'] as string) || (processedRoot.props?.['data-props'] as string);
|
|
1556
1873
|
if (existingPropsStr) {
|
|
1557
|
-
try {
|
|
1874
|
+
try {
|
|
1875
|
+
existingPropsMap = JSON.parse(existingPropsStr);
|
|
1876
|
+
} catch {}
|
|
1558
1877
|
}
|
|
1559
1878
|
existingPropsMap[componentName] = propsForJS;
|
|
1560
1879
|
processedRoot.attributes['data-props'] = JSON.stringify(existingPropsMap);
|
|
@@ -1593,7 +1912,6 @@ async function renderComponent(
|
|
|
1593
1912
|
componentRootPath: ctx.elementPath,
|
|
1594
1913
|
componentResolvedProps: resolvedProps,
|
|
1595
1914
|
});
|
|
1596
|
-
|
|
1597
1915
|
} catch (error) {
|
|
1598
1916
|
return renderNodeFallback(ctx, error, `component ${componentName}`);
|
|
1599
1917
|
}
|
|
@@ -1605,7 +1923,7 @@ async function renderComponent(
|
|
|
1605
1923
|
async function renderLinkNode(
|
|
1606
1924
|
propsWithStyleAndAttrs: Record<string, unknown>,
|
|
1607
1925
|
children: unknown,
|
|
1608
|
-
ctx: SSRContext
|
|
1926
|
+
ctx: SSRContext,
|
|
1609
1927
|
): Promise<string> {
|
|
1610
1928
|
const to = 'to' in propsWithStyleAndAttrs ? propsWithStyleAndAttrs.to : undefined;
|
|
1611
1929
|
const restProps: Record<string, unknown> = { ...propsWithStyleAndAttrs };
|
|
@@ -1614,17 +1932,22 @@ async function renderLinkNode(
|
|
|
1614
1932
|
const href = localizeHref(rawHref, ctx);
|
|
1615
1933
|
|
|
1616
1934
|
// Build class attribute from utility classes
|
|
1617
|
-
const linkClassAttr = restProps.className
|
|
1618
|
-
? ` class="${escapeHtml(String(restProps.className))}"`
|
|
1619
|
-
: '';
|
|
1935
|
+
const linkClassAttr = restProps.className ? ` class="${escapeHtml(String(restProps.className))}"` : '';
|
|
1620
1936
|
|
|
1621
1937
|
const attrs = buildAttributes(restProps, ['style', 'className', 'to']);
|
|
1622
1938
|
const childrenHTML = Array.isArray(children)
|
|
1623
|
-
? (
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1939
|
+
? (
|
|
1940
|
+
await Promise.all(
|
|
1941
|
+
(children as (ComponentNode | string)[]).map((child, index) => {
|
|
1942
|
+
const childPath = ctx.elementPath ? [...ctx.elementPath, index] : [index];
|
|
1943
|
+
return renderNode(child, { ...ctx, elementPath: childPath });
|
|
1944
|
+
}),
|
|
1945
|
+
)
|
|
1946
|
+
).join('')
|
|
1947
|
+
: await renderNode(children as ComponentNode, {
|
|
1948
|
+
...ctx,
|
|
1949
|
+
elementPath: ctx.elementPath ? [...ctx.elementPath, 0] : [0],
|
|
1950
|
+
});
|
|
1628
1951
|
|
|
1629
1952
|
return `<a href="${escapeHtml(String(href))}"${linkClassAttr}${attrs}${editorAttrs(ctx)}>${childrenHTML}</a>`;
|
|
1630
1953
|
}
|
|
@@ -1636,7 +1959,7 @@ async function renderHtmlElement(
|
|
|
1636
1959
|
tag: string,
|
|
1637
1960
|
propsWithStyleAndAttrs: Record<string, unknown>,
|
|
1638
1961
|
children: unknown,
|
|
1639
|
-
ctx: SSRContext
|
|
1962
|
+
ctx: SSRContext,
|
|
1640
1963
|
): Promise<string> {
|
|
1641
1964
|
// Build class attribute from utility classes
|
|
1642
1965
|
let classValue = propsWithStyleAndAttrs.className ? String(propsWithStyleAndAttrs.className) : '';
|
|
@@ -1663,9 +1986,7 @@ async function renderHtmlElement(
|
|
|
1663
1986
|
|
|
1664
1987
|
// For img tags, exclude image-specific props (handled specially in renderImageElement)
|
|
1665
1988
|
const imageProps = ['src', 'alt', 'loading', 'width', 'height', 'sizes', 'srcset', 'fetchpriority'];
|
|
1666
|
-
const excludeProps = tag.toLowerCase() === 'img'
|
|
1667
|
-
? ['style', 'className', ...imageProps]
|
|
1668
|
-
: ['style', 'className'];
|
|
1989
|
+
const excludeProps = tag.toLowerCase() === 'img' ? ['style', 'className', ...imageProps] : ['style', 'className'];
|
|
1669
1990
|
const attrs = buildAttributes(propsWithStyleAndAttrs, excludeProps);
|
|
1670
1991
|
// `<style>` and `<script>` are HTML "raw text" elements: their content is
|
|
1671
1992
|
// not parsed for entities or child elements. Running text children through
|
|
@@ -1682,22 +2003,55 @@ async function renderHtmlElement(
|
|
|
1682
2003
|
if (typeof node === 'string') return node;
|
|
1683
2004
|
if (typeof node === 'number') return String(node);
|
|
1684
2005
|
if (Array.isArray(node)) return node.map(flatten).join('');
|
|
2006
|
+
// Verbatim-code markers ({ _code, expr }) are silent by design — they
|
|
2007
|
+
// render natively in the Astro build and have nothing to emit in
|
|
2008
|
+
// meno-core SSR (mirrors the renderNode top-level handling).
|
|
2009
|
+
if ((node as { _code?: unknown })._code === true) return '';
|
|
2010
|
+
// Raw-text elements can't contain child elements, so any other object
|
|
2011
|
+
// child is dropped from the output — correct per HTML semantics, but
|
|
2012
|
+
// authored content vanishing should be visible, so surface the drop.
|
|
2013
|
+
logRuntimeError(
|
|
2014
|
+
'ssrRenderer.renderHtmlElement',
|
|
2015
|
+
new Error(`Element child dropped inside <${tag}> — raw-text elements render only string content`),
|
|
2016
|
+
{ tag, droppedNodeType: String((node as { type?: unknown }).type ?? typeof node) },
|
|
2017
|
+
);
|
|
1685
2018
|
return '';
|
|
1686
2019
|
};
|
|
1687
2020
|
childrenHTML = flatten(children as any);
|
|
1688
2021
|
} else {
|
|
1689
2022
|
childrenHTML = Array.isArray(children)
|
|
1690
|
-
? (
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
2023
|
+
? (
|
|
2024
|
+
await Promise.all(
|
|
2025
|
+
(children as (ComponentNode | string)[]).map((child, index) => {
|
|
2026
|
+
const childPath = ctx.elementPath ? [...ctx.elementPath, index] : [index];
|
|
2027
|
+
return renderNode(child, { ...ctx, elementPath: childPath });
|
|
2028
|
+
}),
|
|
2029
|
+
)
|
|
2030
|
+
).join('')
|
|
2031
|
+
: await renderNode(children as ComponentNode, {
|
|
2032
|
+
...ctx,
|
|
2033
|
+
elementPath: ctx.elementPath ? [...ctx.elementPath, 0] : [0],
|
|
2034
|
+
});
|
|
1695
2035
|
}
|
|
1696
2036
|
|
|
1697
2037
|
const ea = editorAttrs(ctx);
|
|
1698
2038
|
|
|
1699
2039
|
// Self-closing tags
|
|
1700
|
-
const voidElements = [
|
|
2040
|
+
const voidElements = [
|
|
2041
|
+
'img',
|
|
2042
|
+
'input',
|
|
2043
|
+
'br',
|
|
2044
|
+
'hr',
|
|
2045
|
+
'meta',
|
|
2046
|
+
'link',
|
|
2047
|
+
'area',
|
|
2048
|
+
'base',
|
|
2049
|
+
'col',
|
|
2050
|
+
'embed',
|
|
2051
|
+
'source',
|
|
2052
|
+
'track',
|
|
2053
|
+
'wbr',
|
|
2054
|
+
];
|
|
1701
2055
|
if (voidElements.includes(tag.toLowerCase())) {
|
|
1702
2056
|
// Special handling for img tags - inject srcset and render as <picture> when AVIF available
|
|
1703
2057
|
if (tag.toLowerCase() === 'img') {
|
|
@@ -1718,7 +2072,7 @@ function renderImageElement(
|
|
|
1718
2072
|
classAttr: string,
|
|
1719
2073
|
styleAttr: string,
|
|
1720
2074
|
attrs: string,
|
|
1721
|
-
ctx: SSRContext
|
|
2075
|
+
ctx: SSRContext,
|
|
1722
2076
|
): string {
|
|
1723
2077
|
const imgProps = propsWithStyleAndAttrs;
|
|
1724
2078
|
const src = imgProps.src as string | undefined;
|
|
@@ -1726,8 +2080,8 @@ function renderImageElement(
|
|
|
1726
2080
|
const loading = imgProps.loading as string | undefined;
|
|
1727
2081
|
const sizes = imgProps.sizes as string | undefined;
|
|
1728
2082
|
const fetchpriority = imgProps.fetchpriority as string | undefined;
|
|
1729
|
-
|
|
1730
|
-
|
|
2083
|
+
const width = imgProps.width as string | number | undefined;
|
|
2084
|
+
const height = imgProps.height as string | number | undefined;
|
|
1731
2085
|
|
|
1732
2086
|
// Get image metadata from manifest
|
|
1733
2087
|
const metadata = src ? ctx.imageMetadataMap?.get(src) : undefined;
|
|
@@ -1766,14 +2120,23 @@ function renderImageElement(
|
|
|
1766
2120
|
if (width !== undefined) imgAttrs += ` width="${escapeHtml(String(width))}"`;
|
|
1767
2121
|
if (height !== undefined) imgAttrs += ` height="${escapeHtml(String(height))}"`;
|
|
1768
2122
|
|
|
1769
|
-
|
|
1770
2123
|
// Render as <picture> element if AVIF is available and imageFormat is 'avif'
|
|
1771
2124
|
// Layout classes go on <picture> (block container), image-specific classes on <img>
|
|
1772
2125
|
if (metadata?.avifSrcset && ctx.imageFormat !== 'webp') {
|
|
1773
2126
|
// Image-specific class prefixes that should stay on <img>
|
|
1774
2127
|
const imgClassPrefixes = [
|
|
1775
|
-
'objf-',
|
|
1776
|
-
'
|
|
2128
|
+
'objf-',
|
|
2129
|
+
'objp-',
|
|
2130
|
+
'flt-',
|
|
2131
|
+
'tm-',
|
|
2132
|
+
'bs-',
|
|
2133
|
+
'br-',
|
|
2134
|
+
'bt-',
|
|
2135
|
+
'bb-',
|
|
2136
|
+
'bl-',
|
|
2137
|
+
'border-r-',
|
|
2138
|
+
'bc-',
|
|
2139
|
+
'b-',
|
|
1777
2140
|
];
|
|
1778
2141
|
// Opacity classes (o-NUMBER) go on img, but overflow (o-h, o-a, o-s, o-v) stays on picture
|
|
1779
2142
|
const opacityPattern = /^o-\d/;
|
|
@@ -1785,25 +2148,23 @@ function renderImageElement(
|
|
|
1785
2148
|
const pictureClasses: string[] = [];
|
|
1786
2149
|
|
|
1787
2150
|
for (const cls of allClasses) {
|
|
1788
|
-
if (imgClassPrefixes.some(prefix => cls.startsWith(prefix)) || opacityPattern.test(cls)) {
|
|
2151
|
+
if (imgClassPrefixes.some((prefix) => cls.startsWith(prefix)) || opacityPattern.test(cls)) {
|
|
1789
2152
|
imgClasses.push(cls);
|
|
1790
2153
|
} else {
|
|
1791
2154
|
pictureClasses.push(cls);
|
|
1792
2155
|
}
|
|
1793
2156
|
}
|
|
1794
2157
|
|
|
1795
|
-
const pictureClassAttr = pictureClasses.length > 0
|
|
1796
|
-
|
|
1797
|
-
: '';
|
|
1798
|
-
const imgClassAttr = imgClasses.length > 0
|
|
1799
|
-
? ` class="${escapeHtml(imgClasses.join(' '))}"`
|
|
1800
|
-
: '';
|
|
2158
|
+
const pictureClassAttr = pictureClasses.length > 0 ? ` class="${escapeHtml(pictureClasses.join(' '))}"` : '';
|
|
2159
|
+
const imgClassAttr = imgClasses.length > 0 ? ` class="${escapeHtml(imgClasses.join(' '))}"` : '';
|
|
1801
2160
|
|
|
1802
|
-
return
|
|
2161
|
+
return (
|
|
2162
|
+
`<picture${pictureClassAttr}${styleAttr}>` +
|
|
1803
2163
|
`<source type="image/avif" srcset="${escapeHtml(metadata.avifSrcset)}" sizes="${escapeHtml(sizesAttr)}" />` +
|
|
1804
2164
|
`<source type="image/webp" srcset="${escapeHtml(metadata.srcset)}" sizes="${escapeHtml(sizesAttr)}" />` +
|
|
1805
2165
|
`<img${imgClassAttr}${imgAttrs}${attrs} />` +
|
|
1806
|
-
`</picture
|
|
2166
|
+
`</picture>`
|
|
2167
|
+
);
|
|
1807
2168
|
}
|
|
1808
2169
|
|
|
1809
2170
|
// Fallback: regular img with WebP srcset
|
|
@@ -1839,8 +2200,7 @@ function renderLocaleList(node: import('../../shared/types').LocaleListNode, ctx
|
|
|
1839
2200
|
}
|
|
1840
2201
|
|
|
1841
2202
|
const localeStyleOpts = {
|
|
1842
|
-
fluidActive:
|
|
1843
|
-
ctx.responsiveScales?.enabled === true && ctx.responsiveScales?.mode === 'fluid',
|
|
2203
|
+
fluidActive: ctx.responsiveScales?.enabled === true && ctx.responsiveScales?.mode === 'fluid',
|
|
1844
2204
|
responsiveScales: ctx.responsiveScales,
|
|
1845
2205
|
};
|
|
1846
2206
|
|
|
@@ -1866,18 +2226,14 @@ function renderLocaleList(node: import('../../shared/types').LocaleListNode, ctx
|
|
|
1866
2226
|
localeListStyleAttr = buildCssVariableStyleAttr(localeListCssVariables);
|
|
1867
2227
|
}
|
|
1868
2228
|
|
|
1869
|
-
const containerClassAttr = containerClasses.length > 0
|
|
1870
|
-
? ` class="${escapeHtml(containerClasses.join(' '))}"`
|
|
1871
|
-
: '';
|
|
2229
|
+
const containerClassAttr = containerClasses.length > 0 ? ` class="${escapeHtml(containerClasses.join(' '))}"` : '';
|
|
1872
2230
|
|
|
1873
2231
|
// Convert item styles to utility classes
|
|
1874
2232
|
let itemClasses: string[] = [];
|
|
1875
2233
|
if (node.itemStyle) {
|
|
1876
2234
|
itemClasses = responsiveStylesToClasses(node.itemStyle as ResponsiveStyleObject, localeStyleOpts);
|
|
1877
2235
|
}
|
|
1878
|
-
const itemClassAttr = itemClasses.length > 0
|
|
1879
|
-
? ` class="${escapeHtml(itemClasses.join(' '))}"`
|
|
1880
|
-
: '';
|
|
2236
|
+
const itemClassAttr = itemClasses.length > 0 ? ` class="${escapeHtml(itemClasses.join(' '))}"` : '';
|
|
1881
2237
|
|
|
1882
2238
|
// Convert active item styles to utility classes
|
|
1883
2239
|
let activeItemClasses: string[] = [];
|
|
@@ -1890,24 +2246,19 @@ function renderLocaleList(node: import('../../shared/types').LocaleListNode, ctx
|
|
|
1890
2246
|
if (node.separatorStyle) {
|
|
1891
2247
|
separatorClasses = responsiveStylesToClasses(node.separatorStyle as ResponsiveStyleObject, localeStyleOpts);
|
|
1892
2248
|
}
|
|
1893
|
-
const separatorClassAttr = separatorClasses.length > 0
|
|
1894
|
-
? ` class="${escapeHtml(separatorClasses.join(' '))}"`
|
|
1895
|
-
: '';
|
|
2249
|
+
const separatorClassAttr = separatorClasses.length > 0 ? ` class="${escapeHtml(separatorClasses.join(' '))}"` : '';
|
|
1896
2250
|
|
|
1897
2251
|
// Convert flag styles to utility classes
|
|
1898
2252
|
let flagClasses: string[] = [];
|
|
1899
2253
|
if (node.flagStyle) {
|
|
1900
2254
|
flagClasses = responsiveStylesToClasses(node.flagStyle as ResponsiveStyleObject, localeStyleOpts);
|
|
1901
2255
|
}
|
|
1902
|
-
const flagClassAttr = flagClasses.length > 0
|
|
1903
|
-
? ` class="${escapeHtml(flagClasses.join(' '))}"`
|
|
1904
|
-
: '';
|
|
2256
|
+
const flagClassAttr = flagClasses.length > 0 ? ` class="${escapeHtml(flagClasses.join(' '))}"` : '';
|
|
1905
2257
|
|
|
1906
2258
|
// Current item gets both itemStyle + activeItemStyle (additive/override)
|
|
1907
2259
|
const currentItemClasses = [...itemClasses, ...activeItemClasses];
|
|
1908
|
-
const currentItemClassAttr =
|
|
1909
|
-
? ` class="${escapeHtml(currentItemClasses.join(' '))}"`
|
|
1910
|
-
: '';
|
|
2260
|
+
const currentItemClassAttr =
|
|
2261
|
+
currentItemClasses.length > 0 ? ` class="${escapeHtml(currentItemClasses.join(' '))}"` : '';
|
|
1911
2262
|
|
|
1912
2263
|
// Build links HTML
|
|
1913
2264
|
const links: string[] = [];
|
|
@@ -1940,13 +2291,13 @@ function renderLocaleList(node: import('../../shared/types').LocaleListNode, ctx
|
|
|
1940
2291
|
}
|
|
1941
2292
|
linkContent += `<div>${escapeHtml(displayText)}</div>`;
|
|
1942
2293
|
|
|
1943
|
-
links.push(
|
|
2294
|
+
links.push(
|
|
2295
|
+
`<a href="${escapeHtml(link.path)}"${hreflangAttr}${currentAttr} data-locale="${escapeHtml(link.locale)}"${classAttrForLink}>${linkContent}</a>`,
|
|
2296
|
+
);
|
|
1944
2297
|
}
|
|
1945
2298
|
|
|
1946
2299
|
// Join links with separator (empty span styled via separatorStyle) or concatenate directly
|
|
1947
|
-
const linksHTML = showSeparator
|
|
1948
|
-
? links.join(`<span${separatorClassAttr}></span>`)
|
|
1949
|
-
: links.join('');
|
|
2300
|
+
const linksHTML = showSeparator ? links.join(`<span${separatorClassAttr}></span>`) : links.join('');
|
|
1950
2301
|
|
|
1951
2302
|
// Extract attributes from node
|
|
1952
2303
|
const nodeAttributes = resolveI18nAttrs(extractAttributesFromNode(node), locale, i18nConfig);
|
|
@@ -1979,8 +2330,20 @@ export async function renderPageSSR(
|
|
|
1979
2330
|
cmsContext?: CMSContext,
|
|
1980
2331
|
cmsService?: CMSService,
|
|
1981
2332
|
isProductionBuild?: boolean,
|
|
1982
|
-
injectEditorAttrs?: boolean
|
|
1983
|
-
): Promise<{
|
|
2333
|
+
injectEditorAttrs?: boolean,
|
|
2334
|
+
): Promise<{
|
|
2335
|
+
html: string;
|
|
2336
|
+
meta: string;
|
|
2337
|
+
title: string;
|
|
2338
|
+
javascript: string;
|
|
2339
|
+
componentCSS?: string;
|
|
2340
|
+
locale: string;
|
|
2341
|
+
interactiveStylesMap: Map<string, InteractiveStyles>;
|
|
2342
|
+
preloadImages: PreloadImage[];
|
|
2343
|
+
neededCollections: Set<string>;
|
|
2344
|
+
ssrFallbackCollector: Map<string, string>;
|
|
2345
|
+
processedRawHtmlCollector: Map<string, string>;
|
|
2346
|
+
}> {
|
|
1984
2347
|
// Extract page content
|
|
1985
2348
|
const rootNode = pageData?.root || undefined;
|
|
1986
2349
|
if (!rootNode) {
|
|
@@ -1988,7 +2351,7 @@ export async function renderPageSSR(
|
|
|
1988
2351
|
}
|
|
1989
2352
|
|
|
1990
2353
|
// Load i18n config if not provided
|
|
1991
|
-
const config = i18nConfig || await loadI18nConfig();
|
|
2354
|
+
const config = i18nConfig || (await loadI18nConfig());
|
|
1992
2355
|
|
|
1993
2356
|
// Extract locale from path if not explicitly provided
|
|
1994
2357
|
const { locale: pathLocale, pathWithoutLocale } = extractLocaleFromPath(pagePath, config);
|
|
@@ -2008,7 +2371,10 @@ export async function renderPageSSR(
|
|
|
2008
2371
|
meta = { ...meta, ogTitle: processCMSTemplate(meta.ogTitle, cmsContext.cms, effectiveLocale, config) };
|
|
2009
2372
|
}
|
|
2010
2373
|
if (typeof meta.ogDescription === 'string') {
|
|
2011
|
-
meta = {
|
|
2374
|
+
meta = {
|
|
2375
|
+
...meta,
|
|
2376
|
+
ogDescription: processCMSTemplate(meta.ogDescription, cmsContext.cms, effectiveLocale, config),
|
|
2377
|
+
};
|
|
2012
2378
|
}
|
|
2013
2379
|
if (typeof meta.ogImage === 'string') {
|
|
2014
2380
|
meta = { ...meta, ogImage: processCMSTemplate(meta.ogImage, cmsContext.cms, effectiveLocale, config) };
|
|
@@ -2019,9 +2385,27 @@ export async function renderPageSSR(
|
|
|
2019
2385
|
|
|
2020
2386
|
// Render the component tree to HTML with i18n and CMS support
|
|
2021
2387
|
// Also collect interactive styles, preload images, and needed collections during render
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2388
|
+
// (rootNode is guaranteed non-null — the guard at the top of this function throws otherwise)
|
|
2389
|
+
const {
|
|
2390
|
+
html: contentHTML,
|
|
2391
|
+
interactiveStylesMap,
|
|
2392
|
+
preloadImages,
|
|
2393
|
+
neededCollections,
|
|
2394
|
+
ssrFallbackCollector,
|
|
2395
|
+
processedRawHtmlCollector,
|
|
2396
|
+
} = await buildComponentHTML(
|
|
2397
|
+
rootNode,
|
|
2398
|
+
globalComponents,
|
|
2399
|
+
pageComponents,
|
|
2400
|
+
effectiveLocale,
|
|
2401
|
+
config,
|
|
2402
|
+
slugMappings,
|
|
2403
|
+
pagePath,
|
|
2404
|
+
cmsContext,
|
|
2405
|
+
cmsService,
|
|
2406
|
+
isProductionBuild,
|
|
2407
|
+
injectEditorAttrs,
|
|
2408
|
+
);
|
|
2025
2409
|
|
|
2026
2410
|
// Collect JavaScript and CSS from all components
|
|
2027
2411
|
const javascript = await collectComponentJavaScript(globalComponents, pageComponents);
|