meno-core 1.0.40 → 1.0.41
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/bin/cli.ts +33 -0
- package/build-astro.ts +3 -27
- package/dist/bin/cli.js +30 -2
- package/dist/bin/cli.js.map +2 -2
- package/dist/build-static.js +7 -7
- package/dist/chunks/{chunk-3NOZVNM4.js → chunk-EQOSDQS2.js} +4 -4
- package/dist/chunks/{chunk-V4SVSX3X.js → chunk-IBR2F4IL.js} +4 -5
- package/dist/chunks/{chunk-V4SVSX3X.js.map → chunk-IBR2F4IL.js.map} +2 -2
- package/dist/chunks/{chunk-OJ5SROQN.js → chunk-IGVQF5GY.js} +8 -6
- package/dist/chunks/chunk-IGVQF5GY.js.map +7 -0
- package/dist/chunks/{chunk-Z7SAOCDG.js → chunk-LBWIHPN7.js} +5 -2
- package/dist/chunks/{chunk-Z7SAOCDG.js.map → chunk-LBWIHPN7.js.map} +2 -2
- package/dist/chunks/{chunk-A6KWUEA6.js → chunk-MKB2J6AD.js} +9 -1
- package/dist/chunks/chunk-MKB2J6AD.js.map +7 -0
- package/dist/chunks/{chunk-LOJLO2EY.js → chunk-S2HXJTAF.js} +1 -1
- package/dist/chunks/chunk-S2HXJTAF.js.map +7 -0
- package/dist/chunks/{chunk-GKICS7CF.js → chunk-SK3TLNUP.js} +120 -107
- package/dist/chunks/chunk-SK3TLNUP.js.map +7 -0
- package/dist/chunks/{chunk-MOCRENNU.js → chunk-SNUROC7E.js} +6 -6
- package/dist/chunks/{configService-TXBNUBBL.js → configService-MICL4S2L.js} +2 -2
- package/dist/chunks/{constants-L75FR445.js → constants-ZEU4TZCA.js} +2 -2
- package/dist/entries/server-router.js +7 -7
- package/dist/lib/client/index.js +9 -4
- package/dist/lib/client/index.js.map +2 -2
- package/dist/lib/server/index.js +76 -2966
- package/dist/lib/server/index.js.map +4 -4
- package/dist/lib/shared/index.js +3 -3
- package/dist/lib/test-utils/index.js +1 -1
- package/lib/client/core/ComponentBuilder.ts +1 -1
- package/lib/client/routing/Router.tsx +6 -0
- package/lib/client/templateEngine.test.ts +178 -0
- package/lib/client/templateEngine.ts +1 -2
- package/lib/server/astro/cmsPageEmitter.ts +3 -0
- package/lib/server/astro/componentEmitter.ts +60 -12
- package/lib/server/astro/nodeToAstro.test.ts +1101 -0
- package/lib/server/astro/nodeToAstro.ts +43 -4
- package/lib/server/astro/pageEmitter.ts +4 -0
- package/lib/server/index.ts +18 -4
- package/lib/server/services/configService.ts +12 -0
- package/lib/server/ssr/htmlGenerator.ts +0 -5
- package/lib/server/ssr/imageMetadata.ts +3 -3
- package/lib/server/ssr/ssrRenderer.ts +48 -19
- package/lib/shared/constants.ts +2 -0
- package/lib/shared/types/components.ts +8 -4
- package/lib/shared/validation/propValidator.ts +2 -1
- package/lib/shared/validation/schemas.ts +3 -1
- package/package.json +1 -1
- package/templates/index-router.html +0 -5
- package/dist/chunks/chunk-A6KWUEA6.js.map +0 -7
- package/dist/chunks/chunk-GKICS7CF.js.map +0 -7
- package/dist/chunks/chunk-LOJLO2EY.js.map +0 -7
- package/dist/chunks/chunk-OJ5SROQN.js.map +0 -7
- /package/dist/chunks/{chunk-3NOZVNM4.js.map → chunk-EQOSDQS2.js.map} +0 -0
- /package/dist/chunks/{chunk-MOCRENNU.js.map → chunk-SNUROC7E.js.map} +0 -0
- /package/dist/chunks/{configService-TXBNUBBL.js.map → configService-MICL4S2L.js.map} +0 -0
- /package/dist/chunks/{constants-L75FR445.js.map → constants-ZEU4TZCA.js.map} +0 -0
|
@@ -102,6 +102,8 @@ export interface AstroEmitContext {
|
|
|
102
102
|
slugMappings?: SlugMap[];
|
|
103
103
|
/** I18n default locale (for slug translation) */
|
|
104
104
|
i18nDefaultLocale?: string;
|
|
105
|
+
/** Image format: 'webp' uses plain <img>, 'avif' uses <picture> with AVIF+WebP sources */
|
|
106
|
+
imageFormat?: 'webp' | 'avif';
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
// ---------------------------------------------------------------------------
|
|
@@ -773,8 +775,8 @@ function emitImageNode(node: HtmlNode, ctx: AstroEmitContext): string {
|
|
|
773
775
|
const ifExpr = emitIfOpen(node, ctx);
|
|
774
776
|
const ifClose = emitIfClose(node, ctx);
|
|
775
777
|
|
|
776
|
-
// If we have AVIF srcset, render as <picture> with split classes
|
|
777
|
-
if (metadata?.avifSrcset) {
|
|
778
|
+
// If we have AVIF srcset and format allows, render as <picture> with split classes
|
|
779
|
+
if (metadata?.avifSrcset && ctx.imageFormat !== 'webp') {
|
|
778
780
|
// Extract static classes from classExpr for splitting
|
|
779
781
|
const classMatch = classExpr.match(/class="([^"]*)"/);
|
|
780
782
|
const classListMatch = classExpr.match(/class:list={\[(.+)\]}/);
|
|
@@ -998,6 +1000,31 @@ function emitEmbedNode(node: EmbedNode, ctx: AstroEmitContext): string {
|
|
|
998
1000
|
}
|
|
999
1001
|
}
|
|
1000
1002
|
|
|
1003
|
+
// Handle template expressions in HTML string (e.g., "{{icon}}")
|
|
1004
|
+
if (typeof node.html === 'string' && hasTemplates(node.html) && ctx.isComponentDef) {
|
|
1005
|
+
const fullMatch = node.html.match(/^\{\{(.+)\}\}$/);
|
|
1006
|
+
if (fullMatch) {
|
|
1007
|
+
let propRef = fullMatch[1].trim();
|
|
1008
|
+
if (ctx.listItemBinding) propRef = rewriteItemVar(propRef, ctx.listItemBinding);
|
|
1009
|
+
if (ctx.listIndexVar) propRef = replaceItemMetaVars(propRef, ctx.listIndexVar, ctx.listSourceVar);
|
|
1010
|
+
|
|
1011
|
+
let tplClassExpr = classExpr;
|
|
1012
|
+
if (!classExpr.includes('oem')) {
|
|
1013
|
+
if (classExpr) {
|
|
1014
|
+
tplClassExpr = classExpr.replace(/class="/, 'class="oem ').replace(/class:list={\['/, "class:list={['oem ");
|
|
1015
|
+
} else {
|
|
1016
|
+
tplClassExpr = ' class="oem"';
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
return (
|
|
1021
|
+
`${ifExpr}${ind(ctx)}<div${tplClassExpr}${attrs}>\n` +
|
|
1022
|
+
`${ind(ctx)} <Fragment set:html={${propRef}} />\n` +
|
|
1023
|
+
`${ind(ctx)}</div>\n${emitIfClose(node, ctx)}`
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1001
1028
|
const htmlStr = typeof node.html === 'string' ? node.html : '';
|
|
1002
1029
|
const escapedHtml = escapeTemplateLiteral(htmlStr);
|
|
1003
1030
|
|
|
@@ -1156,7 +1183,7 @@ function emitImageTypeNode(node: any, ctx: AstroEmitContext): string {
|
|
|
1156
1183
|
|
|
1157
1184
|
const sizesValue = DEFAULT_SIZES;
|
|
1158
1185
|
|
|
1159
|
-
if (metadata.avifSrcset) {
|
|
1186
|
+
if (metadata.avifSrcset && ctx.imageFormat !== 'webp') {
|
|
1160
1187
|
const classMatch = classExpr.match(/class="([^"]*)"/);
|
|
1161
1188
|
const allClasses = classMatch ? classMatch[1].split(/\s+/).filter(Boolean) : [];
|
|
1162
1189
|
const { pictureClasses, imgClasses } = splitImageClasses(allClasses);
|
|
@@ -1496,6 +1523,10 @@ function emitIfOpen(node: ComponentNode, ctx: AstroEmitContext): string {
|
|
|
1496
1523
|
// Component prop context
|
|
1497
1524
|
if (ctx.isComponentDef) {
|
|
1498
1525
|
const fullMatch = ifValue.match(/^\{\{(.+)\}\}$/);
|
|
1526
|
+
if (!fullMatch && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(ifValue)) {
|
|
1527
|
+
// Plain string without template syntax and not a valid identifier — always truthy, skip conditional
|
|
1528
|
+
return '';
|
|
1529
|
+
}
|
|
1499
1530
|
let expr = fullMatch ? fullMatch[1].trim() : ifValue.replace(/\{\{(.+?)\}\}/g, '$1');
|
|
1500
1531
|
if (ctx.listItemBinding) expr = rewriteItemVar(expr, ctx.listItemBinding);
|
|
1501
1532
|
if (ctx.listIndexVar) expr = replaceItemMetaVars(expr, ctx.listIndexVar, ctx.listSourceVar);
|
|
@@ -1519,7 +1550,15 @@ function emitIfClose(node: ComponentNode, ctx: AstroEmitContext): string {
|
|
|
1519
1550
|
if (typeof ifValue === 'string') {
|
|
1520
1551
|
const hasCmsCondition = ctx.cmsMode && ifValue.includes('{{cms.');
|
|
1521
1552
|
const hasItemCondition = ctx.listItemBinding && /\{\{/.test(ifValue);
|
|
1522
|
-
if (hasCmsCondition || hasItemCondition
|
|
1553
|
+
if (hasCmsCondition || hasItemCondition) {
|
|
1554
|
+
return `${ind(ctx)})}\n`;
|
|
1555
|
+
}
|
|
1556
|
+
if (ctx.isComponentDef) {
|
|
1557
|
+
const fullMatch = ifValue.match(/^\{\{(.+)\}\}$/);
|
|
1558
|
+
if (!fullMatch && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(ifValue)) {
|
|
1559
|
+
// Matches the skip in emitIfOpen — no open brace was emitted
|
|
1560
|
+
return '';
|
|
1561
|
+
}
|
|
1523
1562
|
return `${ind(ctx)})}\n`;
|
|
1524
1563
|
}
|
|
1525
1564
|
}
|
|
@@ -51,6 +51,8 @@ export interface PageEmitOptions {
|
|
|
51
51
|
currentPageSlugMap?: Record<string, string>;
|
|
52
52
|
/** Slug mappings for translating internal link hrefs */
|
|
53
53
|
slugMappings?: SlugMap[];
|
|
54
|
+
/** Image format: 'webp' uses plain <img>, 'avif' uses <picture> */
|
|
55
|
+
imageFormat?: 'webp' | 'avif';
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
// ---------------------------------------------------------------------------
|
|
@@ -102,6 +104,7 @@ export function emitAstroPage(options: PageEmitOptions): string {
|
|
|
102
104
|
i18nConfig,
|
|
103
105
|
currentPageSlugMap,
|
|
104
106
|
slugMappings,
|
|
107
|
+
imageFormat,
|
|
105
108
|
} = options;
|
|
106
109
|
|
|
107
110
|
const breakpoints = breakpointsOpt ?? DEFAULT_BREAKPOINTS;
|
|
@@ -132,6 +135,7 @@ export function emitAstroPage(options: PageEmitOptions): string {
|
|
|
132
135
|
astroImports: new Set<string>(),
|
|
133
136
|
slugMappings,
|
|
134
137
|
i18nDefaultLocale: i18nConfig?.defaultLocale,
|
|
138
|
+
imageFormat,
|
|
135
139
|
};
|
|
136
140
|
|
|
137
141
|
// Emit the template body
|
package/lib/server/index.ts
CHANGED
|
@@ -17,7 +17,7 @@ export { generateBuildErrorPage, type BuildError, type BuildErrorsData } from '.
|
|
|
17
17
|
export { PageService } from './services/pageService';
|
|
18
18
|
export { ComponentService, type ComponentInfo } from './services/componentService';
|
|
19
19
|
export { CMSService, type ReferenceLocation } from './services/cmsService';
|
|
20
|
-
export { configService, ConfigService } from './services/configService';
|
|
20
|
+
export { configService, ConfigService, type ImageFormat } from './services/configService';
|
|
21
21
|
export { ColorService, colorService } from './services/ColorService';
|
|
22
22
|
export { VariableService, variableService } from './services/VariableService';
|
|
23
23
|
export { EnumService, enumService } from './services/EnumService';
|
|
@@ -62,9 +62,6 @@ export { migrateTemplatesDirectory } from './migrateTemplates';
|
|
|
62
62
|
// Static build
|
|
63
63
|
export { buildStaticPages } from '../../build-static';
|
|
64
64
|
|
|
65
|
-
// Astro export
|
|
66
|
-
export { buildAstroProject } from '../../build-astro';
|
|
67
|
-
|
|
68
65
|
// Webflow export
|
|
69
66
|
export { buildWebflowPayload } from './webflow';
|
|
70
67
|
export type {
|
|
@@ -74,6 +71,23 @@ export type {
|
|
|
74
71
|
WebflowStyleClass,
|
|
75
72
|
} from './webflow';
|
|
76
73
|
|
|
74
|
+
// JSON loaders
|
|
75
|
+
export {
|
|
76
|
+
loadJSONFile,
|
|
77
|
+
loadComponentDirectory,
|
|
78
|
+
mapPageNameToPath,
|
|
79
|
+
parseJSON,
|
|
80
|
+
loadI18nConfig,
|
|
81
|
+
loadBreakpointConfig,
|
|
82
|
+
loadResponsiveScalesConfig,
|
|
83
|
+
} from './jsonLoader';
|
|
84
|
+
|
|
85
|
+
// CSS generation
|
|
86
|
+
export { generateThemeColorVariablesCSS, generateVariablesCSS } from './cssGenerator';
|
|
87
|
+
|
|
88
|
+
// Font utilities
|
|
89
|
+
export { generateFontCSS, generateFontPreloadTags } from '../shared/fontLoader';
|
|
90
|
+
|
|
77
91
|
// Utilities
|
|
78
92
|
export * from './utils';
|
|
79
93
|
|
|
@@ -28,6 +28,8 @@ export interface IconsConfig {
|
|
|
28
28
|
/**
|
|
29
29
|
* Raw project config structure from project.config.json
|
|
30
30
|
*/
|
|
31
|
+
export type ImageFormat = 'webp' | 'avif';
|
|
32
|
+
|
|
31
33
|
interface RawProjectConfig {
|
|
32
34
|
breakpoints?: BreakpointConfigInput;
|
|
33
35
|
responsiveScales?: Partial<ResponsiveScales>;
|
|
@@ -36,6 +38,7 @@ interface RawProjectConfig {
|
|
|
36
38
|
libraries?: LibrariesConfig;
|
|
37
39
|
csp?: CSPConfig;
|
|
38
40
|
baseComponent?: string;
|
|
41
|
+
imageFormat?: ImageFormat;
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
/**
|
|
@@ -253,6 +256,15 @@ export class ConfigService {
|
|
|
253
256
|
return this.config.baseComponent;
|
|
254
257
|
}
|
|
255
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Get image format setting
|
|
261
|
+
* Returns 'webp' (default) or 'avif'
|
|
262
|
+
*/
|
|
263
|
+
getImageFormat(): ImageFormat {
|
|
264
|
+
if (this.config?.imageFormat === 'avif') return 'avif';
|
|
265
|
+
return 'webp';
|
|
266
|
+
}
|
|
267
|
+
|
|
256
268
|
/**
|
|
257
269
|
* Get raw config value by key (for extension)
|
|
258
270
|
*/
|
|
@@ -135,7 +135,7 @@ export function extractImgSrc(imgTag: string): string | null {
|
|
|
135
135
|
* Only rewrites images that exist in the metadata map (project images with generated variants).
|
|
136
136
|
* External/unrecognized images are left unchanged.
|
|
137
137
|
*/
|
|
138
|
-
export function rewriteRichTextImages(html: string, metadataMap: ImageMetadataMap): string {
|
|
138
|
+
export function rewriteRichTextImages(html: string, metadataMap: ImageMetadataMap, imageFormat?: 'webp' | 'avif'): string {
|
|
139
139
|
return html.replace(/<img\b([^>]*)\/?>/gi, (fullMatch, innerAttrs: string) => {
|
|
140
140
|
const src = extractImgSrc(fullMatch);
|
|
141
141
|
if (!src) return fullMatch;
|
|
@@ -158,8 +158,8 @@ export function rewriteRichTextImages(html: string, metadataMap: ImageMetadataMa
|
|
|
158
158
|
|
|
159
159
|
const sizes = DEFAULT_SIZES;
|
|
160
160
|
|
|
161
|
-
// Render as <picture> with AVIF + WebP sources when AVIF is available
|
|
162
|
-
if (metadata.avifSrcset) {
|
|
161
|
+
// Render as <picture> with AVIF + WebP sources when AVIF is available and format allows
|
|
162
|
+
if (metadata.avifSrcset && imageFormat !== 'webp') {
|
|
163
163
|
return `<picture>` +
|
|
164
164
|
`<source type="image/avif" srcset="${escapeHtml(metadata.avifSrcset)}" sizes="${escapeHtml(sizes)}" />` +
|
|
165
165
|
`<source type="image/webp" srcset="${escapeHtml(metadata.srcset)}" sizes="${escapeHtml(sizes)}" />` +
|
|
@@ -12,6 +12,7 @@ import type { BreakpointConfig } from '../../shared/breakpoints';
|
|
|
12
12
|
import { evaluateTemplate, processStructure, isResponsiveStyle, isHtmlMapping, resolveHtmlMapping } from '../../client/templateEngine';
|
|
13
13
|
import { resolvePropsFromDefinition, isRichTextMarker, richTextMarkerToHtml } from '../../shared/propResolver';
|
|
14
14
|
import { loadBreakpointConfig, loadI18nConfig } from '../jsonLoader';
|
|
15
|
+
import { configService } from '../services/configService';
|
|
15
16
|
import type { I18nConfig } from '../../shared/types/components';
|
|
16
17
|
import { extractLocaleFromPath, DEFAULT_I18N_CONFIG, resolveI18nValue, buildLocalizedPath, isI18nValue } from '../../shared/i18n';
|
|
17
18
|
import { NODE_TYPE } from '../../shared/constants';
|
|
@@ -118,6 +119,8 @@ interface SSRContext {
|
|
|
118
119
|
isProductionBuild?: boolean;
|
|
119
120
|
/** Collector for SSR fallback HTML of complex nodes (list, locale-list) keyed by element path */
|
|
120
121
|
ssrFallbackCollector?: Map<string, string>;
|
|
122
|
+
/** Image format: 'webp' uses plain <img>, 'avif' uses <picture> with AVIF+WebP sources */
|
|
123
|
+
imageFormat?: 'webp' | 'avif';
|
|
121
124
|
}
|
|
122
125
|
|
|
123
126
|
/**
|
|
@@ -408,6 +411,7 @@ export async function buildComponentHTML(
|
|
|
408
411
|
neededCollections, // Track collections that need client-side data
|
|
409
412
|
isProductionBuild,
|
|
410
413
|
ssrFallbackCollector, // Collect SSR fallback HTML for complex nodes
|
|
414
|
+
imageFormat: configService.getImageFormat(),
|
|
411
415
|
};
|
|
412
416
|
|
|
413
417
|
const html = await renderNode(node, ctx);
|
|
@@ -800,7 +804,7 @@ async function renderNode(
|
|
|
800
804
|
// Check for raw HTML marker (from rich-text fields) - don't escape
|
|
801
805
|
if (text.startsWith(RAW_HTML_PREFIX)) {
|
|
802
806
|
let rawHtml = text.slice(RAW_HTML_PREFIX.length);
|
|
803
|
-
if (ctx.imageMetadataMap) rawHtml = rewriteRichTextImages(rawHtml, ctx.imageMetadataMap);
|
|
807
|
+
if (ctx.imageMetadataMap) rawHtml = rewriteRichTextImages(rawHtml, ctx.imageMetadataMap, ctx.imageFormat);
|
|
804
808
|
rawHtml = await expandRichTextComponents(rawHtml, ctx);
|
|
805
809
|
rawHtml = localizeRichTextLinks(rawHtml, ctx);
|
|
806
810
|
return rawHtml;
|
|
@@ -820,7 +824,7 @@ async function renderNode(
|
|
|
820
824
|
// Check for raw HTML marker (from rich-text fields) - don't escape
|
|
821
825
|
if (text.startsWith(RAW_HTML_PREFIX)) {
|
|
822
826
|
let rawHtml = text.slice(RAW_HTML_PREFIX.length);
|
|
823
|
-
if (ctx.imageMetadataMap) rawHtml = rewriteRichTextImages(rawHtml, ctx.imageMetadataMap);
|
|
827
|
+
if (ctx.imageMetadataMap) rawHtml = rewriteRichTextImages(rawHtml, ctx.imageMetadataMap, ctx.imageFormat);
|
|
824
828
|
rawHtml = await expandRichTextComponents(rawHtml, ctx);
|
|
825
829
|
rawHtml = localizeRichTextLinks(rawHtml, ctx);
|
|
826
830
|
return rawHtml;
|
|
@@ -892,7 +896,7 @@ async function renderNode(
|
|
|
892
896
|
KEEP_CONTENT: true
|
|
893
897
|
}) : htmlContent;
|
|
894
898
|
const optimizedHtml = ctx.imageMetadataMap
|
|
895
|
-
? rewriteRichTextImages(sanitizedHtml, ctx.imageMetadataMap)
|
|
899
|
+
? rewriteRichTextImages(sanitizedHtml, ctx.imageMetadataMap, ctx.imageFormat)
|
|
896
900
|
: sanitizedHtml;
|
|
897
901
|
|
|
898
902
|
// Extract attributes from node
|
|
@@ -1227,27 +1231,52 @@ async function renderComponent(
|
|
|
1227
1231
|
// Merge instance style overrides, className, and attributes
|
|
1228
1232
|
// processedStructure is typically an HTML node (the component's root element)
|
|
1229
1233
|
// Handle both component nodes and HTML nodes
|
|
1230
|
-
const rootNode = processedStructure as ComponentNode & { props?: Record<string, unknown> };
|
|
1234
|
+
const rootNode = processedStructure as ComponentNode & { props?: Record<string, unknown>; style?: any; attributes?: Record<string, string | number | boolean> };
|
|
1231
1235
|
if (isComponentNode(rootNode) || isHtmlNode(rootNode)) {
|
|
1232
1236
|
if (!rootNode.props) {
|
|
1233
1237
|
rootNode.props = {};
|
|
1234
1238
|
}
|
|
1235
1239
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1240
|
+
// For HTML root nodes, merge styles into node.style (where the renderer reads them)
|
|
1241
|
+
if (isHtmlNode(rootNode)) {
|
|
1242
|
+
if (propsWithStyleAndAttrs.style) {
|
|
1243
|
+
const existingStyle = rootNode.style;
|
|
1244
|
+
if (existingStyle && typeof existingStyle === 'object') {
|
|
1245
|
+
rootNode.style = {
|
|
1246
|
+
...(existingStyle as Record<string, any>),
|
|
1247
|
+
...(propsWithStyleAndAttrs.style as Record<string, any>)
|
|
1248
|
+
};
|
|
1249
|
+
} else {
|
|
1250
|
+
rootNode.style = propsWithStyleAndAttrs.style as any;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
} else {
|
|
1254
|
+
// For component root nodes, merge into props.style
|
|
1255
|
+
if (rootNode.props.style && typeof rootNode.props.style === 'object') {
|
|
1256
|
+
rootNode.props.style = {
|
|
1257
|
+
...(rootNode.props.style as Record<string, unknown>),
|
|
1258
|
+
...(propsWithStyleAndAttrs.style as Record<string, unknown> || {})
|
|
1259
|
+
};
|
|
1260
|
+
} else if (propsWithStyleAndAttrs.style) {
|
|
1261
|
+
rootNode.props.style = propsWithStyleAndAttrs.style;
|
|
1262
|
+
}
|
|
1243
1263
|
}
|
|
1244
1264
|
|
|
1245
1265
|
// Merge className from component instance (includes responsive utility classes)
|
|
1246
1266
|
if (propsWithStyleAndAttrs.className) {
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1267
|
+
if (isHtmlNode(rootNode)) {
|
|
1268
|
+
// For HTML root nodes, merge into attributes where the renderer reads them
|
|
1269
|
+
if (!rootNode.attributes) rootNode.attributes = {};
|
|
1270
|
+
const existingClass = (rootNode.attributes.class || '') as string;
|
|
1271
|
+
rootNode.attributes.class = existingClass
|
|
1272
|
+
? `${existingClass} ${propsWithStyleAndAttrs.className}`
|
|
1273
|
+
: propsWithStyleAndAttrs.className as string;
|
|
1274
|
+
} else {
|
|
1275
|
+
const existingClassName = rootNode.props.className || '';
|
|
1276
|
+
rootNode.props.className = existingClassName
|
|
1277
|
+
? `${existingClassName} ${propsWithStyleAndAttrs.className}`
|
|
1278
|
+
: propsWithStyleAndAttrs.className;
|
|
1279
|
+
}
|
|
1251
1280
|
}
|
|
1252
1281
|
|
|
1253
1282
|
// Merge attributes into props
|
|
@@ -1440,8 +1469,8 @@ function renderImageElement(
|
|
|
1440
1469
|
|
|
1441
1470
|
// Collect high-priority images for preloading in head
|
|
1442
1471
|
if (fetchpriority === 'high' && metadata && ctx.preloadImages) {
|
|
1443
|
-
// Prefer AVIF, fallback to WebP
|
|
1444
|
-
if (metadata.avifSrcset) {
|
|
1472
|
+
// Prefer AVIF when format allows, fallback to WebP
|
|
1473
|
+
if (metadata.avifSrcset && ctx.imageFormat !== 'webp') {
|
|
1445
1474
|
ctx.preloadImages.push({
|
|
1446
1475
|
srcset: metadata.avifSrcset,
|
|
1447
1476
|
type: 'image/avif',
|
|
@@ -1472,9 +1501,9 @@ function renderImageElement(
|
|
|
1472
1501
|
blurStyle = ` style="background-image: url(${escapeHtml(metadata.blurHash)}); background-size: cover;" onload="this.style.backgroundImage=''"`;
|
|
1473
1502
|
}
|
|
1474
1503
|
|
|
1475
|
-
// Render as <picture> element if AVIF is available
|
|
1504
|
+
// Render as <picture> element if AVIF is available and imageFormat is 'avif'
|
|
1476
1505
|
// Layout classes go on <picture> (block container), image-specific classes on <img>
|
|
1477
|
-
if (metadata?.avifSrcset) {
|
|
1506
|
+
if (metadata?.avifSrcset && ctx.imageFormat !== 'webp') {
|
|
1478
1507
|
// Image-specific class prefixes that should stay on <img>
|
|
1479
1508
|
const imgClassPrefixes = [
|
|
1480
1509
|
'objf-', 'objp-', 'flt-', 'tm-', 'bs-',
|
package/lib/shared/constants.ts
CHANGED
|
@@ -131,6 +131,7 @@ export const IFRAME_MESSAGE_TYPES = {
|
|
|
131
131
|
PAGE_DATA_PREVIEW_REVERT: 'PAGE_DATA_PREVIEW_REVERT', // Editor → Iframe to revert preview
|
|
132
132
|
PAGE_DATA_COMMITTED: 'PAGE_DATA_COMMITTED', // Editor → Iframe for committed mutations
|
|
133
133
|
COMPONENT_DEFINITION_COMMITTED: 'COMPONENT_DEFINITION_COMMITTED', // Editor → Iframe for component definition updates
|
|
134
|
+
CSS_VARIABLE_UPDATE: 'CSS_VARIABLE_UPDATE', // Editor → Iframe for instant CSS variable preview
|
|
134
135
|
} as const;
|
|
135
136
|
|
|
136
137
|
// Component node type constants
|
|
@@ -152,6 +153,7 @@ export const SPECIAL_PATHS = {
|
|
|
152
153
|
COMPONENT_JAVASCRIPT: 'component_javascript',
|
|
153
154
|
COMPONENT_CSS: 'component_css',
|
|
154
155
|
COMPONENT_LIBRARIES: 'component_libraries',
|
|
156
|
+
COMPONENT_ACCEPTS_STYLES: 'component_acceptsStyles',
|
|
155
157
|
STRUCTURE_STYLE: 'structure_style',
|
|
156
158
|
} as const;
|
|
157
159
|
|
|
@@ -132,6 +132,8 @@ export interface StructuredComponentDefinition {
|
|
|
132
132
|
defineVars?: true | string[];
|
|
133
133
|
/** External JS/CSS libraries required by this component */
|
|
134
134
|
libraries?: LibrariesConfig;
|
|
135
|
+
/** Whether instances of this component can have styles applied to the wrapper */
|
|
136
|
+
acceptsStyles?: boolean;
|
|
135
137
|
}
|
|
136
138
|
|
|
137
139
|
/**
|
|
@@ -166,13 +168,14 @@ export interface JSONPage {
|
|
|
166
168
|
/**
|
|
167
169
|
* Page data type that can be either a JSONPage or a component definition structure
|
|
168
170
|
*/
|
|
169
|
-
export type PageData = JSONPage | {
|
|
170
|
-
component: {
|
|
171
|
-
structure?: ComponentNode;
|
|
171
|
+
export type PageData = JSONPage | {
|
|
172
|
+
component: {
|
|
173
|
+
structure?: ComponentNode;
|
|
172
174
|
interface?: Record<string, PropDefinition>;
|
|
173
175
|
javascript?: string;
|
|
174
176
|
css?: string;
|
|
175
|
-
|
|
177
|
+
acceptsStyles?: boolean;
|
|
178
|
+
}
|
|
176
179
|
};
|
|
177
180
|
|
|
178
181
|
/**
|
|
@@ -185,5 +188,6 @@ export interface PageDataWithComponent {
|
|
|
185
188
|
javascript?: string;
|
|
186
189
|
css?: string;
|
|
187
190
|
libraries?: LibrariesConfig;
|
|
191
|
+
acceptsStyles?: boolean;
|
|
188
192
|
};
|
|
189
193
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import type { PropDefinition } from '../types/components';
|
|
8
8
|
import { isI18nValue } from '../i18n';
|
|
9
|
+
import { isTiptapDocument } from '../richtext/types';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Validation error with context for prop validation
|
|
@@ -158,7 +159,7 @@ function validateSingleProp(
|
|
|
158
159
|
// Rich-text accepts both strings and i18n values (i18n values will be resolved later)
|
|
159
160
|
// Coerce numbers/booleans to strings (e.g. from CMS template resolution like {{item.value}})
|
|
160
161
|
coercedValue = (typeof value === 'number' || typeof value === 'boolean') ? String(value) : value;
|
|
161
|
-
typeValid = typeof coercedValue === 'string' || isI18nValue(coercedValue);
|
|
162
|
+
typeValid = typeof coercedValue === 'string' || isI18nValue(coercedValue) || isTiptapDocument(coercedValue);
|
|
162
163
|
break;
|
|
163
164
|
|
|
164
165
|
case 'file':
|
|
@@ -379,6 +379,7 @@ export const StructuredComponentDefinitionSchema = z.object({
|
|
|
379
379
|
css: z.string().optional(),
|
|
380
380
|
category: z.string().optional(),
|
|
381
381
|
libraries: LibrariesConfigSchema.optional(),
|
|
382
|
+
acceptsStyles: z.boolean().optional(),
|
|
382
383
|
}).passthrough();
|
|
383
384
|
|
|
384
385
|
/**
|
|
@@ -477,7 +478,8 @@ export const PageDataWithComponentSchema = z.object({
|
|
|
477
478
|
interface: z.record(z.string(), PropDefinitionSchema).optional(),
|
|
478
479
|
javascript: z.string().optional(),
|
|
479
480
|
css: z.string().optional(),
|
|
480
|
-
|
|
481
|
+
acceptsStyles: z.boolean().optional(),
|
|
482
|
+
}).passthrough(),
|
|
481
483
|
}).passthrough();
|
|
482
484
|
|
|
483
485
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../lib/server/services/configService.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Config Service\n * Centralized configuration loading and access\n *\n * Consolidates multiple config loaders into a single service that loads\n * the project.config.json file once and exposes typed sections.\n */\n\nimport type { BreakpointConfig, BreakpointConfigInput, BreakpointEntry } from '../../shared/breakpoints';\nimport { DEFAULT_BREAKPOINTS, normalizeBreakpointConfig } from '../../shared/breakpoints';\nimport type { ResponsiveScales, BreakpointScales } from '../../shared/responsiveScaling';\nimport { DEFAULT_RESPONSIVE_SCALES } from '../../shared/responsiveScaling';\nimport type { I18nConfig } from '../../shared/types/components';\nimport type { LibrariesConfig, JSLibraryConfig, CSSLibraryConfig } from '../../shared/types/libraries';\nimport type { CSPConfig } from '../../shared/types/config';\nimport { DEFAULT_I18N_CONFIG, migrateI18nConfig } from '../../shared/i18n';\nimport { projectPaths } from '../projectContext';\nimport { readTextFile, fileExists } from '../runtime';\n\n/**\n * Icons configuration\n */\nexport interface IconsConfig {\n favicon?: string;\n appleTouchIcon?: string;\n}\n\n/**\n * Raw project config structure from project.config.json\n */\ninterface RawProjectConfig {\n breakpoints?: BreakpointConfigInput;\n responsiveScales?: Partial<ResponsiveScales>;\n i18n?: unknown;\n icons?: IconsConfig;\n libraries?: LibrariesConfig;\n csp?: CSPConfig;\n baseComponent?: string;\n}\n\n/**\n * ConfigService\n * Loads project configuration once and provides typed access to sections\n */\nexport class ConfigService {\n private config: RawProjectConfig | null = null;\n private loaded = false;\n\n /**\n * Load configuration from project.config.json\n * Safe to call multiple times - only loads once\n */\n async load(): Promise<void> {\n if (this.loaded) {\n return;\n }\n\n try {\n if (await fileExists(projectPaths.config())) {\n const content = await readTextFile(projectPaths.config());\n this.config = JSON.parse(content);\n }\n } catch {\n // Fall through to defaults\n this.config = null;\n }\n\n this.loaded = true;\n }\n\n /**\n * Check if configuration has been loaded\n */\n isLoaded(): boolean {\n return this.loaded;\n }\n\n /**\n * Reset the service (for testing)\n */\n reset(): void {\n this.config = null;\n this.loaded = false;\n }\n\n /**\n * Get breakpoint configuration\n * Returns validated and normalized breakpoints (always object format)\n * Supports both legacy format { tablet: 1024 } and new format { tablet: { breakpoint: 1024, previewPoint: 768 } }\n */\n getBreakpoints(): BreakpointConfig {\n if (!this.config?.breakpoints || typeof this.config.breakpoints !== 'object') {\n return { ...DEFAULT_BREAKPOINTS };\n }\n\n // Validate breakpoint values before normalization\n const validInput: BreakpointConfigInput = {};\n for (const [key, value] of Object.entries(this.config.breakpoints)) {\n if (typeof value === 'number' && value > 0) {\n // Legacy format: number\n validInput[key] = value;\n } else if (typeof value === 'object' && value !== null) {\n // New format: object with breakpoint and optional previewPoint\n const entry = value as BreakpointEntry;\n if (typeof entry.breakpoint === 'number' && entry.breakpoint > 0) {\n validInput[key] = {\n breakpoint: entry.breakpoint,\n previewPoint: typeof entry.previewPoint === 'number' && entry.previewPoint > 0\n ? entry.previewPoint\n : entry.breakpoint,\n };\n }\n }\n }\n\n // Return normalized breakpoints or defaults if none valid\n if (Object.keys(validInput).length === 0) {\n return { ...DEFAULT_BREAKPOINTS };\n }\n\n return normalizeBreakpointConfig(validInput);\n }\n\n /**\n * Get i18n configuration\n * Automatically migrates old string[] format to LocaleConfig[] format\n */\n getI18n(): I18nConfig {\n if (!this.config?.i18n) {\n return { ...DEFAULT_I18N_CONFIG };\n }\n\n return migrateI18nConfig(this.config.i18n);\n }\n\n /**\n * Deep merge scale categories, preserving user-defined breakpoints\n * while filling in missing values from defaults\n */\n private mergeScaleCategory(\n userScales: BreakpointScales | undefined,\n defaultScales: BreakpointScales | undefined\n ): BreakpointScales | undefined {\n if (!userScales && !defaultScales) return undefined;\n if (!userScales) return defaultScales ? { ...defaultScales } : undefined;\n if (!defaultScales) return { ...userScales };\n\n // User scales take precedence, but include defaults for breakpoints not specified\n return {\n ...defaultScales,\n ...userScales,\n };\n }\n\n /**\n * Get responsive scales configuration\n * Supports dynamic breakpoints - scales are keyed by breakpoint name\n * Deep merges scale categories to preserve user breakpoint definitions\n */\n getResponsiveScales(): ResponsiveScales {\n if (!this.config?.responsiveScales || typeof this.config.responsiveScales !== 'object') {\n return { ...DEFAULT_RESPONSIVE_SCALES };\n }\n\n const userScales = this.config.responsiveScales;\n\n return {\n enabled: userScales.enabled ?? DEFAULT_RESPONSIVE_SCALES.enabled,\n baseReference: userScales.baseReference ?? DEFAULT_RESPONSIVE_SCALES.baseReference,\n fontSize: this.mergeScaleCategory(\n userScales.fontSize as BreakpointScales | undefined,\n DEFAULT_RESPONSIVE_SCALES.fontSize\n ),\n padding: this.mergeScaleCategory(\n userScales.padding as BreakpointScales | undefined,\n DEFAULT_RESPONSIVE_SCALES.padding\n ),\n margin: this.mergeScaleCategory(\n userScales.margin as BreakpointScales | undefined,\n DEFAULT_RESPONSIVE_SCALES.margin\n ),\n gap: this.mergeScaleCategory(\n userScales.gap as BreakpointScales | undefined,\n DEFAULT_RESPONSIVE_SCALES.gap\n ),\n };\n }\n\n /**\n * Get icons configuration\n * Returns empty object if not configured\n */\n getIcons(): IconsConfig {\n if (!this.config?.icons || typeof this.config.icons !== 'object') {\n return {};\n }\n\n return this.config.icons;\n }\n\n /**\n * Get libraries configuration\n * Returns empty arrays if not configured\n * Normalizes string URLs to object format for backwards compatibility\n */\n getLibraries(): LibrariesConfig {\n if (!this.config?.libraries || typeof this.config.libraries !== 'object') {\n return { js: [], css: [] };\n }\n\n const libs = this.config.libraries;\n\n // Normalize JS libraries: support both string URLs and object format\n const normalizedJs = Array.isArray(libs.js)\n ? libs.js.map((lib) =>\n typeof lib === 'string' ? { url: lib } : lib\n ) as JSLibraryConfig[]\n : [];\n\n // Normalize CSS libraries: support both string URLs and object format\n const normalizedCss = Array.isArray(libs.css)\n ? libs.css.map((lib) =>\n typeof lib === 'string' ? { url: lib } : lib\n ) as CSSLibraryConfig[]\n : [];\n\n return {\n js: normalizedJs,\n css: normalizedCss,\n };\n }\n\n /**\n * Get CSP configuration\n * Returns empty object if not configured\n */\n getCSP(): CSPConfig {\n if (!this.config?.csp || typeof this.config.csp !== 'object') {\n return {};\n }\n\n return this.config.csp;\n }\n\n /**\n * Get base component name for new pages\n * Returns undefined if not configured\n */\n getBaseComponent(): string | undefined {\n if (!this.config?.baseComponent || typeof this.config.baseComponent !== 'string') {\n return undefined;\n }\n return this.config.baseComponent;\n }\n\n /**\n * Get raw config value by key (for extension)\n */\n getRaw<T>(key: string): T | undefined {\n if (!this.config) {\n return undefined;\n }\n return (this.config as Record<string, unknown>)[key] as T | undefined;\n }\n}\n\n/**\n * Singleton instance for global access\n * Use this for convenience, or create your own instance for testing\n */\nexport const configService = new ConfigService();\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|