meno-core 1.0.49 → 1.0.51

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.
Files changed (66) hide show
  1. package/build-astro.ts +6 -2
  2. package/build-static.ts +8 -1
  3. package/dist/bin/cli.js +1 -1
  4. package/dist/build-static.js +5 -5
  5. package/dist/chunks/{chunk-KPU2XHOS.js → chunk-2MHDV5BF.js} +11 -1
  6. package/dist/chunks/chunk-2MHDV5BF.js.map +7 -0
  7. package/dist/chunks/{chunk-JER5NQVM.js → chunk-3KJ6SJZC.js} +5 -5
  8. package/dist/chunks/{chunk-JER5NQVM.js.map → chunk-3KJ6SJZC.js.map} +2 -2
  9. package/dist/chunks/{chunk-S2CX6HFM.js → chunk-7NIC4I3V.js} +42 -20
  10. package/dist/chunks/chunk-7NIC4I3V.js.map +7 -0
  11. package/dist/chunks/{chunk-EQYDSPBB.js → chunk-DM54NPEC.js} +114 -31
  12. package/dist/chunks/chunk-DM54NPEC.js.map +7 -0
  13. package/dist/chunks/{chunk-LKAGAQ3M.js → chunk-EDQSMAMP.js} +13 -2
  14. package/dist/chunks/{chunk-LKAGAQ3M.js.map → chunk-EDQSMAMP.js.map} +2 -2
  15. package/dist/chunks/{chunk-4OFZP5NQ.js → chunk-HNLUO36W.js} +15 -4
  16. package/dist/chunks/chunk-HNLUO36W.js.map +7 -0
  17. package/dist/chunks/{chunk-6IVUG7FY.js → chunk-LPVETICS.js} +19 -2
  18. package/dist/chunks/{chunk-6IVUG7FY.js.map → chunk-LPVETICS.js.map} +2 -2
  19. package/dist/chunks/{chunk-CHD5UCFF.js → chunk-V7CD7V7W.js} +149 -46
  20. package/dist/chunks/chunk-V7CD7V7W.js.map +7 -0
  21. package/dist/chunks/{configService-CCA6AIDI.js → configService-R3OGU2UD.js} +2 -2
  22. package/dist/entries/server-router.js +5 -5
  23. package/dist/lib/client/index.js +41 -15
  24. package/dist/lib/client/index.js.map +3 -3
  25. package/dist/lib/server/index.js +12 -10
  26. package/dist/lib/server/index.js.map +2 -2
  27. package/dist/lib/shared/index.js +2 -2
  28. package/lib/client/core/ComponentBuilder.test.ts +34 -0
  29. package/lib/client/core/ComponentBuilder.ts +25 -3
  30. package/lib/client/core/builders/embedBuilder.ts +13 -5
  31. package/lib/client/core/builders/linkNodeBuilder.ts +13 -5
  32. package/lib/client/core/builders/localeListBuilder.ts +13 -5
  33. package/lib/client/templateEngine.ts +24 -0
  34. package/lib/server/fileWatcher.test.ts +134 -0
  35. package/lib/server/fileWatcher.ts +100 -32
  36. package/lib/server/jsonLoader.ts +1 -0
  37. package/lib/server/providers/fileSystemCMSProvider.ts +46 -14
  38. package/lib/server/routes/pages.ts +37 -2
  39. package/lib/server/services/cmsService.ts +21 -0
  40. package/lib/server/services/configService.ts +21 -0
  41. package/lib/server/services/fileWatcherService.ts +17 -0
  42. package/lib/server/ssr/buildErrorOverlay.ts +22 -4
  43. package/lib/server/ssr/errorOverlay.ts +11 -3
  44. package/lib/server/ssr/htmlGenerator.nonce.test.ts +165 -0
  45. package/lib/server/ssr/htmlGenerator.ts +36 -9
  46. package/lib/server/ssr/liveReloadIntegration.test.ts +3 -1
  47. package/lib/server/ssr/metaTagGenerator.ts +35 -5
  48. package/lib/server/ssr/ssrRenderer.test.ts +258 -0
  49. package/lib/server/ssr/ssrRenderer.ts +47 -5
  50. package/lib/server/ssrRenderer.test.ts +87 -2
  51. package/lib/server/webflow/buildWebflow.ts +1 -1
  52. package/lib/server/websocketManager.test.ts +61 -6
  53. package/lib/server/websocketManager.ts +25 -1
  54. package/lib/shared/cssProperties.test.ts +28 -0
  55. package/lib/shared/cssProperties.ts +27 -1
  56. package/lib/shared/types/api.ts +10 -1
  57. package/lib/shared/types/cms.ts +18 -9
  58. package/lib/shared/validation/schemas.test.ts +93 -0
  59. package/lib/shared/validation/schemas.ts +56 -15
  60. package/package.json +1 -1
  61. package/dist/chunks/chunk-4OFZP5NQ.js.map +0 -7
  62. package/dist/chunks/chunk-CHD5UCFF.js.map +0 -7
  63. package/dist/chunks/chunk-EQYDSPBB.js.map +0 -7
  64. package/dist/chunks/chunk-KPU2XHOS.js.map +0 -7
  65. package/dist/chunks/chunk-S2CX6HFM.js.map +0 -7
  66. /package/dist/chunks/{configService-CCA6AIDI.js.map → configService-R3OGU2UD.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  configService
3
- } from "./chunk-KPU2XHOS.js";
3
+ } from "./chunk-2MHDV5BF.js";
4
4
  import {
5
5
  projectPaths,
6
6
  resolveProjectPath,
@@ -26,7 +26,7 @@ import {
26
26
  processStructure,
27
27
  resolveHtmlMapping,
28
28
  skipEmptyTemplateAttributes
29
- } from "./chunk-LKAGAQ3M.js";
29
+ } from "./chunk-EDQSMAMP.js";
30
30
  import {
31
31
  DEFAULT_PREFETCH_CONFIG,
32
32
  SSRRegistry,
@@ -66,7 +66,7 @@ import {
66
66
  validateCMSDraftItem,
67
67
  validateCMSItem,
68
68
  validateComponentDefinition
69
- } from "./chunk-S2CX6HFM.js";
69
+ } from "./chunk-7NIC4I3V.js";
70
70
  import {
71
71
  DEFAULT_BREAKPOINTS,
72
72
  DEFAULT_FLUID_RANGE,
@@ -1070,6 +1070,25 @@ var CMSService = class {
1070
1070
  await this.provider.saveDraft(collection, item);
1071
1071
  this.itemsCache.delete(collection);
1072
1072
  }
1073
+ /**
1074
+ * Save a published item. Wraps the provider so the items cache is invalidated
1075
+ * immediately — without this, the editor's post-save refetch could hit a
1076
+ * stale snapshot inside the 5s TTL window and overwrite an optimistic preview.
1077
+ */
1078
+ async saveItem(collection, item) {
1079
+ if (!this.provider) throw new Error("CMS provider not configured");
1080
+ await this.provider.saveItem(collection, item);
1081
+ this.itemsCache.delete(collection);
1082
+ }
1083
+ /**
1084
+ * Delete an item (and its draft sibling). Cache invalidation, same rationale
1085
+ * as `saveItem`.
1086
+ */
1087
+ async deleteItem(collection, filename) {
1088
+ if (!this.provider) throw new Error("CMS provider not configured");
1089
+ await this.provider.deleteItem(collection, filename);
1090
+ this.itemsCache.delete(collection);
1091
+ }
1073
1092
  async discardDraft(collection, filename) {
1074
1093
  if (!this.provider) throw new Error("CMS provider not configured");
1075
1094
  await this.provider.discardDraft(collection, filename);
@@ -1622,7 +1641,7 @@ function extractPageMeta(pageData) {
1622
1641
  }
1623
1642
  return meta;
1624
1643
  }
1625
- function generateMetaTags(meta, url = "", locale = "en", config = DEFAULT_I18N_CONFIG, hreflangOptions) {
1644
+ function generateMetaTags(meta, url = "", locale = "en", config = DEFAULT_I18N_CONFIG, options) {
1626
1645
  const tags = [];
1627
1646
  const resolve = (value) => {
1628
1647
  const resolved = resolveI18nValue(value, locale, config);
@@ -1659,11 +1678,28 @@ function generateMetaTags(meta, url = "", locale = "en", config = DEFAULT_I18N_C
1659
1678
  if (url) {
1660
1679
  tags.push(`<meta property="og:url" content="${escapeHtml(url)}" />`);
1661
1680
  }
1681
+ const hasAnyMeta = title || description || ogImage || ogTitle || ogDescription;
1682
+ if (hasAnyMeta) {
1683
+ const cardType = ogImage ? "summary_large_image" : "summary";
1684
+ tags.push(`<meta name="twitter:card" content="${cardType}" />`);
1685
+ }
1686
+ if (ogTitle) {
1687
+ tags.push(`<meta name="twitter:title" content="${escapeHtml(ogTitle)}" />`);
1688
+ }
1689
+ if (ogDescription) {
1690
+ tags.push(`<meta name="twitter:description" content="${escapeHtml(ogDescription)}" />`);
1691
+ }
1692
+ const rawHandle = options?.social?.twitterHandle?.trim();
1693
+ if (rawHandle) {
1694
+ const handle = rawHandle.startsWith("@") ? rawHandle : `@${rawHandle}`;
1695
+ tags.push(`<meta name="twitter:site" content="${escapeHtml(handle)}" />`);
1696
+ tags.push(`<meta name="twitter:creator" content="${escapeHtml(handle)}" />`);
1697
+ }
1662
1698
  if (url) {
1663
1699
  tags.push(`<link rel="canonical" href="${escapeHtml(url)}" />`);
1664
1700
  }
1665
- if (hreflangOptions?.slugMappings && hreflangOptions.slugMappings.length > 0 && config.locales.length > 1) {
1666
- const { slugMappings, pagePath = "/", baseUrl = "" } = hreflangOptions;
1701
+ if (options?.slugMappings && options.slugMappings.length > 0 && config.locales.length > 1) {
1702
+ const { slugMappings, pagePath = "/", baseUrl = "" } = options;
1667
1703
  const slugIndex = buildSlugIndex(slugMappings);
1668
1704
  const localeLinks = getLocaleLinks(pagePath, locale, config, slugIndex);
1669
1705
  for (const link of localeLinks) {
@@ -2038,6 +2074,18 @@ function getDOMPurify() {
2038
2074
  }
2039
2075
  return _DOMPurify;
2040
2076
  }
2077
+ function resolveI18nAttrs(attrs, locale, i18nConfig) {
2078
+ let mutated = null;
2079
+ const config = i18nConfig ?? DEFAULT_I18N_CONFIG;
2080
+ const effectiveLocale = locale || config.defaultLocale;
2081
+ for (const [key, value] of Object.entries(attrs)) {
2082
+ if (isI18nValue(value)) {
2083
+ mutated = mutated ?? { ...attrs };
2084
+ mutated[key] = resolveI18nValue(value, effectiveLocale, config);
2085
+ }
2086
+ }
2087
+ return mutated ?? attrs;
2088
+ }
2041
2089
  function getTemplateContext(ctx) {
2042
2090
  return ctx.templateContext || null;
2043
2091
  }
@@ -2520,6 +2568,15 @@ async function renderNode(node, ctx) {
2520
2568
  }))).join("");
2521
2569
  }
2522
2570
  if (typeof node !== "object") return "";
2571
+ if (isI18nValue(node)) {
2572
+ const i18nResolveConfig = i18nConfig ?? DEFAULT_I18N_CONFIG;
2573
+ const i18nEffectiveLocale = locale || i18nResolveConfig.defaultLocale;
2574
+ const resolved = resolveI18nValue(node, i18nEffectiveLocale, i18nResolveConfig);
2575
+ return renderNode(
2576
+ resolved,
2577
+ ctx
2578
+ );
2579
+ }
2523
2580
  if (!evaluateIfCondition(node, ctx)) {
2524
2581
  return "";
2525
2582
  }
@@ -2559,7 +2616,7 @@ async function renderNode(node, ctx) {
2559
2616
  KEEP_CONTENT: true
2560
2617
  }) : htmlContent;
2561
2618
  const optimizedHtml = ctx.imageMetadataMap ? rewriteRichTextImages(sanitizedHtml, ctx.imageMetadataMap, ctx.imageFormat) : sanitizedHtml;
2562
- const nodeAttributes2 = extractAttributesFromNode(node);
2619
+ const nodeAttributes2 = resolveI18nAttrs(extractAttributesFromNode(node), locale, i18nConfig);
2563
2620
  const classNames = ["oem"];
2564
2621
  if (nodeStyle) {
2565
2622
  const utilityClasses2 = processStyleToClasses(nodeStyle, ctx);
@@ -2618,7 +2675,7 @@ async function renderNode(node, ctx) {
2618
2675
  href = processCMSTemplate(href, ctx.cmsContext.cms, locale, i18nConfig);
2619
2676
  }
2620
2677
  href = localizeHref(href, ctx);
2621
- const nodeAttributes2 = extractAttributesFromNode(node);
2678
+ const nodeAttributes2 = resolveI18nAttrs(extractAttributesFromNode(node), locale, i18nConfig);
2622
2679
  const classNames = ["olink"];
2623
2680
  if (nodeStyle) {
2624
2681
  const utilityClasses2 = processStyleToClasses(nodeStyle, ctx);
@@ -2670,7 +2727,7 @@ async function renderNode(node, ctx) {
2670
2727
  if (!ctx.templateMode && templateCtx && Object.keys(nodeProps).length > 0) {
2671
2728
  nodeProps = processItemPropsTemplate(nodeProps, templateCtx, i18nResolver);
2672
2729
  }
2673
- let nodeAttributes = extractAttributesFromNode(node);
2730
+ let nodeAttributes = resolveI18nAttrs(extractAttributesFromNode(node), locale, i18nConfig);
2674
2731
  const originalAttributes = { ...nodeAttributes };
2675
2732
  if (ctx.cmsContext?.cms && Object.keys(nodeAttributes).length > 0) {
2676
2733
  nodeAttributes = processCMSPropsTemplate(nodeAttributes, ctx.cmsContext.cms, locale, i18nConfig);
@@ -3056,7 +3113,7 @@ function renderLocaleList(node, ctx) {
3056
3113
  links.push(`<a href="${escapeHtml(link.path)}"${hreflangAttr}${currentAttr} data-locale="${escapeHtml(link.locale)}"${classAttrForLink}>${linkContent}</a>`);
3057
3114
  }
3058
3115
  const linksHTML = showSeparator ? links.join(`<span${separatorClassAttr}></span>`) : links.join("");
3059
- const nodeAttributes = extractAttributesFromNode(node);
3116
+ const nodeAttributes = resolveI18nAttrs(extractAttributesFromNode(node), locale, i18nConfig);
3060
3117
  const attrsStr = buildAttributes(nodeAttributes);
3061
3118
  const localeListResult = `<div data-locale-list="true"${containerClassAttr}${localeListStyleAttr}${attrsStr}${editorAttrs(ctx)}>${linksHTML}</div>`;
3062
3119
  if (ctx.ssrFallbackCollector && ctx.elementPath) {
@@ -3097,7 +3154,8 @@ async function renderPageSSR(pageData, globalComponents = {}, pagePath = "/", ba
3097
3154
  const metaTags = generateMetaTags(meta, fullUrl, effectiveLocale, config, {
3098
3155
  slugMappings,
3099
3156
  pagePath,
3100
- baseUrl
3157
+ baseUrl,
3158
+ social: configService.getSocial()
3101
3159
  });
3102
3160
  const resolvedTitle = resolveI18nValue(meta.title, effectiveLocale, config);
3103
3161
  return {
@@ -3454,8 +3512,10 @@ async function generateSSRHTML(pageDataOrOptions, globalComponents = {}, pagePat
3454
3512
  injectEditorAttrs = false,
3455
3513
  isEditor = false,
3456
3514
  isProductionBuild = false,
3457
- serverPort
3515
+ serverPort,
3516
+ cspNonce
3458
3517
  } = options;
3518
+ const nonceAttr = cspNonce ? ` nonce="${cspNonce}"` : "";
3459
3519
  const rendered = await renderPageSSR(pageData, components, path2, base, loc, void 0, slugs, cms, cmsServ, isProductionBuild, injectEditorAttrs);
3460
3520
  let finalClientDataCollections = clientDataCollections;
3461
3521
  if (rendered.neededCollections.size > 0 && cmsServ) {
@@ -3486,7 +3546,7 @@ async function generateSSRHTML(pageDataOrOptions, globalComponents = {}, pagePat
3486
3546
  await configService.load();
3487
3547
  const globalLibraries = configService.getLibraries() || { js: [], css: [] };
3488
3548
  const globalCustomCode = configService.getCustomCode();
3489
- const menoBadgeHtml = configService.getShowMenoBadge() ? `<a href="https://meno.so" target="_blank" rel="noopener" style="position:fixed;bottom:12px;left:12px;z-index:9999;background:#000;color:#fff;padding:4px 10px;border-radius:6px;font-size:12px;font-family:system-ui,sans-serif;text-decoration:none;opacity:0.8;transition:opacity 0.2s" onmouseenter="this.style.opacity='1'" onmouseleave="this.style.opacity='0.8'">Made in Meno</a>` : "";
3549
+ const menoBadgeHtml = configService.getShowMenoBadge() ? `<style>.meno-badge{position:fixed;bottom:12px;left:12px;z-index:9999;background:#000;color:#fff;padding:4px 10px;border-radius:6px;font-size:12px;font-family:system-ui,sans-serif;text-decoration:none;opacity:0.8;transition:opacity 0.2s}.meno-badge:hover,.meno-badge:focus{opacity:1}</style><a class="meno-badge" href="https://meno.so" target="_blank" rel="noopener">Made in Meno</a>` : "";
3490
3550
  const mergedCustomCode = {
3491
3551
  head: [globalCustomCode.head, pageCustomCode?.head].filter(Boolean).join("\n"),
3492
3552
  bodyStart: [globalCustomCode.bodyStart, pageCustomCode?.bodyStart].filter(Boolean).join("\n"),
@@ -3555,7 +3615,7 @@ async function generateSSRHTML(pageDataOrOptions, globalComponents = {}, pagePat
3555
3615
  } else {
3556
3616
  const escapedJavaScript = allJavaScript.replace(/<\/script>/gi, "<\\/script>");
3557
3617
  componentScript = `
3558
- <script>
3618
+ <script${nonceAttr}>
3559
3619
  ${escapedJavaScript}
3560
3620
  </script>`;
3561
3621
  }
@@ -3622,24 +3682,26 @@ picture {
3622
3682
  const prefetchConfig = await loadPrefetchConfig();
3623
3683
  const menoConfig = prefetchConfig.enabled ? { prefetch: prefetchConfig } : {};
3624
3684
  const hasConfig = Object.keys(menoConfig).length > 0;
3625
- const configInlineScript = hasConfig && !extScriptPath && !returnSeparateJS ? `<script>window.__MENO_CONFIG__=${JSON.stringify(menoConfig)}</script>
3685
+ const configInlineScript = hasConfig && !extScriptPath && !returnSeparateJS ? `<script${nonceAttr}>window.__MENO_CONFIG__=${JSON.stringify(menoConfig)}</script>
3626
3686
  ` : "";
3627
3687
  if (hasConfig && externalJavaScript !== null) {
3628
3688
  externalJavaScript = `window.__MENO_CONFIG__=${JSON.stringify(menoConfig)};
3629
3689
 
3630
3690
  ` + externalJavaScript;
3631
3691
  }
3632
- const cmsInlineScript = cmsTemplatePath && cms && (!useBundled || injectLiveReload) ? `<script>window.__MENO_CMS__=${JSON.stringify({ item: cms.cms, templatePath: cmsTemplatePath })}</script>
3692
+ const cmsInlineScript = cmsTemplatePath && cms && (!useBundled || injectLiveReload) ? `<script${nonceAttr}>window.__MENO_CMS__=${JSON.stringify({ item: cms.cms, templatePath: cmsTemplatePath })}</script>
3633
3693
  ` : "";
3634
3694
  const clientDataScripts = finalClientDataCollections && finalClientDataCollections.size > 0 ? generateAllInlineDataScripts(finalClientDataCollections) + "\n " : "";
3635
- const faviconTag = iconsConfig.favicon ? `<link rel="icon" href="${escapeHtml(iconsConfig.favicon)}" />` : "";
3695
+ const hasDarkFavicon = !!(iconsConfig.favicon && iconsConfig.faviconDark);
3696
+ const faviconTag = iconsConfig.favicon ? `<link rel="icon" href="${escapeHtml(iconsConfig.favicon)}"${hasDarkFavicon ? ' media="(prefers-color-scheme: light)"' : ""} />` : "";
3697
+ const faviconDarkTag = iconsConfig.faviconDark ? `<link rel="icon" href="${escapeHtml(iconsConfig.faviconDark)}" media="(prefers-color-scheme: dark)" />` : "";
3636
3698
  const appleTouchIconTag = iconsConfig.appleTouchIcon ? `<link rel="apple-touch-icon" href="${escapeHtml(iconsConfig.appleTouchIcon)}" />` : "";
3637
- const iconTags = [faviconTag, appleTouchIconTag].filter(Boolean).join("\n ");
3699
+ const iconTags = [faviconTag, faviconDarkTag, appleTouchIconTag].filter(Boolean).join("\n ");
3638
3700
  const scriptPreloadTag = extScriptPath ? `<link rel="preload" href="${extScriptPath}" as="script">` : "";
3639
3701
  const imagePreloadTags = generateImagePreloadTags(rendered.preloadImages);
3640
3702
  const wsUrl = serverPort ? `'ws://localhost:${serverPort}/hmr'` : `location.origin.replace('http','ws')+'/hmr'`;
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>` : "";
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>` : "";
3703
+ const liveReloadScript = injectLiveReload ? `<script${nonceAttr}>(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>` : "";
3704
+ const scrollHandlerScript = injectLiveReload ? `<script${nonceAttr}>(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>` : "";
3643
3705
  const styleContent = useBundled ? finalCSS : `
3644
3706
  ${combinedCSS.split("\n").join("\n ")}
3645
3707
  `;
@@ -5833,8 +5895,10 @@ async function loadJSONFile2(filePath) {
5833
5895
  }
5834
5896
  }
5835
5897
  function normalizeItem(content, filename, isDraft = false) {
5898
+ const raw = content;
5836
5899
  const base = {
5837
- ...content,
5900
+ ...raw,
5901
+ _id: raw._id || filename,
5838
5902
  _slug: filename,
5839
5903
  _filename: filename
5840
5904
  };
@@ -5842,8 +5906,12 @@ function normalizeItem(content, filename, isDraft = false) {
5842
5906
  return base;
5843
5907
  }
5844
5908
  function stripTransient(item) {
5845
- const { _slug, _isDraft, _hasDraft, _url, ...rest } = item;
5846
- return rest;
5909
+ const { _slug, _isDraft, _hasDraft, _url, _filename, ...rest } = item;
5910
+ const out = rest;
5911
+ if (typeof _filename === "string" && _filename !== out._id) {
5912
+ out._filename = _filename;
5913
+ }
5914
+ return out;
5847
5915
  }
5848
5916
  var FileSystemCMSProvider = class {
5849
5917
  constructor(templatesDir, cmsDir) {
@@ -5974,8 +6042,10 @@ var FileSystemCMSProvider = class {
5974
6042
  return items.find((item) => item._id === id) || null;
5975
6043
  }
5976
6044
  /**
5977
- * Save item to file system
5978
- * Uses _filename for file path (stable, doesn't change when slug changes)
6045
+ * Save item to file system.
6046
+ * The on-disk filename is derived from `_filename` (legacy alias) when set,
6047
+ * otherwise from `_id` (canonical identifier; equals the filename for new
6048
+ * items). Falls back to the slugField value as a last resort.
5979
6049
  */
5980
6050
  async saveItem(collection, item) {
5981
6051
  this.validateCollection(collection);
@@ -5991,10 +6061,16 @@ var FileSystemCMSProvider = class {
5991
6061
  } else {
5992
6062
  const slugField = schemaInfo.schema.slugField;
5993
6063
  const slugValue = item[slugField];
5994
- filename = typeof slugValue === "string" ? slugValue : String(slugValue);
6064
+ if (typeof slugValue === "string" && slugValue) {
6065
+ filename = slugValue;
6066
+ } else if (typeof item._id === "string" && item._id) {
6067
+ filename = item._id;
6068
+ } else {
6069
+ filename = String(slugValue);
6070
+ }
5995
6071
  }
5996
6072
  if (!filename || filename === "[object Object]") {
5997
- throw new Error("Missing _filename field. Items must have _filename set on creation.");
6073
+ throw new Error("Cannot derive filename: item is missing _id, _filename, and a usable slug-field value.");
5998
6074
  }
5999
6075
  this.validateFilename(filename);
6000
6076
  const collectionDir = join2(this.cmsDir, collection);
@@ -6082,7 +6158,8 @@ var FileSystemCMSProvider = class {
6082
6158
  /**
6083
6159
  * Save the draft version of an item. Loose validation — drafts may have
6084
6160
  * missing required fields or partial data. Strict validation only runs at
6085
- * publish time. The item's `_filename` determines the target file.
6161
+ * publish time. The on-disk filename is derived the same way as for
6162
+ * `saveItem` (prefer `_filename`, fall back to `_id`).
6086
6163
  */
6087
6164
  async saveDraft(collection, item) {
6088
6165
  this.validateCollection(collection);
@@ -6098,10 +6175,16 @@ var FileSystemCMSProvider = class {
6098
6175
  } else {
6099
6176
  const slugField = schemaInfo.schema.slugField;
6100
6177
  const slugValue = item[slugField];
6101
- filename = typeof slugValue === "string" ? slugValue : String(slugValue);
6178
+ if (typeof slugValue === "string" && slugValue) {
6179
+ filename = slugValue;
6180
+ } else if (typeof item._id === "string" && item._id) {
6181
+ filename = item._id;
6182
+ } else {
6183
+ filename = String(slugValue);
6184
+ }
6102
6185
  }
6103
6186
  if (!filename || filename === "[object Object]") {
6104
- throw new Error("Missing _filename field. Drafts must have _filename set on creation.");
6187
+ throw new Error("Cannot derive draft filename: item is missing _id, _filename, and a usable slug-field value.");
6105
6188
  }
6106
6189
  this.validateFilename(filename);
6107
6190
  const collectionDir = join2(this.cmsDir, collection);
@@ -6288,4 +6371,4 @@ export {
6288
6371
  FileSystemCMSProvider,
6289
6372
  migrateTemplatesDirectory
6290
6373
  };
6291
- //# sourceMappingURL=chunk-EQYDSPBB.js.map
6374
+ //# sourceMappingURL=chunk-DM54NPEC.js.map