meno-core 1.0.47 → 1.0.49
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/build-astro.ts +2 -2
- package/dist/build-static.js +7 -7
- package/dist/chunks/{chunk-UUA5LEWF.js → chunk-6IVUG7FY.js} +138 -7
- package/dist/chunks/chunk-6IVUG7FY.js.map +7 -0
- package/dist/chunks/{chunk-XSWR3QLI.js → chunk-AZQYF6KE.js} +261 -130
- package/dist/chunks/chunk-AZQYF6KE.js.map +7 -0
- package/dist/chunks/{chunk-47UNLQUU.js → chunk-CHD5UCFF.js} +57 -12
- package/dist/chunks/chunk-CHD5UCFF.js.map +7 -0
- package/dist/chunks/{chunk-FGUZOYJX.js → chunk-EQYDSPBB.js} +435 -131
- package/dist/chunks/chunk-EQYDSPBB.js.map +7 -0
- package/dist/chunks/{chunk-IF3RATBY.js → chunk-H4JSCDNW.js} +2 -2
- package/dist/chunks/{chunk-KITQJYZV.js → chunk-J23ZX5AP.js} +40 -4
- package/dist/chunks/chunk-J23ZX5AP.js.map +7 -0
- package/dist/chunks/{chunk-LJFB5EBT.js → chunk-JER5NQVM.js} +5 -5
- package/dist/chunks/{chunk-ZTKHJQ2Z.js → chunk-KPU2XHOS.js} +5 -2
- package/dist/chunks/{chunk-ZTKHJQ2Z.js.map → chunk-KPU2XHOS.js.map} +2 -2
- package/dist/chunks/{chunk-BCLGRZ3U.js → chunk-LKAGAQ3M.js} +2 -2
- package/dist/chunks/{chunk-FED5MME6.js → chunk-S2CX6HFM.js} +262 -26
- package/dist/chunks/chunk-S2CX6HFM.js.map +7 -0
- package/dist/chunks/{configService-DYCUEURL.js → configService-CCA6AIDI.js} +3 -3
- package/dist/entries/server-router.js +9 -9
- package/dist/entries/server-router.js.map +2 -2
- package/dist/lib/client/index.js +64 -20
- package/dist/lib/client/index.js.map +3 -3
- package/dist/lib/server/index.js +1737 -296
- package/dist/lib/server/index.js.map +4 -4
- package/dist/lib/shared/index.js +50 -10
- package/dist/lib/shared/index.js.map +3 -3
- package/entries/server-router.tsx +6 -2
- package/lib/client/core/ComponentBuilder.test.ts +17 -0
- package/lib/client/core/ComponentBuilder.ts +25 -1
- package/lib/client/core/builders/embedBuilder.ts +15 -2
- package/lib/client/core/builders/linkNodeBuilder.ts +15 -2
- package/lib/client/core/builders/localeListBuilder.ts +17 -6
- package/lib/client/styles/StyleInjector.ts +3 -2
- package/lib/client/theme.ts +4 -4
- package/lib/server/cssGenerator.test.ts +64 -1
- package/lib/server/cssGenerator.ts +48 -9
- package/lib/server/index.ts +1 -1
- package/lib/server/jsonLoader.test.ts +0 -17
- package/lib/server/jsonLoader.ts +0 -81
- package/lib/server/providers/fileSystemCMSProvider.test.ts +163 -0
- package/lib/server/providers/fileSystemCMSProvider.ts +200 -11
- package/lib/server/routes/api/variables.ts +4 -2
- package/lib/server/routes/index.ts +1 -1
- package/lib/server/routes/pages.ts +23 -1
- package/lib/server/services/cmsService.test.ts +246 -0
- package/lib/server/services/cmsService.ts +122 -5
- package/lib/server/services/configService.ts +5 -0
- package/lib/server/ssr/attributeBuilder.ts +41 -0
- package/lib/server/ssr/htmlGenerator.test.ts +114 -2
- package/lib/server/ssr/htmlGenerator.ts +53 -6
- package/lib/server/ssr/liveReloadIntegration.test.ts +209 -0
- package/lib/server/ssr/ssrRenderer.test.ts +362 -1
- package/lib/server/ssr/ssrRenderer.ts +216 -72
- package/lib/server/utils/jsonLineMapper.test.ts +53 -1
- package/lib/server/utils/jsonLineMapper.ts +43 -3
- package/lib/server/webflow/buildWebflow.ts +343 -123
- package/lib/server/webflow/index.ts +1 -0
- package/lib/server/webflow/nodeToWebflow.test.ts +3170 -0
- package/lib/server/webflow/nodeToWebflow.ts +2141 -129
- package/lib/server/webflow/styleMapper.test.ts +389 -0
- package/lib/server/webflow/styleMapper.ts +517 -63
- package/lib/server/webflow/templateWrapper.ts +49 -0
- package/lib/server/webflow/types.ts +218 -18
- package/lib/shared/cssGeneration.test.ts +267 -1
- package/lib/shared/cssGeneration.ts +240 -18
- package/lib/shared/cssProperties.test.ts +247 -1
- package/lib/shared/cssProperties.ts +196 -6
- package/lib/shared/elementClassName.test.ts +15 -0
- package/lib/shared/elementClassName.ts +7 -3
- package/lib/shared/interfaces/contentProvider.ts +39 -6
- package/lib/shared/pathSecurity.ts +16 -0
- package/lib/shared/registry/nodeTypes/ListNodeType.ts +1 -1
- package/lib/shared/responsiveScaling.test.ts +143 -0
- package/lib/shared/responsiveScaling.ts +253 -2
- package/lib/shared/themeDefaults.test.ts +3 -3
- package/lib/shared/themeDefaults.ts +3 -3
- package/lib/shared/types/cms.ts +28 -3
- package/lib/shared/types/index.ts +2 -0
- package/lib/shared/types/variables.ts +37 -0
- package/lib/shared/utilityClassConfig.ts +3 -0
- package/lib/shared/utilityClassMapper.test.ts +123 -0
- package/lib/shared/utilityClassMapper.ts +179 -8
- package/lib/shared/validation/schemas.ts +15 -1
- package/lib/shared/validation/validators.ts +26 -1
- package/package.json +1 -1
- package/dist/chunks/chunk-47UNLQUU.js.map +0 -7
- package/dist/chunks/chunk-FED5MME6.js.map +0 -7
- package/dist/chunks/chunk-FGUZOYJX.js.map +0 -7
- package/dist/chunks/chunk-KITQJYZV.js.map +0 -7
- package/dist/chunks/chunk-UUA5LEWF.js.map +0 -7
- package/dist/chunks/chunk-XSWR3QLI.js.map +0 -7
- /package/dist/chunks/{chunk-IF3RATBY.js.map → chunk-H4JSCDNW.js.map} +0 -0
- /package/dist/chunks/{chunk-LJFB5EBT.js.map → chunk-JER5NQVM.js.map} +0 -0
- /package/dist/chunks/{chunk-BCLGRZ3U.js.map → chunk-LKAGAQ3M.js.map} +0 -0
- /package/dist/chunks/{configService-DYCUEURL.js.map → configService-CCA6AIDI.js.map} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
configService
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-KPU2XHOS.js";
|
|
4
4
|
import {
|
|
5
5
|
projectPaths,
|
|
6
6
|
resolveProjectPath,
|
|
@@ -13,17 +13,20 @@ import {
|
|
|
13
13
|
writeFile
|
|
14
14
|
} from "./chunk-WQFG7PAH.js";
|
|
15
15
|
import {
|
|
16
|
+
CMS_DRAFT_SUFFIX,
|
|
17
|
+
isReservedDraftFilename,
|
|
16
18
|
isSafePathSegment,
|
|
17
19
|
isValidIdentifier,
|
|
18
20
|
resolvePaletteColor
|
|
19
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-J23ZX5AP.js";
|
|
20
22
|
import {
|
|
21
23
|
extractAttributesFromNode,
|
|
22
24
|
isHtmlMapping,
|
|
25
|
+
mergeNodeStyles,
|
|
23
26
|
processStructure,
|
|
24
27
|
resolveHtmlMapping,
|
|
25
28
|
skipEmptyTemplateAttributes
|
|
26
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-LKAGAQ3M.js";
|
|
27
30
|
import {
|
|
28
31
|
DEFAULT_PREFETCH_CONFIG,
|
|
29
32
|
SSRRegistry,
|
|
@@ -60,21 +63,26 @@ import {
|
|
|
60
63
|
resolveTemplateRawValue,
|
|
61
64
|
responsiveStylesToClasses,
|
|
62
65
|
singularize,
|
|
66
|
+
validateCMSDraftItem,
|
|
63
67
|
validateCMSItem,
|
|
64
68
|
validateComponentDefinition
|
|
65
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-S2CX6HFM.js";
|
|
66
70
|
import {
|
|
67
71
|
DEFAULT_BREAKPOINTS,
|
|
72
|
+
DEFAULT_FLUID_RANGE,
|
|
68
73
|
DEFAULT_I18N_CONFIG,
|
|
69
|
-
|
|
74
|
+
DEFAULT_SITE_MARGIN,
|
|
75
|
+
buildFluidPropertyValue,
|
|
70
76
|
buildLocalizedPath,
|
|
77
|
+
buildSiteMarginClamp,
|
|
71
78
|
extractLocaleFromPath,
|
|
79
|
+
getSmallestBreakpointName,
|
|
72
80
|
isI18nValue,
|
|
73
81
|
migrateI18nConfig,
|
|
74
82
|
normalizeBreakpointConfig,
|
|
75
83
|
resolveI18nValue,
|
|
76
84
|
scalePropertyValue
|
|
77
|
-
} from "./chunk-
|
|
85
|
+
} from "./chunk-AZQYF6KE.js";
|
|
78
86
|
import {
|
|
79
87
|
isTiptapDocument,
|
|
80
88
|
tiptapToHtml
|
|
@@ -361,48 +369,6 @@ async function loadBreakpointConfig() {
|
|
|
361
369
|
}
|
|
362
370
|
return { ...DEFAULT_BREAKPOINTS };
|
|
363
371
|
}
|
|
364
|
-
function mergeScaleCategory(userScales, defaultScales) {
|
|
365
|
-
if (!userScales && !defaultScales) return void 0;
|
|
366
|
-
if (!userScales) return defaultScales ? { ...defaultScales } : void 0;
|
|
367
|
-
if (!defaultScales) return { ...userScales };
|
|
368
|
-
return {
|
|
369
|
-
...defaultScales,
|
|
370
|
-
...userScales
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
async function loadResponsiveScalesConfig() {
|
|
374
|
-
try {
|
|
375
|
-
const configContent = await loadJSONFile(projectPaths.config());
|
|
376
|
-
if (configContent) {
|
|
377
|
-
const config = parseJSON(configContent);
|
|
378
|
-
if (config.responsiveScales && typeof config.responsiveScales === "object") {
|
|
379
|
-
const scales = {
|
|
380
|
-
enabled: config.responsiveScales.enabled ?? DEFAULT_RESPONSIVE_SCALES.enabled,
|
|
381
|
-
baseReference: config.responsiveScales.baseReference ?? DEFAULT_RESPONSIVE_SCALES.baseReference,
|
|
382
|
-
fontSize: mergeScaleCategory(
|
|
383
|
-
config.responsiveScales.fontSize,
|
|
384
|
-
DEFAULT_RESPONSIVE_SCALES.fontSize
|
|
385
|
-
),
|
|
386
|
-
padding: mergeScaleCategory(
|
|
387
|
-
config.responsiveScales.padding,
|
|
388
|
-
DEFAULT_RESPONSIVE_SCALES.padding
|
|
389
|
-
),
|
|
390
|
-
margin: mergeScaleCategory(
|
|
391
|
-
config.responsiveScales.margin,
|
|
392
|
-
DEFAULT_RESPONSIVE_SCALES.margin
|
|
393
|
-
),
|
|
394
|
-
gap: mergeScaleCategory(
|
|
395
|
-
config.responsiveScales.gap,
|
|
396
|
-
DEFAULT_RESPONSIVE_SCALES.gap
|
|
397
|
-
)
|
|
398
|
-
};
|
|
399
|
-
return scales;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
} catch (error) {
|
|
403
|
-
}
|
|
404
|
-
return { ...DEFAULT_RESPONSIVE_SCALES };
|
|
405
|
-
}
|
|
406
372
|
async function loadI18nConfig() {
|
|
407
373
|
try {
|
|
408
374
|
const configContent = await loadJSONFile(projectPaths.config());
|
|
@@ -759,6 +725,7 @@ var CMSService = class {
|
|
|
759
725
|
schemaCache = /* @__PURE__ */ new Map();
|
|
760
726
|
routePatterns = [];
|
|
761
727
|
provider;
|
|
728
|
+
previewMode;
|
|
762
729
|
/** Item cache with TTL-based expiration */
|
|
763
730
|
itemsCache = /* @__PURE__ */ new Map();
|
|
764
731
|
/** Cache TTL in milliseconds (5 seconds) */
|
|
@@ -766,9 +733,11 @@ var CMSService = class {
|
|
|
766
733
|
/**
|
|
767
734
|
* Creates a new CMSService instance
|
|
768
735
|
* @param provider - Optional CMSProvider for loading data (enables DI for testing)
|
|
736
|
+
* @param options - Service-level flags (preview mode for dev server)
|
|
769
737
|
*/
|
|
770
|
-
constructor(provider) {
|
|
738
|
+
constructor(provider, options = {}) {
|
|
771
739
|
this.provider = provider;
|
|
740
|
+
this.previewMode = options.previewMode === true;
|
|
772
741
|
}
|
|
773
742
|
/**
|
|
774
743
|
* Set the CMS provider
|
|
@@ -779,9 +748,14 @@ var CMSService = class {
|
|
|
779
748
|
this.provider = provider;
|
|
780
749
|
}
|
|
781
750
|
/**
|
|
782
|
-
* Get items with caching
|
|
783
|
-
*
|
|
784
|
-
*
|
|
751
|
+
* Get items with caching, used by SSR-facing read methods.
|
|
752
|
+
*
|
|
753
|
+
* In preview mode (dev server) drafts are merged over published — drafts
|
|
754
|
+
* win on a per-`_filename` basis and draft-only items are included — so
|
|
755
|
+
* the editor preview reflects unpublished edits. Returns published-only
|
|
756
|
+
* otherwise. Rich-text fields are preprocessed to HTML for template
|
|
757
|
+
* interpolation either way.
|
|
758
|
+
*
|
|
785
759
|
* @param collection - Collection ID to fetch items for
|
|
786
760
|
* @returns Array of CMSItems with rich-text fields converted to HTML markers
|
|
787
761
|
*/
|
|
@@ -791,7 +765,20 @@ var CMSService = class {
|
|
|
791
765
|
if (cached && now - cached.timestamp < this.ITEMS_CACHE_TTL) {
|
|
792
766
|
return cached.items;
|
|
793
767
|
}
|
|
794
|
-
|
|
768
|
+
let rawItems = await this.provider.getItems(collection);
|
|
769
|
+
if (this.previewMode) {
|
|
770
|
+
const drafts = await this.provider.getAllDrafts(collection);
|
|
771
|
+
if (drafts.length > 0) {
|
|
772
|
+
const byFilename = /* @__PURE__ */ new Map();
|
|
773
|
+
for (const item of rawItems) {
|
|
774
|
+
if (item._filename) byFilename.set(item._filename, item);
|
|
775
|
+
}
|
|
776
|
+
for (const draft of drafts) {
|
|
777
|
+
if (draft._filename) byFilename.set(draft._filename, draft);
|
|
778
|
+
}
|
|
779
|
+
rawItems = Array.from(byFilename.values());
|
|
780
|
+
}
|
|
781
|
+
}
|
|
795
782
|
const items = this.preprocessRichTextFields(collection, rawItems);
|
|
796
783
|
this.itemsCache.set(collection, { items, timestamp: now });
|
|
797
784
|
return items;
|
|
@@ -1029,6 +1016,71 @@ var CMSService = class {
|
|
|
1029
1016
|
* Only clears items cache and provider cache before re-initializing.
|
|
1030
1017
|
* Schema/route caches are swapped atomically inside initialize().
|
|
1031
1018
|
*/
|
|
1019
|
+
// ----------------------------------------------------------------------
|
|
1020
|
+
// Draft-version methods (Studio-only — never used by SSR / static export)
|
|
1021
|
+
// ----------------------------------------------------------------------
|
|
1022
|
+
/**
|
|
1023
|
+
* Load both published and draft versions of an item, used by the editor.
|
|
1024
|
+
* Returns `{}` when neither version exists.
|
|
1025
|
+
*/
|
|
1026
|
+
async getItemVersions(collection, filename) {
|
|
1027
|
+
if (!this.provider) return {};
|
|
1028
|
+
const [published, draft] = await Promise.all([
|
|
1029
|
+
this.provider.getItemByFilename(collection, filename),
|
|
1030
|
+
this.provider.getDraft(collection, filename)
|
|
1031
|
+
]);
|
|
1032
|
+
const result = {};
|
|
1033
|
+
if (published) result.published = published;
|
|
1034
|
+
if (draft) result.draft = draft;
|
|
1035
|
+
return result;
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* List all items in a collection for the Studio item list:
|
|
1039
|
+
* - Published items annotated with `_hasDraft: true` when a draft sibling exists.
|
|
1040
|
+
* - Draft-only items (no published file yet) returned with `_isDraft: true`.
|
|
1041
|
+
*/
|
|
1042
|
+
async listItemsWithDraftFlag(collection) {
|
|
1043
|
+
if (!this.provider) return [];
|
|
1044
|
+
const [published, drafts] = await Promise.all([
|
|
1045
|
+
this.provider.getItems(collection),
|
|
1046
|
+
this.provider.getAllDrafts(collection)
|
|
1047
|
+
]);
|
|
1048
|
+
const draftFilenames = new Set(drafts.map((d) => d._filename).filter(Boolean));
|
|
1049
|
+
const annotatedPublished = published.map((item) => {
|
|
1050
|
+
if (item._filename && draftFilenames.has(item._filename)) {
|
|
1051
|
+
return { ...item, _hasDraft: true };
|
|
1052
|
+
}
|
|
1053
|
+
return item;
|
|
1054
|
+
});
|
|
1055
|
+
const publishedFilenames = new Set(published.map((i) => i._filename).filter(Boolean));
|
|
1056
|
+
const draftOnly = drafts.filter((d) => d._filename && !publishedFilenames.has(d._filename));
|
|
1057
|
+
return [...annotatedPublished, ...draftOnly];
|
|
1058
|
+
}
|
|
1059
|
+
/** Pass-through to provider; no caching (drafts are read fresh in the editor). */
|
|
1060
|
+
async getDraft(collection, filename) {
|
|
1061
|
+
if (!this.provider) return null;
|
|
1062
|
+
return this.provider.getDraft(collection, filename);
|
|
1063
|
+
}
|
|
1064
|
+
async hasDraft(collection, filename) {
|
|
1065
|
+
if (!this.provider) return false;
|
|
1066
|
+
return this.provider.hasDraft(collection, filename);
|
|
1067
|
+
}
|
|
1068
|
+
async saveDraft(collection, item) {
|
|
1069
|
+
if (!this.provider) throw new Error("CMS provider not configured");
|
|
1070
|
+
await this.provider.saveDraft(collection, item);
|
|
1071
|
+
this.itemsCache.delete(collection);
|
|
1072
|
+
}
|
|
1073
|
+
async discardDraft(collection, filename) {
|
|
1074
|
+
if (!this.provider) throw new Error("CMS provider not configured");
|
|
1075
|
+
await this.provider.discardDraft(collection, filename);
|
|
1076
|
+
this.itemsCache.delete(collection);
|
|
1077
|
+
}
|
|
1078
|
+
async publishDraft(collection, filename) {
|
|
1079
|
+
if (!this.provider) throw new Error("CMS provider not configured");
|
|
1080
|
+
const item = await this.provider.publishDraft(collection, filename);
|
|
1081
|
+
this.itemsCache.delete(collection);
|
|
1082
|
+
return item;
|
|
1083
|
+
}
|
|
1032
1084
|
async refreshSchemas() {
|
|
1033
1085
|
if (!this.provider) {
|
|
1034
1086
|
return;
|
|
@@ -1337,20 +1389,45 @@ ${cssVars.join("\n")}
|
|
|
1337
1389
|
return cssBlocks.join("\n\n");
|
|
1338
1390
|
}
|
|
1339
1391
|
function generateVariablesCSS(config, breakpoints, responsiveScales) {
|
|
1340
|
-
|
|
1392
|
+
const cssBlocks = [];
|
|
1393
|
+
const fluidActive = responsiveScales?.enabled === true && responsiveScales?.mode === "fluid";
|
|
1394
|
+
const fluidRange = responsiveScales?.fluidRange ?? DEFAULT_FLUID_RANGE;
|
|
1395
|
+
const fluidBaseRef = responsiveScales?.baseReference || 16;
|
|
1396
|
+
const smallestBp = fluidActive ? getSmallestBreakpointName(breakpoints) : null;
|
|
1397
|
+
const baseVars = (config.variables ?? []).map((v) => {
|
|
1398
|
+
if (fluidActive && smallestBp && v.type !== "none") {
|
|
1399
|
+
const categoryScales = responsiveScales?.[v.type];
|
|
1400
|
+
const scale = categoryScales?.[smallestBp];
|
|
1401
|
+
if (scale != null && scale !== 1) {
|
|
1402
|
+
const fluid = buildFluidPropertyValue(
|
|
1403
|
+
v.value,
|
|
1404
|
+
scale,
|
|
1405
|
+
fluidRange.min,
|
|
1406
|
+
fluidRange.max,
|
|
1407
|
+
fluidBaseRef
|
|
1408
|
+
);
|
|
1409
|
+
if (fluid) return ` ${v.cssVar}: ${fluid};`;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
return ` ${v.cssVar}: ${v.value};`;
|
|
1413
|
+
});
|
|
1414
|
+
if (fluidActive) {
|
|
1415
|
+
const siteMargin = responsiveScales?.siteMargin ?? DEFAULT_SITE_MARGIN;
|
|
1416
|
+
baseVars.push(` --site-margin: ${buildSiteMarginClamp(siteMargin, fluidRange)};`);
|
|
1417
|
+
}
|
|
1418
|
+
if (baseVars.length === 0) {
|
|
1341
1419
|
return "";
|
|
1342
1420
|
}
|
|
1343
|
-
const cssBlocks = [];
|
|
1344
|
-
const baseVars = config.variables.map((v) => ` ${v.cssVar}: ${v.value};`);
|
|
1345
1421
|
cssBlocks.push(`:root {
|
|
1346
1422
|
${baseVars.join("\n")}
|
|
1347
1423
|
}`);
|
|
1348
|
-
|
|
1424
|
+
const userVariables = config.variables ?? [];
|
|
1425
|
+
if (breakpoints && responsiveScales?.enabled && !fluidActive && userVariables.length > 0) {
|
|
1349
1426
|
const baseRef = responsiveScales.baseReference || 16;
|
|
1350
1427
|
const sortedBreakpoints = Object.entries(breakpoints).sort((a, b) => b[1].breakpoint - a[1].breakpoint);
|
|
1351
1428
|
for (const [bpName, bpEntry] of sortedBreakpoints) {
|
|
1352
1429
|
const scaledVars = [];
|
|
1353
|
-
for (const variable of
|
|
1430
|
+
for (const variable of userVariables) {
|
|
1354
1431
|
if (variable.scales && variable.scales[bpName]) {
|
|
1355
1432
|
const overrideValue = variable.scales[bpName];
|
|
1356
1433
|
if (overrideValue !== variable.value) {
|
|
@@ -1431,6 +1508,27 @@ function buildAttributes(props, exclude = []) {
|
|
|
1431
1508
|
}
|
|
1432
1509
|
return attrs.length > 0 ? " " + attrs.join(" ") : "";
|
|
1433
1510
|
}
|
|
1511
|
+
function buildEditorAttrs(opts) {
|
|
1512
|
+
const { elementPath } = opts;
|
|
1513
|
+
if (!elementPath) return "";
|
|
1514
|
+
const parts = [`data-element-path="${escapeHtml(elementPath.join(","))}"`];
|
|
1515
|
+
if (opts.cmsItemIndexPath && opts.cmsItemIndexPath.length > 0) {
|
|
1516
|
+
parts.push(`data-cms-item-index="${escapeHtml(opts.cmsItemIndexPath.join("."))}"`);
|
|
1517
|
+
if (opts.cmsListPaths && opts.cmsListPaths.length === opts.cmsItemIndexPath.length) {
|
|
1518
|
+
const ctx = JSON.stringify({ itemIndexPath: opts.cmsItemIndexPath, listPaths: opts.cmsListPaths });
|
|
1519
|
+
parts.push(`data-cms-context="${escapeHtml(ctx)}"`);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
if (opts.isCMSListContainer) parts.push(`data-cms-list="true"`);
|
|
1523
|
+
if (opts.isComponentRoot) parts.push(`data-component-root="true"`);
|
|
1524
|
+
if (opts.parentComponentName) {
|
|
1525
|
+
parts.push(`data-parent-component="${escapeHtml(opts.parentComponentName)}"`);
|
|
1526
|
+
}
|
|
1527
|
+
if (opts.componentContext && !opts.isSlotContent) {
|
|
1528
|
+
parts.push(`data-component-context="${escapeHtml(opts.componentContext)}"`);
|
|
1529
|
+
}
|
|
1530
|
+
return " " + parts.join(" ");
|
|
1531
|
+
}
|
|
1434
1532
|
function styleToString(style) {
|
|
1435
1533
|
if (!style || Object.keys(style).length === 0) return "";
|
|
1436
1534
|
const declarations = [];
|
|
@@ -1943,6 +2041,12 @@ function getDOMPurify() {
|
|
|
1943
2041
|
function getTemplateContext(ctx) {
|
|
1944
2042
|
return ctx.templateContext || null;
|
|
1945
2043
|
}
|
|
2044
|
+
function buildListResolutionScope(ctx) {
|
|
2045
|
+
const tplCtx = ctx.templateContext;
|
|
2046
|
+
const props = ctx.componentResolvedProps;
|
|
2047
|
+
if (!tplCtx && !props) return void 0;
|
|
2048
|
+
return { ...props ?? {}, ...tplCtx ?? {} };
|
|
2049
|
+
}
|
|
1946
2050
|
function getI18nResolver(ctx) {
|
|
1947
2051
|
return createI18nResolver(ctx.locale, ctx.i18nConfig);
|
|
1948
2052
|
}
|
|
@@ -1981,7 +2085,11 @@ function processStyleToClasses(style, ctx) {
|
|
|
1981
2085
|
getI18nResolver(ctx)
|
|
1982
2086
|
);
|
|
1983
2087
|
}
|
|
1984
|
-
|
|
2088
|
+
const fluidActive = ctx.responsiveScales?.enabled === true && ctx.responsiveScales?.mode === "fluid";
|
|
2089
|
+
return responsiveStylesToClasses(
|
|
2090
|
+
processedStyle,
|
|
2091
|
+
{ fluidActive, responsiveScales: ctx.responsiveScales }
|
|
2092
|
+
);
|
|
1985
2093
|
}
|
|
1986
2094
|
function evaluateIfCondition(node, ctx) {
|
|
1987
2095
|
const ifValue = hasIf(node) ? node.if : void 0;
|
|
@@ -2006,35 +2114,38 @@ function evaluateIfCondition(node, ctx) {
|
|
|
2006
2114
|
if (ctx.cmsContext?.cms && resolved.includes("{{cms.")) {
|
|
2007
2115
|
resolved = processCMSTemplate(resolved, ctx.cmsContext.cms, ctx.locale, ctx.i18nConfig);
|
|
2008
2116
|
}
|
|
2117
|
+
if (resolved.includes("{{") && resolved.includes("}}")) {
|
|
2118
|
+
return false;
|
|
2119
|
+
}
|
|
2009
2120
|
return Boolean(resolved) && resolved !== "false" && resolved !== "0" && resolved !== "";
|
|
2010
2121
|
}
|
|
2011
2122
|
return true;
|
|
2012
2123
|
}
|
|
2013
|
-
function resolveFilterValue(value,
|
|
2014
|
-
if (!
|
|
2124
|
+
function resolveFilterValue(value, scope) {
|
|
2125
|
+
if (!scope || typeof value !== "string" || !value.startsWith("{{") || !value.endsWith("}}")) {
|
|
2015
2126
|
return value;
|
|
2016
2127
|
}
|
|
2017
2128
|
const path2 = value.slice(2, -2).trim();
|
|
2018
|
-
const resolved = getNestedValue(
|
|
2129
|
+
const resolved = getNestedValue(scope, path2);
|
|
2019
2130
|
return resolved !== void 0 ? resolved : value;
|
|
2020
2131
|
}
|
|
2021
|
-
function resolveFilterTemplates(filter,
|
|
2022
|
-
if (!filter || !
|
|
2132
|
+
function resolveFilterTemplates(filter, scope) {
|
|
2133
|
+
if (!filter || !scope) return filter;
|
|
2023
2134
|
if (Array.isArray(filter)) {
|
|
2024
2135
|
return filter.map((cond) => ({
|
|
2025
2136
|
...cond,
|
|
2026
|
-
value: resolveFilterValue(cond.value,
|
|
2137
|
+
value: resolveFilterValue(cond.value, scope)
|
|
2027
2138
|
}));
|
|
2028
2139
|
}
|
|
2029
2140
|
if ("field" in filter && "value" in filter) {
|
|
2030
2141
|
return {
|
|
2031
2142
|
...filter,
|
|
2032
|
-
value: resolveFilterValue(filter.value,
|
|
2143
|
+
value: resolveFilterValue(filter.value, scope)
|
|
2033
2144
|
};
|
|
2034
2145
|
}
|
|
2035
2146
|
const resolved = {};
|
|
2036
2147
|
for (const [key, value] of Object.entries(filter)) {
|
|
2037
|
-
resolved[key] = resolveFilterValue(value,
|
|
2148
|
+
resolved[key] = resolveFilterValue(value, scope);
|
|
2038
2149
|
}
|
|
2039
2150
|
return resolved;
|
|
2040
2151
|
}
|
|
@@ -2071,7 +2182,7 @@ async function expandRichTextComponents(html, ctx) {
|
|
|
2071
2182
|
return resolved.join("");
|
|
2072
2183
|
}
|
|
2073
2184
|
var ssrComponentRegistry = new SSRRegistry();
|
|
2074
|
-
async function buildComponentHTML(node, globalComponents = {}, pageComponents = {}, locale, i18nConfig, slugMappings, pagePath, cmsContext, cmsService, isProductionBuild) {
|
|
2185
|
+
async function buildComponentHTML(node, globalComponents = {}, pageComponents = {}, locale, i18nConfig, slugMappings, pagePath, cmsContext, cmsService, isProductionBuild, injectEditorAttrs) {
|
|
2075
2186
|
const interactiveStylesMap = /* @__PURE__ */ new Map();
|
|
2076
2187
|
const preloadImages = [];
|
|
2077
2188
|
const neededCollections = /* @__PURE__ */ new Set();
|
|
@@ -2106,7 +2217,9 @@ async function buildComponentHTML(node, globalComponents = {}, pageComponents =
|
|
|
2106
2217
|
// Collect SSR fallback HTML for complex nodes
|
|
2107
2218
|
processedRawHtmlCollector,
|
|
2108
2219
|
// Collect raw→processed HTML for Astro exporter
|
|
2109
|
-
imageFormat: configService.getImageFormat()
|
|
2220
|
+
imageFormat: configService.getImageFormat(),
|
|
2221
|
+
injectEditorAttrs,
|
|
2222
|
+
responsiveScales: configService.getResponsiveScales()
|
|
2110
2223
|
};
|
|
2111
2224
|
const html = await renderNode(node, ctx);
|
|
2112
2225
|
return { html, interactiveStylesMap, preloadImages, neededCollections, ssrFallbackCollector, processedRawHtmlCollector };
|
|
@@ -2137,7 +2250,7 @@ async function renderNestedListPlaceholder(node, ctx) {
|
|
|
2137
2250
|
};
|
|
2138
2251
|
const templateContent = node.children ? await renderChildrenAsync(node.children, childTemplateCtx) : "";
|
|
2139
2252
|
const configJson = escapeHtml(JSON.stringify(config));
|
|
2140
|
-
return `<div data-cms-list-nested="true" data-collection="${escapeHtml(sourceStr)}" data-cms-config="${configJson}"><template data-nested-template>${templateContent}</template></div>`;
|
|
2253
|
+
return `<div data-cms-list-nested="true" data-collection="${escapeHtml(sourceStr)}" data-cms-config="${configJson}"${editorAttrs(ctx, { isCMSListContainer: true })}><template data-nested-template>${templateContent}</template></div>`;
|
|
2141
2254
|
}
|
|
2142
2255
|
async function processList(node, ctx) {
|
|
2143
2256
|
const nodeType = node.type;
|
|
@@ -2196,7 +2309,9 @@ async function processList(node, ctx) {
|
|
|
2196
2309
|
);
|
|
2197
2310
|
const itemCtx = {
|
|
2198
2311
|
...ctx,
|
|
2199
|
-
templateContext
|
|
2312
|
+
templateContext,
|
|
2313
|
+
cmsItemIndexPath: [...ctx.cmsItemIndexPath ?? [], i],
|
|
2314
|
+
cmsListPaths: [...ctx.cmsListPaths ?? [], ctx.elementPath ?? []]
|
|
2200
2315
|
};
|
|
2201
2316
|
const childrenHtml = await renderChildrenAsync(node.children || [], itemCtx);
|
|
2202
2317
|
renderedItems.push(childrenHtml);
|
|
@@ -2240,7 +2355,8 @@ async function getCollectionItems(node, source, ctx) {
|
|
|
2240
2355
|
resolvedIds = Array.isArray(value) ? value.map((v) => String(v)) : String(value);
|
|
2241
2356
|
}
|
|
2242
2357
|
} else {
|
|
2243
|
-
const
|
|
2358
|
+
const mergedScope = buildListResolutionScope(ctx) ?? {};
|
|
2359
|
+
const parentContext = { _type: "template", ...mergedScope };
|
|
2244
2360
|
resolvedIds = resolveItemsTemplate(node.items, parentContext);
|
|
2245
2361
|
}
|
|
2246
2362
|
if (!resolvedIds) {
|
|
@@ -2258,7 +2374,7 @@ async function getCollectionItems(node, source, ctx) {
|
|
|
2258
2374
|
} else {
|
|
2259
2375
|
const query = {
|
|
2260
2376
|
collection: source,
|
|
2261
|
-
filter: resolveFilterTemplates(node.filter, ctx
|
|
2377
|
+
filter: resolveFilterTemplates(node.filter, buildListResolutionScope(ctx)),
|
|
2262
2378
|
sort: node.sort,
|
|
2263
2379
|
limit: node.limit,
|
|
2264
2380
|
offset: node.offset,
|
|
@@ -2313,11 +2429,13 @@ function buildNodeElementClass(ctx, label, isSlotContent2) {
|
|
|
2313
2429
|
const useComponentContext = !isSlotContent2 && Boolean(ctx.componentContext);
|
|
2314
2430
|
const effectiveFileType = useComponentContext ? "component" : "page";
|
|
2315
2431
|
const effectiveFileName = useComponentContext ? ctx.componentContext : pagePath ? pagePath.replace(/^\//, "").replace(/\//g, "_") || "index" : "page";
|
|
2432
|
+
const rawPath = ctx.elementPath || [];
|
|
2433
|
+
const path2 = useComponentContext && ctx.componentRootPath ? rawPath.slice(ctx.componentRootPath.length) : rawPath;
|
|
2316
2434
|
const elementClassCtx = {
|
|
2317
2435
|
fileType: effectiveFileType,
|
|
2318
2436
|
fileName: effectiveFileName || "page",
|
|
2319
2437
|
label,
|
|
2320
|
-
path:
|
|
2438
|
+
path: path2
|
|
2321
2439
|
};
|
|
2322
2440
|
return generateElementClassName(elementClassCtx);
|
|
2323
2441
|
}
|
|
@@ -2337,6 +2455,26 @@ function buildCssVariableStyleAttr(cssVariables) {
|
|
|
2337
2455
|
const styleString = Object.entries(cssVariables).map(([k, v]) => `${k}: ${v}`).join("; ");
|
|
2338
2456
|
return ` style="${escapeHtml(styleString)}"`;
|
|
2339
2457
|
}
|
|
2458
|
+
function arraysEqual(a, b) {
|
|
2459
|
+
if (!a || !b || a.length !== b.length) return false;
|
|
2460
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
2461
|
+
return true;
|
|
2462
|
+
}
|
|
2463
|
+
function editorAttrs(ctx, opts = {}) {
|
|
2464
|
+
if (!ctx.injectEditorAttrs) return "";
|
|
2465
|
+
const isComponentRoot = !!ctx.componentContext && !opts.isSlotContent && arraysEqual(ctx.elementPath, ctx.componentRootPath);
|
|
2466
|
+
const effectiveParent = opts.isSlotContent ? ctx.parentComponentName : isComponentRoot ? ctx.parentComponentName : ctx.componentContext ?? ctx.parentComponentName;
|
|
2467
|
+
return buildEditorAttrs({
|
|
2468
|
+
elementPath: ctx.elementPath,
|
|
2469
|
+
cmsItemIndexPath: ctx.cmsItemIndexPath,
|
|
2470
|
+
cmsListPaths: ctx.cmsListPaths,
|
|
2471
|
+
componentContext: ctx.componentContext,
|
|
2472
|
+
parentComponentName: effectiveParent,
|
|
2473
|
+
isComponentRoot,
|
|
2474
|
+
isSlotContent: opts.isSlotContent,
|
|
2475
|
+
isCMSListContainer: opts.isCMSListContainer
|
|
2476
|
+
});
|
|
2477
|
+
}
|
|
2340
2478
|
async function renderNode(node, ctx) {
|
|
2341
2479
|
const { breakpoints, viewportWidth, locale, i18nConfig, slugMappings, pagePath } = ctx;
|
|
2342
2480
|
if (node === null || node === void 0) return "";
|
|
@@ -2445,7 +2583,7 @@ async function renderNode(node, ctx) {
|
|
|
2445
2583
|
delete nodeAttributes2.class;
|
|
2446
2584
|
const attrs2 = buildAttributes(nodeAttributes2);
|
|
2447
2585
|
const classAttr2 = classNames.length > 0 ? ` class="${escapeHtml(classNames.filter(Boolean).join(" "))}"` : "";
|
|
2448
|
-
return `<span${classAttr2}${embedStyleAttr}${attrs2}>${optimizedHtml}</span>`;
|
|
2586
|
+
return `<span${classAttr2}${embedStyleAttr}${attrs2}${editorAttrs(ctx, { isSlotContent: isSlotContent(node) })}>${optimizedHtml}</span>`;
|
|
2449
2587
|
}
|
|
2450
2588
|
const attrClassName2 = nodeAttributes2.className || nodeAttributes2.class || "";
|
|
2451
2589
|
if (attrClassName2) {
|
|
@@ -2455,7 +2593,7 @@ async function renderNode(node, ctx) {
|
|
|
2455
2593
|
delete nodeAttributes2.class;
|
|
2456
2594
|
const attrs = buildAttributes(nodeAttributes2);
|
|
2457
2595
|
const classAttr = classNames.length > 0 ? ` class="${escapeHtml(classNames.filter(Boolean).join(" "))}"` : "";
|
|
2458
|
-
return `<span${classAttr}${attrs}>${optimizedHtml}</span>`;
|
|
2596
|
+
return `<span${classAttr}${attrs}${editorAttrs(ctx, { isSlotContent: isSlotContent(node) })}>${optimizedHtml}</span>`;
|
|
2459
2597
|
}
|
|
2460
2598
|
if (isLinkNode(node)) {
|
|
2461
2599
|
let href = typeof node.href === "string" ? node.href : "#";
|
|
@@ -2516,7 +2654,7 @@ async function renderNode(node, ctx) {
|
|
|
2516
2654
|
const childPath = ctx.elementPath ? [...ctx.elementPath, index] : [index];
|
|
2517
2655
|
return renderNode(child, { ...ctx, elementPath: childPath });
|
|
2518
2656
|
}))).join("") : await renderNode(children, { ...ctx, elementPath: ctx.elementPath ? [...ctx.elementPath, 0] : [0] });
|
|
2519
|
-
return `<a href="${escapeHtml(String(href))}"${classAttr}${olinkStyleAttr}${attrs}>${childrenHTML}</a>`;
|
|
2657
|
+
return `<a href="${escapeHtml(String(href))}"${classAttr}${olinkStyleAttr}${attrs}${editorAttrs(ctx, { isSlotContent: isSlotContent(node) })}>${childrenHTML}</a>`;
|
|
2520
2658
|
}
|
|
2521
2659
|
if (isLocaleListNode(node)) {
|
|
2522
2660
|
return renderLocaleList(node, ctx);
|
|
@@ -2546,9 +2684,15 @@ async function renderNode(node, ctx) {
|
|
|
2546
2684
|
if (!tag && !componentName) return "";
|
|
2547
2685
|
let utilityClasses = [];
|
|
2548
2686
|
let resolvedStyle = {};
|
|
2687
|
+
const isCustomComponentNode = nodeType === NODE_TYPE.COMPONENT && isComponentNode(node) && ssrComponentRegistry.has(node.component);
|
|
2688
|
+
let deferredComponentStyle;
|
|
2549
2689
|
if (nodeStyle) {
|
|
2550
|
-
|
|
2551
|
-
|
|
2690
|
+
if (isCustomComponentNode) {
|
|
2691
|
+
deferredComponentStyle = nodeStyle;
|
|
2692
|
+
} else {
|
|
2693
|
+
validateStyleCoverage(nodeStyle, `Node: ${nodeType || "unknown"}`);
|
|
2694
|
+
utilityClasses = processStyleToClasses(nodeStyle, ctx);
|
|
2695
|
+
}
|
|
2552
2696
|
} else if (nodeProps.style) {
|
|
2553
2697
|
if (isResponsiveStyle(nodeProps.style) && breakpoints && viewportWidth) {
|
|
2554
2698
|
resolvedStyle = mergeResponsiveStyles(nodeProps.style, "viewport", viewportWidth, breakpoints);
|
|
@@ -2582,7 +2726,7 @@ async function renderNode(node, ctx) {
|
|
|
2582
2726
|
...nodeProps,
|
|
2583
2727
|
...nodeAttributesWithoutClass,
|
|
2584
2728
|
...mergedClassName ? { className: mergedClassName } : {},
|
|
2585
|
-
...Object.keys(resolvedStyle).length > 0 ? { style: resolvedStyle } : {}
|
|
2729
|
+
...deferredComponentStyle ? { style: deferredComponentStyle } : Object.keys(resolvedStyle).length > 0 ? { style: resolvedStyle } : {}
|
|
2586
2730
|
};
|
|
2587
2731
|
if (nodeType === NODE_TYPE.COMPONENT && componentName && ssrComponentRegistry.has(componentName)) {
|
|
2588
2732
|
return renderComponent(componentName, propsWithStyleAndAttrs, children, nodeAttributes, ctx);
|
|
@@ -2631,27 +2775,8 @@ async function renderComponent(componentName, propsWithStyleAndAttrs, children,
|
|
|
2631
2775
|
if (!rootNode.props) {
|
|
2632
2776
|
rootNode.props = {};
|
|
2633
2777
|
}
|
|
2634
|
-
if (
|
|
2635
|
-
|
|
2636
|
-
const existingStyle = rootNode.style;
|
|
2637
|
-
if (existingStyle && typeof existingStyle === "object") {
|
|
2638
|
-
rootNode.style = {
|
|
2639
|
-
...existingStyle,
|
|
2640
|
-
...propsWithStyleAndAttrs.style
|
|
2641
|
-
};
|
|
2642
|
-
} else {
|
|
2643
|
-
rootNode.style = propsWithStyleAndAttrs.style;
|
|
2644
|
-
}
|
|
2645
|
-
}
|
|
2646
|
-
} else {
|
|
2647
|
-
if (rootNode.props.style && typeof rootNode.props.style === "object") {
|
|
2648
|
-
rootNode.props.style = {
|
|
2649
|
-
...rootNode.props.style,
|
|
2650
|
-
...propsWithStyleAndAttrs.style || {}
|
|
2651
|
-
};
|
|
2652
|
-
} else if (propsWithStyleAndAttrs.style) {
|
|
2653
|
-
rootNode.props.style = propsWithStyleAndAttrs.style;
|
|
2654
|
-
}
|
|
2778
|
+
if (propsWithStyleAndAttrs.style) {
|
|
2779
|
+
mergeNodeStyles(rootNode, propsWithStyleAndAttrs.style);
|
|
2655
2780
|
}
|
|
2656
2781
|
if (propsWithStyleAndAttrs.className) {
|
|
2657
2782
|
if (isHtmlNode(rootNode) || isLinkNode(rootNode)) {
|
|
@@ -2703,8 +2828,10 @@ async function renderComponent(componentName, propsWithStyleAndAttrs, children,
|
|
|
2703
2828
|
}
|
|
2704
2829
|
return await renderNode(processedStructure, {
|
|
2705
2830
|
...ctx,
|
|
2831
|
+
// The previously-active component (if any) becomes the parent for editor attrs
|
|
2832
|
+
parentComponentName: ctx.componentContext,
|
|
2706
2833
|
componentContext: componentName,
|
|
2707
|
-
|
|
2834
|
+
componentRootPath: ctx.elementPath,
|
|
2708
2835
|
componentResolvedProps: resolvedProps
|
|
2709
2836
|
});
|
|
2710
2837
|
} catch (error) {
|
|
@@ -2724,7 +2851,7 @@ async function renderLinkNode(propsWithStyleAndAttrs, children, ctx) {
|
|
|
2724
2851
|
const childPath = ctx.elementPath ? [...ctx.elementPath, index] : [index];
|
|
2725
2852
|
return renderNode(child, { ...ctx, elementPath: childPath });
|
|
2726
2853
|
}))).join("") : await renderNode(children, { ...ctx, elementPath: ctx.elementPath ? [...ctx.elementPath, 0] : [0] });
|
|
2727
|
-
return `<a href="${escapeHtml(String(href))}"${linkClassAttr}${attrs}>${childrenHTML}</a>`;
|
|
2854
|
+
return `<a href="${escapeHtml(String(href))}"${linkClassAttr}${attrs}${editorAttrs(ctx)}>${childrenHTML}</a>`;
|
|
2728
2855
|
}
|
|
2729
2856
|
async function renderHtmlElement(tag, propsWithStyleAndAttrs, children, ctx) {
|
|
2730
2857
|
let classValue = propsWithStyleAndAttrs.className ? String(propsWithStyleAndAttrs.className) : "";
|
|
@@ -2746,18 +2873,32 @@ async function renderHtmlElement(tag, propsWithStyleAndAttrs, children, ctx) {
|
|
|
2746
2873
|
const imageProps = ["src", "alt", "loading", "width", "height", "sizes", "srcset", "fetchpriority"];
|
|
2747
2874
|
const excludeProps = tag.toLowerCase() === "img" ? ["style", "className", ...imageProps] : ["style", "className"];
|
|
2748
2875
|
const attrs = buildAttributes(propsWithStyleAndAttrs, excludeProps);
|
|
2749
|
-
const
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2876
|
+
const isRawTextElement = tag.toLowerCase() === "style" || tag.toLowerCase() === "script";
|
|
2877
|
+
let childrenHTML;
|
|
2878
|
+
if (isRawTextElement) {
|
|
2879
|
+
const flatten = (node) => {
|
|
2880
|
+
if (node == null) return "";
|
|
2881
|
+
if (typeof node === "string") return node;
|
|
2882
|
+
if (typeof node === "number") return String(node);
|
|
2883
|
+
if (Array.isArray(node)) return node.map(flatten).join("");
|
|
2884
|
+
return "";
|
|
2885
|
+
};
|
|
2886
|
+
childrenHTML = flatten(children);
|
|
2887
|
+
} else {
|
|
2888
|
+
childrenHTML = Array.isArray(children) ? (await Promise.all(children.map((child, index) => {
|
|
2889
|
+
const childPath = ctx.elementPath ? [...ctx.elementPath, index] : [index];
|
|
2890
|
+
return renderNode(child, { ...ctx, elementPath: childPath });
|
|
2891
|
+
}))).join("") : await renderNode(children, { ...ctx, elementPath: ctx.elementPath ? [...ctx.elementPath, 0] : [0] });
|
|
2892
|
+
}
|
|
2893
|
+
const ea = editorAttrs(ctx);
|
|
2753
2894
|
const voidElements = ["img", "input", "br", "hr", "meta", "link", "area", "base", "col", "embed", "source", "track", "wbr"];
|
|
2754
2895
|
if (voidElements.includes(tag.toLowerCase())) {
|
|
2755
2896
|
if (tag.toLowerCase() === "img") {
|
|
2756
|
-
return renderImageElement(propsWithStyleAndAttrs, classAttr, styleAttr, attrs, ctx);
|
|
2897
|
+
return renderImageElement(propsWithStyleAndAttrs, classAttr, styleAttr, attrs + ea, ctx);
|
|
2757
2898
|
}
|
|
2758
|
-
return `<${tag}${classAttr}${styleAttr}${attrs} />`;
|
|
2899
|
+
return `<${tag}${classAttr}${styleAttr}${attrs}${ea} />`;
|
|
2759
2900
|
}
|
|
2760
|
-
return `<${tag}${classAttr}${styleAttr}${attrs}>${childrenHTML}</${tag}>`;
|
|
2901
|
+
return `<${tag}${classAttr}${styleAttr}${attrs}${ea}>${childrenHTML}</${tag}>`;
|
|
2761
2902
|
}
|
|
2762
2903
|
function renderImageElement(propsWithStyleAndAttrs, classAttr, styleAttr, attrs, ctx) {
|
|
2763
2904
|
const imgProps = propsWithStyleAndAttrs;
|
|
@@ -2845,9 +2986,13 @@ function renderLocaleList(node, ctx) {
|
|
|
2845
2986
|
localeIconMap.set(localeConfig.code, localeConfig.icon);
|
|
2846
2987
|
}
|
|
2847
2988
|
}
|
|
2989
|
+
const localeStyleOpts = {
|
|
2990
|
+
fluidActive: ctx.responsiveScales?.enabled === true && ctx.responsiveScales?.mode === "fluid",
|
|
2991
|
+
responsiveScales: ctx.responsiveScales
|
|
2992
|
+
};
|
|
2848
2993
|
let containerClasses = [];
|
|
2849
2994
|
if (nodeStyle) {
|
|
2850
|
-
containerClasses = responsiveStylesToClasses(nodeStyle);
|
|
2995
|
+
containerClasses = responsiveStylesToClasses(nodeStyle, localeStyleOpts);
|
|
2851
2996
|
}
|
|
2852
2997
|
const localeListInteractiveStyles = node.interactiveStyles;
|
|
2853
2998
|
const localeListGenerateElementClass = node.generateElementClass;
|
|
@@ -2864,21 +3009,21 @@ function renderLocaleList(node, ctx) {
|
|
|
2864
3009
|
const containerClassAttr = containerClasses.length > 0 ? ` class="${escapeHtml(containerClasses.join(" "))}"` : "";
|
|
2865
3010
|
let itemClasses = [];
|
|
2866
3011
|
if (node.itemStyle) {
|
|
2867
|
-
itemClasses = responsiveStylesToClasses(node.itemStyle);
|
|
3012
|
+
itemClasses = responsiveStylesToClasses(node.itemStyle, localeStyleOpts);
|
|
2868
3013
|
}
|
|
2869
3014
|
const itemClassAttr = itemClasses.length > 0 ? ` class="${escapeHtml(itemClasses.join(" "))}"` : "";
|
|
2870
3015
|
let activeItemClasses = [];
|
|
2871
3016
|
if (node.activeItemStyle) {
|
|
2872
|
-
activeItemClasses = responsiveStylesToClasses(node.activeItemStyle);
|
|
3017
|
+
activeItemClasses = responsiveStylesToClasses(node.activeItemStyle, localeStyleOpts);
|
|
2873
3018
|
}
|
|
2874
3019
|
let separatorClasses = [];
|
|
2875
3020
|
if (node.separatorStyle) {
|
|
2876
|
-
separatorClasses = responsiveStylesToClasses(node.separatorStyle);
|
|
3021
|
+
separatorClasses = responsiveStylesToClasses(node.separatorStyle, localeStyleOpts);
|
|
2877
3022
|
}
|
|
2878
3023
|
const separatorClassAttr = separatorClasses.length > 0 ? ` class="${escapeHtml(separatorClasses.join(" "))}"` : "";
|
|
2879
3024
|
let flagClasses = [];
|
|
2880
3025
|
if (node.flagStyle) {
|
|
2881
|
-
flagClasses = responsiveStylesToClasses(node.flagStyle);
|
|
3026
|
+
flagClasses = responsiveStylesToClasses(node.flagStyle, localeStyleOpts);
|
|
2882
3027
|
}
|
|
2883
3028
|
const flagClassAttr = flagClasses.length > 0 ? ` class="${escapeHtml(flagClasses.join(" "))}"` : "";
|
|
2884
3029
|
const currentItemClasses = [...itemClasses, ...activeItemClasses];
|
|
@@ -2913,7 +3058,7 @@ function renderLocaleList(node, ctx) {
|
|
|
2913
3058
|
const linksHTML = showSeparator ? links.join(`<span${separatorClassAttr}></span>`) : links.join("");
|
|
2914
3059
|
const nodeAttributes = extractAttributesFromNode(node);
|
|
2915
3060
|
const attrsStr = buildAttributes(nodeAttributes);
|
|
2916
|
-
const localeListResult = `<div data-locale-list="true"${containerClassAttr}${localeListStyleAttr}${attrsStr}>${linksHTML}</div>`;
|
|
3061
|
+
const localeListResult = `<div data-locale-list="true"${containerClassAttr}${localeListStyleAttr}${attrsStr}${editorAttrs(ctx)}>${linksHTML}</div>`;
|
|
2917
3062
|
if (ctx.ssrFallbackCollector && ctx.elementPath) {
|
|
2918
3063
|
ctx.ssrFallbackCollector.set(ctx.elementPath.join("."), localeListResult);
|
|
2919
3064
|
}
|
|
@@ -2921,7 +3066,7 @@ function renderLocaleList(node, ctx) {
|
|
|
2921
3066
|
}
|
|
2922
3067
|
return '<div data-locale-list="true"></div>';
|
|
2923
3068
|
}
|
|
2924
|
-
async function renderPageSSR(pageData, globalComponents = {}, pagePath = "/", baseUrl = "", locale, i18nConfig, slugMappings, cmsContext, cmsService, isProductionBuild) {
|
|
3069
|
+
async function renderPageSSR(pageData, globalComponents = {}, pagePath = "/", baseUrl = "", locale, i18nConfig, slugMappings, cmsContext, cmsService, isProductionBuild, injectEditorAttrs) {
|
|
2925
3070
|
const rootNode = pageData?.root || void 0;
|
|
2926
3071
|
if (!rootNode) {
|
|
2927
3072
|
throw new Error("Page data must have a root node");
|
|
@@ -2945,7 +3090,7 @@ async function renderPageSSR(pageData, globalComponents = {}, pagePath = "/", ba
|
|
|
2945
3090
|
}
|
|
2946
3091
|
}
|
|
2947
3092
|
const pageComponents = pageData?.components || {};
|
|
2948
|
-
const { html: contentHTML, interactiveStylesMap, preloadImages, neededCollections, ssrFallbackCollector, processedRawHtmlCollector } = rootNode ? await buildComponentHTML(rootNode, globalComponents, pageComponents, effectiveLocale, config, slugMappings, pagePath, cmsContext, cmsService, isProductionBuild) : { html: "", interactiveStylesMap: /* @__PURE__ */ new Map(), preloadImages: [], neededCollections: /* @__PURE__ */ new Set(), ssrFallbackCollector: /* @__PURE__ */ new Map(), processedRawHtmlCollector: /* @__PURE__ */ new Map() };
|
|
3093
|
+
const { html: contentHTML, interactiveStylesMap, preloadImages, neededCollections, ssrFallbackCollector, processedRawHtmlCollector } = rootNode ? await buildComponentHTML(rootNode, globalComponents, pageComponents, effectiveLocale, config, slugMappings, pagePath, cmsContext, cmsService, isProductionBuild, injectEditorAttrs) : { html: "", interactiveStylesMap: /* @__PURE__ */ new Map(), preloadImages: [], neededCollections: /* @__PURE__ */ new Set(), ssrFallbackCollector: /* @__PURE__ */ new Map(), processedRawHtmlCollector: /* @__PURE__ */ new Map() };
|
|
2949
3094
|
const javascript = await collectComponentJavaScript(globalComponents, pageComponents);
|
|
2950
3095
|
const componentCSS = collectComponentCSS(globalComponents, pageComponents);
|
|
2951
3096
|
const fullUrl = baseUrl ? `${baseUrl}${pagePath}` : pagePath;
|
|
@@ -3269,7 +3414,7 @@ function generateImagePreloadTags(preloadImages) {
|
|
|
3269
3414
|
}
|
|
3270
3415
|
function minifyCSS(code) {
|
|
3271
3416
|
if (!code.trim()) return code;
|
|
3272
|
-
return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s*([{}
|
|
3417
|
+
return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s*([{};:,>~])\s*/g, "$1").replace(/\s+/g, " ").replace(/\{\s+/g, "{").replace(/\s+\}/g, "}").replace(/;}/g, "}").trim();
|
|
3273
3418
|
}
|
|
3274
3419
|
async function generateSSRHTML(pageDataOrOptions, globalComponents = {}, pagePath = "/", baseUrl = "", useBuiltBundle = false, locale, slugMappings, cmsContext, cmsService, externalScriptPath) {
|
|
3275
3420
|
let options;
|
|
@@ -3306,11 +3451,12 @@ async function generateSSRHTML(pageDataOrOptions, globalComponents = {}, pagePat
|
|
|
3306
3451
|
pageCustomCode,
|
|
3307
3452
|
clientDataCollections,
|
|
3308
3453
|
injectLiveReload = false,
|
|
3454
|
+
injectEditorAttrs = false,
|
|
3309
3455
|
isEditor = false,
|
|
3310
3456
|
isProductionBuild = false,
|
|
3311
3457
|
serverPort
|
|
3312
3458
|
} = options;
|
|
3313
|
-
const rendered = await renderPageSSR(pageData, components, path2, base, loc, void 0, slugs, cms, cmsServ, isProductionBuild);
|
|
3459
|
+
const rendered = await renderPageSSR(pageData, components, path2, base, loc, void 0, slugs, cms, cmsServ, isProductionBuild, injectEditorAttrs);
|
|
3314
3460
|
let finalClientDataCollections = clientDataCollections;
|
|
3315
3461
|
if (rendered.neededCollections.size > 0 && cmsServ) {
|
|
3316
3462
|
finalClientDataCollections = clientDataCollections ? new Map(clientDataCollections) : /* @__PURE__ */ new Map();
|
|
@@ -3423,7 +3569,7 @@ ${escapedJavaScript}
|
|
|
3423
3569
|
const componentCSS = rendered.componentCSS || "";
|
|
3424
3570
|
const usedUtilityClasses = extractUtilityClassesFromHTML(rendered.html);
|
|
3425
3571
|
const breakpointConfig = await loadBreakpointConfig();
|
|
3426
|
-
const responsiveScalesConfig =
|
|
3572
|
+
const responsiveScalesConfig = configService.getResponsiveScales();
|
|
3427
3573
|
const variablesConfig = await variableService.loadConfig();
|
|
3428
3574
|
const variablesCSS = generateVariablesCSS(variablesConfig, breakpointConfig, responsiveScalesConfig);
|
|
3429
3575
|
const remConversionConfig = configService.getRemConversion();
|
|
@@ -3492,7 +3638,7 @@ picture {
|
|
|
3492
3638
|
const scriptPreloadTag = extScriptPath ? `<link rel="preload" href="${extScriptPath}" as="script">` : "";
|
|
3493
3639
|
const imagePreloadTags = generateImagePreloadTags(rendered.preloadImages);
|
|
3494
3640
|
const wsUrl = serverPort ? `'ws://localhost:${serverPort}/hmr'` : `location.origin.replace('http','ws')+'/hmr'`;
|
|
3495
|
-
const liveReloadScript = injectLiveReload ? `<script>(function(){var ws,timer,gen=0;function connect(){ws=new WebSocket(${wsUrl});ws.onmessage=function(e){var d=JSON.parse(e.data);if(d.type==='hmr:libraries-update'){location.reload()}else if(d.type==='hmr:update'||d.type==='hmr:cms-update'||d.type==='hmr:colors-update'||d.type==='hmr:variables-update')hotReload()};ws.onclose=function(){clearTimeout(timer);timer=setTimeout(connect,1000)}}function hotReload(){var g=++gen;var sx=window.scrollX,sy=window.scrollY;fetch(location.href,{cache:'no-store'}).then(function(r){return r.text()}).then(function(html){if(g!==gen)return;var p=new DOMParser();var d=p.parseFromString(html,'text/html');var or=document.getElementById('root')
|
|
3641
|
+
const liveReloadScript = injectLiveReload ? `<script>(function(){var ws,timer,gen=0,lastSrvRoot=null;function strip(s){return s?s.replace(/[?&]_r=\\d+/,''):''}function classList(el){return (el.getAttribute('class')||'').split(/\\s+/).filter(Boolean)}function syncEl(cur,srv,old){var cc=classList(cur),sc=classList(srv),oc=old?new Set(classList(old)):new Set();var rt=cc.filter(function(c){return !oc.has(c)});var seen=new Set(),fin=[];sc.concat(rt).forEach(function(c){if(!seen.has(c)){seen.add(c);fin.push(c)}});var fs=fin.join(' ');if((cur.getAttribute('class')||'')!==fs){if(fs)cur.setAttribute('class',fs);else cur.removeAttribute('class')}for(var i=0;i<srv.attributes.length;i++){var a=srv.attributes[i];if(a.name==='class')continue;if(cur.getAttribute(a.name)!==a.value)cur.setAttribute(a.name,a.value)}if(old){for(var i=0;i<old.attributes.length;i++){var a=old.attributes[i];if(a.name==='class')continue;if(!srv.hasAttribute(a.name)&&cur.hasAttribute(a.name))cur.removeAttribute(a.name)}}}function syncText(cur,srv){var cc=cur.childNodes,sc=srv.childNodes;for(var i=0;i<sc.length;i++){var s=sc[i],c=cc[i];if(s.nodeType===3&&c&&c.nodeType===3){if(c.textContent!==s.textContent)c.textContent=s.textContent}}}function smartUpdate(curR,srvR,oldR){var ce=curR.querySelectorAll('[data-element-path]'),se=srvR.querySelectorAll('[data-element-path]');if(ce.length!==se.length){if(curR.innerHTML!==srvR.innerHTML)curR.innerHTML=srvR.innerHTML;return}var sbp={};for(var i=0;i<se.length;i++)sbp[se[i].getAttribute('data-element-path')]=se[i];var obp={};if(oldR){var oe=oldR.querySelectorAll('[data-element-path]');for(var i=0;i<oe.length;i++)obp[oe[i].getAttribute('data-element-path')]=oe[i]}for(var i=0;i<ce.length;i++){var c=ce[i],p=c.getAttribute('data-element-path'),s=sbp[p];if(!s){if(curR.innerHTML!==srvR.innerHTML)curR.innerHTML=srvR.innerHTML;return}syncEl(c,s,obp[p]);syncText(c,s)}syncText(curR,srvR)}function connect(){ws=new WebSocket(${wsUrl});ws.onmessage=function(e){var d=JSON.parse(e.data);if(d.type==='hmr:libraries-update'){location.reload()}else if(d.type==='hmr:update'||d.type==='hmr:cms-update'||d.type==='hmr:colors-update'||d.type==='hmr:variables-update')hotReload()};ws.onclose=function(){clearTimeout(timer);timer=setTimeout(connect,1000)}}function hotReload(){var g=++gen;var sx=window.scrollX,sy=window.scrollY;fetch(location.href,{cache:'no-store'}).then(function(r){return r.text()}).then(function(html){if(g!==gen)return;var p=new DOMParser();var d=p.parseFromString(html,'text/html');var or=document.getElementById('root'),nr=d.getElementById('root');if(or&&nr)smartUpdate(or,nr,lastSrvRoot);if(nr)lastSrvRoot=nr.cloneNode(true);var os=document.getElementById('meno-styles'),ns=d.getElementById('meno-styles');if(os&&ns&&os.textContent!==ns.textContent)os.parentNode.replaceChild(ns.cloneNode(true),os);var nh=d.documentElement;if(nh){var nl=nh.getAttribute('lang')||'en',nt=nh.getAttribute('theme')||'light';if(document.documentElement.getAttribute('lang')!==nl)document.documentElement.setAttribute('lang',nl);if(document.documentElement.getAttribute('theme')!==nt)document.documentElement.setAttribute('theme',nt)}var ocms=document.querySelectorAll('script[id^="meno-cms-"]'),ncms=d.querySelectorAll('script[id^="meno-cms-"]');var ock=JSON.stringify(Array.prototype.map.call(ocms,function(s){return [s.id,s.textContent]}));var nck=JSON.stringify(Array.prototype.map.call(ncms,function(s){return [s.id,s.textContent]}));if(ock!==nck){ocms.forEach(function(s){s.remove()});ncms.forEach(function(s){var c=document.createElement('script');c.type=s.type;c.id=s.id;c.textContent=s.textContent;document.head.appendChild(c)})}window.__menoHotReload=true;var olib=document.querySelectorAll('body > script[src^="/libraries/"]'),nlib=d.querySelectorAll('body > script[src^="/libraries/"]');var olk=JSON.stringify(Array.prototype.map.call(olib,function(s){return strip(s.getAttribute('src'))}).sort());var nlk=JSON.stringify(Array.prototype.map.call(nlib,function(s){return strip(s.getAttribute('src'))}).sort());if(olk!==nlk){olib.forEach(function(o){o.remove()});nlib.forEach(function(n){var src=n.getAttribute('src');var ls=document.createElement('script');ls.src=src+(src.indexOf('?')>-1?'&':'?')+'_r='+Date.now();document.body.appendChild(ls)})}var oscr=document.querySelector('script[src^="/_scripts/"]'),nscr=d.querySelector('script[src^="/_scripts/"]');var oss=oscr?strip(oscr.getAttribute('src')):'',nss=nscr?strip(nscr.getAttribute('src')):'';if(oss===nss){window.scrollTo(sx,sy)}else{if(oscr)oscr.remove();if(nscr){var src=nscr.getAttribute('src');var s=document.createElement('script');s.src=src+(src.indexOf('?')>-1?'&':'?')+'_r='+Date.now();s.onload=function(){document.dispatchEvent(new Event('DOMContentLoaded'));window.scrollTo(sx,sy)};s.onerror=function(){window.scrollTo(sx,sy)};document.body.appendChild(s)}else{document.dispatchEvent(new Event('DOMContentLoaded'));window.scrollTo(sx,sy)}}}).catch(function(){location.reload()})}var iR=document.getElementById('root');if(iR)lastSrvRoot=iR.cloneNode(true);connect()})()</script>` : "";
|
|
3496
3642
|
const scrollHandlerScript = injectLiveReload ? `<script>(function(){window.addEventListener('message',function(e){if(e.data.type==='GET_SCROLL_POSITION'){window.parent.postMessage({type:'SCROLL_POSITION_RESPONSE',scrollX:window.scrollX,scrollY:window.scrollY},'*')}else if(e.data.type==='SET_SCROLL_POSITION'){window.scrollTo(e.data.scrollX,e.data.scrollY)}})})()</script>` : "";
|
|
3497
3643
|
const styleContent = useBundled ? finalCSS : `
|
|
3498
3644
|
${combinedCSS.split("\n").join("\n ")}
|
|
@@ -5674,6 +5820,7 @@ autoInit();
|
|
|
5674
5820
|
// lib/server/providers/fileSystemCMSProvider.ts
|
|
5675
5821
|
import { existsSync as existsSync2, readdirSync as readdirSync2, mkdirSync } from "fs";
|
|
5676
5822
|
import { join as join2 } from "path";
|
|
5823
|
+
var DRAFT_FILE_SUFFIX = `${CMS_DRAFT_SUFFIX}.json`;
|
|
5677
5824
|
async function loadJSONFile2(filePath) {
|
|
5678
5825
|
try {
|
|
5679
5826
|
if (await fileExists(filePath)) {
|
|
@@ -5685,12 +5832,18 @@ async function loadJSONFile2(filePath) {
|
|
|
5685
5832
|
return null;
|
|
5686
5833
|
}
|
|
5687
5834
|
}
|
|
5688
|
-
function normalizeItem(content, filename) {
|
|
5689
|
-
|
|
5835
|
+
function normalizeItem(content, filename, isDraft = false) {
|
|
5836
|
+
const base = {
|
|
5690
5837
|
...content,
|
|
5691
5838
|
_slug: filename,
|
|
5692
5839
|
_filename: filename
|
|
5693
5840
|
};
|
|
5841
|
+
if (isDraft) base._isDraft = true;
|
|
5842
|
+
return base;
|
|
5843
|
+
}
|
|
5844
|
+
function stripTransient(item) {
|
|
5845
|
+
const { _slug, _isDraft, _hasDraft, _url, ...rest } = item;
|
|
5846
|
+
return rest;
|
|
5694
5847
|
}
|
|
5695
5848
|
var FileSystemCMSProvider = class {
|
|
5696
5849
|
constructor(templatesDir, cmsDir) {
|
|
@@ -5708,13 +5861,16 @@ var FileSystemCMSProvider = class {
|
|
|
5708
5861
|
}
|
|
5709
5862
|
}
|
|
5710
5863
|
/**
|
|
5711
|
-
* Validate filename to prevent path traversal attacks
|
|
5864
|
+
* Validate filename to prevent path traversal attacks and reserved-suffix collisions.
|
|
5712
5865
|
* @throws Error if filename is invalid
|
|
5713
5866
|
*/
|
|
5714
5867
|
validateFilename(filename) {
|
|
5715
5868
|
if (!isSafePathSegment(filename)) {
|
|
5716
5869
|
throw new Error(`Invalid filename: "${filename}". Filenames cannot contain path separators or traversal sequences.`);
|
|
5717
5870
|
}
|
|
5871
|
+
if (isReservedDraftFilename(filename)) {
|
|
5872
|
+
throw new Error(`Invalid filename: "${filename}". The "${CMS_DRAFT_SUFFIX}" suffix is reserved for draft files.`);
|
|
5873
|
+
}
|
|
5718
5874
|
}
|
|
5719
5875
|
/**
|
|
5720
5876
|
* Load all CMS schemas from page files with source: 'cms' in templates/
|
|
@@ -5761,7 +5917,7 @@ var FileSystemCMSProvider = class {
|
|
|
5761
5917
|
return [];
|
|
5762
5918
|
}
|
|
5763
5919
|
const files = readdirSync2(collectionDir);
|
|
5764
|
-
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
5920
|
+
const jsonFiles = files.filter((f) => f.endsWith(".json") && !f.endsWith(DRAFT_FILE_SUFFIX));
|
|
5765
5921
|
const results = await Promise.all(
|
|
5766
5922
|
jsonFiles.map(async (file) => {
|
|
5767
5923
|
const filePath = join2(collectionDir, file);
|
|
@@ -5845,22 +6001,170 @@ var FileSystemCMSProvider = class {
|
|
|
5845
6001
|
if (!existsSync2(collectionDir)) {
|
|
5846
6002
|
mkdirSync(collectionDir, { recursive: true });
|
|
5847
6003
|
}
|
|
5848
|
-
const
|
|
6004
|
+
const itemData = stripTransient(item);
|
|
5849
6005
|
const filePath = join2(collectionDir, `${filename}.json`);
|
|
5850
6006
|
await writeFile2(filePath, JSON.stringify(itemData, null, 2), "utf-8");
|
|
5851
6007
|
}
|
|
5852
6008
|
/**
|
|
5853
|
-
* Delete item by filename
|
|
6009
|
+
* Delete item by filename. Removes the published file AND any draft sibling.
|
|
5854
6010
|
*/
|
|
5855
6011
|
async deleteItem(collection, filename) {
|
|
5856
6012
|
this.validateCollection(collection);
|
|
5857
6013
|
this.validateFilename(filename);
|
|
5858
6014
|
const { unlink } = await import("fs/promises");
|
|
5859
|
-
const
|
|
6015
|
+
const publishedPath = join2(this.cmsDir, collection, `${filename}.json`);
|
|
6016
|
+
if (existsSync2(publishedPath)) {
|
|
6017
|
+
await unlink(publishedPath);
|
|
6018
|
+
}
|
|
6019
|
+
const draftPath = this.draftPath(collection, filename);
|
|
6020
|
+
if (existsSync2(draftPath)) {
|
|
6021
|
+
await unlink(draftPath);
|
|
6022
|
+
}
|
|
6023
|
+
}
|
|
6024
|
+
// ---- Draft helpers ----------------------------------------------------
|
|
6025
|
+
draftPath(collection, filename) {
|
|
6026
|
+
return join2(this.cmsDir, collection, `${filename}${DRAFT_FILE_SUFFIX}`);
|
|
6027
|
+
}
|
|
6028
|
+
/**
|
|
6029
|
+
* Get the draft version of an item, or null if no draft file exists.
|
|
6030
|
+
* Drafts skip strict validation — they may be partial / WIP.
|
|
6031
|
+
*/
|
|
6032
|
+
async getDraft(collection, filename) {
|
|
6033
|
+
this.validateCollection(collection);
|
|
6034
|
+
this.validateFilename(filename);
|
|
6035
|
+
const filePath = this.draftPath(collection, filename);
|
|
6036
|
+
const content = await loadJSONFile2(filePath);
|
|
6037
|
+
if (!content || typeof content !== "object") {
|
|
6038
|
+
return null;
|
|
6039
|
+
}
|
|
6040
|
+
return normalizeItem(
|
|
6041
|
+
content,
|
|
6042
|
+
filename,
|
|
6043
|
+
/*isDraft*/
|
|
6044
|
+
true
|
|
6045
|
+
);
|
|
6046
|
+
}
|
|
6047
|
+
/**
|
|
6048
|
+
* List all drafts in a collection. Used by the Studio item list to mark
|
|
6049
|
+
* items that have an outstanding draft sibling (or are draft-only).
|
|
6050
|
+
*/
|
|
6051
|
+
async getAllDrafts(collection) {
|
|
6052
|
+
this.validateCollection(collection);
|
|
6053
|
+
const collectionDir = join2(this.cmsDir, collection);
|
|
6054
|
+
if (!existsSync2(collectionDir)) return [];
|
|
6055
|
+
const files = readdirSync2(collectionDir).filter((f) => f.endsWith(DRAFT_FILE_SUFFIX));
|
|
6056
|
+
const results = await Promise.all(
|
|
6057
|
+
files.map(async (file) => {
|
|
6058
|
+
const filePath = join2(collectionDir, file);
|
|
6059
|
+
const content = await loadJSONFile2(filePath);
|
|
6060
|
+
return { file, content };
|
|
6061
|
+
})
|
|
6062
|
+
);
|
|
6063
|
+
const drafts = [];
|
|
6064
|
+
for (const { file, content } of results) {
|
|
6065
|
+
if (content && typeof content === "object") {
|
|
6066
|
+
const filename = file.slice(0, -DRAFT_FILE_SUFFIX.length);
|
|
6067
|
+
drafts.push(normalizeItem(
|
|
6068
|
+
content,
|
|
6069
|
+
filename,
|
|
6070
|
+
/*isDraft*/
|
|
6071
|
+
true
|
|
6072
|
+
));
|
|
6073
|
+
}
|
|
6074
|
+
}
|
|
6075
|
+
return drafts;
|
|
6076
|
+
}
|
|
6077
|
+
async hasDraft(collection, filename) {
|
|
6078
|
+
this.validateCollection(collection);
|
|
6079
|
+
this.validateFilename(filename);
|
|
6080
|
+
return existsSync2(this.draftPath(collection, filename));
|
|
6081
|
+
}
|
|
6082
|
+
/**
|
|
6083
|
+
* Save the draft version of an item. Loose validation — drafts may have
|
|
6084
|
+
* missing required fields or partial data. Strict validation only runs at
|
|
6085
|
+
* publish time. The item's `_filename` determines the target file.
|
|
6086
|
+
*/
|
|
6087
|
+
async saveDraft(collection, item) {
|
|
6088
|
+
this.validateCollection(collection);
|
|
6089
|
+
const { writeFile: writeFile2 } = await import("fs/promises");
|
|
6090
|
+
const schemas = await this.getAllSchemas();
|
|
6091
|
+
const schemaInfo = schemas.get(collection);
|
|
6092
|
+
if (!schemaInfo) {
|
|
6093
|
+
throw new Error(`Unknown collection: ${collection}`);
|
|
6094
|
+
}
|
|
6095
|
+
let filename;
|
|
6096
|
+
if (item._filename) {
|
|
6097
|
+
filename = item._filename;
|
|
6098
|
+
} else {
|
|
6099
|
+
const slugField = schemaInfo.schema.slugField;
|
|
6100
|
+
const slugValue = item[slugField];
|
|
6101
|
+
filename = typeof slugValue === "string" ? slugValue : String(slugValue);
|
|
6102
|
+
}
|
|
6103
|
+
if (!filename || filename === "[object Object]") {
|
|
6104
|
+
throw new Error("Missing _filename field. Drafts must have _filename set on creation.");
|
|
6105
|
+
}
|
|
6106
|
+
this.validateFilename(filename);
|
|
6107
|
+
const collectionDir = join2(this.cmsDir, collection);
|
|
6108
|
+
if (!existsSync2(collectionDir)) {
|
|
6109
|
+
mkdirSync(collectionDir, { recursive: true });
|
|
6110
|
+
}
|
|
6111
|
+
const itemData = stripTransient(item);
|
|
6112
|
+
const validation = validateCMSDraftItem(itemData);
|
|
6113
|
+
if (!validation.valid) {
|
|
6114
|
+
const messages = validation.errors.map((e) => `${e.path}: ${e.message}`).join(", ");
|
|
6115
|
+
throw new Error(`Invalid draft: ${messages}`);
|
|
6116
|
+
}
|
|
6117
|
+
const filePath = this.draftPath(collection, filename);
|
|
6118
|
+
await writeFile2(filePath, JSON.stringify(itemData, null, 2), "utf-8");
|
|
6119
|
+
}
|
|
6120
|
+
/**
|
|
6121
|
+
* Discard the draft version of an item. No-op if no draft exists.
|
|
6122
|
+
*/
|
|
6123
|
+
async discardDraft(collection, filename) {
|
|
6124
|
+
this.validateCollection(collection);
|
|
6125
|
+
this.validateFilename(filename);
|
|
6126
|
+
const { unlink } = await import("fs/promises");
|
|
6127
|
+
const filePath = this.draftPath(collection, filename);
|
|
5860
6128
|
if (existsSync2(filePath)) {
|
|
5861
6129
|
await unlink(filePath);
|
|
5862
6130
|
}
|
|
5863
6131
|
}
|
|
6132
|
+
/**
|
|
6133
|
+
* Promote a draft to published. Reads `{filename}.draft.json`, writes the
|
|
6134
|
+
* content to `{filename}.json`, then unlinks the draft. The published write
|
|
6135
|
+
* happens first so a crash mid-operation leaves a valid published file plus
|
|
6136
|
+
* an orphan draft (recoverable via the editor's Discard button) — never a
|
|
6137
|
+
* gap with no published content.
|
|
6138
|
+
*
|
|
6139
|
+
* Throws if no draft exists.
|
|
6140
|
+
*/
|
|
6141
|
+
async publishDraft(collection, filename) {
|
|
6142
|
+
this.validateCollection(collection);
|
|
6143
|
+
this.validateFilename(filename);
|
|
6144
|
+
const { writeFile: writeFile2, unlink } = await import("fs/promises");
|
|
6145
|
+
const draftFilePath = this.draftPath(collection, filename);
|
|
6146
|
+
const content = await loadJSONFile2(draftFilePath);
|
|
6147
|
+
if (!content || typeof content !== "object") {
|
|
6148
|
+
throw new Error(`No draft to publish for ${collection}/${filename}`);
|
|
6149
|
+
}
|
|
6150
|
+
const item = normalizeItem(content, filename);
|
|
6151
|
+
const validation = validateCMSItem(item);
|
|
6152
|
+
if (!validation.valid) {
|
|
6153
|
+
const messages = validation.errors.map((e) => `${e.path}: ${e.message}`).join(", ");
|
|
6154
|
+
throw new Error(`Cannot publish invalid draft: ${messages}`);
|
|
6155
|
+
}
|
|
6156
|
+
const collectionDir = join2(this.cmsDir, collection);
|
|
6157
|
+
if (!existsSync2(collectionDir)) {
|
|
6158
|
+
mkdirSync(collectionDir, { recursive: true });
|
|
6159
|
+
}
|
|
6160
|
+
const itemData = stripTransient(validation.data);
|
|
6161
|
+
const publishedPath = join2(collectionDir, `${filename}.json`);
|
|
6162
|
+
await writeFile2(publishedPath, JSON.stringify(itemData, null, 2), "utf-8");
|
|
6163
|
+
if (existsSync2(draftFilePath)) {
|
|
6164
|
+
await unlink(draftFilePath);
|
|
6165
|
+
}
|
|
6166
|
+
return normalizeItem(itemData, filename);
|
|
6167
|
+
}
|
|
5864
6168
|
/**
|
|
5865
6169
|
* Clear schema cache (useful when pages are modified)
|
|
5866
6170
|
*/
|
|
@@ -5935,7 +6239,6 @@ export {
|
|
|
5935
6239
|
loadComponentDirectory,
|
|
5936
6240
|
mapPageNameToPath,
|
|
5937
6241
|
loadBreakpointConfig,
|
|
5938
|
-
loadResponsiveScalesConfig,
|
|
5939
6242
|
loadI18nConfig,
|
|
5940
6243
|
loadIconsConfig,
|
|
5941
6244
|
CachedConfigLoader,
|
|
@@ -5952,6 +6255,7 @@ export {
|
|
|
5952
6255
|
generateVariablesCSS,
|
|
5953
6256
|
buildSlugIndex,
|
|
5954
6257
|
translatePath,
|
|
6258
|
+
getLocaleLinks,
|
|
5955
6259
|
resolveSlugToPageId,
|
|
5956
6260
|
escapeHtml,
|
|
5957
6261
|
buildAttributes,
|
|
@@ -5984,4 +6288,4 @@ export {
|
|
|
5984
6288
|
FileSystemCMSProvider,
|
|
5985
6289
|
migrateTemplatesDirectory
|
|
5986
6290
|
};
|
|
5987
|
-
//# sourceMappingURL=chunk-
|
|
6291
|
+
//# sourceMappingURL=chunk-EQYDSPBB.js.map
|