meno-core 1.0.53 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +1 -3
- package/bin/cli.ts +66 -261
- package/build-astro.ts +150 -128
- package/dist/bin/cli.js +42 -185
- package/dist/bin/cli.js.map +2 -2
- package/dist/chunks/chunk-2AR55GYH.js +42 -0
- package/dist/chunks/chunk-2AR55GYH.js.map +7 -0
- package/dist/chunks/{chunk-JGWFTO6P.js → chunk-2IIQK7T3.js} +2624 -1389
- package/dist/chunks/chunk-2IIQK7T3.js.map +7 -0
- package/dist/chunks/chunk-4ZRU52J2.js +169 -0
- package/dist/chunks/chunk-4ZRU52J2.js.map +7 -0
- package/dist/chunks/{chunk-UB44F4Z2.js → chunk-5X4HCN7J.js} +5 -7
- package/dist/chunks/chunk-5X4HCN7J.js.map +7 -0
- package/dist/chunks/{chunk-WQFG7PAH.js → chunk-7CCVOL5M.js} +15 -19
- package/dist/chunks/chunk-7CCVOL5M.js.map +7 -0
- package/dist/chunks/{chunk-YBLHKYFF.js → chunk-AR7LHIFY.js} +8 -2
- package/dist/chunks/chunk-AR7LHIFY.js.map +7 -0
- package/dist/chunks/{chunk-O3NAGJP4.js → chunk-NUP7H7D3.js} +135 -41
- package/dist/chunks/chunk-NUP7H7D3.js.map +7 -0
- package/dist/chunks/{chunk-X754AHS5.js → chunk-QWTQZHG3.js} +194 -90
- package/dist/chunks/chunk-QWTQZHG3.js.map +7 -0
- package/dist/chunks/{chunk-H3GJ4H2U.js → chunk-XTKNX4FW.js} +216 -87
- package/dist/chunks/chunk-XTKNX4FW.js.map +7 -0
- package/dist/chunks/{fs-JGINUXGL.js → fs-G3ANWEFP.js} +2 -2
- package/dist/lib/client/index.js +1003 -475
- package/dist/lib/client/index.js.map +3 -3
- package/dist/lib/server/index.js +14393 -6371
- package/dist/lib/server/index.js.map +4 -4
- package/dist/lib/shared/index.js +342 -101
- package/dist/lib/shared/index.js.map +4 -4
- package/dist/lib/shared/richtext/index.js +1 -1
- package/dist/lib/test-utils/index.js +64 -80
- package/dist/lib/test-utils/index.js.map +2 -2
- package/entries/client-router.tsx +10 -8
- package/lib/client/ClientInitializer.ts +8 -8
- package/lib/client/ErrorBoundary.test.tsx +246 -186
- package/lib/client/ErrorBoundary.tsx +183 -132
- package/lib/client/componentRegistry.test.ts +96 -108
- package/lib/client/componentRegistry.ts +1 -3
- package/lib/client/contexts/ThemeContext.tsx +3 -2
- package/lib/client/core/ComponentBuilder.test.ts +663 -560
- package/lib/client/core/ComponentBuilder.ts +427 -155
- package/lib/client/core/ComponentRenderer.test.tsx +2 -4
- package/lib/client/core/ComponentRenderer.tsx +46 -33
- package/lib/client/core/builders/embedBuilder.ts +242 -54
- package/lib/client/core/builders/linkBuilder.ts +72 -45
- package/lib/client/core/builders/linkNodeBuilder.ts +79 -54
- package/lib/client/core/builders/listBuilder.ts +155 -89
- package/lib/client/core/builders/localeListBuilder.ts +97 -62
- package/lib/client/core/builders/types.ts +5 -5
- package/lib/client/core/cmsTemplateProcessor.ts +8 -9
- 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 +47 -30
- package/lib/client/hmrCssReload.ts +49 -14
- package/lib/client/hmrWebSocket.ts +14 -19
- package/lib/client/hooks/useColorVariables.test.ts +21 -21
- package/lib/client/hooks/useColorVariables.ts +23 -18
- package/lib/client/hooks/usePropertyAutocomplete.ts +4 -6
- package/lib/client/hooks/useVariables.ts +6 -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 +47 -49
- package/lib/client/meno-filter/MenoFilter.ts +14 -15
- package/lib/client/meno-filter/bindings.ts +18 -43
- package/lib/client/meno-filter/init.ts +4 -4
- package/lib/client/meno-filter/renderer.ts +30 -32
- package/lib/client/meno-filter/script.generated.ts +1 -3
- package/lib/client/meno-filter/ui.ts +4 -6
- package/lib/client/meno-filter/updates.ts +18 -23
- package/lib/client/meno-filter/utils.ts +1 -1
- package/lib/client/navigation.test.ts +174 -178
- package/lib/client/navigation.ts +0 -1
- package/lib/client/responsiveStyleResolver.knownTokens.test.ts +71 -0
- package/lib/client/responsiveStyleResolver.test.ts +234 -232
- package/lib/client/responsiveStyleResolver.ts +74 -20
- package/lib/client/routing/RouteLoader.test.ts +24 -25
- package/lib/client/routing/RouteLoader.ts +27 -35
- package/lib/client/routing/Router.tsx +210 -153
- package/lib/client/scripts/ScriptExecutor.test.ts +157 -158
- package/lib/client/scripts/ScriptExecutor.ts +18 -36
- package/lib/client/services/PrefetchService.test.ts +3 -3
- package/lib/client/services/PrefetchService.ts +23 -29
- package/lib/client/styleProcessor.test.ts +9 -9
- package/lib/client/styleProcessor.ts +24 -15
- package/lib/client/styles/StyleInjector.test.ts +122 -115
- package/lib/client/styles/StyleInjector.ts +10 -7
- package/lib/client/styles/UtilityClassCollector.ts +62 -28
- package/lib/client/styles/cspNonce.test.ts +2 -5
- package/lib/client/templateEngine.test.ts +554 -488
- package/lib/client/templateEngine.ts +203 -73
- package/lib/client/theme.test.ts +1 -1
- 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 +4 -5
- package/lib/server/__integration__/ssr-rendering.test.ts +47 -37
- package/lib/server/__integration__/static-assets.test.ts +3 -3
- package/lib/server/__integration__/test-helpers.ts +87 -74
- package/lib/server/ab/generateFunctions.ts +12 -10
- package/lib/server/astro/cmsPageEmitter.ts +26 -42
- package/lib/server/astro/componentEmitter.ts +35 -43
- package/lib/server/astro/cssCollector.ts +10 -26
- package/lib/server/astro/nodeToAstro.test.ts +1754 -34
- package/lib/server/astro/nodeToAstro.ts +232 -216
- package/lib/server/astro/normalizeOrphanTemplateProps.test.ts +115 -119
- package/lib/server/astro/normalizeOrphanTemplateProps.ts +11 -18
- 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 +17 -20
- package/lib/server/createServer.ts +7 -19
- package/lib/server/cssGenerator.test.ts +35 -44
- package/lib/server/cssGenerator.ts +8 -19
- package/lib/server/fileWatcher.test.ts +164 -13
- package/lib/server/fileWatcher.ts +209 -124
- package/lib/server/index.ts +14 -17
- package/lib/server/jsonLoader.test.ts +39 -2
- package/lib/server/jsonLoader.ts +39 -34
- package/lib/server/middleware/cors.test.ts +21 -21
- package/lib/server/middleware/cors.ts +28 -4
- package/lib/server/middleware/errorHandler.test.ts +7 -5
- package/lib/server/middleware/errorHandler.ts +3 -8
- package/lib/server/middleware/index.ts +0 -1
- package/lib/server/middleware/logger.test.ts +10 -8
- package/lib/server/middleware/logger.ts +17 -29
- package/lib/server/migrateTemplates.ts +3 -3
- package/lib/server/pageCache.test.ts +76 -77
- package/lib/server/pageCache.ts +0 -1
- package/lib/server/projectContext.ts +5 -4
- package/lib/server/providers/fileSystemCMSProvider.test.ts +142 -99
- package/lib/server/providers/fileSystemCMSProvider.ts +62 -38
- package/lib/server/routes/api/cms.test.ts +26 -14
- package/lib/server/routes/api/cms.ts +9 -14
- package/lib/server/routes/api/colors.test.ts +2 -2
- package/lib/server/routes/api/components.ts +31 -35
- package/lib/server/routes/api/config.ts +3 -4
- package/lib/server/routes/api/core-routes.ts +49 -76
- package/lib/server/routes/api/enums.test.ts +2 -2
- package/lib/server/routes/api/functions.ts +9 -17
- 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 +3 -5
- package/lib/server/routes/api/variables.ts +1 -1
- package/lib/server/routes/index.ts +25 -28
- package/lib/server/routes/pages.ts +35 -31
- package/lib/server/routes/static.ts +17 -6
- package/lib/server/runtime/bundler.ts +98 -61
- package/lib/server/runtime/fs.ts +16 -26
- package/lib/server/runtime/httpServer.ts +8 -12
- package/lib/server/services/ColorService.test.ts +3 -3
- package/lib/server/services/ColorService.ts +37 -29
- package/lib/server/services/EnumService.test.ts +4 -8
- package/lib/server/services/EnumService.ts +11 -3
- package/lib/server/services/VariableService.test.ts +8 -12
- package/lib/server/services/VariableService.ts +11 -3
- package/lib/server/services/cmsService.test.ts +158 -120
- package/lib/server/services/cmsService.ts +36 -146
- package/lib/server/services/componentService.test.ts +125 -45
- package/lib/server/services/componentService.ts +70 -46
- package/lib/server/services/configService.test.ts +22 -45
- package/lib/server/services/configService.ts +50 -27
- package/lib/server/services/fileWatcherService.ts +42 -8
- package/lib/server/services/index.ts +0 -1
- package/lib/server/services/pageService.test.ts +8 -10
- package/lib/server/services/pageService.ts +32 -18
- package/lib/server/ssr/attributeBuilder.ts +18 -8
- package/lib/server/ssr/buildErrorOverlay.ts +11 -12
- package/lib/server/ssr/clientDataInjector.ts +7 -21
- package/lib/server/ssr/cmsSSRProcessor.ts +4 -8
- 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 +9 -14
- package/lib/server/ssr/htmlGenerator.test.ts +120 -44
- package/lib/server/ssr/htmlGenerator.ts +126 -91
- package/lib/server/ssr/imageMetadata.test.ts +3 -1
- package/lib/server/ssr/imageMetadata.ts +27 -21
- package/lib/server/ssr/jsCollector.test.ts +8 -18
- package/lib/server/ssr/jsCollector.ts +3 -8
- package/lib/server/ssr/liveReloadIntegration.test.ts +76 -36
- 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 +316 -256
- package/lib/server/ssr/ssrRenderer.ts +795 -243
- package/lib/server/ssrRenderer.test.ts +1048 -954
- package/lib/server/utils/jsonLineMapper.test.ts +28 -28
- package/lib/server/utils/jsonLineMapper.ts +7 -7
- package/lib/server/validateStyleCoverage.ts +18 -21
- package/lib/server/websocketManager.ts +28 -19
- package/lib/shared/attributeNodeUtils.test.ts +15 -15
- package/lib/shared/attributeNodeUtils.ts +5 -12
- package/lib/shared/breakpoints.test.ts +5 -5
- package/lib/shared/breakpoints.ts +4 -11
- package/lib/shared/cmsQuery.test.ts +81 -0
- package/lib/shared/cmsQuery.ts +92 -0
- package/lib/shared/cmsQueryParser.test.ts +50 -42
- package/lib/shared/cmsQueryParser.ts +60 -38
- package/lib/shared/colorConversions.ts +9 -9
- package/lib/shared/colorVariableUtils.test.ts +27 -5
- package/lib/shared/colorVariableUtils.ts +21 -12
- package/lib/shared/componentRefs.ts +1 -5
- package/lib/shared/constants.test.ts +3 -3
- package/lib/shared/constants.ts +8 -8
- package/lib/shared/cssGeneration.test.ts +285 -144
- package/lib/shared/cssGeneration.ts +217 -531
- package/lib/shared/cssNamedColors.ts +152 -30
- package/lib/shared/cssProperties.test.ts +69 -70
- package/lib/shared/cssProperties.ts +298 -114
- package/lib/shared/elementClassName.test.ts +109 -109
- package/lib/shared/elementClassName.ts +3 -3
- package/lib/shared/elementUtils.ts +12 -16
- package/lib/shared/errorLogger.test.ts +12 -12
- 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 +145 -0
- package/lib/shared/expressionEvaluator.ts +52 -24
- package/lib/shared/fontLoader.test.ts +32 -18
- package/lib/shared/fontLoader.ts +2 -2
- package/lib/shared/friendlyError.ts +2 -3
- package/lib/shared/gradientUtils.test.ts +3 -7
- package/lib/shared/gradientUtils.ts +17 -14
- package/lib/shared/hrefRefs.test.ts +2 -2
- package/lib/shared/hrefRefs.ts +2 -10
- package/lib/shared/i18n.test.ts +4 -4
- package/lib/shared/i18n.ts +18 -38
- package/lib/shared/index.ts +27 -0
- package/lib/shared/inlineSvgStyleRules.ts +14 -13
- package/lib/shared/interactiveStyleMappings.test.ts +32 -54
- package/lib/shared/interactiveStyleMappings.ts +11 -24
- package/lib/shared/interactiveStyles.test.ts +165 -188
- package/lib/shared/interfaces/contentProvider.ts +1 -1
- package/lib/shared/itemTemplateUtils.test.ts +11 -17
- package/lib/shared/itemTemplateUtils.ts +27 -39
- package/lib/shared/jsonRepair.ts +16 -10
- package/lib/shared/libraryLoader.test.ts +21 -55
- package/lib/shared/libraryLoader.ts +7 -22
- package/lib/shared/linkUtils.ts +1 -1
- package/lib/shared/logger.test.ts +66 -0
- package/lib/shared/logger.ts +94 -0
- package/lib/shared/markdown.ts +25 -0
- package/lib/shared/netlifyLocale404.test.ts +179 -0
- package/lib/shared/netlifyLocale404.ts +110 -0
- package/lib/shared/nodeUtils.test.ts +28 -16
- package/lib/shared/nodeUtils.ts +76 -23
- package/lib/shared/pathArrayUtils.test.ts +1 -2
- package/lib/shared/pathArrayUtils.ts +1 -1
- package/lib/shared/pathSecurity.ts +2 -2
- package/lib/shared/pathUtils.test.ts +4 -6
- package/lib/shared/pathUtils.ts +42 -49
- 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 +17 -20
- package/lib/shared/paths/PathUtils.ts +13 -13
- package/lib/shared/paths/PathValidator.test.ts +2 -15
- package/lib/shared/paths/PathValidator.ts +12 -10
- package/lib/shared/paths/index.ts +1 -2
- package/lib/shared/permissions.test.ts +168 -0
- package/lib/shared/permissions.ts +162 -0
- package/lib/shared/propResolver.test.ts +240 -244
- package/lib/shared/propResolver.ts +15 -26
- package/lib/shared/pxToRem.test.ts +7 -6
- package/lib/shared/pxToRem.ts +2 -5
- package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +12 -6
- package/lib/shared/registry/ClientRegistry.ts +0 -2
- package/lib/shared/registry/ComponentRegistry.test.ts +46 -32
- package/lib/shared/registry/ComponentRegistry.ts +12 -9
- package/lib/shared/registry/NodeTypeDefinition.ts +16 -9
- package/lib/shared/registry/RegistryManager.ts +1 -2
- package/lib/shared/registry/SSRRegistry.ts +0 -2
- package/lib/shared/registry/createNodeType.ts +7 -9
- package/lib/shared/registry/defineNodeType.ts +2 -6
- package/lib/shared/registry/fieldPresets.ts +3 -1
- package/lib/shared/registry/index.ts +0 -1
- package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +14 -15
- package/lib/shared/registry/nodeTypes/CustomNodeType.ts +103 -0
- package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +18 -11
- package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +48 -19
- package/lib/shared/registry/nodeTypes/IslandNodeType.ts +129 -0
- package/lib/shared/registry/nodeTypes/LinkNodeType.ts +21 -19
- package/lib/shared/registry/nodeTypes/ListNodeType.ts +117 -77
- package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +27 -21
- package/lib/shared/registry/nodeTypes/MarkdownNodeType.ts +78 -0
- package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +12 -8
- package/lib/shared/registry/nodeTypes/index.ts +26 -3
- package/lib/shared/responsiveScaling.test.ts +16 -34
- package/lib/shared/responsiveScaling.ts +61 -42
- package/lib/shared/responsiveStyleUtils.test.ts +0 -1
- package/lib/shared/responsiveStyleUtils.ts +11 -13
- package/lib/shared/richtext/htmlToTiptap.test.ts +139 -130
- package/lib/shared/richtext/htmlToTiptap.ts +2 -4
- 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 +18 -20
- package/lib/shared/styleUtils.test.ts +87 -61
- package/lib/shared/styleUtils.ts +5 -6
- package/lib/shared/themeDefaults.test.ts +11 -14
- package/lib/shared/themeDefaults.ts +3 -4
- package/lib/shared/tree/PathBuilder.test.ts +63 -145
- package/lib/shared/tree/PathBuilder.ts +59 -47
- package/lib/shared/treePathUtils.test.ts +51 -12
- package/lib/shared/treePathUtils.ts +91 -60
- package/lib/shared/types/api.ts +105 -8
- package/lib/shared/types/cms.test.ts +34 -0
- package/lib/shared/types/cms.ts +90 -34
- package/lib/shared/types/comment.ts +49 -16
- package/lib/shared/types/components.ts +51 -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 +25 -2
- package/lib/shared/types/permissions.ts +139 -0
- package/lib/shared/types/styles.ts +0 -1
- package/lib/shared/types/variables.test.ts +4 -13
- package/lib/shared/types/variables.ts +51 -30
- package/lib/shared/types.ts +1 -2
- package/lib/shared/utilityClassConfig.ts +751 -321
- package/lib/shared/utilityClassMapper.durability.test.ts +117 -0
- package/lib/shared/utilityClassMapper.test.ts +433 -95
- package/lib/shared/utilityClassMapper.ts +322 -249
- package/lib/shared/utilityClassNames.test.ts +38 -0
- package/lib/shared/utilityClassNames.ts +458 -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/index.ts +1 -0
- package/lib/shared/validation/permissionsValidators.test.ts +49 -0
- package/lib/shared/validation/permissionsValidators.ts +49 -0
- package/lib/shared/validation/propValidator.test.ts +19 -21
- package/lib/shared/validation/propValidator.ts +13 -18
- package/lib/shared/validation/schemas.test.ts +64 -32
- package/lib/shared/validation/schemas.ts +616 -345
- package/lib/shared/validation/validators.test.ts +3 -8
- package/lib/shared/validation/validators.ts +89 -68
- package/lib/shared/viewportUnits.integration.test.ts +14 -10
- package/lib/shared/viewportUnits.test.ts +24 -23
- package/lib/shared/viewportUnits.ts +12 -5
- package/lib/test-utils/dom-setup.ts +1 -1
- package/lib/test-utils/factories/ConsoleMockFactory.ts +10 -14
- package/lib/test-utils/factories/DomMockFactory.ts +17 -23
- 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 +6 -10
- package/lib/test-utils/factories/StoreMockFactory.ts +18 -29
- 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 +13 -19
- package/lib/test-utils/mocks.ts +6 -4
- package/package.json +3 -5
- package/scripts/build-for-publish.mjs +2 -4
- package/scripts/build-meno-filter.ts +4 -7
- package/tsconfig.json +1 -0
- package/vite.config.ts +4 -4
- package/build-next.ts +0 -1361
- package/build-static.test.ts +0 -424
- package/build-static.ts +0 -1074
- package/dist/build-static.js +0 -36
- package/dist/chunks/chunk-2MHDV5BF.js +0 -251
- package/dist/chunks/chunk-2MHDV5BF.js.map +0 -7
- package/dist/chunks/chunk-AZQYF6KE.js +0 -559
- package/dist/chunks/chunk-AZQYF6KE.js.map +0 -7
- package/dist/chunks/chunk-GZHGVVW3.js +0 -322
- package/dist/chunks/chunk-GZHGVVW3.js.map +0 -7
- package/dist/chunks/chunk-H3GJ4H2U.js.map +0 -7
- package/dist/chunks/chunk-I7YIGZXT.js +0 -467
- package/dist/chunks/chunk-I7YIGZXT.js.map +0 -7
- package/dist/chunks/chunk-IGYR22T6.js +0 -6192
- package/dist/chunks/chunk-IGYR22T6.js.map +0 -7
- package/dist/chunks/chunk-JGP5A3Y5.js +0 -1136
- 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 +0 -77
- package/dist/chunks/chunk-QB2LNO4W.js.map +0 -7
- package/dist/chunks/chunk-R6XHAFBF.js +0 -3821
- 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 +0 -7
- package/dist/chunks/constants-STK2YBIW.js +0 -46
- package/dist/chunks/constants-STK2YBIW.js.map +0 -7
- package/dist/chunks/fs-JGINUXGL.js.map +0 -7
- package/dist/entries/server-router.js +0 -67
- package/dist/entries/server-router.js.map +0 -7
- package/entries/server-router.tsx +0 -81
- package/lib/server/providers/fileSystemPageProvider.ts +0 -160
- package/lib/server/webflow/buildWebflow.ts +0 -635
- package/lib/server/webflow/index.ts +0 -23
- package/lib/server/webflow/nodeToWebflow.test.ts +0 -3170
- package/lib/server/webflow/nodeToWebflow.ts +0 -2435
- package/lib/server/webflow/styleMapper.test.ts +0 -389
- package/lib/server/webflow/styleMapper.ts +0 -695
- package/lib/server/webflow/templateWrapper.ts +0 -49
- package/lib/server/webflow/types.ts +0 -396
- /package/dist/{build-static.js.map → chunks/fs-G3ANWEFP.js.map} +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { test, expect, describe } from 'bun:test';
|
|
2
|
+
import { applyCmsFilters, applyCmsSorting, matchCmsCondition, isCmsFilterCondition } from './cmsQuery';
|
|
3
|
+
|
|
4
|
+
describe('cmsQuery — filters', () => {
|
|
5
|
+
const items = [
|
|
6
|
+
{ _id: 'a', title: 'Alpha', views: 10, featured: true },
|
|
7
|
+
{ _id: 'b', title: 'Beta', views: 30, featured: false },
|
|
8
|
+
{ _id: 'c', title: 'Gamma', views: 20, featured: true },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
test('plain equality map filters on every key', () => {
|
|
12
|
+
expect(applyCmsFilters(items, { featured: true }).map((i) => i._id)).toEqual(['a', 'c']);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('operators eq/neq/gt/gte/lt/lte/contains/in', () => {
|
|
16
|
+
expect(applyCmsFilters(items, { field: 'views', operator: 'gt', value: 15 }).map((i) => i._id)).toEqual(['b', 'c']);
|
|
17
|
+
expect(applyCmsFilters(items, { field: 'views', operator: 'lte', value: 20 }).map((i) => i._id)).toEqual([
|
|
18
|
+
'a',
|
|
19
|
+
'c',
|
|
20
|
+
]);
|
|
21
|
+
expect(applyCmsFilters(items, { field: 'title', operator: 'contains', value: 'amm' }).map((i) => i._id)).toEqual([
|
|
22
|
+
'c',
|
|
23
|
+
]);
|
|
24
|
+
expect(applyCmsFilters(items, { field: '_id', operator: 'in', value: ['a', 'b'] }).map((i) => i._id)).toEqual([
|
|
25
|
+
'a',
|
|
26
|
+
'b',
|
|
27
|
+
]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('array of conditions ANDs', () => {
|
|
31
|
+
const r = applyCmsFilters(items, [
|
|
32
|
+
{ field: 'featured', operator: 'eq', value: true },
|
|
33
|
+
{ field: 'views', operator: 'gte', value: 20 },
|
|
34
|
+
]);
|
|
35
|
+
expect(r.map((i) => i._id)).toEqual(['c']);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('unknown operator never matches; isCmsFilterCondition discriminates', () => {
|
|
39
|
+
expect(matchCmsCondition(items[0]!, { field: 'x', operator: 'weird' as never, value: 1 })).toBe(false);
|
|
40
|
+
expect(isCmsFilterCondition({ field: 'x' })).toBe(true);
|
|
41
|
+
expect(isCmsFilterCondition({ featured: true })).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('cmsQuery — sorting', () => {
|
|
46
|
+
test('numeric asc/desc, does not mutate input', () => {
|
|
47
|
+
const items = [{ n: 3 }, { n: 1 }, { n: 2 }];
|
|
48
|
+
expect(applyCmsSorting(items, { field: 'n', order: 'asc' }).map((i) => i.n)).toEqual([1, 2, 3]);
|
|
49
|
+
expect(applyCmsSorting(items, { field: 'n', order: 'desc' }).map((i) => i.n)).toEqual([3, 2, 1]);
|
|
50
|
+
expect(items.map((i) => i.n)).toEqual([3, 1, 2]); // original untouched
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Regression for the SSR-remote drift: the copy there had dropped this case, so a
|
|
54
|
+
// boolean field sorted differently for remote/Sanity items than for local ones.
|
|
55
|
+
test('boolean fields: desc → true first, asc → false first', () => {
|
|
56
|
+
const items = [
|
|
57
|
+
{ id: 'x', on: false },
|
|
58
|
+
{ id: 'y', on: true },
|
|
59
|
+
{ id: 'z', on: false },
|
|
60
|
+
];
|
|
61
|
+
expect(applyCmsSorting(items, { field: 'on', order: 'desc' })[0]!.id).toBe('y');
|
|
62
|
+
expect(applyCmsSorting(items, { field: 'on', order: 'asc' }).at(-1)!.id).toBe('y');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('multi-key sort: primary then tiebreaker', () => {
|
|
66
|
+
const items = [
|
|
67
|
+
{ cat: 'b', n: 1 },
|
|
68
|
+
{ cat: 'a', n: 2 },
|
|
69
|
+
{ cat: 'a', n: 1 },
|
|
70
|
+
];
|
|
71
|
+
const r = applyCmsSorting(items, [
|
|
72
|
+
{ field: 'cat', order: 'asc' },
|
|
73
|
+
{ field: 'n', order: 'desc' },
|
|
74
|
+
]);
|
|
75
|
+
expect(r).toEqual([
|
|
76
|
+
{ cat: 'a', n: 2 },
|
|
77
|
+
{ cat: 'a', n: 1 },
|
|
78
|
+
{ cat: 'b', n: 1 },
|
|
79
|
+
]);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CMS query primitives — the SINGLE source of truth for filtering and sorting
|
|
3
|
+
* CMS items. Pure (no IO), so it is shared by every consumer that has to apply a
|
|
4
|
+
* `CMSListQuery`'s `filter`/`sort` to a set of items:
|
|
5
|
+
* - `meno-core` CMSService (local file-backed collections)
|
|
6
|
+
* - `meno-core` SSR renderer (remote / Sanity-backed list items)
|
|
7
|
+
* - `meno-astro` runtime `getCollectionList` (built/SSR Astro pages)
|
|
8
|
+
*
|
|
9
|
+
* These used to be three hand-copied implementations; one (the SSR remote path)
|
|
10
|
+
* had already drifted and dropped the boolean-sort special case, so a boolean
|
|
11
|
+
* field sorted differently for remote items than for local ones. Keeping the
|
|
12
|
+
* logic here — operating on plain `Record<string, unknown>` rows so it works for
|
|
13
|
+
* both `CMSItem` and the astro runtime's item shape — prevents that recurring.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { CMSFilterCondition, CMSSortConfig } from './types/cms';
|
|
17
|
+
|
|
18
|
+
/** A CMS item, structurally — query primitives only ever index it by field name. */
|
|
19
|
+
type Row = Record<string, unknown>;
|
|
20
|
+
|
|
21
|
+
/** True when `obj` is a single {@link CMSFilterCondition} (vs a plain equality map). */
|
|
22
|
+
export function isCmsFilterCondition(obj: unknown): obj is CMSFilterCondition {
|
|
23
|
+
return typeof obj === 'object' && obj !== null && 'field' in obj;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Evaluate one filter condition against an item. Unknown operators never match. */
|
|
27
|
+
export function matchCmsCondition(item: Row, condition: CMSFilterCondition): boolean {
|
|
28
|
+
const value = item[condition.field];
|
|
29
|
+
switch (condition.operator || 'eq') {
|
|
30
|
+
case 'eq':
|
|
31
|
+
return value === condition.value;
|
|
32
|
+
case 'neq':
|
|
33
|
+
return value !== condition.value;
|
|
34
|
+
case 'gt':
|
|
35
|
+
return (value as number) > (condition.value as number);
|
|
36
|
+
case 'gte':
|
|
37
|
+
return (value as number) >= (condition.value as number);
|
|
38
|
+
case 'lt':
|
|
39
|
+
return (value as number) < (condition.value as number);
|
|
40
|
+
case 'lte':
|
|
41
|
+
return (value as number) <= (condition.value as number);
|
|
42
|
+
case 'contains':
|
|
43
|
+
return String(value).includes(String(condition.value));
|
|
44
|
+
case 'in':
|
|
45
|
+
return Array.isArray(condition.value) && condition.value.includes(value);
|
|
46
|
+
default:
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Filter items by a `CMSListQuery.filter`: a plain equality map (`{ featured: true }`
|
|
53
|
+
* → every key must match), a single condition, or an array of conditions (AND).
|
|
54
|
+
*/
|
|
55
|
+
export function applyCmsFilters<T extends Row>(
|
|
56
|
+
items: T[],
|
|
57
|
+
filter: CMSFilterCondition | CMSFilterCondition[] | Record<string, unknown>,
|
|
58
|
+
): T[] {
|
|
59
|
+
if (!Array.isArray(filter) && !isCmsFilterCondition(filter)) {
|
|
60
|
+
const entries = Object.entries(filter);
|
|
61
|
+
return items.filter((item) => entries.every(([key, value]) => item[key] === value));
|
|
62
|
+
}
|
|
63
|
+
const conditions = Array.isArray(filter) ? filter : [filter as CMSFilterCondition];
|
|
64
|
+
return items.filter((item) => conditions.every((cond) => matchCmsCondition(item, cond)));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Stable multi-key sort. Booleans compare specially (desc → true first); other
|
|
69
|
+
* values compare as primitives. Returns a new array (input is not mutated).
|
|
70
|
+
*/
|
|
71
|
+
export function applyCmsSorting<T extends Row>(items: T[], sort: CMSSortConfig | CMSSortConfig[]): T[] {
|
|
72
|
+
const sorts = Array.isArray(sort) ? sort : [sort];
|
|
73
|
+
return [...items].sort((a, b) => {
|
|
74
|
+
for (const s of sorts) {
|
|
75
|
+
const aVal = a[s.field];
|
|
76
|
+
const bVal = b[s.field];
|
|
77
|
+
const isDesc = s.order === 'desc';
|
|
78
|
+
|
|
79
|
+
// Boolean fields: desc puts `true` first, asc puts `false` first.
|
|
80
|
+
if (typeof aVal === 'boolean' && typeof bVal === 'boolean') {
|
|
81
|
+
if (aVal === bVal) continue;
|
|
82
|
+
return isDesc ? (aVal ? -1 : 1) : aVal ? 1 : -1;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let result = 0;
|
|
86
|
+
if ((aVal as string | number) < (bVal as string | number)) result = -1;
|
|
87
|
+
else if ((aVal as string | number) > (bVal as string | number)) result = 1;
|
|
88
|
+
if (result !== 0) return isDesc ? -result : result;
|
|
89
|
+
}
|
|
90
|
+
return 0;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
parseSortExpression,
|
|
8
8
|
serializeSortExpression,
|
|
9
9
|
parseFilterExpression,
|
|
10
|
-
serializeFilterExpression
|
|
10
|
+
serializeFilterExpression,
|
|
11
11
|
} from './cmsQueryParser';
|
|
12
12
|
|
|
13
13
|
describe('parseSortExpression', () => {
|
|
@@ -29,11 +29,11 @@ describe('parseSortExpression', () => {
|
|
|
29
29
|
test('parses multiple fields', () => {
|
|
30
30
|
expect(parseSortExpression('date desc, title')).toEqual([
|
|
31
31
|
{ field: 'date', order: 'desc' },
|
|
32
|
-
{ field: 'title', order: 'asc' }
|
|
32
|
+
{ field: 'title', order: 'asc' },
|
|
33
33
|
]);
|
|
34
34
|
expect(parseSortExpression('date desc, title asc')).toEqual([
|
|
35
35
|
{ field: 'date', order: 'desc' },
|
|
36
|
-
{ field: 'title', order: 'asc' }
|
|
36
|
+
{ field: 'title', order: 'asc' },
|
|
37
37
|
]);
|
|
38
38
|
});
|
|
39
39
|
|
|
@@ -41,7 +41,7 @@ describe('parseSortExpression', () => {
|
|
|
41
41
|
expect(parseSortExpression(' date desc ')).toEqual({ field: 'date', order: 'desc' });
|
|
42
42
|
expect(parseSortExpression('date desc, title asc')).toEqual([
|
|
43
43
|
{ field: 'date', order: 'desc' },
|
|
44
|
-
{ field: 'title', order: 'asc' }
|
|
44
|
+
{ field: 'title', order: 'asc' },
|
|
45
45
|
]);
|
|
46
46
|
});
|
|
47
47
|
});
|
|
@@ -60,10 +60,12 @@ describe('serializeSortExpression', () => {
|
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
test('serializes multiple fields', () => {
|
|
63
|
-
expect(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
expect(
|
|
64
|
+
serializeSortExpression([
|
|
65
|
+
{ field: 'date', order: 'desc' },
|
|
66
|
+
{ field: 'title', order: 'asc' },
|
|
67
|
+
]),
|
|
68
|
+
).toBe('date desc, title');
|
|
67
69
|
});
|
|
68
70
|
});
|
|
69
71
|
|
|
@@ -98,32 +100,32 @@ describe('parseFilterExpression', () => {
|
|
|
98
100
|
expect(parseFilterExpression('price > 100')).toEqual({
|
|
99
101
|
field: 'price',
|
|
100
102
|
operator: 'gt',
|
|
101
|
-
value: 100
|
|
103
|
+
value: 100,
|
|
102
104
|
});
|
|
103
105
|
expect(parseFilterExpression('price >= 100')).toEqual({
|
|
104
106
|
field: 'price',
|
|
105
107
|
operator: 'gte',
|
|
106
|
-
value: 100
|
|
108
|
+
value: 100,
|
|
107
109
|
});
|
|
108
110
|
expect(parseFilterExpression('price < 100')).toEqual({
|
|
109
111
|
field: 'price',
|
|
110
112
|
operator: 'lt',
|
|
111
|
-
value: 100
|
|
113
|
+
value: 100,
|
|
112
114
|
});
|
|
113
115
|
expect(parseFilterExpression('price <= 100')).toEqual({
|
|
114
116
|
field: 'price',
|
|
115
117
|
operator: 'lte',
|
|
116
|
-
value: 100
|
|
118
|
+
value: 100,
|
|
117
119
|
});
|
|
118
120
|
expect(parseFilterExpression('status != draft')).toEqual({
|
|
119
121
|
field: 'status',
|
|
120
122
|
operator: 'neq',
|
|
121
|
-
value: 'draft'
|
|
123
|
+
value: 'draft',
|
|
122
124
|
});
|
|
123
125
|
expect(parseFilterExpression('status <> draft')).toEqual({
|
|
124
126
|
field: 'status',
|
|
125
127
|
operator: 'neq',
|
|
126
|
-
value: 'draft'
|
|
128
|
+
value: 'draft',
|
|
127
129
|
});
|
|
128
130
|
});
|
|
129
131
|
|
|
@@ -131,12 +133,12 @@ describe('parseFilterExpression', () => {
|
|
|
131
133
|
expect(parseFilterExpression('title contains hello')).toEqual({
|
|
132
134
|
field: 'title',
|
|
133
135
|
operator: 'contains',
|
|
134
|
-
value: 'hello'
|
|
136
|
+
value: 'hello',
|
|
135
137
|
});
|
|
136
138
|
expect(parseFilterExpression('title ~ hello')).toEqual({
|
|
137
139
|
field: 'title',
|
|
138
140
|
operator: 'contains',
|
|
139
|
-
value: 'hello'
|
|
141
|
+
value: 'hello',
|
|
140
142
|
});
|
|
141
143
|
});
|
|
142
144
|
|
|
@@ -144,35 +146,35 @@ describe('parseFilterExpression', () => {
|
|
|
144
146
|
expect(parseFilterExpression('category in news, blog')).toEqual({
|
|
145
147
|
field: 'category',
|
|
146
148
|
operator: 'in',
|
|
147
|
-
value: ['news', 'blog']
|
|
149
|
+
value: ['news', 'blog'],
|
|
148
150
|
});
|
|
149
151
|
expect(parseFilterExpression('status in draft, published, archived')).toEqual({
|
|
150
152
|
field: 'status',
|
|
151
153
|
operator: 'in',
|
|
152
|
-
value: ['draft', 'published', 'archived']
|
|
154
|
+
value: ['draft', 'published', 'archived'],
|
|
153
155
|
});
|
|
154
156
|
});
|
|
155
157
|
|
|
156
158
|
test('parses template values', () => {
|
|
157
159
|
expect(parseFilterExpression('category = {{cms.category}}')).toEqual({
|
|
158
|
-
category: '{{cms.category}}'
|
|
160
|
+
category: '{{cms.category}}',
|
|
159
161
|
});
|
|
160
162
|
expect(parseFilterExpression('author = {{cms.authorId}}')).toEqual({
|
|
161
|
-
author: '{{cms.authorId}}'
|
|
163
|
+
author: '{{cms.authorId}}',
|
|
162
164
|
});
|
|
163
165
|
});
|
|
164
166
|
|
|
165
167
|
test('parses multiple conditions (newline)', () => {
|
|
166
168
|
expect(parseFilterExpression('featured = true\nprice > 100')).toEqual([
|
|
167
169
|
{ field: 'featured', value: true },
|
|
168
|
-
{ field: 'price', operator: 'gt', value: 100 }
|
|
170
|
+
{ field: 'price', operator: 'gt', value: 100 },
|
|
169
171
|
]);
|
|
170
172
|
});
|
|
171
173
|
|
|
172
174
|
test('parses multiple conditions (semicolon)', () => {
|
|
173
175
|
expect(parseFilterExpression('featured = true; price > 100')).toEqual([
|
|
174
176
|
{ field: 'featured', value: true },
|
|
175
|
-
{ field: 'price', operator: 'gt', value: 100 }
|
|
177
|
+
{ field: 'price', operator: 'gt', value: 100 },
|
|
176
178
|
]);
|
|
177
179
|
});
|
|
178
180
|
|
|
@@ -198,20 +200,20 @@ describe('parseFilterExpression', () => {
|
|
|
198
200
|
expect(parseFilterExpression('price gt 100')).toEqual({
|
|
199
201
|
field: 'price',
|
|
200
202
|
operator: 'gt',
|
|
201
|
-
value: 100
|
|
203
|
+
value: 100,
|
|
202
204
|
});
|
|
203
205
|
expect(parseFilterExpression('price gte 100')).toEqual({
|
|
204
206
|
field: 'price',
|
|
205
207
|
operator: 'gte',
|
|
206
|
-
value: 100
|
|
208
|
+
value: 100,
|
|
207
209
|
});
|
|
208
210
|
expect(parseFilterExpression('status eq active')).toEqual({
|
|
209
|
-
status: 'active'
|
|
211
|
+
status: 'active',
|
|
210
212
|
});
|
|
211
213
|
expect(parseFilterExpression('status neq draft')).toEqual({
|
|
212
214
|
field: 'status',
|
|
213
215
|
operator: 'neq',
|
|
214
|
-
value: 'draft'
|
|
216
|
+
value: 'draft',
|
|
215
217
|
});
|
|
216
218
|
});
|
|
217
219
|
});
|
|
@@ -227,32 +229,38 @@ describe('serializeFilterExpression', () => {
|
|
|
227
229
|
});
|
|
228
230
|
|
|
229
231
|
test('serializes condition with operator', () => {
|
|
230
|
-
expect(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
232
|
+
expect(
|
|
233
|
+
serializeFilterExpression({
|
|
234
|
+
field: 'price',
|
|
235
|
+
operator: 'gt',
|
|
236
|
+
value: 100,
|
|
237
|
+
}),
|
|
238
|
+
).toBe('price > 100');
|
|
239
|
+
expect(
|
|
240
|
+
serializeFilterExpression({
|
|
241
|
+
field: 'status',
|
|
242
|
+
operator: 'neq',
|
|
243
|
+
value: 'draft',
|
|
244
|
+
}),
|
|
245
|
+
).toBe('status != draft');
|
|
240
246
|
});
|
|
241
247
|
|
|
242
248
|
test('serializes multiple conditions', () => {
|
|
243
249
|
const result = serializeFilterExpression([
|
|
244
250
|
{ field: 'featured', value: true },
|
|
245
|
-
{ field: 'price', operator: 'gt', value: 100 }
|
|
251
|
+
{ field: 'price', operator: 'gt', value: 100 },
|
|
246
252
|
]);
|
|
247
253
|
expect(result).toBe('featured = true\nprice > 100');
|
|
248
254
|
});
|
|
249
255
|
|
|
250
256
|
test('serializes in operator', () => {
|
|
251
|
-
expect(
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
257
|
+
expect(
|
|
258
|
+
serializeFilterExpression({
|
|
259
|
+
field: 'category',
|
|
260
|
+
operator: 'in',
|
|
261
|
+
value: ['news', 'blog'],
|
|
262
|
+
}),
|
|
263
|
+
).toBe('category in news, blog');
|
|
256
264
|
});
|
|
257
265
|
|
|
258
266
|
test('serializes template values', () => {
|
|
@@ -22,15 +22,18 @@ import type { CMSFilterCondition, CMSSortConfig, CMSFilterOperator } from './typ
|
|
|
22
22
|
export function parseSortExpression(expr: string): CMSSortConfig | CMSSortConfig[] | undefined {
|
|
23
23
|
if (!expr.trim()) return undefined;
|
|
24
24
|
|
|
25
|
-
const parts = expr
|
|
26
|
-
|
|
25
|
+
const parts = expr
|
|
26
|
+
.split(',')
|
|
27
|
+
.map((p) => p.trim())
|
|
28
|
+
.filter(Boolean);
|
|
29
|
+
const sorts: CMSSortConfig[] = parts.map((part) => {
|
|
27
30
|
const tokens = part.split(/\s+/);
|
|
28
|
-
const field = tokens[0];
|
|
31
|
+
const field = tokens[0] ?? '';
|
|
29
32
|
const order = tokens[1]?.toLowerCase() === 'desc' ? 'desc' : 'asc';
|
|
30
33
|
return { field, order };
|
|
31
34
|
});
|
|
32
35
|
|
|
33
|
-
return sorts.length === 1 ? sorts[0] : sorts;
|
|
36
|
+
return sorts.length === 1 ? (sorts[0] as CMSSortConfig) : sorts;
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
/**
|
|
@@ -39,19 +42,22 @@ export function parseSortExpression(expr: string): CMSSortConfig | CMSSortConfig
|
|
|
39
42
|
export function serializeSortExpression(sort: CMSSortConfig | CMSSortConfig[] | undefined): string {
|
|
40
43
|
if (!sort) return '';
|
|
41
44
|
const sorts = Array.isArray(sort) ? sort : [sort];
|
|
42
|
-
return sorts.map(s => `${s.field}${s.order === 'desc' ? ' desc' : ''}`).join(', ');
|
|
45
|
+
return sorts.map((s) => `${s.field}${s.order === 'desc' ? ' desc' : ''}`).join(', ');
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
/**
|
|
46
49
|
* Parse filter expression: "featured = true; price > 100"
|
|
47
50
|
*/
|
|
48
51
|
export function parseFilterExpression(
|
|
49
|
-
expr: string
|
|
52
|
+
expr: string,
|
|
50
53
|
): CMSFilterCondition | CMSFilterCondition[] | Record<string, unknown> | undefined {
|
|
51
54
|
if (!expr.trim()) return undefined;
|
|
52
55
|
|
|
53
56
|
// Split by newline or semicolon
|
|
54
|
-
const lines = expr
|
|
57
|
+
const lines = expr
|
|
58
|
+
.split(/[;\n]/)
|
|
59
|
+
.map((l) => l.trim())
|
|
60
|
+
.filter(Boolean);
|
|
55
61
|
const conditions: CMSFilterCondition[] = [];
|
|
56
62
|
|
|
57
63
|
for (const line of lines) {
|
|
@@ -62,11 +68,12 @@ export function parseFilterExpression(
|
|
|
62
68
|
if (conditions.length === 0) return undefined;
|
|
63
69
|
|
|
64
70
|
// Single simple equality can use shorthand: { field: value }
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
const first = conditions[0];
|
|
72
|
+
if (conditions.length === 1 && first && (!first.operator || first.operator === 'eq')) {
|
|
73
|
+
return { [first.field]: first.value };
|
|
67
74
|
}
|
|
68
75
|
|
|
69
|
-
return conditions.length === 1 ?
|
|
76
|
+
return conditions.length === 1 && first ? first : conditions;
|
|
70
77
|
}
|
|
71
78
|
|
|
72
79
|
function parseFilterLine(line: string): CMSFilterCondition | null {
|
|
@@ -75,7 +82,10 @@ function parseFilterLine(line: string): CMSFilterCondition | null {
|
|
|
75
82
|
const match = line.match(/^(\w+)\s*(==|!=|<>|>=|<=|=|>|<|contains|~|in|gte|lte|neq|gt|lt|eq)\s*(.+)$/i);
|
|
76
83
|
if (!match) return null;
|
|
77
84
|
|
|
78
|
-
|
|
85
|
+
// The regex matched (guarded above), so groups 1–3 are present; default for the type system.
|
|
86
|
+
const field = match[1] ?? '';
|
|
87
|
+
const op = match[2] ?? '';
|
|
88
|
+
const rawValue = match[3] ?? '';
|
|
79
89
|
const operator = normalizeOperator(op);
|
|
80
90
|
const value = parseValue(rawValue.trim(), operator);
|
|
81
91
|
|
|
@@ -90,14 +100,23 @@ function parseFilterLine(line: string): CMSFilterCondition | null {
|
|
|
90
100
|
function normalizeOperator(op: string): CMSFilterOperator {
|
|
91
101
|
const normalized = op.toLowerCase();
|
|
92
102
|
const map: Record<string, CMSFilterOperator> = {
|
|
93
|
-
'=': 'eq',
|
|
94
|
-
'
|
|
95
|
-
|
|
96
|
-
'
|
|
97
|
-
'
|
|
98
|
-
|
|
99
|
-
'
|
|
100
|
-
|
|
103
|
+
'=': 'eq',
|
|
104
|
+
'==': 'eq',
|
|
105
|
+
eq: 'eq',
|
|
106
|
+
'!=': 'neq',
|
|
107
|
+
'<>': 'neq',
|
|
108
|
+
neq: 'neq',
|
|
109
|
+
'>': 'gt',
|
|
110
|
+
gt: 'gt',
|
|
111
|
+
'>=': 'gte',
|
|
112
|
+
gte: 'gte',
|
|
113
|
+
'<': 'lt',
|
|
114
|
+
lt: 'lt',
|
|
115
|
+
'<=': 'lte',
|
|
116
|
+
lte: 'lte',
|
|
117
|
+
contains: 'contains',
|
|
118
|
+
'~': 'contains',
|
|
119
|
+
in: 'in',
|
|
101
120
|
};
|
|
102
121
|
return map[normalized] || 'eq';
|
|
103
122
|
}
|
|
@@ -110,7 +129,7 @@ function parseValue(raw: string, operator: CMSFilterOperator): unknown {
|
|
|
110
129
|
|
|
111
130
|
// Handle 'in' operator - comma separated list
|
|
112
131
|
if (operator === 'in') {
|
|
113
|
-
return raw.split(',').map(v => parseScalarValue(v.trim()));
|
|
132
|
+
return raw.split(',').map((v) => parseScalarValue(v.trim()));
|
|
114
133
|
}
|
|
115
134
|
|
|
116
135
|
return parseScalarValue(raw);
|
|
@@ -123,7 +142,7 @@ function parseScalarValue(raw: string): unknown {
|
|
|
123
142
|
|
|
124
143
|
// Number
|
|
125
144
|
const num = Number(raw);
|
|
126
|
-
if (!isNaN(num) && raw !== '') return num;
|
|
145
|
+
if (!Number.isNaN(num) && raw !== '') return num;
|
|
127
146
|
|
|
128
147
|
// String (strip quotes if present)
|
|
129
148
|
if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) {
|
|
@@ -137,7 +156,7 @@ function parseScalarValue(raw: string): unknown {
|
|
|
137
156
|
* Serialize filter config back to expression
|
|
138
157
|
*/
|
|
139
158
|
export function serializeFilterExpression(
|
|
140
|
-
filter: CMSFilterCondition | CMSFilterCondition[] | Record<string, unknown> | undefined
|
|
159
|
+
filter: CMSFilterCondition | CMSFilterCondition[] | Record<string, unknown> | undefined,
|
|
141
160
|
): string {
|
|
142
161
|
if (!filter) return '';
|
|
143
162
|
|
|
@@ -150,26 +169,29 @@ export function serializeFilterExpression(
|
|
|
150
169
|
|
|
151
170
|
// Array or single condition
|
|
152
171
|
const conditions = Array.isArray(filter) ? filter : [filter as CMSFilterCondition];
|
|
153
|
-
return conditions
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
172
|
+
return conditions
|
|
173
|
+
.map((c) => {
|
|
174
|
+
const op = c.operator || 'eq';
|
|
175
|
+
const opStr = operatorToString(op);
|
|
176
|
+
const valStr =
|
|
177
|
+
op === 'in' && Array.isArray(c.value)
|
|
178
|
+
? c.value.map((v) => serializeValue(v)).join(', ')
|
|
179
|
+
: serializeValue(c.value);
|
|
180
|
+
return `${c.field} ${opStr} ${valStr}`;
|
|
181
|
+
})
|
|
182
|
+
.join('\n');
|
|
161
183
|
}
|
|
162
184
|
|
|
163
185
|
function operatorToString(op: CMSFilterOperator): string {
|
|
164
186
|
const map: Record<CMSFilterOperator, string> = {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
187
|
+
eq: '=',
|
|
188
|
+
neq: '!=',
|
|
189
|
+
gt: '>',
|
|
190
|
+
gte: '>=',
|
|
191
|
+
lt: '<',
|
|
192
|
+
lte: '<=',
|
|
193
|
+
contains: 'contains',
|
|
194
|
+
in: 'in',
|
|
173
195
|
};
|
|
174
196
|
return map[op] || '=';
|
|
175
197
|
}
|
|
@@ -39,9 +39,9 @@ export function parseCssRgb(value: string): RgbColor | null {
|
|
|
39
39
|
const m = value.trim().match(RGB_RE);
|
|
40
40
|
if (!m) return null;
|
|
41
41
|
return {
|
|
42
|
-
r: clamp(parseInt(m[1], 10), 0, 255),
|
|
43
|
-
g: clamp(parseInt(m[2], 10), 0, 255),
|
|
44
|
-
b: clamp(parseInt(m[3], 10), 0, 255),
|
|
42
|
+
r: clamp(parseInt(m[1] ?? '', 10), 0, 255),
|
|
43
|
+
g: clamp(parseInt(m[2] ?? '', 10), 0, 255),
|
|
44
|
+
b: clamp(parseInt(m[3] ?? '', 10), 0, 255),
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -50,9 +50,9 @@ export function parseCssHsl(value: string): HslColor | null {
|
|
|
50
50
|
const m = value.trim().match(HSL_RE);
|
|
51
51
|
if (!m) return null;
|
|
52
52
|
return {
|
|
53
|
-
h: clamp(parseFloat(m[1]), 0, 360),
|
|
54
|
-
s: clamp(parseFloat(m[2]), 0, 100),
|
|
55
|
-
l: clamp(parseFloat(m[3]), 0, 100),
|
|
53
|
+
h: clamp(parseFloat(m[1] ?? ''), 0, 360),
|
|
54
|
+
s: clamp(parseFloat(m[2] ?? ''), 0, 100),
|
|
55
|
+
l: clamp(parseFloat(m[3] ?? ''), 0, 100),
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -74,9 +74,9 @@ export function hslToCssString(hsl: HslColor): string {
|
|
|
74
74
|
function expandHex(hex: string): string {
|
|
75
75
|
const h = hex.replace('#', '');
|
|
76
76
|
if (h.length === 3) {
|
|
77
|
-
return
|
|
77
|
+
return `#${h[0]}${h[0]}${h[1]}${h[1]}${h[2]}${h[2]}`;
|
|
78
78
|
}
|
|
79
|
-
return
|
|
79
|
+
return `#${h}`;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/** Convert a hex color string to an RgbColor */
|
|
@@ -94,7 +94,7 @@ export function rgbToHex(rgb: RgbColor): string {
|
|
|
94
94
|
const r = clamp(Math.round(rgb.r), 0, 255);
|
|
95
95
|
const g = clamp(Math.round(rgb.g), 0, 255);
|
|
96
96
|
const b = clamp(Math.round(rgb.b), 0, 255);
|
|
97
|
-
return
|
|
97
|
+
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
// ── RGB ↔ HSL ──────────────────────────────────────────────
|