meno-core 1.0.48 → 1.0.50
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 +6 -2
- package/dist/build-static.js +7 -7
- package/dist/chunks/{chunk-D5E3OKSL.js → chunk-56EUSC6D.js} +5 -5
- package/dist/chunks/{chunk-3FHJUHAS.js → chunk-7NIC4I3V.js} +300 -43
- package/dist/chunks/chunk-7NIC4I3V.js.map +7 -0
- package/dist/chunks/{chunk-B2RTLDXY.js → chunk-AZQYF6KE.js} +132 -1
- package/dist/chunks/chunk-AZQYF6KE.js.map +7 -0
- package/dist/chunks/{chunk-TPQ7APVQ.js → chunk-CVLFID6V.js} +473 -73
- package/dist/chunks/chunk-CVLFID6V.js.map +7 -0
- package/dist/chunks/{chunk-NP76N4HQ.js → chunk-EDQSMAMP.js} +13 -2
- package/dist/chunks/{chunk-NP76N4HQ.js.map → chunk-EDQSMAMP.js.map} +2 -2
- package/dist/chunks/{chunk-RQSTH2BS.js → chunk-H4JSCDNW.js} +2 -2
- package/dist/chunks/{chunk-EK4KESLU.js → chunk-J23ZX5AP.js} +8 -2
- package/dist/chunks/{chunk-EK4KESLU.js.map → chunk-J23ZX5AP.js.map} +2 -2
- package/dist/chunks/{chunk-UUA5LEWF.js → chunk-LPVETICS.js} +156 -8
- package/dist/chunks/chunk-LPVETICS.js.map +7 -0
- package/dist/chunks/{chunk-BJRKEPMP.js → chunk-PQ2HRXDR.js} +5 -2
- package/dist/chunks/chunk-PQ2HRXDR.js.map +7 -0
- package/dist/chunks/{chunk-NKUV77SR.js → chunk-YWJJD5D6.js} +133 -37
- package/dist/chunks/chunk-YWJJD5D6.js.map +7 -0
- package/dist/chunks/{configService-IGJEC3MC.js → configService-VOY2MY2K.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 +92 -32
- package/dist/lib/client/index.js.map +3 -3
- package/dist/lib/server/index.js +14 -12
- package/dist/lib/server/index.js.map +2 -2
- package/dist/lib/shared/index.js +46 -10
- package/dist/lib/shared/index.js.map +3 -3
- package/entries/server-router.tsx +6 -2
- package/lib/client/core/ComponentBuilder.test.ts +34 -0
- package/lib/client/core/ComponentBuilder.ts +33 -4
- package/lib/client/core/builders/embedBuilder.ts +28 -7
- package/lib/client/core/builders/linkNodeBuilder.ts +28 -7
- package/lib/client/core/builders/localeListBuilder.ts +30 -11
- package/lib/client/styles/StyleInjector.ts +3 -2
- package/lib/client/templateEngine.ts +24 -0
- 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/fileWatcher.test.ts +134 -0
- package/lib/server/fileWatcher.ts +100 -32
- package/lib/server/jsonLoader.ts +1 -0
- package/lib/server/providers/fileSystemCMSProvider.test.ts +163 -0
- package/lib/server/providers/fileSystemCMSProvider.ts +240 -19
- 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 +6 -0
- package/lib/server/services/fileWatcherService.ts +17 -0
- package/lib/server/ssr/attributeBuilder.ts +41 -0
- package/lib/server/ssr/htmlGenerator.test.ts +113 -0
- package/lib/server/ssr/htmlGenerator.ts +62 -7
- package/lib/server/ssr/liveReloadIntegration.test.ts +209 -0
- package/lib/server/ssr/ssrRenderer.test.ts +564 -0
- package/lib/server/ssr/ssrRenderer.ts +228 -49
- package/lib/server/webflow/buildWebflow.ts +1 -1
- package/lib/server/websocketManager.test.ts +61 -6
- package/lib/server/websocketManager.ts +25 -1
- package/lib/shared/cssGeneration.test.ts +267 -1
- package/lib/shared/cssGeneration.ts +240 -18
- package/lib/shared/cssProperties.test.ts +275 -1
- package/lib/shared/cssProperties.ts +223 -7
- package/lib/shared/interfaces/contentProvider.ts +39 -6
- package/lib/shared/pathSecurity.ts +16 -0
- 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/api.ts +10 -1
- package/lib/shared/types/cms.ts +46 -12
- package/lib/shared/types/index.ts +1 -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.test.ts +93 -0
- package/lib/shared/validation/schemas.ts +71 -16
- package/lib/shared/validation/validators.ts +26 -1
- package/package.json +1 -1
- package/dist/chunks/chunk-3FHJUHAS.js.map +0 -7
- package/dist/chunks/chunk-B2RTLDXY.js.map +0 -7
- package/dist/chunks/chunk-BJRKEPMP.js.map +0 -7
- package/dist/chunks/chunk-NKUV77SR.js.map +0 -7
- package/dist/chunks/chunk-TPQ7APVQ.js.map +0 -7
- package/dist/chunks/chunk-UUA5LEWF.js.map +0 -7
- /package/dist/chunks/{chunk-D5E3OKSL.js.map → chunk-56EUSC6D.js.map} +0 -0
- /package/dist/chunks/{chunk-RQSTH2BS.js.map → chunk-H4JSCDNW.js.map} +0 -0
- /package/dist/chunks/{configService-IGJEC3MC.js.map → configService-VOY2MY2K.js.map} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
configService
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-PQ2HRXDR.js";
|
|
4
4
|
import {
|
|
5
5
|
projectPaths,
|
|
6
6
|
resolveProjectPath,
|
|
@@ -13,10 +13,12 @@ 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,
|
|
@@ -24,7 +26,7 @@ import {
|
|
|
24
26
|
processStructure,
|
|
25
27
|
resolveHtmlMapping,
|
|
26
28
|
skipEmptyTemplateAttributes
|
|
27
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-EDQSMAMP.js";
|
|
28
30
|
import {
|
|
29
31
|
DEFAULT_PREFETCH_CONFIG,
|
|
30
32
|
SSRRegistry,
|
|
@@ -61,20 +63,26 @@ import {
|
|
|
61
63
|
resolveTemplateRawValue,
|
|
62
64
|
responsiveStylesToClasses,
|
|
63
65
|
singularize,
|
|
66
|
+
validateCMSDraftItem,
|
|
64
67
|
validateCMSItem,
|
|
65
68
|
validateComponentDefinition
|
|
66
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-7NIC4I3V.js";
|
|
67
70
|
import {
|
|
68
71
|
DEFAULT_BREAKPOINTS,
|
|
72
|
+
DEFAULT_FLUID_RANGE,
|
|
69
73
|
DEFAULT_I18N_CONFIG,
|
|
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
|
|
@@ -717,6 +725,7 @@ var CMSService = class {
|
|
|
717
725
|
schemaCache = /* @__PURE__ */ new Map();
|
|
718
726
|
routePatterns = [];
|
|
719
727
|
provider;
|
|
728
|
+
previewMode;
|
|
720
729
|
/** Item cache with TTL-based expiration */
|
|
721
730
|
itemsCache = /* @__PURE__ */ new Map();
|
|
722
731
|
/** Cache TTL in milliseconds (5 seconds) */
|
|
@@ -724,9 +733,11 @@ var CMSService = class {
|
|
|
724
733
|
/**
|
|
725
734
|
* Creates a new CMSService instance
|
|
726
735
|
* @param provider - Optional CMSProvider for loading data (enables DI for testing)
|
|
736
|
+
* @param options - Service-level flags (preview mode for dev server)
|
|
727
737
|
*/
|
|
728
|
-
constructor(provider) {
|
|
738
|
+
constructor(provider, options = {}) {
|
|
729
739
|
this.provider = provider;
|
|
740
|
+
this.previewMode = options.previewMode === true;
|
|
730
741
|
}
|
|
731
742
|
/**
|
|
732
743
|
* Set the CMS provider
|
|
@@ -737,9 +748,14 @@ var CMSService = class {
|
|
|
737
748
|
this.provider = provider;
|
|
738
749
|
}
|
|
739
750
|
/**
|
|
740
|
-
* Get items with caching
|
|
741
|
-
*
|
|
742
|
-
*
|
|
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
|
+
*
|
|
743
759
|
* @param collection - Collection ID to fetch items for
|
|
744
760
|
* @returns Array of CMSItems with rich-text fields converted to HTML markers
|
|
745
761
|
*/
|
|
@@ -749,7 +765,20 @@ var CMSService = class {
|
|
|
749
765
|
if (cached && now - cached.timestamp < this.ITEMS_CACHE_TTL) {
|
|
750
766
|
return cached.items;
|
|
751
767
|
}
|
|
752
|
-
|
|
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
|
+
}
|
|
753
782
|
const items = this.preprocessRichTextFields(collection, rawItems);
|
|
754
783
|
this.itemsCache.set(collection, { items, timestamp: now });
|
|
755
784
|
return items;
|
|
@@ -987,6 +1016,71 @@ var CMSService = class {
|
|
|
987
1016
|
* Only clears items cache and provider cache before re-initializing.
|
|
988
1017
|
* Schema/route caches are swapped atomically inside initialize().
|
|
989
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
|
+
}
|
|
990
1084
|
async refreshSchemas() {
|
|
991
1085
|
if (!this.provider) {
|
|
992
1086
|
return;
|
|
@@ -1295,20 +1389,45 @@ ${cssVars.join("\n")}
|
|
|
1295
1389
|
return cssBlocks.join("\n\n");
|
|
1296
1390
|
}
|
|
1297
1391
|
function generateVariablesCSS(config, breakpoints, responsiveScales) {
|
|
1298
|
-
|
|
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) {
|
|
1299
1419
|
return "";
|
|
1300
1420
|
}
|
|
1301
|
-
const cssBlocks = [];
|
|
1302
|
-
const baseVars = config.variables.map((v) => ` ${v.cssVar}: ${v.value};`);
|
|
1303
1421
|
cssBlocks.push(`:root {
|
|
1304
1422
|
${baseVars.join("\n")}
|
|
1305
1423
|
}`);
|
|
1306
|
-
|
|
1424
|
+
const userVariables = config.variables ?? [];
|
|
1425
|
+
if (breakpoints && responsiveScales?.enabled && !fluidActive && userVariables.length > 0) {
|
|
1307
1426
|
const baseRef = responsiveScales.baseReference || 16;
|
|
1308
1427
|
const sortedBreakpoints = Object.entries(breakpoints).sort((a, b) => b[1].breakpoint - a[1].breakpoint);
|
|
1309
1428
|
for (const [bpName, bpEntry] of sortedBreakpoints) {
|
|
1310
1429
|
const scaledVars = [];
|
|
1311
|
-
for (const variable of
|
|
1430
|
+
for (const variable of userVariables) {
|
|
1312
1431
|
if (variable.scales && variable.scales[bpName]) {
|
|
1313
1432
|
const overrideValue = variable.scales[bpName];
|
|
1314
1433
|
if (overrideValue !== variable.value) {
|
|
@@ -1389,6 +1508,27 @@ function buildAttributes(props, exclude = []) {
|
|
|
1389
1508
|
}
|
|
1390
1509
|
return attrs.length > 0 ? " " + attrs.join(" ") : "";
|
|
1391
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
|
+
}
|
|
1392
1532
|
function styleToString(style) {
|
|
1393
1533
|
if (!style || Object.keys(style).length === 0) return "";
|
|
1394
1534
|
const declarations = [];
|
|
@@ -1898,9 +2038,27 @@ function getDOMPurify() {
|
|
|
1898
2038
|
}
|
|
1899
2039
|
return _DOMPurify;
|
|
1900
2040
|
}
|
|
2041
|
+
function resolveI18nAttrs(attrs, locale, i18nConfig) {
|
|
2042
|
+
let mutated = null;
|
|
2043
|
+
const config = i18nConfig ?? DEFAULT_I18N_CONFIG;
|
|
2044
|
+
const effectiveLocale = locale || config.defaultLocale;
|
|
2045
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
2046
|
+
if (isI18nValue(value)) {
|
|
2047
|
+
mutated = mutated ?? { ...attrs };
|
|
2048
|
+
mutated[key] = resolveI18nValue(value, effectiveLocale, config);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
return mutated ?? attrs;
|
|
2052
|
+
}
|
|
1901
2053
|
function getTemplateContext(ctx) {
|
|
1902
2054
|
return ctx.templateContext || null;
|
|
1903
2055
|
}
|
|
2056
|
+
function buildListResolutionScope(ctx) {
|
|
2057
|
+
const tplCtx = ctx.templateContext;
|
|
2058
|
+
const props = ctx.componentResolvedProps;
|
|
2059
|
+
if (!tplCtx && !props) return void 0;
|
|
2060
|
+
return { ...props ?? {}, ...tplCtx ?? {} };
|
|
2061
|
+
}
|
|
1904
2062
|
function getI18nResolver(ctx) {
|
|
1905
2063
|
return createI18nResolver(ctx.locale, ctx.i18nConfig);
|
|
1906
2064
|
}
|
|
@@ -1939,7 +2097,11 @@ function processStyleToClasses(style, ctx) {
|
|
|
1939
2097
|
getI18nResolver(ctx)
|
|
1940
2098
|
);
|
|
1941
2099
|
}
|
|
1942
|
-
|
|
2100
|
+
const fluidActive = ctx.responsiveScales?.enabled === true && ctx.responsiveScales?.mode === "fluid";
|
|
2101
|
+
return responsiveStylesToClasses(
|
|
2102
|
+
processedStyle,
|
|
2103
|
+
{ fluidActive, responsiveScales: ctx.responsiveScales }
|
|
2104
|
+
);
|
|
1943
2105
|
}
|
|
1944
2106
|
function evaluateIfCondition(node, ctx) {
|
|
1945
2107
|
const ifValue = hasIf(node) ? node.if : void 0;
|
|
@@ -1971,31 +2133,31 @@ function evaluateIfCondition(node, ctx) {
|
|
|
1971
2133
|
}
|
|
1972
2134
|
return true;
|
|
1973
2135
|
}
|
|
1974
|
-
function resolveFilterValue(value,
|
|
1975
|
-
if (!
|
|
2136
|
+
function resolveFilterValue(value, scope) {
|
|
2137
|
+
if (!scope || typeof value !== "string" || !value.startsWith("{{") || !value.endsWith("}}")) {
|
|
1976
2138
|
return value;
|
|
1977
2139
|
}
|
|
1978
2140
|
const path2 = value.slice(2, -2).trim();
|
|
1979
|
-
const resolved = getNestedValue(
|
|
2141
|
+
const resolved = getNestedValue(scope, path2);
|
|
1980
2142
|
return resolved !== void 0 ? resolved : value;
|
|
1981
2143
|
}
|
|
1982
|
-
function resolveFilterTemplates(filter,
|
|
1983
|
-
if (!filter || !
|
|
2144
|
+
function resolveFilterTemplates(filter, scope) {
|
|
2145
|
+
if (!filter || !scope) return filter;
|
|
1984
2146
|
if (Array.isArray(filter)) {
|
|
1985
2147
|
return filter.map((cond) => ({
|
|
1986
2148
|
...cond,
|
|
1987
|
-
value: resolveFilterValue(cond.value,
|
|
2149
|
+
value: resolveFilterValue(cond.value, scope)
|
|
1988
2150
|
}));
|
|
1989
2151
|
}
|
|
1990
2152
|
if ("field" in filter && "value" in filter) {
|
|
1991
2153
|
return {
|
|
1992
2154
|
...filter,
|
|
1993
|
-
value: resolveFilterValue(filter.value,
|
|
2155
|
+
value: resolveFilterValue(filter.value, scope)
|
|
1994
2156
|
};
|
|
1995
2157
|
}
|
|
1996
2158
|
const resolved = {};
|
|
1997
2159
|
for (const [key, value] of Object.entries(filter)) {
|
|
1998
|
-
resolved[key] = resolveFilterValue(value,
|
|
2160
|
+
resolved[key] = resolveFilterValue(value, scope);
|
|
1999
2161
|
}
|
|
2000
2162
|
return resolved;
|
|
2001
2163
|
}
|
|
@@ -2032,7 +2194,7 @@ async function expandRichTextComponents(html, ctx) {
|
|
|
2032
2194
|
return resolved.join("");
|
|
2033
2195
|
}
|
|
2034
2196
|
var ssrComponentRegistry = new SSRRegistry();
|
|
2035
|
-
async function buildComponentHTML(node, globalComponents = {}, pageComponents = {}, locale, i18nConfig, slugMappings, pagePath, cmsContext, cmsService, isProductionBuild) {
|
|
2197
|
+
async function buildComponentHTML(node, globalComponents = {}, pageComponents = {}, locale, i18nConfig, slugMappings, pagePath, cmsContext, cmsService, isProductionBuild, injectEditorAttrs) {
|
|
2036
2198
|
const interactiveStylesMap = /* @__PURE__ */ new Map();
|
|
2037
2199
|
const preloadImages = [];
|
|
2038
2200
|
const neededCollections = /* @__PURE__ */ new Set();
|
|
@@ -2067,7 +2229,9 @@ async function buildComponentHTML(node, globalComponents = {}, pageComponents =
|
|
|
2067
2229
|
// Collect SSR fallback HTML for complex nodes
|
|
2068
2230
|
processedRawHtmlCollector,
|
|
2069
2231
|
// Collect raw→processed HTML for Astro exporter
|
|
2070
|
-
imageFormat: configService.getImageFormat()
|
|
2232
|
+
imageFormat: configService.getImageFormat(),
|
|
2233
|
+
injectEditorAttrs,
|
|
2234
|
+
responsiveScales: configService.getResponsiveScales()
|
|
2071
2235
|
};
|
|
2072
2236
|
const html = await renderNode(node, ctx);
|
|
2073
2237
|
return { html, interactiveStylesMap, preloadImages, neededCollections, ssrFallbackCollector, processedRawHtmlCollector };
|
|
@@ -2098,7 +2262,7 @@ async function renderNestedListPlaceholder(node, ctx) {
|
|
|
2098
2262
|
};
|
|
2099
2263
|
const templateContent = node.children ? await renderChildrenAsync(node.children, childTemplateCtx) : "";
|
|
2100
2264
|
const configJson = escapeHtml(JSON.stringify(config));
|
|
2101
|
-
return `<div data-cms-list-nested="true" data-collection="${escapeHtml(sourceStr)}" data-cms-config="${configJson}"><template data-nested-template>${templateContent}</template></div>`;
|
|
2265
|
+
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>`;
|
|
2102
2266
|
}
|
|
2103
2267
|
async function processList(node, ctx) {
|
|
2104
2268
|
const nodeType = node.type;
|
|
@@ -2157,7 +2321,9 @@ async function processList(node, ctx) {
|
|
|
2157
2321
|
);
|
|
2158
2322
|
const itemCtx = {
|
|
2159
2323
|
...ctx,
|
|
2160
|
-
templateContext
|
|
2324
|
+
templateContext,
|
|
2325
|
+
cmsItemIndexPath: [...ctx.cmsItemIndexPath ?? [], i],
|
|
2326
|
+
cmsListPaths: [...ctx.cmsListPaths ?? [], ctx.elementPath ?? []]
|
|
2161
2327
|
};
|
|
2162
2328
|
const childrenHtml = await renderChildrenAsync(node.children || [], itemCtx);
|
|
2163
2329
|
renderedItems.push(childrenHtml);
|
|
@@ -2201,7 +2367,8 @@ async function getCollectionItems(node, source, ctx) {
|
|
|
2201
2367
|
resolvedIds = Array.isArray(value) ? value.map((v) => String(v)) : String(value);
|
|
2202
2368
|
}
|
|
2203
2369
|
} else {
|
|
2204
|
-
const
|
|
2370
|
+
const mergedScope = buildListResolutionScope(ctx) ?? {};
|
|
2371
|
+
const parentContext = { _type: "template", ...mergedScope };
|
|
2205
2372
|
resolvedIds = resolveItemsTemplate(node.items, parentContext);
|
|
2206
2373
|
}
|
|
2207
2374
|
if (!resolvedIds) {
|
|
@@ -2219,7 +2386,7 @@ async function getCollectionItems(node, source, ctx) {
|
|
|
2219
2386
|
} else {
|
|
2220
2387
|
const query = {
|
|
2221
2388
|
collection: source,
|
|
2222
|
-
filter: resolveFilterTemplates(node.filter, ctx
|
|
2389
|
+
filter: resolveFilterTemplates(node.filter, buildListResolutionScope(ctx)),
|
|
2223
2390
|
sort: node.sort,
|
|
2224
2391
|
limit: node.limit,
|
|
2225
2392
|
offset: node.offset,
|
|
@@ -2274,11 +2441,13 @@ function buildNodeElementClass(ctx, label, isSlotContent2) {
|
|
|
2274
2441
|
const useComponentContext = !isSlotContent2 && Boolean(ctx.componentContext);
|
|
2275
2442
|
const effectiveFileType = useComponentContext ? "component" : "page";
|
|
2276
2443
|
const effectiveFileName = useComponentContext ? ctx.componentContext : pagePath ? pagePath.replace(/^\//, "").replace(/\//g, "_") || "index" : "page";
|
|
2444
|
+
const rawPath = ctx.elementPath || [];
|
|
2445
|
+
const path2 = useComponentContext && ctx.componentRootPath ? rawPath.slice(ctx.componentRootPath.length) : rawPath;
|
|
2277
2446
|
const elementClassCtx = {
|
|
2278
2447
|
fileType: effectiveFileType,
|
|
2279
2448
|
fileName: effectiveFileName || "page",
|
|
2280
2449
|
label,
|
|
2281
|
-
path:
|
|
2450
|
+
path: path2
|
|
2282
2451
|
};
|
|
2283
2452
|
return generateElementClassName(elementClassCtx);
|
|
2284
2453
|
}
|
|
@@ -2298,6 +2467,26 @@ function buildCssVariableStyleAttr(cssVariables) {
|
|
|
2298
2467
|
const styleString = Object.entries(cssVariables).map(([k, v]) => `${k}: ${v}`).join("; ");
|
|
2299
2468
|
return ` style="${escapeHtml(styleString)}"`;
|
|
2300
2469
|
}
|
|
2470
|
+
function arraysEqual(a, b) {
|
|
2471
|
+
if (!a || !b || a.length !== b.length) return false;
|
|
2472
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
2473
|
+
return true;
|
|
2474
|
+
}
|
|
2475
|
+
function editorAttrs(ctx, opts = {}) {
|
|
2476
|
+
if (!ctx.injectEditorAttrs) return "";
|
|
2477
|
+
const isComponentRoot = !!ctx.componentContext && !opts.isSlotContent && arraysEqual(ctx.elementPath, ctx.componentRootPath);
|
|
2478
|
+
const effectiveParent = opts.isSlotContent ? ctx.parentComponentName : isComponentRoot ? ctx.parentComponentName : ctx.componentContext ?? ctx.parentComponentName;
|
|
2479
|
+
return buildEditorAttrs({
|
|
2480
|
+
elementPath: ctx.elementPath,
|
|
2481
|
+
cmsItemIndexPath: ctx.cmsItemIndexPath,
|
|
2482
|
+
cmsListPaths: ctx.cmsListPaths,
|
|
2483
|
+
componentContext: ctx.componentContext,
|
|
2484
|
+
parentComponentName: effectiveParent,
|
|
2485
|
+
isComponentRoot,
|
|
2486
|
+
isSlotContent: opts.isSlotContent,
|
|
2487
|
+
isCMSListContainer: opts.isCMSListContainer
|
|
2488
|
+
});
|
|
2489
|
+
}
|
|
2301
2490
|
async function renderNode(node, ctx) {
|
|
2302
2491
|
const { breakpoints, viewportWidth, locale, i18nConfig, slugMappings, pagePath } = ctx;
|
|
2303
2492
|
if (node === null || node === void 0) return "";
|
|
@@ -2343,6 +2532,15 @@ async function renderNode(node, ctx) {
|
|
|
2343
2532
|
}))).join("");
|
|
2344
2533
|
}
|
|
2345
2534
|
if (typeof node !== "object") return "";
|
|
2535
|
+
if (isI18nValue(node)) {
|
|
2536
|
+
const i18nResolveConfig = i18nConfig ?? DEFAULT_I18N_CONFIG;
|
|
2537
|
+
const i18nEffectiveLocale = locale || i18nResolveConfig.defaultLocale;
|
|
2538
|
+
const resolved = resolveI18nValue(node, i18nEffectiveLocale, i18nResolveConfig);
|
|
2539
|
+
return renderNode(
|
|
2540
|
+
resolved,
|
|
2541
|
+
ctx
|
|
2542
|
+
);
|
|
2543
|
+
}
|
|
2346
2544
|
if (!evaluateIfCondition(node, ctx)) {
|
|
2347
2545
|
return "";
|
|
2348
2546
|
}
|
|
@@ -2382,7 +2580,7 @@ async function renderNode(node, ctx) {
|
|
|
2382
2580
|
KEEP_CONTENT: true
|
|
2383
2581
|
}) : htmlContent;
|
|
2384
2582
|
const optimizedHtml = ctx.imageMetadataMap ? rewriteRichTextImages(sanitizedHtml, ctx.imageMetadataMap, ctx.imageFormat) : sanitizedHtml;
|
|
2385
|
-
const nodeAttributes2 = extractAttributesFromNode(node);
|
|
2583
|
+
const nodeAttributes2 = resolveI18nAttrs(extractAttributesFromNode(node), locale, i18nConfig);
|
|
2386
2584
|
const classNames = ["oem"];
|
|
2387
2585
|
if (nodeStyle) {
|
|
2388
2586
|
const utilityClasses2 = processStyleToClasses(nodeStyle, ctx);
|
|
@@ -2406,7 +2604,7 @@ async function renderNode(node, ctx) {
|
|
|
2406
2604
|
delete nodeAttributes2.class;
|
|
2407
2605
|
const attrs2 = buildAttributes(nodeAttributes2);
|
|
2408
2606
|
const classAttr2 = classNames.length > 0 ? ` class="${escapeHtml(classNames.filter(Boolean).join(" "))}"` : "";
|
|
2409
|
-
return `<span${classAttr2}${embedStyleAttr}${attrs2}>${optimizedHtml}</span>`;
|
|
2607
|
+
return `<span${classAttr2}${embedStyleAttr}${attrs2}${editorAttrs(ctx, { isSlotContent: isSlotContent(node) })}>${optimizedHtml}</span>`;
|
|
2410
2608
|
}
|
|
2411
2609
|
const attrClassName2 = nodeAttributes2.className || nodeAttributes2.class || "";
|
|
2412
2610
|
if (attrClassName2) {
|
|
@@ -2416,7 +2614,7 @@ async function renderNode(node, ctx) {
|
|
|
2416
2614
|
delete nodeAttributes2.class;
|
|
2417
2615
|
const attrs = buildAttributes(nodeAttributes2);
|
|
2418
2616
|
const classAttr = classNames.length > 0 ? ` class="${escapeHtml(classNames.filter(Boolean).join(" "))}"` : "";
|
|
2419
|
-
return `<span${classAttr}${attrs}>${optimizedHtml}</span>`;
|
|
2617
|
+
return `<span${classAttr}${attrs}${editorAttrs(ctx, { isSlotContent: isSlotContent(node) })}>${optimizedHtml}</span>`;
|
|
2420
2618
|
}
|
|
2421
2619
|
if (isLinkNode(node)) {
|
|
2422
2620
|
let href = typeof node.href === "string" ? node.href : "#";
|
|
@@ -2441,7 +2639,7 @@ async function renderNode(node, ctx) {
|
|
|
2441
2639
|
href = processCMSTemplate(href, ctx.cmsContext.cms, locale, i18nConfig);
|
|
2442
2640
|
}
|
|
2443
2641
|
href = localizeHref(href, ctx);
|
|
2444
|
-
const nodeAttributes2 = extractAttributesFromNode(node);
|
|
2642
|
+
const nodeAttributes2 = resolveI18nAttrs(extractAttributesFromNode(node), locale, i18nConfig);
|
|
2445
2643
|
const classNames = ["olink"];
|
|
2446
2644
|
if (nodeStyle) {
|
|
2447
2645
|
const utilityClasses2 = processStyleToClasses(nodeStyle, ctx);
|
|
@@ -2477,7 +2675,7 @@ async function renderNode(node, ctx) {
|
|
|
2477
2675
|
const childPath = ctx.elementPath ? [...ctx.elementPath, index] : [index];
|
|
2478
2676
|
return renderNode(child, { ...ctx, elementPath: childPath });
|
|
2479
2677
|
}))).join("") : await renderNode(children, { ...ctx, elementPath: ctx.elementPath ? [...ctx.elementPath, 0] : [0] });
|
|
2480
|
-
return `<a href="${escapeHtml(String(href))}"${classAttr}${olinkStyleAttr}${attrs}>${childrenHTML}</a>`;
|
|
2678
|
+
return `<a href="${escapeHtml(String(href))}"${classAttr}${olinkStyleAttr}${attrs}${editorAttrs(ctx, { isSlotContent: isSlotContent(node) })}>${childrenHTML}</a>`;
|
|
2481
2679
|
}
|
|
2482
2680
|
if (isLocaleListNode(node)) {
|
|
2483
2681
|
return renderLocaleList(node, ctx);
|
|
@@ -2493,7 +2691,7 @@ async function renderNode(node, ctx) {
|
|
|
2493
2691
|
if (!ctx.templateMode && templateCtx && Object.keys(nodeProps).length > 0) {
|
|
2494
2692
|
nodeProps = processItemPropsTemplate(nodeProps, templateCtx, i18nResolver);
|
|
2495
2693
|
}
|
|
2496
|
-
let nodeAttributes = extractAttributesFromNode(node);
|
|
2694
|
+
let nodeAttributes = resolveI18nAttrs(extractAttributesFromNode(node), locale, i18nConfig);
|
|
2497
2695
|
const originalAttributes = { ...nodeAttributes };
|
|
2498
2696
|
if (ctx.cmsContext?.cms && Object.keys(nodeAttributes).length > 0) {
|
|
2499
2697
|
nodeAttributes = processCMSPropsTemplate(nodeAttributes, ctx.cmsContext.cms, locale, i18nConfig);
|
|
@@ -2651,8 +2849,10 @@ async function renderComponent(componentName, propsWithStyleAndAttrs, children,
|
|
|
2651
2849
|
}
|
|
2652
2850
|
return await renderNode(processedStructure, {
|
|
2653
2851
|
...ctx,
|
|
2852
|
+
// The previously-active component (if any) becomes the parent for editor attrs
|
|
2853
|
+
parentComponentName: ctx.componentContext,
|
|
2654
2854
|
componentContext: componentName,
|
|
2655
|
-
|
|
2855
|
+
componentRootPath: ctx.elementPath,
|
|
2656
2856
|
componentResolvedProps: resolvedProps
|
|
2657
2857
|
});
|
|
2658
2858
|
} catch (error) {
|
|
@@ -2672,7 +2872,7 @@ async function renderLinkNode(propsWithStyleAndAttrs, children, ctx) {
|
|
|
2672
2872
|
const childPath = ctx.elementPath ? [...ctx.elementPath, index] : [index];
|
|
2673
2873
|
return renderNode(child, { ...ctx, elementPath: childPath });
|
|
2674
2874
|
}))).join("") : await renderNode(children, { ...ctx, elementPath: ctx.elementPath ? [...ctx.elementPath, 0] : [0] });
|
|
2675
|
-
return `<a href="${escapeHtml(String(href))}"${linkClassAttr}${attrs}>${childrenHTML}</a>`;
|
|
2875
|
+
return `<a href="${escapeHtml(String(href))}"${linkClassAttr}${attrs}${editorAttrs(ctx)}>${childrenHTML}</a>`;
|
|
2676
2876
|
}
|
|
2677
2877
|
async function renderHtmlElement(tag, propsWithStyleAndAttrs, children, ctx) {
|
|
2678
2878
|
let classValue = propsWithStyleAndAttrs.className ? String(propsWithStyleAndAttrs.className) : "";
|
|
@@ -2694,18 +2894,32 @@ async function renderHtmlElement(tag, propsWithStyleAndAttrs, children, ctx) {
|
|
|
2694
2894
|
const imageProps = ["src", "alt", "loading", "width", "height", "sizes", "srcset", "fetchpriority"];
|
|
2695
2895
|
const excludeProps = tag.toLowerCase() === "img" ? ["style", "className", ...imageProps] : ["style", "className"];
|
|
2696
2896
|
const attrs = buildAttributes(propsWithStyleAndAttrs, excludeProps);
|
|
2697
|
-
const
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2897
|
+
const isRawTextElement = tag.toLowerCase() === "style" || tag.toLowerCase() === "script";
|
|
2898
|
+
let childrenHTML;
|
|
2899
|
+
if (isRawTextElement) {
|
|
2900
|
+
const flatten = (node) => {
|
|
2901
|
+
if (node == null) return "";
|
|
2902
|
+
if (typeof node === "string") return node;
|
|
2903
|
+
if (typeof node === "number") return String(node);
|
|
2904
|
+
if (Array.isArray(node)) return node.map(flatten).join("");
|
|
2905
|
+
return "";
|
|
2906
|
+
};
|
|
2907
|
+
childrenHTML = flatten(children);
|
|
2908
|
+
} else {
|
|
2909
|
+
childrenHTML = Array.isArray(children) ? (await Promise.all(children.map((child, index) => {
|
|
2910
|
+
const childPath = ctx.elementPath ? [...ctx.elementPath, index] : [index];
|
|
2911
|
+
return renderNode(child, { ...ctx, elementPath: childPath });
|
|
2912
|
+
}))).join("") : await renderNode(children, { ...ctx, elementPath: ctx.elementPath ? [...ctx.elementPath, 0] : [0] });
|
|
2913
|
+
}
|
|
2914
|
+
const ea = editorAttrs(ctx);
|
|
2701
2915
|
const voidElements = ["img", "input", "br", "hr", "meta", "link", "area", "base", "col", "embed", "source", "track", "wbr"];
|
|
2702
2916
|
if (voidElements.includes(tag.toLowerCase())) {
|
|
2703
2917
|
if (tag.toLowerCase() === "img") {
|
|
2704
|
-
return renderImageElement(propsWithStyleAndAttrs, classAttr, styleAttr, attrs, ctx);
|
|
2918
|
+
return renderImageElement(propsWithStyleAndAttrs, classAttr, styleAttr, attrs + ea, ctx);
|
|
2705
2919
|
}
|
|
2706
|
-
return `<${tag}${classAttr}${styleAttr}${attrs} />`;
|
|
2920
|
+
return `<${tag}${classAttr}${styleAttr}${attrs}${ea} />`;
|
|
2707
2921
|
}
|
|
2708
|
-
return `<${tag}${classAttr}${styleAttr}${attrs}>${childrenHTML}</${tag}>`;
|
|
2922
|
+
return `<${tag}${classAttr}${styleAttr}${attrs}${ea}>${childrenHTML}</${tag}>`;
|
|
2709
2923
|
}
|
|
2710
2924
|
function renderImageElement(propsWithStyleAndAttrs, classAttr, styleAttr, attrs, ctx) {
|
|
2711
2925
|
const imgProps = propsWithStyleAndAttrs;
|
|
@@ -2793,9 +3007,13 @@ function renderLocaleList(node, ctx) {
|
|
|
2793
3007
|
localeIconMap.set(localeConfig.code, localeConfig.icon);
|
|
2794
3008
|
}
|
|
2795
3009
|
}
|
|
3010
|
+
const localeStyleOpts = {
|
|
3011
|
+
fluidActive: ctx.responsiveScales?.enabled === true && ctx.responsiveScales?.mode === "fluid",
|
|
3012
|
+
responsiveScales: ctx.responsiveScales
|
|
3013
|
+
};
|
|
2796
3014
|
let containerClasses = [];
|
|
2797
3015
|
if (nodeStyle) {
|
|
2798
|
-
containerClasses = responsiveStylesToClasses(nodeStyle);
|
|
3016
|
+
containerClasses = responsiveStylesToClasses(nodeStyle, localeStyleOpts);
|
|
2799
3017
|
}
|
|
2800
3018
|
const localeListInteractiveStyles = node.interactiveStyles;
|
|
2801
3019
|
const localeListGenerateElementClass = node.generateElementClass;
|
|
@@ -2812,21 +3030,21 @@ function renderLocaleList(node, ctx) {
|
|
|
2812
3030
|
const containerClassAttr = containerClasses.length > 0 ? ` class="${escapeHtml(containerClasses.join(" "))}"` : "";
|
|
2813
3031
|
let itemClasses = [];
|
|
2814
3032
|
if (node.itemStyle) {
|
|
2815
|
-
itemClasses = responsiveStylesToClasses(node.itemStyle);
|
|
3033
|
+
itemClasses = responsiveStylesToClasses(node.itemStyle, localeStyleOpts);
|
|
2816
3034
|
}
|
|
2817
3035
|
const itemClassAttr = itemClasses.length > 0 ? ` class="${escapeHtml(itemClasses.join(" "))}"` : "";
|
|
2818
3036
|
let activeItemClasses = [];
|
|
2819
3037
|
if (node.activeItemStyle) {
|
|
2820
|
-
activeItemClasses = responsiveStylesToClasses(node.activeItemStyle);
|
|
3038
|
+
activeItemClasses = responsiveStylesToClasses(node.activeItemStyle, localeStyleOpts);
|
|
2821
3039
|
}
|
|
2822
3040
|
let separatorClasses = [];
|
|
2823
3041
|
if (node.separatorStyle) {
|
|
2824
|
-
separatorClasses = responsiveStylesToClasses(node.separatorStyle);
|
|
3042
|
+
separatorClasses = responsiveStylesToClasses(node.separatorStyle, localeStyleOpts);
|
|
2825
3043
|
}
|
|
2826
3044
|
const separatorClassAttr = separatorClasses.length > 0 ? ` class="${escapeHtml(separatorClasses.join(" "))}"` : "";
|
|
2827
3045
|
let flagClasses = [];
|
|
2828
3046
|
if (node.flagStyle) {
|
|
2829
|
-
flagClasses = responsiveStylesToClasses(node.flagStyle);
|
|
3047
|
+
flagClasses = responsiveStylesToClasses(node.flagStyle, localeStyleOpts);
|
|
2830
3048
|
}
|
|
2831
3049
|
const flagClassAttr = flagClasses.length > 0 ? ` class="${escapeHtml(flagClasses.join(" "))}"` : "";
|
|
2832
3050
|
const currentItemClasses = [...itemClasses, ...activeItemClasses];
|
|
@@ -2859,9 +3077,9 @@ function renderLocaleList(node, ctx) {
|
|
|
2859
3077
|
links.push(`<a href="${escapeHtml(link.path)}"${hreflangAttr}${currentAttr} data-locale="${escapeHtml(link.locale)}"${classAttrForLink}>${linkContent}</a>`);
|
|
2860
3078
|
}
|
|
2861
3079
|
const linksHTML = showSeparator ? links.join(`<span${separatorClassAttr}></span>`) : links.join("");
|
|
2862
|
-
const nodeAttributes = extractAttributesFromNode(node);
|
|
3080
|
+
const nodeAttributes = resolveI18nAttrs(extractAttributesFromNode(node), locale, i18nConfig);
|
|
2863
3081
|
const attrsStr = buildAttributes(nodeAttributes);
|
|
2864
|
-
const localeListResult = `<div data-locale-list="true"${containerClassAttr}${localeListStyleAttr}${attrsStr}>${linksHTML}</div>`;
|
|
3082
|
+
const localeListResult = `<div data-locale-list="true"${containerClassAttr}${localeListStyleAttr}${attrsStr}${editorAttrs(ctx)}>${linksHTML}</div>`;
|
|
2865
3083
|
if (ctx.ssrFallbackCollector && ctx.elementPath) {
|
|
2866
3084
|
ctx.ssrFallbackCollector.set(ctx.elementPath.join("."), localeListResult);
|
|
2867
3085
|
}
|
|
@@ -2869,7 +3087,7 @@ function renderLocaleList(node, ctx) {
|
|
|
2869
3087
|
}
|
|
2870
3088
|
return '<div data-locale-list="true"></div>';
|
|
2871
3089
|
}
|
|
2872
|
-
async function renderPageSSR(pageData, globalComponents = {}, pagePath = "/", baseUrl = "", locale, i18nConfig, slugMappings, cmsContext, cmsService, isProductionBuild) {
|
|
3090
|
+
async function renderPageSSR(pageData, globalComponents = {}, pagePath = "/", baseUrl = "", locale, i18nConfig, slugMappings, cmsContext, cmsService, isProductionBuild, injectEditorAttrs) {
|
|
2873
3091
|
const rootNode = pageData?.root || void 0;
|
|
2874
3092
|
if (!rootNode) {
|
|
2875
3093
|
throw new Error("Page data must have a root node");
|
|
@@ -2893,7 +3111,7 @@ async function renderPageSSR(pageData, globalComponents = {}, pagePath = "/", ba
|
|
|
2893
3111
|
}
|
|
2894
3112
|
}
|
|
2895
3113
|
const pageComponents = pageData?.components || {};
|
|
2896
|
-
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() };
|
|
3114
|
+
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() };
|
|
2897
3115
|
const javascript = await collectComponentJavaScript(globalComponents, pageComponents);
|
|
2898
3116
|
const componentCSS = collectComponentCSS(globalComponents, pageComponents);
|
|
2899
3117
|
const fullUrl = baseUrl ? `${baseUrl}${pagePath}` : pagePath;
|
|
@@ -3217,7 +3435,7 @@ function generateImagePreloadTags(preloadImages) {
|
|
|
3217
3435
|
}
|
|
3218
3436
|
function minifyCSS(code) {
|
|
3219
3437
|
if (!code.trim()) return code;
|
|
3220
|
-
return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s*([{}
|
|
3438
|
+
return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s*([{};:,>~])\s*/g, "$1").replace(/\s+/g, " ").replace(/\{\s+/g, "{").replace(/\s+\}/g, "}").replace(/;}/g, "}").trim();
|
|
3221
3439
|
}
|
|
3222
3440
|
async function generateSSRHTML(pageDataOrOptions, globalComponents = {}, pagePath = "/", baseUrl = "", useBuiltBundle = false, locale, slugMappings, cmsContext, cmsService, externalScriptPath) {
|
|
3223
3441
|
let options;
|
|
@@ -3254,11 +3472,12 @@ async function generateSSRHTML(pageDataOrOptions, globalComponents = {}, pagePat
|
|
|
3254
3472
|
pageCustomCode,
|
|
3255
3473
|
clientDataCollections,
|
|
3256
3474
|
injectLiveReload = false,
|
|
3475
|
+
injectEditorAttrs = false,
|
|
3257
3476
|
isEditor = false,
|
|
3258
3477
|
isProductionBuild = false,
|
|
3259
3478
|
serverPort
|
|
3260
3479
|
} = options;
|
|
3261
|
-
const rendered = await renderPageSSR(pageData, components, path2, base, loc, void 0, slugs, cms, cmsServ, isProductionBuild);
|
|
3480
|
+
const rendered = await renderPageSSR(pageData, components, path2, base, loc, void 0, slugs, cms, cmsServ, isProductionBuild, injectEditorAttrs);
|
|
3262
3481
|
let finalClientDataCollections = clientDataCollections;
|
|
3263
3482
|
if (rendered.neededCollections.size > 0 && cmsServ) {
|
|
3264
3483
|
finalClientDataCollections = clientDataCollections ? new Map(clientDataCollections) : /* @__PURE__ */ new Map();
|
|
@@ -3434,13 +3653,15 @@ picture {
|
|
|
3434
3653
|
const cmsInlineScript = cmsTemplatePath && cms && (!useBundled || injectLiveReload) ? `<script>window.__MENO_CMS__=${JSON.stringify({ item: cms.cms, templatePath: cmsTemplatePath })}</script>
|
|
3435
3654
|
` : "";
|
|
3436
3655
|
const clientDataScripts = finalClientDataCollections && finalClientDataCollections.size > 0 ? generateAllInlineDataScripts(finalClientDataCollections) + "\n " : "";
|
|
3437
|
-
const
|
|
3656
|
+
const hasDarkFavicon = !!(iconsConfig.favicon && iconsConfig.faviconDark);
|
|
3657
|
+
const faviconTag = iconsConfig.favicon ? `<link rel="icon" href="${escapeHtml(iconsConfig.favicon)}"${hasDarkFavicon ? ' media="(prefers-color-scheme: light)"' : ""} />` : "";
|
|
3658
|
+
const faviconDarkTag = iconsConfig.faviconDark ? `<link rel="icon" href="${escapeHtml(iconsConfig.faviconDark)}" media="(prefers-color-scheme: dark)" />` : "";
|
|
3438
3659
|
const appleTouchIconTag = iconsConfig.appleTouchIcon ? `<link rel="apple-touch-icon" href="${escapeHtml(iconsConfig.appleTouchIcon)}" />` : "";
|
|
3439
|
-
const iconTags = [faviconTag, appleTouchIconTag].filter(Boolean).join("\n ");
|
|
3660
|
+
const iconTags = [faviconTag, faviconDarkTag, appleTouchIconTag].filter(Boolean).join("\n ");
|
|
3440
3661
|
const scriptPreloadTag = extScriptPath ? `<link rel="preload" href="${extScriptPath}" as="script">` : "";
|
|
3441
3662
|
const imagePreloadTags = generateImagePreloadTags(rendered.preloadImages);
|
|
3442
3663
|
const wsUrl = serverPort ? `'ws://localhost:${serverPort}/hmr'` : `location.origin.replace('http','ws')+'/hmr'`;
|
|
3443
|
-
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')
|
|
3664
|
+
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>` : "";
|
|
3444
3665
|
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>` : "";
|
|
3445
3666
|
const styleContent = useBundled ? finalCSS : `
|
|
3446
3667
|
${combinedCSS.split("\n").join("\n ")}
|
|
@@ -5622,6 +5843,7 @@ autoInit();
|
|
|
5622
5843
|
// lib/server/providers/fileSystemCMSProvider.ts
|
|
5623
5844
|
import { existsSync as existsSync2, readdirSync as readdirSync2, mkdirSync } from "fs";
|
|
5624
5845
|
import { join as join2 } from "path";
|
|
5846
|
+
var DRAFT_FILE_SUFFIX = `${CMS_DRAFT_SUFFIX}.json`;
|
|
5625
5847
|
async function loadJSONFile2(filePath) {
|
|
5626
5848
|
try {
|
|
5627
5849
|
if (await fileExists(filePath)) {
|
|
@@ -5633,12 +5855,24 @@ async function loadJSONFile2(filePath) {
|
|
|
5633
5855
|
return null;
|
|
5634
5856
|
}
|
|
5635
5857
|
}
|
|
5636
|
-
function normalizeItem(content, filename) {
|
|
5637
|
-
|
|
5638
|
-
|
|
5858
|
+
function normalizeItem(content, filename, isDraft = false) {
|
|
5859
|
+
const raw = content;
|
|
5860
|
+
const base = {
|
|
5861
|
+
...raw,
|
|
5862
|
+
_id: raw._id || filename,
|
|
5639
5863
|
_slug: filename,
|
|
5640
5864
|
_filename: filename
|
|
5641
5865
|
};
|
|
5866
|
+
if (isDraft) base._isDraft = true;
|
|
5867
|
+
return base;
|
|
5868
|
+
}
|
|
5869
|
+
function stripTransient(item) {
|
|
5870
|
+
const { _slug, _isDraft, _hasDraft, _url, _filename, ...rest } = item;
|
|
5871
|
+
const out = rest;
|
|
5872
|
+
if (typeof _filename === "string" && _filename !== out._id) {
|
|
5873
|
+
out._filename = _filename;
|
|
5874
|
+
}
|
|
5875
|
+
return out;
|
|
5642
5876
|
}
|
|
5643
5877
|
var FileSystemCMSProvider = class {
|
|
5644
5878
|
constructor(templatesDir, cmsDir) {
|
|
@@ -5656,13 +5890,16 @@ var FileSystemCMSProvider = class {
|
|
|
5656
5890
|
}
|
|
5657
5891
|
}
|
|
5658
5892
|
/**
|
|
5659
|
-
* Validate filename to prevent path traversal attacks
|
|
5893
|
+
* Validate filename to prevent path traversal attacks and reserved-suffix collisions.
|
|
5660
5894
|
* @throws Error if filename is invalid
|
|
5661
5895
|
*/
|
|
5662
5896
|
validateFilename(filename) {
|
|
5663
5897
|
if (!isSafePathSegment(filename)) {
|
|
5664
5898
|
throw new Error(`Invalid filename: "${filename}". Filenames cannot contain path separators or traversal sequences.`);
|
|
5665
5899
|
}
|
|
5900
|
+
if (isReservedDraftFilename(filename)) {
|
|
5901
|
+
throw new Error(`Invalid filename: "${filename}". The "${CMS_DRAFT_SUFFIX}" suffix is reserved for draft files.`);
|
|
5902
|
+
}
|
|
5666
5903
|
}
|
|
5667
5904
|
/**
|
|
5668
5905
|
* Load all CMS schemas from page files with source: 'cms' in templates/
|
|
@@ -5709,7 +5946,7 @@ var FileSystemCMSProvider = class {
|
|
|
5709
5946
|
return [];
|
|
5710
5947
|
}
|
|
5711
5948
|
const files = readdirSync2(collectionDir);
|
|
5712
|
-
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
5949
|
+
const jsonFiles = files.filter((f) => f.endsWith(".json") && !f.endsWith(DRAFT_FILE_SUFFIX));
|
|
5713
5950
|
const results = await Promise.all(
|
|
5714
5951
|
jsonFiles.map(async (file) => {
|
|
5715
5952
|
const filePath = join2(collectionDir, file);
|
|
@@ -5766,8 +6003,10 @@ var FileSystemCMSProvider = class {
|
|
|
5766
6003
|
return items.find((item) => item._id === id) || null;
|
|
5767
6004
|
}
|
|
5768
6005
|
/**
|
|
5769
|
-
* Save item to file system
|
|
5770
|
-
*
|
|
6006
|
+
* Save item to file system.
|
|
6007
|
+
* The on-disk filename is derived from `_filename` (legacy alias) when set,
|
|
6008
|
+
* otherwise from `_id` (canonical identifier; equals the filename for new
|
|
6009
|
+
* items). Falls back to the slugField value as a last resort.
|
|
5771
6010
|
*/
|
|
5772
6011
|
async saveItem(collection, item) {
|
|
5773
6012
|
this.validateCollection(collection);
|
|
@@ -5783,32 +6022,193 @@ var FileSystemCMSProvider = class {
|
|
|
5783
6022
|
} else {
|
|
5784
6023
|
const slugField = schemaInfo.schema.slugField;
|
|
5785
6024
|
const slugValue = item[slugField];
|
|
5786
|
-
|
|
6025
|
+
if (typeof slugValue === "string" && slugValue) {
|
|
6026
|
+
filename = slugValue;
|
|
6027
|
+
} else if (typeof item._id === "string" && item._id) {
|
|
6028
|
+
filename = item._id;
|
|
6029
|
+
} else {
|
|
6030
|
+
filename = String(slugValue);
|
|
6031
|
+
}
|
|
5787
6032
|
}
|
|
5788
6033
|
if (!filename || filename === "[object Object]") {
|
|
5789
|
-
throw new Error("
|
|
6034
|
+
throw new Error("Cannot derive filename: item is missing _id, _filename, and a usable slug-field value.");
|
|
5790
6035
|
}
|
|
5791
6036
|
this.validateFilename(filename);
|
|
5792
6037
|
const collectionDir = join2(this.cmsDir, collection);
|
|
5793
6038
|
if (!existsSync2(collectionDir)) {
|
|
5794
6039
|
mkdirSync(collectionDir, { recursive: true });
|
|
5795
6040
|
}
|
|
5796
|
-
const
|
|
6041
|
+
const itemData = stripTransient(item);
|
|
5797
6042
|
const filePath = join2(collectionDir, `${filename}.json`);
|
|
5798
6043
|
await writeFile2(filePath, JSON.stringify(itemData, null, 2), "utf-8");
|
|
5799
6044
|
}
|
|
5800
6045
|
/**
|
|
5801
|
-
* Delete item by filename
|
|
6046
|
+
* Delete item by filename. Removes the published file AND any draft sibling.
|
|
5802
6047
|
*/
|
|
5803
6048
|
async deleteItem(collection, filename) {
|
|
5804
6049
|
this.validateCollection(collection);
|
|
5805
6050
|
this.validateFilename(filename);
|
|
5806
6051
|
const { unlink } = await import("fs/promises");
|
|
5807
|
-
const
|
|
6052
|
+
const publishedPath = join2(this.cmsDir, collection, `${filename}.json`);
|
|
6053
|
+
if (existsSync2(publishedPath)) {
|
|
6054
|
+
await unlink(publishedPath);
|
|
6055
|
+
}
|
|
6056
|
+
const draftPath = this.draftPath(collection, filename);
|
|
6057
|
+
if (existsSync2(draftPath)) {
|
|
6058
|
+
await unlink(draftPath);
|
|
6059
|
+
}
|
|
6060
|
+
}
|
|
6061
|
+
// ---- Draft helpers ----------------------------------------------------
|
|
6062
|
+
draftPath(collection, filename) {
|
|
6063
|
+
return join2(this.cmsDir, collection, `${filename}${DRAFT_FILE_SUFFIX}`);
|
|
6064
|
+
}
|
|
6065
|
+
/**
|
|
6066
|
+
* Get the draft version of an item, or null if no draft file exists.
|
|
6067
|
+
* Drafts skip strict validation — they may be partial / WIP.
|
|
6068
|
+
*/
|
|
6069
|
+
async getDraft(collection, filename) {
|
|
6070
|
+
this.validateCollection(collection);
|
|
6071
|
+
this.validateFilename(filename);
|
|
6072
|
+
const filePath = this.draftPath(collection, filename);
|
|
6073
|
+
const content = await loadJSONFile2(filePath);
|
|
6074
|
+
if (!content || typeof content !== "object") {
|
|
6075
|
+
return null;
|
|
6076
|
+
}
|
|
6077
|
+
return normalizeItem(
|
|
6078
|
+
content,
|
|
6079
|
+
filename,
|
|
6080
|
+
/*isDraft*/
|
|
6081
|
+
true
|
|
6082
|
+
);
|
|
6083
|
+
}
|
|
6084
|
+
/**
|
|
6085
|
+
* List all drafts in a collection. Used by the Studio item list to mark
|
|
6086
|
+
* items that have an outstanding draft sibling (or are draft-only).
|
|
6087
|
+
*/
|
|
6088
|
+
async getAllDrafts(collection) {
|
|
6089
|
+
this.validateCollection(collection);
|
|
6090
|
+
const collectionDir = join2(this.cmsDir, collection);
|
|
6091
|
+
if (!existsSync2(collectionDir)) return [];
|
|
6092
|
+
const files = readdirSync2(collectionDir).filter((f) => f.endsWith(DRAFT_FILE_SUFFIX));
|
|
6093
|
+
const results = await Promise.all(
|
|
6094
|
+
files.map(async (file) => {
|
|
6095
|
+
const filePath = join2(collectionDir, file);
|
|
6096
|
+
const content = await loadJSONFile2(filePath);
|
|
6097
|
+
return { file, content };
|
|
6098
|
+
})
|
|
6099
|
+
);
|
|
6100
|
+
const drafts = [];
|
|
6101
|
+
for (const { file, content } of results) {
|
|
6102
|
+
if (content && typeof content === "object") {
|
|
6103
|
+
const filename = file.slice(0, -DRAFT_FILE_SUFFIX.length);
|
|
6104
|
+
drafts.push(normalizeItem(
|
|
6105
|
+
content,
|
|
6106
|
+
filename,
|
|
6107
|
+
/*isDraft*/
|
|
6108
|
+
true
|
|
6109
|
+
));
|
|
6110
|
+
}
|
|
6111
|
+
}
|
|
6112
|
+
return drafts;
|
|
6113
|
+
}
|
|
6114
|
+
async hasDraft(collection, filename) {
|
|
6115
|
+
this.validateCollection(collection);
|
|
6116
|
+
this.validateFilename(filename);
|
|
6117
|
+
return existsSync2(this.draftPath(collection, filename));
|
|
6118
|
+
}
|
|
6119
|
+
/**
|
|
6120
|
+
* Save the draft version of an item. Loose validation — drafts may have
|
|
6121
|
+
* missing required fields or partial data. Strict validation only runs at
|
|
6122
|
+
* publish time. The on-disk filename is derived the same way as for
|
|
6123
|
+
* `saveItem` (prefer `_filename`, fall back to `_id`).
|
|
6124
|
+
*/
|
|
6125
|
+
async saveDraft(collection, item) {
|
|
6126
|
+
this.validateCollection(collection);
|
|
6127
|
+
const { writeFile: writeFile2 } = await import("fs/promises");
|
|
6128
|
+
const schemas = await this.getAllSchemas();
|
|
6129
|
+
const schemaInfo = schemas.get(collection);
|
|
6130
|
+
if (!schemaInfo) {
|
|
6131
|
+
throw new Error(`Unknown collection: ${collection}`);
|
|
6132
|
+
}
|
|
6133
|
+
let filename;
|
|
6134
|
+
if (item._filename) {
|
|
6135
|
+
filename = item._filename;
|
|
6136
|
+
} else {
|
|
6137
|
+
const slugField = schemaInfo.schema.slugField;
|
|
6138
|
+
const slugValue = item[slugField];
|
|
6139
|
+
if (typeof slugValue === "string" && slugValue) {
|
|
6140
|
+
filename = slugValue;
|
|
6141
|
+
} else if (typeof item._id === "string" && item._id) {
|
|
6142
|
+
filename = item._id;
|
|
6143
|
+
} else {
|
|
6144
|
+
filename = String(slugValue);
|
|
6145
|
+
}
|
|
6146
|
+
}
|
|
6147
|
+
if (!filename || filename === "[object Object]") {
|
|
6148
|
+
throw new Error("Cannot derive draft filename: item is missing _id, _filename, and a usable slug-field value.");
|
|
6149
|
+
}
|
|
6150
|
+
this.validateFilename(filename);
|
|
6151
|
+
const collectionDir = join2(this.cmsDir, collection);
|
|
6152
|
+
if (!existsSync2(collectionDir)) {
|
|
6153
|
+
mkdirSync(collectionDir, { recursive: true });
|
|
6154
|
+
}
|
|
6155
|
+
const itemData = stripTransient(item);
|
|
6156
|
+
const validation = validateCMSDraftItem(itemData);
|
|
6157
|
+
if (!validation.valid) {
|
|
6158
|
+
const messages = validation.errors.map((e) => `${e.path}: ${e.message}`).join(", ");
|
|
6159
|
+
throw new Error(`Invalid draft: ${messages}`);
|
|
6160
|
+
}
|
|
6161
|
+
const filePath = this.draftPath(collection, filename);
|
|
6162
|
+
await writeFile2(filePath, JSON.stringify(itemData, null, 2), "utf-8");
|
|
6163
|
+
}
|
|
6164
|
+
/**
|
|
6165
|
+
* Discard the draft version of an item. No-op if no draft exists.
|
|
6166
|
+
*/
|
|
6167
|
+
async discardDraft(collection, filename) {
|
|
6168
|
+
this.validateCollection(collection);
|
|
6169
|
+
this.validateFilename(filename);
|
|
6170
|
+
const { unlink } = await import("fs/promises");
|
|
6171
|
+
const filePath = this.draftPath(collection, filename);
|
|
5808
6172
|
if (existsSync2(filePath)) {
|
|
5809
6173
|
await unlink(filePath);
|
|
5810
6174
|
}
|
|
5811
6175
|
}
|
|
6176
|
+
/**
|
|
6177
|
+
* Promote a draft to published. Reads `{filename}.draft.json`, writes the
|
|
6178
|
+
* content to `{filename}.json`, then unlinks the draft. The published write
|
|
6179
|
+
* happens first so a crash mid-operation leaves a valid published file plus
|
|
6180
|
+
* an orphan draft (recoverable via the editor's Discard button) — never a
|
|
6181
|
+
* gap with no published content.
|
|
6182
|
+
*
|
|
6183
|
+
* Throws if no draft exists.
|
|
6184
|
+
*/
|
|
6185
|
+
async publishDraft(collection, filename) {
|
|
6186
|
+
this.validateCollection(collection);
|
|
6187
|
+
this.validateFilename(filename);
|
|
6188
|
+
const { writeFile: writeFile2, unlink } = await import("fs/promises");
|
|
6189
|
+
const draftFilePath = this.draftPath(collection, filename);
|
|
6190
|
+
const content = await loadJSONFile2(draftFilePath);
|
|
6191
|
+
if (!content || typeof content !== "object") {
|
|
6192
|
+
throw new Error(`No draft to publish for ${collection}/${filename}`);
|
|
6193
|
+
}
|
|
6194
|
+
const item = normalizeItem(content, filename);
|
|
6195
|
+
const validation = validateCMSItem(item);
|
|
6196
|
+
if (!validation.valid) {
|
|
6197
|
+
const messages = validation.errors.map((e) => `${e.path}: ${e.message}`).join(", ");
|
|
6198
|
+
throw new Error(`Cannot publish invalid draft: ${messages}`);
|
|
6199
|
+
}
|
|
6200
|
+
const collectionDir = join2(this.cmsDir, collection);
|
|
6201
|
+
if (!existsSync2(collectionDir)) {
|
|
6202
|
+
mkdirSync(collectionDir, { recursive: true });
|
|
6203
|
+
}
|
|
6204
|
+
const itemData = stripTransient(validation.data);
|
|
6205
|
+
const publishedPath = join2(collectionDir, `${filename}.json`);
|
|
6206
|
+
await writeFile2(publishedPath, JSON.stringify(itemData, null, 2), "utf-8");
|
|
6207
|
+
if (existsSync2(draftFilePath)) {
|
|
6208
|
+
await unlink(draftFilePath);
|
|
6209
|
+
}
|
|
6210
|
+
return normalizeItem(itemData, filename);
|
|
6211
|
+
}
|
|
5812
6212
|
/**
|
|
5813
6213
|
* Clear schema cache (useful when pages are modified)
|
|
5814
6214
|
*/
|
|
@@ -5932,4 +6332,4 @@ export {
|
|
|
5932
6332
|
FileSystemCMSProvider,
|
|
5933
6333
|
migrateTemplatesDirectory
|
|
5934
6334
|
};
|
|
5935
|
-
//# sourceMappingURL=chunk-
|
|
6335
|
+
//# sourceMappingURL=chunk-CVLFID6V.js.map
|