@xiwebdesign/xicms-core 7.12.0 → 7.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/dist/admin/AdminApp.js +2 -0
  2. package/dist/admin/AdminApp.js.map +1 -1
  3. package/dist/admin/lib/zodFields.d.ts +2 -2
  4. package/dist/admin/lib/zodFields.js +2 -0
  5. package/dist/admin/lib/zodFields.js.map +1 -1
  6. package/dist/admin/ui/toast.d.ts +4 -0
  7. package/dist/admin/ui/toast.js +9 -3
  8. package/dist/admin/ui/toast.js.map +1 -1
  9. package/dist/admin/visual/VisualEditor.js +32 -2
  10. package/dist/admin/visual/VisualEditor.js.map +1 -1
  11. package/dist/blocks/builtinSchemas.d.ts +23 -0
  12. package/dist/blocks/builtinSchemas.js +138 -0
  13. package/dist/blocks/builtinSchemas.js.map +1 -0
  14. package/dist/blocks/builtins/Accordion.js +0 -2
  15. package/dist/blocks/builtins/Accordion.js.map +1 -1
  16. package/dist/blocks/builtins/Agenda.js +0 -2
  17. package/dist/blocks/builtins/Agenda.js.map +1 -1
  18. package/dist/blocks/builtins/Banner.js +0 -2
  19. package/dist/blocks/builtins/Banner.js.map +1 -1
  20. package/dist/blocks/builtins/BeforeAfter.js +0 -2
  21. package/dist/blocks/builtins/BeforeAfter.js.map +1 -1
  22. package/dist/blocks/builtins/Booking.js +0 -2
  23. package/dist/blocks/builtins/Booking.js.map +1 -1
  24. package/dist/blocks/builtins/Breadcrumbs.js +0 -2
  25. package/dist/blocks/builtins/Breadcrumbs.js.map +1 -1
  26. package/dist/blocks/builtins/Cards.js +0 -2
  27. package/dist/blocks/builtins/Cards.js.map +1 -1
  28. package/dist/blocks/builtins/Columns.js +0 -2
  29. package/dist/blocks/builtins/Columns.js.map +1 -1
  30. package/dist/blocks/builtins/Contact.d.ts +1 -1
  31. package/dist/blocks/builtins/Contact.js +0 -3
  32. package/dist/blocks/builtins/Contact.js.map +1 -1
  33. package/dist/blocks/builtins/Countdown.js +0 -2
  34. package/dist/blocks/builtins/Countdown.js.map +1 -1
  35. package/dist/blocks/builtins/Cta.js +0 -2
  36. package/dist/blocks/builtins/Cta.js.map +1 -1
  37. package/dist/blocks/builtins/Downloads.js +0 -2
  38. package/dist/blocks/builtins/Downloads.js.map +1 -1
  39. package/dist/blocks/builtins/Elements.js +0 -4
  40. package/dist/blocks/builtins/Elements.js.map +1 -1
  41. package/dist/blocks/builtins/Embed.js +0 -2
  42. package/dist/blocks/builtins/Embed.js.map +1 -1
  43. package/dist/blocks/builtins/Faq.js +0 -2
  44. package/dist/blocks/builtins/Faq.js.map +1 -1
  45. package/dist/blocks/builtins/FormBlock.js +0 -2
  46. package/dist/blocks/builtins/FormBlock.js.map +1 -1
  47. package/dist/blocks/builtins/Gallery.d.ts +1 -1
  48. package/dist/blocks/builtins/Gallery.js +0 -2
  49. package/dist/blocks/builtins/Gallery.js.map +1 -1
  50. package/dist/blocks/builtins/Hero.js +0 -2
  51. package/dist/blocks/builtins/Hero.js.map +1 -1
  52. package/dist/blocks/builtins/ImageBlock.js +0 -2
  53. package/dist/blocks/builtins/ImageBlock.js.map +1 -1
  54. package/dist/blocks/builtins/Jobs.d.ts +1 -1
  55. package/dist/blocks/builtins/Jobs.js +0 -2
  56. package/dist/blocks/builtins/Jobs.js.map +1 -1
  57. package/dist/blocks/builtins/Layout.js +0 -3
  58. package/dist/blocks/builtins/Layout.js.map +1 -1
  59. package/dist/blocks/builtins/MapBlock.js +0 -2
  60. package/dist/blocks/builtins/MapBlock.js.map +1 -1
  61. package/dist/blocks/builtins/NewsListBlock.js +0 -2
  62. package/dist/blocks/builtins/NewsListBlock.js.map +1 -1
  63. package/dist/blocks/builtins/Newsletter.js +0 -2
  64. package/dist/blocks/builtins/Newsletter.js.map +1 -1
  65. package/dist/blocks/builtins/OpeningHours.js +0 -2
  66. package/dist/blocks/builtins/OpeningHours.js.map +1 -1
  67. package/dist/blocks/builtins/PdfBlock.js +0 -2
  68. package/dist/blocks/builtins/PdfBlock.js.map +1 -1
  69. package/dist/blocks/builtins/Portfolio.js +0 -2
  70. package/dist/blocks/builtins/Portfolio.js.map +1 -1
  71. package/dist/blocks/builtins/Pricing.d.ts +1 -1
  72. package/dist/blocks/builtins/Pricing.js +0 -2
  73. package/dist/blocks/builtins/Pricing.js.map +1 -1
  74. package/dist/blocks/builtins/RichTextBlock.js +0 -2
  75. package/dist/blocks/builtins/RichTextBlock.js.map +1 -1
  76. package/dist/blocks/builtins/Search.js +0 -2
  77. package/dist/blocks/builtins/Search.js.map +1 -1
  78. package/dist/blocks/builtins/Sections.js +0 -10
  79. package/dist/blocks/builtins/Sections.js.map +1 -1
  80. package/dist/blocks/builtins/Slider.js +0 -2
  81. package/dist/blocks/builtins/Slider.js.map +1 -1
  82. package/dist/blocks/builtins/Spacer.js +0 -2
  83. package/dist/blocks/builtins/Spacer.js.map +1 -1
  84. package/dist/blocks/builtins/SymbolRef.js +3 -5
  85. package/dist/blocks/builtins/SymbolRef.js.map +1 -1
  86. package/dist/blocks/builtins/Team.js +0 -2
  87. package/dist/blocks/builtins/Team.js.map +1 -1
  88. package/dist/blocks/builtins/Testimonials.d.ts +1 -1
  89. package/dist/blocks/builtins/Testimonials.js +0 -2
  90. package/dist/blocks/builtins/Testimonials.js.map +1 -1
  91. package/dist/blocks/builtins/Video.d.ts +1 -1
  92. package/dist/blocks/builtins/Video.js +0 -2
  93. package/dist/blocks/builtins/Video.js.map +1 -1
  94. package/dist/blocks/builtins/WhatsappChat.js +0 -2
  95. package/dist/blocks/builtins/WhatsappChat.js.map +1 -1
  96. package/dist/blocks/builtins/Widgets.js +0 -26
  97. package/dist/blocks/builtins/Widgets.js.map +1 -1
  98. package/dist/blocks/customTemplate/definition.js +1 -1
  99. package/dist/blocks/customTemplate/definition.js.map +1 -1
  100. package/dist/blocks/registry.d.ts +24 -13
  101. package/dist/blocks/registry.js +15 -4
  102. package/dist/blocks/registry.js.map +1 -1
  103. package/dist/media/ImageContext.js +24 -1
  104. package/dist/media/ImageContext.js.map +1 -1
  105. package/dist/schema/blockBase.d.ts +99 -0
  106. package/dist/schema/blockBase.js +22 -0
  107. package/dist/schema/blockBase.js.map +1 -0
  108. package/dist/schema/blocks.d.ts +1 -91
  109. package/dist/schema/blocks.js +64 -77
  110. package/dist/schema/blocks.js.map +1 -1
  111. package/dist/schema/pages.js +1 -1
  112. package/dist/schema/pages.js.map +1 -1
  113. package/dist/schema/shop.js +1 -1
  114. package/dist/schema/shop.js.map +1 -1
  115. package/dist/schema/symbols.js +1 -1
  116. package/dist/schema/symbols.js.map +1 -1
  117. package/dist/server/blockData.js +29 -9
  118. package/dist/server/blockData.js.map +1 -1
  119. package/dist/server/index.d.ts +1 -1
  120. package/dist/server/index.js +2 -1
  121. package/dist/server/index.js.map +1 -1
  122. package/dist/server/pages.d.ts +16 -0
  123. package/dist/server/pages.js +10 -1
  124. package/dist/server/pages.js.map +1 -1
  125. package/dist/server/shop.js +6 -10
  126. package/dist/server/shop.js.map +1 -1
  127. package/dist/styles.css +45 -0
  128. package/dist/version.js +1 -1
  129. package/package.json +1 -1
@@ -1,5 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { registerBuiltinSchemas } from "../blocks/builtinSchemas.js";
4
+ registerBuiltinSchemas();
3
5
  import { useState } from "react";
4
6
  import { useAuth } from "../auth/AuthProvider.js";
5
7
  import { LoginForm } from "../auth/LoginForm.js";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/admin/AdminApp.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport { useAuth } from \"../auth/AuthProvider\";\nimport { LoginForm } from \"../auth/LoginForm\";\nimport { useSiteConfig } from \"../config\";\nimport { useModules } from \"../modules/context\";\nimport { canEdit, canManageSite, type Role } from \"../auth/roles\";\nimport { Tabs, TabsContent } from \"./ui/tabs\";\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport { ToastProvider } from \"./ui/toast\";\nimport { ConfirmHost } from \"./ui/ConfirmDialog\";\nimport { Button } from \"./ui/primitives\";\nimport { PanelIcon } from \"./ui/PanelIcon\";\nimport { builtinPanels, type PanelDefinition } from \"./panelRegistry\";\nimport { AdminHelp } from \"./AdminHelp\";\nimport { CoreVersion } from \"./CoreVersion\";\n\n/**\n * Rol-bewaakt beheer (XiCMS 3.0). De secties komen uit de paneel-registry, gefilterd op de actieve\n * modules (`SiteConfig.modules`/`features`) en de rol van de gebruiker. Een module met een eigen\n * paneel (ook klant-modules via `extraPanels`) verschijnt automatisch.\n *\n * VORM: een schermvullend werkblad met een vaste zijbalk, geen kaart in de pagina. Het beheer viel\n * hiervoor binnen de site-chrome — nav erboven, footer eronder, cookiebanner eroverheen — en las\n * daardoor als een blok ergens op de website in plaats van als gereedschap. De site-chrome leeft nu\n * in `app/(site)/layout.tsx`; /beheer valt daarbuiten.\n *\n * De zijbalk verving de horizontale tabstrip omdat die bij zeven of meer panelen al omsloeg naar een\n * tweede regel. Verticaal is er bovendien ruimte voor een icoon naast het woord, en `orientation`\n * houdt de pijltjestoetsen correct (Radix).\n *\n * Wikkel in `.xicms-admin` zodat de Tailwind-stijlen uit `@xiwebdesign/xicms-core/admin.css` scoped\n * blijven en niet naar de site lekken. Importeer die CSS alleen op de /beheer-route.\n */\nfunction roleAllows(role: Role | null, min: PanelDefinition[\"minRole\"]): boolean {\n return min === \"siteadmin\" ? canManageSite(role) : canEdit(role);\n}\n\n/** Gecentreerde boodschap voor de toestanden waarin er (nog) geen beheer te tonen is. */\nfunction Melding({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"xicms-admin__midden grid place-items-center p-6\">\n <div className=\"w-full max-w-md rounded-xl border border-slate-200 bg-white p-6 text-sm shadow-sm\">{children}</div>\n </div>\n );\n}\n\nexport function AdminApp({\n extraPanels = [],\n embedded = false,\n}: {\n extraPanels?: PanelDefinition[];\n /**\n * Draait het beheer BINNEN de site-schil (header, logo, menu, footer eromheen) in plaats van als\n * schermvullend werkblad op een kale pagina?\n *\n * Het verschil is niet cosmetisch. Los rekent het werkblad met het VENSTER: `min-h-screen`, en een\n * zijbalk die met `sticky top-0` aan de bovenkant van het scherm plakt. Zet je daar een\n * site-header boven, dan loopt de pagina 100vh + de hoogte van die header, en plakt de zijbalk\n * onder de header vast in plaats van erlangs. Ingebed laat de zijbalk daarom los van het venster\n * en wordt hij een balk over de volle breedte — dat vecht niet met de navigatie erboven.\n */\n embedded?: boolean;\n}) {\n const { user, role, loading, configured, signOut } = useAuth();\n const { name, siteId } = useSiteConfig();\n const { activeIds } = useModules();\n\n // Panelen van actieve modules waar de rol toegang toe heeft, gesorteerd op volgorde.\n const panels = [...builtinPanels, ...extraPanels]\n .filter((p) => activeIds.has(p.moduleId) && roleAllows(role, p.minRole))\n .sort((a, b) => a.order - b.order);\n /** Sleutel van een paneel in de tabbladen: eigen `id` als die er is, anders de module-id. */\n const sleutel = (p: PanelDefinition) => p.id ?? p.moduleId;\n const firstTab = panels[0] && sleutel(panels[0]);\n // Gecontroleerd i.p.v. `defaultValue`: het werkblad toont de naam van de sectie waar je in zit.\n // Zonder die kop lees je alleen aan de zijbalk-markering af waar je bent, en dat is te weinig\n // houvast op een breed scherm waar de zijbalk ver van je blik af staat.\n const [actief, setActief] = useState<string | undefined>(firstTab);\n const actievePanel = panels.find((p) => sleutel(p) === (actief ?? firstTab));\n\n return (\n <div className={embedded ? \"xicms-admin xicms-admin--ingebed\" : \"xicms-admin\"}>\n <ToastProvider>\n <ConfirmHost>\n {!configured ? (\n <Melding>\n Firebase is nog niet geconfigureerd. Vul de <code>NEXT_PUBLIC_FIREBASE_*</code>-variabelen in\n om het beheer aan te zetten.\n </Melding>\n ) : loading ? (\n <Melding>\n <span className=\"text-slate-500\">Bezig met laden…</span>\n </Melding>\n ) : !user ? (\n <div className=\"xicms-admin__midden grid place-items-center p-6\">\n <div className=\"w-full max-w-sm\">\n <p className=\"mb-4 text-center text-sm text-slate-500\">Beheer · {name}</p>\n {/*\n Na inloggen naar de HOMEPAGINA, niet naar dit scherm.\n\n Dat is een sturende keuze, geen detail. Wie op het beheer uitkomt gaat daar\n rondkijken en bewerkt zijn teksten in formulieren. Wie op zijn eigen site uitkomt\n ziet de beheerbalk over de pagina heen en past de tekst aan op de plek waar hij\n staat — en dat is waar XiCMS voor gemaakt is. Het beheer blijft één klik weg, via\n \"Beheren\" in die balk, voor het werk dat er wél thuishoort.\n\n Ook hier en niet alleen op /login: je komt op /beheer terecht via een bladwijzer of\n een oude link, en dan gold die keuze ineens niet.\n */}\n <LoginForm onSuccess={() => { window.location.replace(\"/\"); }} />\n </div>\n </div>\n ) : !canEdit(role) ? (\n <Melding>\n Je bent ingelogd als <strong>{user.email}</strong>, maar hebt geen beheerrechten voor deze site.\n <div className=\"mt-3\">\n <Button variant=\"secondary\" size=\"sm\" onClick={() => void signOut()}>Uitloggen</Button>\n </div>\n </Melding>\n ) : panels.length === 0 ? (\n <Melding>Geen actieve beheer-modules voor deze site.</Melding>\n ) : (\n <Tabs value={actief ?? firstTab} onValueChange={setActief} orientation=\"vertical\" className=\"xicms-admin__raster md:grid md:grid-cols-[248px_1fr]\">\n {/* ── Zijbalk ─────────────────────────────────────────────────────────── */}\n <div\n className=\"xicms-admin__rail flex flex-col gap-4 border-b border-slate-200 p-4 md:sticky md:top-0 md:h-screen md:border-r md:border-b-0\"\n style={{ background: \"var(--admin-sidebar)\" }}\n >\n <div className=\"min-w-0 px-2 pt-1\">\n <p className=\"truncate text-sm font-semibold\">{name}</p>\n <p className=\"text-xs text-slate-500\">Beheer</p>\n </div>\n\n <TabsPrimitive.List aria-label=\"Beheer-secties\" className=\"xicms-admin__lijst -mx-1 flex gap-1 overflow-x-auto md:flex-col md:overflow-visible\">\n {panels.map((p) => (\n <TabsPrimitive.Trigger\n key={sleutel(p)}\n value={sleutel(p)}\n className=\"flex shrink-0 items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-medium text-slate-600 hover:bg-slate-200 hover:text-slate-900 focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none data-[state=active]:bg-slate-100 data-[state=active]:text-slate-900 data-[state=active]:shadow-[inset_2px_0_0_0_var(--color-brand-500)]\"\n >\n <PanelIcon id={p.moduleId} />\n {p.label}\n </TabsPrimitive.Trigger>\n ))}\n </TabsPrimitive.List>\n\n {/* Voet van de zijbalk: wie ben ik, welke engine draait er, en eruit. Stond eerder als\n vier losse regels op elkaar gepakt — e-mail, rol, versie en een onderstreepte link\n liepen visueel in elkaar over. Nu drie duidelijke lagen met lucht ertussen, en\n Uitloggen als een echte knop in plaats van een tekstlink. */}\n <div className=\"xicms-admin__voet mt-auto hidden min-w-0 flex-col gap-3 border-t border-slate-200 pt-3 md:flex\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-xs font-medium text-slate-600\" title={user.email ?? undefined}>{user.email}</p>\n <p className=\"text-xs text-slate-400\">{role}</p>\n </div>\n <CoreVersion />\n <Button variant=\"secondary\" size=\"sm\" onClick={() => void signOut()}>\n Uitloggen\n </Button>\n </div>\n </div>\n\n {/* ── Werkblad ────────────────────────────────────────────────────────── */}\n <div className=\"min-w-0\">\n <header className=\"xicms-admin__kop sticky top-0 z-10 flex items-center justify-between gap-4 border-b border-slate-200 px-4 py-3 md:px-8\" style={{ background: \"var(--admin-canvas)\" }}>\n <h1 className=\"truncate text-base font-semibold\">{actievePanel?.label ?? \"Beheer\"}</h1>\n <a\n className=\"inline-flex h-8 shrink-0 items-center rounded-lg border border-slate-200 px-3 text-sm hover:bg-slate-100 focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none\"\n href=\"/\"\n >\n Bekijk site ↗\n </a>\n </header>\n <div className=\"grid gap-6 p-4 md:p-8\">\n {panels.map((p) => (\n <TabsContent key={sleutel(p)} value={sleutel(p)} className=\"pt-0\">\n <p.Panel siteId={siteId} />\n </TabsContent>\n ))}\n <AdminHelp />\n </div>\n </div>\n </Tabs>\n )}\n </ConfirmHost>\n </ToastProvider>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/admin/AdminApp.tsx"],"sourcesContent":["\"use client\";\n\nimport { registerBuiltinSchemas } from \"../blocks/builtinSchemas\";\n// Schema's zijn lazy (7.13): dit is een editor-chunk, dus hier horen ze thuis.\nregisterBuiltinSchemas();\nimport { useState } from \"react\";\nimport { useAuth } from \"../auth/AuthProvider\";\nimport { LoginForm } from \"../auth/LoginForm\";\nimport { useSiteConfig } from \"../config\";\nimport { useModules } from \"../modules/context\";\nimport { canEdit, canManageSite, type Role } from \"../auth/roles\";\nimport { Tabs, TabsContent } from \"./ui/tabs\";\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport { ToastProvider } from \"./ui/toast\";\nimport { ConfirmHost } from \"./ui/ConfirmDialog\";\nimport { Button } from \"./ui/primitives\";\nimport { PanelIcon } from \"./ui/PanelIcon\";\nimport { builtinPanels, type PanelDefinition } from \"./panelRegistry\";\nimport { AdminHelp } from \"./AdminHelp\";\nimport { CoreVersion } from \"./CoreVersion\";\n\n/**\n * Rol-bewaakt beheer (XiCMS 3.0). De secties komen uit de paneel-registry, gefilterd op de actieve\n * modules (`SiteConfig.modules`/`features`) en de rol van de gebruiker. Een module met een eigen\n * paneel (ook klant-modules via `extraPanels`) verschijnt automatisch.\n *\n * VORM: een schermvullend werkblad met een vaste zijbalk, geen kaart in de pagina. Het beheer viel\n * hiervoor binnen de site-chrome — nav erboven, footer eronder, cookiebanner eroverheen — en las\n * daardoor als een blok ergens op de website in plaats van als gereedschap. De site-chrome leeft nu\n * in `app/(site)/layout.tsx`; /beheer valt daarbuiten.\n *\n * De zijbalk verving de horizontale tabstrip omdat die bij zeven of meer panelen al omsloeg naar een\n * tweede regel. Verticaal is er bovendien ruimte voor een icoon naast het woord, en `orientation`\n * houdt de pijltjestoetsen correct (Radix).\n *\n * Wikkel in `.xicms-admin` zodat de Tailwind-stijlen uit `@xiwebdesign/xicms-core/admin.css` scoped\n * blijven en niet naar de site lekken. Importeer die CSS alleen op de /beheer-route.\n */\nfunction roleAllows(role: Role | null, min: PanelDefinition[\"minRole\"]): boolean {\n return min === \"siteadmin\" ? canManageSite(role) : canEdit(role);\n}\n\n/** Gecentreerde boodschap voor de toestanden waarin er (nog) geen beheer te tonen is. */\nfunction Melding({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"xicms-admin__midden grid place-items-center p-6\">\n <div className=\"w-full max-w-md rounded-xl border border-slate-200 bg-white p-6 text-sm shadow-sm\">{children}</div>\n </div>\n );\n}\n\nexport function AdminApp({\n extraPanels = [],\n embedded = false,\n}: {\n extraPanels?: PanelDefinition[];\n /**\n * Draait het beheer BINNEN de site-schil (header, logo, menu, footer eromheen) in plaats van als\n * schermvullend werkblad op een kale pagina?\n *\n * Het verschil is niet cosmetisch. Los rekent het werkblad met het VENSTER: `min-h-screen`, en een\n * zijbalk die met `sticky top-0` aan de bovenkant van het scherm plakt. Zet je daar een\n * site-header boven, dan loopt de pagina 100vh + de hoogte van die header, en plakt de zijbalk\n * onder de header vast in plaats van erlangs. Ingebed laat de zijbalk daarom los van het venster\n * en wordt hij een balk over de volle breedte — dat vecht niet met de navigatie erboven.\n */\n embedded?: boolean;\n}) {\n const { user, role, loading, configured, signOut } = useAuth();\n const { name, siteId } = useSiteConfig();\n const { activeIds } = useModules();\n\n // Panelen van actieve modules waar de rol toegang toe heeft, gesorteerd op volgorde.\n const panels = [...builtinPanels, ...extraPanels]\n .filter((p) => activeIds.has(p.moduleId) && roleAllows(role, p.minRole))\n .sort((a, b) => a.order - b.order);\n /** Sleutel van een paneel in de tabbladen: eigen `id` als die er is, anders de module-id. */\n const sleutel = (p: PanelDefinition) => p.id ?? p.moduleId;\n const firstTab = panels[0] && sleutel(panels[0]);\n // Gecontroleerd i.p.v. `defaultValue`: het werkblad toont de naam van de sectie waar je in zit.\n // Zonder die kop lees je alleen aan de zijbalk-markering af waar je bent, en dat is te weinig\n // houvast op een breed scherm waar de zijbalk ver van je blik af staat.\n const [actief, setActief] = useState<string | undefined>(firstTab);\n const actievePanel = panels.find((p) => sleutel(p) === (actief ?? firstTab));\n\n return (\n <div className={embedded ? \"xicms-admin xicms-admin--ingebed\" : \"xicms-admin\"}>\n <ToastProvider>\n <ConfirmHost>\n {!configured ? (\n <Melding>\n Firebase is nog niet geconfigureerd. Vul de <code>NEXT_PUBLIC_FIREBASE_*</code>-variabelen in\n om het beheer aan te zetten.\n </Melding>\n ) : loading ? (\n <Melding>\n <span className=\"text-slate-500\">Bezig met laden…</span>\n </Melding>\n ) : !user ? (\n <div className=\"xicms-admin__midden grid place-items-center p-6\">\n <div className=\"w-full max-w-sm\">\n <p className=\"mb-4 text-center text-sm text-slate-500\">Beheer · {name}</p>\n {/*\n Na inloggen naar de HOMEPAGINA, niet naar dit scherm.\n\n Dat is een sturende keuze, geen detail. Wie op het beheer uitkomt gaat daar\n rondkijken en bewerkt zijn teksten in formulieren. Wie op zijn eigen site uitkomt\n ziet de beheerbalk over de pagina heen en past de tekst aan op de plek waar hij\n staat — en dat is waar XiCMS voor gemaakt is. Het beheer blijft één klik weg, via\n \"Beheren\" in die balk, voor het werk dat er wél thuishoort.\n\n Ook hier en niet alleen op /login: je komt op /beheer terecht via een bladwijzer of\n een oude link, en dan gold die keuze ineens niet.\n */}\n <LoginForm onSuccess={() => { window.location.replace(\"/\"); }} />\n </div>\n </div>\n ) : !canEdit(role) ? (\n <Melding>\n Je bent ingelogd als <strong>{user.email}</strong>, maar hebt geen beheerrechten voor deze site.\n <div className=\"mt-3\">\n <Button variant=\"secondary\" size=\"sm\" onClick={() => void signOut()}>Uitloggen</Button>\n </div>\n </Melding>\n ) : panels.length === 0 ? (\n <Melding>Geen actieve beheer-modules voor deze site.</Melding>\n ) : (\n <Tabs value={actief ?? firstTab} onValueChange={setActief} orientation=\"vertical\" className=\"xicms-admin__raster md:grid md:grid-cols-[248px_1fr]\">\n {/* ── Zijbalk ─────────────────────────────────────────────────────────── */}\n <div\n className=\"xicms-admin__rail flex flex-col gap-4 border-b border-slate-200 p-4 md:sticky md:top-0 md:h-screen md:border-r md:border-b-0\"\n style={{ background: \"var(--admin-sidebar)\" }}\n >\n <div className=\"min-w-0 px-2 pt-1\">\n <p className=\"truncate text-sm font-semibold\">{name}</p>\n <p className=\"text-xs text-slate-500\">Beheer</p>\n </div>\n\n <TabsPrimitive.List aria-label=\"Beheer-secties\" className=\"xicms-admin__lijst -mx-1 flex gap-1 overflow-x-auto md:flex-col md:overflow-visible\">\n {panels.map((p) => (\n <TabsPrimitive.Trigger\n key={sleutel(p)}\n value={sleutel(p)}\n className=\"flex shrink-0 items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-medium text-slate-600 hover:bg-slate-200 hover:text-slate-900 focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none data-[state=active]:bg-slate-100 data-[state=active]:text-slate-900 data-[state=active]:shadow-[inset_2px_0_0_0_var(--color-brand-500)]\"\n >\n <PanelIcon id={p.moduleId} />\n {p.label}\n </TabsPrimitive.Trigger>\n ))}\n </TabsPrimitive.List>\n\n {/* Voet van de zijbalk: wie ben ik, welke engine draait er, en eruit. Stond eerder als\n vier losse regels op elkaar gepakt — e-mail, rol, versie en een onderstreepte link\n liepen visueel in elkaar over. Nu drie duidelijke lagen met lucht ertussen, en\n Uitloggen als een echte knop in plaats van een tekstlink. */}\n <div className=\"xicms-admin__voet mt-auto hidden min-w-0 flex-col gap-3 border-t border-slate-200 pt-3 md:flex\">\n <div className=\"min-w-0\">\n <p className=\"truncate text-xs font-medium text-slate-600\" title={user.email ?? undefined}>{user.email}</p>\n <p className=\"text-xs text-slate-400\">{role}</p>\n </div>\n <CoreVersion />\n <Button variant=\"secondary\" size=\"sm\" onClick={() => void signOut()}>\n Uitloggen\n </Button>\n </div>\n </div>\n\n {/* ── Werkblad ────────────────────────────────────────────────────────── */}\n <div className=\"min-w-0\">\n <header className=\"xicms-admin__kop sticky top-0 z-10 flex items-center justify-between gap-4 border-b border-slate-200 px-4 py-3 md:px-8\" style={{ background: \"var(--admin-canvas)\" }}>\n <h1 className=\"truncate text-base font-semibold\">{actievePanel?.label ?? \"Beheer\"}</h1>\n <a\n className=\"inline-flex h-8 shrink-0 items-center rounded-lg border border-slate-200 px-3 text-sm hover:bg-slate-100 focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none\"\n href=\"/\"\n >\n Bekijk site ↗\n </a>\n </header>\n <div className=\"grid gap-6 p-4 md:p-8\">\n {panels.map((p) => (\n <TabsContent key={sleutel(p)} value={sleutel(p)} className=\"pt-0\">\n <p.Panel siteId={siteId} />\n </TabsContent>\n ))}\n <AdminHelp />\n </div>\n </div>\n </Tabs>\n )}\n </ConfirmHost>\n </ToastProvider>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -17,7 +17,7 @@ export interface SchemaField {
17
17
  * (array/object/record) worden als `json` teruggegeven zodat een fallback-editor ze tóch kan
18
18
  * aanbieden; een aanroeper die alleen simpele velden wil, filtert op `kind !== "json"`.
19
19
  */
20
- export declare function introspectObjectSchema(schema: ZodTypeAny): SchemaField[];
20
+ export declare function introspectObjectSchema(schema: ZodTypeAny | undefined): SchemaField[];
21
21
  export type { FieldHints } from "../../schema/fields.js";
22
22
  import type { FieldHints } from "../../schema/fields.js";
23
23
  /**
@@ -28,4 +28,4 @@ import type { FieldHints } from "../../schema/fields.js";
28
28
  * - al het overige (records, scalar-arrays, unions) → **json** (laatste redmiddel).
29
29
  * `hints` verfijnt per (dotted) sleutel: type-overrides (date/slider/…) en `showIf`.
30
30
  */
31
- export declare function introspectToFields(schema: ZodTypeAny, hints?: FieldHints, prefix?: string): Field[];
31
+ export declare function introspectToFields(schema: ZodTypeAny | undefined, hints?: FieldHints, prefix?: string): Field[];
@@ -29,6 +29,7 @@ function unwrap(schema) {
29
29
  return { def: defOf(s), description, inner: s };
30
30
  }
31
31
  function introspectObjectSchema(schema) {
32
+ if (!schema) return [];
32
33
  const shape = shapeOf(schema);
33
34
  if (!shape) return [];
34
35
  const out = [];
@@ -65,6 +66,7 @@ function introspectObjectSchema(schema) {
65
66
  const IMAGE_KEY = /image|photo|foto|logo|cover|avatar|poster/i;
66
67
  const URL_KEY = /^(href|url|link)$|Url$|Href$/;
67
68
  function introspectToFields(schema, hints, prefix = "") {
69
+ if (!schema) return [];
68
70
  const shape = shapeOf(schema);
69
71
  if (!shape) return [];
70
72
  const out = [];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/lib/zodFields.ts"],"sourcesContent":["/**\n * Zod-introspectie voor automatisch gegenereerde formulieren. Gedeeld door:\n * - de `GenericEditor` (fallback-editor voor blokken zonder eigen `Editor`), en\n * - het `ModuleSettingsForm` (instellingen van een module in /beheer).\n *\n * Twee lagen:\n * - `introspectObjectSchema` (legacy, plat): simpele kinds — blijft bestaan voor module-settings.\n * - `introspectToFields` (5.0): levert de rijke **Field-IR** (schema/fields.ts, primo-port) met\n * échte repeaters/groepen (recursief), richtext-/image-/link-detectie en optionele per-block\n * hints (`defineBlock({ fields })`). De GenericEditor 2.0 rendert deze IR.\n *\n * Een veld krijgt zijn label uit `.describe(\"…\")` op het schema; zonder beschrijving valt het\n * terug op de veldsleutel. Puur (geen React) — apart testbaar.\n */\nimport { globalRegistry } from \"zod/mini\";\nimport type { $ZodType as ZodTypeAny } from \"zod/v4/core\";\nimport type { Field } from \"../../schema/fields\";\n\nexport interface SchemaField {\n key: string;\n kind: \"number\" | \"boolean\" | \"string\" | \"enum\" | \"json\";\n /** Leesbaar label: `.describe()` van het schema, anders de veldsleutel. */\n label: string;\n options?: string[];\n /** Max-lengte van een string (bepaalt Input vs Textarea in de UI). */\n maxLength?: number;\n /** Het volledige veld-schema — voor validatie van json-velden vóór commit. */\n schema: ZodTypeAny;\n}\n\n/**\n * Zod 4-interne vorm. In Zod 3 heette dit `_def.typeName` (\"ZodObject\") met `shape` als FUNCTIE en\n * de beschrijving op `_def.description`; sinds Zod 4 is het `_def.type` (\"object\") met `shape` als\n * gewoon object, staat de beschrijving op de schema-instantie, dragen enums hun waarden in\n * `entries`, en zit een string-check achter `checks[i]._zod.def` als\n * `{ check: \"max_length\", maximum: n }`.\n *\n * Dit blijft leunen op interne velden — er is geen publieke introspectie-API in Zod. De\n * Field-IR-tests (fields.test.ts) zijn daarom het vangnet: die vallen om zodra Zod zijn interne\n * vorm opnieuw wijzigt, in plaats van dat de GenericEditor stilletjes lege formulieren toont.\n */\ntype ZodKind =\n | \"object\" | \"string\" | \"number\" | \"boolean\" | \"enum\" | \"array\" | \"record\"\n | \"default\" | \"optional\" | \"nullable\" | \"literal\" | (string & {});\n\ninterface AnyDef {\n type?: ZodKind;\n innerType?: unknown;\n entries?: Record<string, string>;\n element?: unknown;\n checks?: { _zod?: { def?: { check?: string; maximum?: number } } }[];\n shape?: Record<string, unknown>;\n}\n\n/**\n * De interne definitie van een schema.\n *\n * LET OP: `_def` is een alias die alléén de classic-façade aanbiedt — `zod/mini` (waar core op\n * draait) heeft hem niet en zet alles op `_zod.def`. Lees dus `_zod.def` eerst, met `_def` als\n * terugval voor classic-schema's uit een klantrepo. Zonder die volgorde levert introspectie\n * stilletjes niets op en toont de GenericEditor lege formulieren.\n */\nconst defOf = (schema: unknown): AnyDef | undefined =>\n (schema as { _zod?: { def?: AnyDef } })?._zod?.def ?? (schema as { _def?: AnyDef })?._def;\n\n/** Max-lengte van een string-schema (Zod 4: checks dragen hun regel onder `_zod.def`). */\nfunction maxLengthOf(def: AnyDef | undefined): number | undefined {\n for (const c of def?.checks ?? []) {\n const rule = c?._zod?.def;\n if (rule?.check === \"max_length\") return rule.maximum;\n }\n return undefined;\n}\n\n/** Waarden van een enum (Zod 4 levert een entries-map i.p.v. een values-array). */\nconst valuesOf = (def: AnyDef | undefined): string[] => Object.values(def?.entries ?? {});\n\n/** De velden van een objectschema; leeg als het geen object is. */\nfunction shapeOf(schema: unknown): Record<string, unknown> | null {\n const def = defOf(schema);\n if (def?.type !== \"object\" || !def.shape || typeof def.shape !== \"object\") return null;\n return def.shape;\n}\n\n/**\n * De beschrijving van een schema — de bron van de veldlabels in /beheer.\n *\n * `zod/mini` heeft géén `.description`-getter (die hoort bij de classic-façade), maar `.describe()`\n * en `.register()` schrijven allebei naar dezelfde `globalRegistry`. Die lezen we hier dus direct,\n * met de getter als terugval zodat een classic-schema uit een klantrepo blijft werken.\n */\nfunction describedAs(s: unknown): string | undefined {\n const uitRegister = globalRegistry.get(s as never)?.description;\n return uitRegister ?? (s as { description?: string } | null)?.description;\n}\n\n/** Pakt het binnenste type uit default/optional/nullable; bewaart de buitenste beschrijving. */\nfunction unwrap(schema: unknown): { def?: AnyDef; description?: string; inner?: unknown } {\n let s: unknown = schema;\n let description = describedAs(s);\n for (;;) {\n const def = defOf(s);\n if (def?.type !== \"default\" && def?.type !== \"optional\" && def?.type !== \"nullable\") break;\n s = def.innerType;\n description = description ?? describedAs(s);\n }\n return { def: defOf(s), description, inner: s };\n}\n\n/**\n * Leidt een formulier-beschrijving af uit een Zod-objectschema. Interne velden (`_key`/`_type`/\n * `_style`) worden overgeslagen — die beheren de editor-chrome en de StyleEditor. Complexe velden\n * (array/object/record) worden als `json` teruggegeven zodat een fallback-editor ze tóch kan\n * aanbieden; een aanroeper die alleen simpele velden wil, filtert op `kind !== \"json\"`.\n */\nexport function introspectObjectSchema(schema: ZodTypeAny): SchemaField[] {\n const shape = shapeOf(schema);\n if (!shape) return [];\n const out: SchemaField[] = [];\n for (const [key, field] of Object.entries(shape)) {\n if (key.startsWith(\"_\")) continue;\n const { def: inner, description } = unwrap(field);\n const label = description ?? key;\n const fieldSchema = field as ZodTypeAny;\n switch (inner?.type) {\n case \"number\":\n out.push({ key, kind: \"number\", label, schema: fieldSchema });\n break;\n case \"boolean\":\n out.push({ key, kind: \"boolean\", label, schema: fieldSchema });\n break;\n case \"enum\":\n out.push({ key, kind: \"enum\", label, options: valuesOf(inner), schema: fieldSchema });\n break;\n case \"string\": {\n out.push({ key, kind: \"string\", label, maxLength: maxLengthOf(inner), schema: fieldSchema });\n break;\n }\n case \"array\":\n case \"object\":\n case \"record\":\n out.push({ key, kind: \"json\", label, schema: fieldSchema });\n break;\n default:\n // ZodLiteral (bv. _type), unions e.d.: geen zinvol generiek veld.\n break;\n }\n }\n return out;\n}\n\nexport type { FieldHints } from \"../../schema/fields\";\nimport type { FieldHints } from \"../../schema/fields\";\n\n/** Herkent een beeld-URL-veld aan de sleutelnaam (zelfde heuristiek als de oude GenericEditor). */\nconst IMAGE_KEY = /image|photo|foto|logo|cover|avatar|poster/i;\n/** Herkent een URL-veld aan de sleutelnaam. */\nconst URL_KEY = /^(href|url|link)$|Url$|Href$/;\n\n/**\n * Leidt de rijke Field-IR af uit een Zod-objectschema (5.0). Verschillen met de platte laag:\n * - `z.array(z.object)` → **repeater** met recursief geïntrospecteerde subvelden;\n * - genest `z.object` → **group** (of **link** als de vorm exact `{label, href}` is);\n * - stringvelden `html`/`*Html` → **richtext**, beeld-sleutels → **image**, URL-sleutels → **url**;\n * - al het overige (records, scalar-arrays, unions) → **json** (laatste redmiddel).\n * `hints` verfijnt per (dotted) sleutel: type-overrides (date/slider/…) en `showIf`.\n */\nexport function introspectToFields(schema: ZodTypeAny, hints?: FieldHints, prefix = \"\"): Field[] {\n const shape = shapeOf(schema);\n if (!shape) return [];\n const out: Field[] = [];\n for (const [key, raw] of Object.entries(shape)) {\n if (key.startsWith(\"_\")) continue;\n const { def: inner, description, inner: innerSchema } = unwrap(raw);\n const label = description ?? key;\n const path = prefix ? `${prefix}.${key}` : key;\n const hint = hints?.[path];\n const push = (f: Field) => out.push({ ...f, ...(hint as Partial<Field>), key, label: (hint?.label as string) ?? label } as Field);\n switch (inner?.type) {\n case \"number\":\n push({ key, label, type: \"number\" });\n break;\n case \"boolean\":\n push({ key, label, type: \"switch\" });\n break;\n case \"enum\":\n push({ key, label, type: \"select\", options: valuesOf(inner).map((v) => ({ value: v, label: v })) });\n break;\n case \"string\": {\n const max = maxLengthOf(inner);\n if (key === \"html\" || key.endsWith(\"Html\")) push({ key, label, type: \"richtext\" });\n else if (IMAGE_KEY.test(key)) push({ key, label, type: \"image\" });\n else if (URL_KEY.test(key)) push({ key, label, type: \"url\" });\n else if ((max ?? 0) > 500) push({ key, label, type: \"textarea\", maxLength: max });\n else push({ key, label, type: \"text\", maxLength: max });\n break;\n }\n case \"object\": {\n const sub = introspectToFields(innerSchema as ZodTypeAny, hints, path);\n const isLink = sub.length === 2 && sub.some((f) => f.key === \"label\") && sub.some((f) => f.key === \"href\");\n if (isLink) push({ key, label, type: \"link\" });\n else if (sub.length > 0) push({ key, label, type: \"group\", fields: sub });\n else push({ key, label, type: \"json\" });\n break;\n }\n case \"array\": {\n const { def: elDef, inner: elSchema } = unwrap(inner?.element);\n if (elDef?.type === \"object\") {\n const sub = introspectToFields(elSchema as ZodTypeAny, hints, path);\n if (sub.length > 0) {\n push({ key, label, type: \"repeater\", fields: sub });\n break;\n }\n }\n push({ key, label, type: \"json\" });\n break;\n }\n case \"record\":\n push({ key, label, type: \"json\" });\n break;\n default:\n break;\n }\n }\n return out;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/lib/zodFields.ts"],"sourcesContent":["/**\n * Zod-introspectie voor automatisch gegenereerde formulieren. Gedeeld door:\n * - de `GenericEditor` (fallback-editor voor blokken zonder eigen `Editor`), en\n * - het `ModuleSettingsForm` (instellingen van een module in /beheer).\n *\n * Twee lagen:\n * - `introspectObjectSchema` (legacy, plat): simpele kinds — blijft bestaan voor module-settings.\n * - `introspectToFields` (5.0): levert de rijke **Field-IR** (schema/fields.ts, primo-port) met\n * échte repeaters/groepen (recursief), richtext-/image-/link-detectie en optionele per-block\n * hints (`defineBlock({ fields })`). De GenericEditor 2.0 rendert deze IR.\n *\n * Een veld krijgt zijn label uit `.describe(\"…\")` op het schema; zonder beschrijving valt het\n * terug op de veldsleutel. Puur (geen React) — apart testbaar.\n */\nimport { globalRegistry } from \"zod/mini\";\nimport type { $ZodType as ZodTypeAny } from \"zod/v4/core\";\nimport type { Field } from \"../../schema/fields\";\n\nexport interface SchemaField {\n key: string;\n kind: \"number\" | \"boolean\" | \"string\" | \"enum\" | \"json\";\n /** Leesbaar label: `.describe()` van het schema, anders de veldsleutel. */\n label: string;\n options?: string[];\n /** Max-lengte van een string (bepaalt Input vs Textarea in de UI). */\n maxLength?: number;\n /** Het volledige veld-schema — voor validatie van json-velden vóór commit. */\n schema: ZodTypeAny;\n}\n\n/**\n * Zod 4-interne vorm. In Zod 3 heette dit `_def.typeName` (\"ZodObject\") met `shape` als FUNCTIE en\n * de beschrijving op `_def.description`; sinds Zod 4 is het `_def.type` (\"object\") met `shape` als\n * gewoon object, staat de beschrijving op de schema-instantie, dragen enums hun waarden in\n * `entries`, en zit een string-check achter `checks[i]._zod.def` als\n * `{ check: \"max_length\", maximum: n }`.\n *\n * Dit blijft leunen op interne velden — er is geen publieke introspectie-API in Zod. De\n * Field-IR-tests (fields.test.ts) zijn daarom het vangnet: die vallen om zodra Zod zijn interne\n * vorm opnieuw wijzigt, in plaats van dat de GenericEditor stilletjes lege formulieren toont.\n */\ntype ZodKind =\n | \"object\" | \"string\" | \"number\" | \"boolean\" | \"enum\" | \"array\" | \"record\"\n | \"default\" | \"optional\" | \"nullable\" | \"literal\" | (string & {});\n\ninterface AnyDef {\n type?: ZodKind;\n innerType?: unknown;\n entries?: Record<string, string>;\n element?: unknown;\n checks?: { _zod?: { def?: { check?: string; maximum?: number } } }[];\n shape?: Record<string, unknown>;\n}\n\n/**\n * De interne definitie van een schema.\n *\n * LET OP: `_def` is een alias die alléén de classic-façade aanbiedt — `zod/mini` (waar core op\n * draait) heeft hem niet en zet alles op `_zod.def`. Lees dus `_zod.def` eerst, met `_def` als\n * terugval voor classic-schema's uit een klantrepo. Zonder die volgorde levert introspectie\n * stilletjes niets op en toont de GenericEditor lege formulieren.\n */\nconst defOf = (schema: unknown): AnyDef | undefined =>\n (schema as { _zod?: { def?: AnyDef } })?._zod?.def ?? (schema as { _def?: AnyDef })?._def;\n\n/** Max-lengte van een string-schema (Zod 4: checks dragen hun regel onder `_zod.def`). */\nfunction maxLengthOf(def: AnyDef | undefined): number | undefined {\n for (const c of def?.checks ?? []) {\n const rule = c?._zod?.def;\n if (rule?.check === \"max_length\") return rule.maximum;\n }\n return undefined;\n}\n\n/** Waarden van een enum (Zod 4 levert een entries-map i.p.v. een values-array). */\nconst valuesOf = (def: AnyDef | undefined): string[] => Object.values(def?.entries ?? {});\n\n/** De velden van een objectschema; leeg als het geen object is. */\nfunction shapeOf(schema: unknown): Record<string, unknown> | null {\n const def = defOf(schema);\n if (def?.type !== \"object\" || !def.shape || typeof def.shape !== \"object\") return null;\n return def.shape;\n}\n\n/**\n * De beschrijving van een schema — de bron van de veldlabels in /beheer.\n *\n * `zod/mini` heeft géén `.description`-getter (die hoort bij de classic-façade), maar `.describe()`\n * en `.register()` schrijven allebei naar dezelfde `globalRegistry`. Die lezen we hier dus direct,\n * met de getter als terugval zodat een classic-schema uit een klantrepo blijft werken.\n */\nfunction describedAs(s: unknown): string | undefined {\n const uitRegister = globalRegistry.get(s as never)?.description;\n return uitRegister ?? (s as { description?: string } | null)?.description;\n}\n\n/** Pakt het binnenste type uit default/optional/nullable; bewaart de buitenste beschrijving. */\nfunction unwrap(schema: unknown): { def?: AnyDef; description?: string; inner?: unknown } {\n let s: unknown = schema;\n let description = describedAs(s);\n for (;;) {\n const def = defOf(s);\n if (def?.type !== \"default\" && def?.type !== \"optional\" && def?.type !== \"nullable\") break;\n s = def.innerType;\n description = description ?? describedAs(s);\n }\n return { def: defOf(s), description, inner: s };\n}\n\n/**\n * Leidt een formulier-beschrijving af uit een Zod-objectschema. Interne velden (`_key`/`_type`/\n * `_style`) worden overgeslagen — die beheren de editor-chrome en de StyleEditor. Complexe velden\n * (array/object/record) worden als `json` teruggegeven zodat een fallback-editor ze tóch kan\n * aanbieden; een aanroeper die alleen simpele velden wil, filtert op `kind !== \"json\"`.\n */\nexport function introspectObjectSchema(schema: ZodTypeAny | undefined): SchemaField[] {\n if (!schema) return [];\n const shape = shapeOf(schema);\n if (!shape) return [];\n const out: SchemaField[] = [];\n for (const [key, field] of Object.entries(shape)) {\n if (key.startsWith(\"_\")) continue;\n const { def: inner, description } = unwrap(field);\n const label = description ?? key;\n const fieldSchema = field as ZodTypeAny;\n switch (inner?.type) {\n case \"number\":\n out.push({ key, kind: \"number\", label, schema: fieldSchema });\n break;\n case \"boolean\":\n out.push({ key, kind: \"boolean\", label, schema: fieldSchema });\n break;\n case \"enum\":\n out.push({ key, kind: \"enum\", label, options: valuesOf(inner), schema: fieldSchema });\n break;\n case \"string\": {\n out.push({ key, kind: \"string\", label, maxLength: maxLengthOf(inner), schema: fieldSchema });\n break;\n }\n case \"array\":\n case \"object\":\n case \"record\":\n out.push({ key, kind: \"json\", label, schema: fieldSchema });\n break;\n default:\n // ZodLiteral (bv. _type), unions e.d.: geen zinvol generiek veld.\n break;\n }\n }\n return out;\n}\n\nexport type { FieldHints } from \"../../schema/fields\";\nimport type { FieldHints } from \"../../schema/fields\";\n\n/** Herkent een beeld-URL-veld aan de sleutelnaam (zelfde heuristiek als de oude GenericEditor). */\nconst IMAGE_KEY = /image|photo|foto|logo|cover|avatar|poster/i;\n/** Herkent een URL-veld aan de sleutelnaam. */\nconst URL_KEY = /^(href|url|link)$|Url$|Href$/;\n\n/**\n * Leidt de rijke Field-IR af uit een Zod-objectschema (5.0). Verschillen met de platte laag:\n * - `z.array(z.object)` → **repeater** met recursief geïntrospecteerde subvelden;\n * - genest `z.object` → **group** (of **link** als de vorm exact `{label, href}` is);\n * - stringvelden `html`/`*Html` → **richtext**, beeld-sleutels → **image**, URL-sleutels → **url**;\n * - al het overige (records, scalar-arrays, unions) → **json** (laatste redmiddel).\n * `hints` verfijnt per (dotted) sleutel: type-overrides (date/slider/…) en `showIf`.\n */\nexport function introspectToFields(schema: ZodTypeAny | undefined, hints?: FieldHints, prefix = \"\"): Field[] {\n // Lazy schema's (7.13): vóór registerBuiltinSchemas kan een def nog geen schema hebben —\n // dan geen velden i.p.v. een crash (komt in de praktijk alleen in tests/randgevallen voor).\n if (!schema) return [];\n const shape = shapeOf(schema);\n if (!shape) return [];\n const out: Field[] = [];\n for (const [key, raw] of Object.entries(shape)) {\n if (key.startsWith(\"_\")) continue;\n const { def: inner, description, inner: innerSchema } = unwrap(raw);\n const label = description ?? key;\n const path = prefix ? `${prefix}.${key}` : key;\n const hint = hints?.[path];\n const push = (f: Field) => out.push({ ...f, ...(hint as Partial<Field>), key, label: (hint?.label as string) ?? label } as Field);\n switch (inner?.type) {\n case \"number\":\n push({ key, label, type: \"number\" });\n break;\n case \"boolean\":\n push({ key, label, type: \"switch\" });\n break;\n case \"enum\":\n push({ key, label, type: \"select\", options: valuesOf(inner).map((v) => ({ value: v, label: v })) });\n break;\n case \"string\": {\n const max = maxLengthOf(inner);\n if (key === \"html\" || key.endsWith(\"Html\")) push({ key, label, type: \"richtext\" });\n else if (IMAGE_KEY.test(key)) push({ key, label, type: \"image\" });\n else if (URL_KEY.test(key)) push({ key, label, type: \"url\" });\n else if ((max ?? 0) > 500) push({ key, label, type: \"textarea\", maxLength: max });\n else push({ key, label, type: \"text\", maxLength: max });\n break;\n }\n case \"object\": {\n const sub = introspectToFields(innerSchema as ZodTypeAny, hints, path);\n const isLink = sub.length === 2 && sub.some((f) => f.key === \"label\") && sub.some((f) => f.key === \"href\");\n if (isLink) push({ key, label, type: \"link\" });\n else if (sub.length > 0) push({ key, label, type: \"group\", fields: sub });\n else push({ key, label, type: \"json\" });\n break;\n }\n case \"array\": {\n const { def: elDef, inner: elSchema } = unwrap(inner?.element);\n if (elDef?.type === \"object\") {\n const sub = introspectToFields(elSchema as ZodTypeAny, hints, path);\n if (sub.length > 0) {\n push({ key, label, type: \"repeater\", fields: sub });\n break;\n }\n }\n push({ key, label, type: \"json\" });\n break;\n }\n case \"record\":\n push({ key, label, type: \"json\" });\n break;\n default:\n break;\n }\n }\n return out;\n}\n"],"mappings":"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","names":[]}
@@ -5,6 +5,10 @@ interface ToastApi {
5
5
  title: string;
6
6
  description?: string;
7
7
  tone?: ToastTone;
8
+ action?: {
9
+ label: string;
10
+ onClick: () => void;
11
+ };
8
12
  }) => void;
9
13
  }
10
14
  /**
@@ -9,14 +9,20 @@ function useToast() {
9
9
  const show = useAstryxToast();
10
10
  const showRef = useRef(show);
11
11
  showRef.current = show;
12
- const toast = useCallback(({ title, description, tone = "default" }) => {
12
+ const toast = useCallback(({ title, description, tone = "default", action }) => {
13
13
  showRef.current({
14
14
  body: /* @__PURE__ */ jsxs("span", { children: [
15
15
  /* @__PURE__ */ jsx("strong", { children: title }),
16
- description ? /* @__PURE__ */ jsx("span", { style: { display: "block", opacity: 0.8 }, children: description }) : null
16
+ description ? /* @__PURE__ */ jsx("span", { style: { display: "block", opacity: 0.8 }, children: description }) : null,
17
+ action ? (
18
+ // Eén optionele vervolg-actie in de melding zelf ("Bekijk als bezoeker →"): de melding
19
+ // is precies het moment waarop die vraag leeft, en een knop sluit de mentale lus die
20
+ // een instructie in prozavorm open laat staan.
21
+ /* @__PURE__ */ jsx("button", { type: "button", className: "xicms-toast__actie", onClick: action.onClick, children: action.label })
22
+ ) : null
17
23
  ] }),
18
24
  type: tone === "error" ? "error" : "info",
19
- isAutoHide: tone !== "error"
25
+ isAutoHide: tone !== "error" && !action
20
26
  });
21
27
  }, []);
22
28
  return useMemo(() => ({ toast }), [toast]);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/ui/toast.tsx"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useMemo, useRef, type ReactNode } from \"react\";\nimport { useToast as useAstryxToast, ToastViewport } from \"@astryxdesign/core/Toast\";\n\ntype ToastTone = \"default\" | \"success\" | \"error\";\ninterface ToastApi {\n toast: (t: { title: string; description?: string; tone?: ToastTone }) => void;\n}\n\n/**\n * Admin-toasts op de **Astryx Toast** (ToastViewport + useToast). De bestaande API\n * `toast({title, description, tone})` blijft, zodat de editors ongewijzigd blijven. Fouten blijven\n * staan tot de gebruiker ze sluit (auto-hide uit); overige verdwijnen vanzelf.\n */\nexport function ToastProvider({ children }: { children: ReactNode }) {\n return (\n <ToastViewport position=\"bottomEnd\" maxVisible={4}>\n {children}\n </ToastViewport>\n );\n}\n\n/**\n * `toast` MOET stabiel zijn over renders heen.\n *\n * Dit gaf `{ toast: (t) => … }` terug — een nieuw object met een nieuwe functie bij ELKE render.\n * Op zichzelf onschuldig, tot je bedenkt dat drie effecten in het beheer `toast` in hun\n * afhankelijkheden hebben (de mediakiezer, de mediabeheerder en de visuele editor). Zo'n effect\n * draait dan bij elke render opnieuw, zet state, en die state veroorzaakt weer een render:\n * \"Maximum update depth exceeded\", en een pagina die niet meer laadt.\n *\n * Dat maakte de visuele editor onbruikbaar op élke pagina mét werkversie — daar hangt de\n * mediakiezer in de boom. Op een pagina zónder werkversie kwam de editor nooit zo ver, en precies\n * daarom kon dit lang onopgemerkt blijven: wie het probeerde op een lege pagina zag niets bijzonders.\n *\n * `show` komt uit Astryx en biedt geen garantie over zijn eigen stabiliteit; daarom via een ref, en\n * niet als afhankelijkheid. Anders verplaatsen we het probleem alleen maar één laag.\n */\nexport function useToast(): ToastApi {\n const show = useAstryxToast();\n const showRef = useRef(show);\n showRef.current = show;\n\n const toast = useCallback<ToastApi[\"toast\"]>(({ title, description, tone = \"default\" }) => {\n showRef.current({\n body: (\n <span>\n <strong>{title}</strong>\n {description ? <span style={{ display: \"block\", opacity: 0.8 }}>{description}</span> : null}\n </span>\n ),\n type: tone === \"error\" ? \"error\" : \"info\",\n isAutoHide: tone !== \"error\",\n });\n }, []);\n\n return useMemo(() => ({ toast }), [toast]);\n}\n"],"mappings":";AAiBI,cA8BI,YA9BJ;AAfJ,SAAS,aAAa,SAAS,cAA8B;AAC7D,SAAS,YAAY,gBAAgB,qBAAqB;AAYnD,SAAS,cAAc,EAAE,SAAS,GAA4B;AACnE,SACE,oBAAC,iBAAc,UAAS,aAAY,YAAY,GAC7C,UACH;AAEJ;AAkBO,SAAS,WAAqB;AACnC,QAAM,OAAO,eAAe;AAC5B,QAAM,UAAU,OAAO,IAAI;AAC3B,UAAQ,UAAU;AAElB,QAAM,QAAQ,YAA+B,CAAC,EAAE,OAAO,aAAa,OAAO,UAAU,MAAM;AACzF,YAAQ,QAAQ;AAAA,MACd,MACE,qBAAC,UACC;AAAA,4BAAC,YAAQ,iBAAM;AAAA,QACd,cAAc,oBAAC,UAAK,OAAO,EAAE,SAAS,SAAS,SAAS,IAAI,GAAI,uBAAY,IAAU;AAAA,SACzF;AAAA,MAEF,MAAM,SAAS,UAAU,UAAU;AAAA,MACnC,YAAY,SAAS;AAAA,IACvB,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAEL,SAAO,QAAQ,OAAO,EAAE,MAAM,IAAI,CAAC,KAAK,CAAC;AAC3C;","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/ui/toast.tsx"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useMemo, useRef, type ReactNode } from \"react\";\nimport { useToast as useAstryxToast, ToastViewport } from \"@astryxdesign/core/Toast\";\n\ntype ToastTone = \"default\" | \"success\" | \"error\";\ninterface ToastApi {\n toast: (t: { title: string; description?: string; tone?: ToastTone; action?: { label: string; onClick: () => void } }) => void;\n}\n\n/**\n * Admin-toasts op de **Astryx Toast** (ToastViewport + useToast). De bestaande API\n * `toast({title, description, tone})` blijft, zodat de editors ongewijzigd blijven. Fouten blijven\n * staan tot de gebruiker ze sluit (auto-hide uit); overige verdwijnen vanzelf.\n */\nexport function ToastProvider({ children }: { children: ReactNode }) {\n return (\n <ToastViewport position=\"bottomEnd\" maxVisible={4}>\n {children}\n </ToastViewport>\n );\n}\n\n/**\n * `toast` MOET stabiel zijn over renders heen.\n *\n * Dit gaf `{ toast: (t) => … }` terug — een nieuw object met een nieuwe functie bij ELKE render.\n * Op zichzelf onschuldig, tot je bedenkt dat drie effecten in het beheer `toast` in hun\n * afhankelijkheden hebben (de mediakiezer, de mediabeheerder en de visuele editor). Zo'n effect\n * draait dan bij elke render opnieuw, zet state, en die state veroorzaakt weer een render:\n * \"Maximum update depth exceeded\", en een pagina die niet meer laadt.\n *\n * Dat maakte de visuele editor onbruikbaar op élke pagina mét werkversie — daar hangt de\n * mediakiezer in de boom. Op een pagina zónder werkversie kwam de editor nooit zo ver, en precies\n * daarom kon dit lang onopgemerkt blijven: wie het probeerde op een lege pagina zag niets bijzonders.\n *\n * `show` komt uit Astryx en biedt geen garantie over zijn eigen stabiliteit; daarom via een ref, en\n * niet als afhankelijkheid. Anders verplaatsen we het probleem alleen maar één laag.\n */\nexport function useToast(): ToastApi {\n const show = useAstryxToast();\n const showRef = useRef(show);\n showRef.current = show;\n\n const toast = useCallback<ToastApi[\"toast\"]>(({ title, description, tone = \"default\", action }) => {\n showRef.current({\n body: (\n <span>\n <strong>{title}</strong>\n {description ? <span style={{ display: \"block\", opacity: 0.8 }}>{description}</span> : null}\n {action ? (\n // Eén optionele vervolg-actie in de melding zelf (\"Bekijk als bezoeker →\"): de melding\n // is precies het moment waarop die vraag leeft, en een knop sluit de mentale lus die\n // een instructie in prozavorm open laat staan.\n <button type=\"button\" className=\"xicms-toast__actie\" onClick={action.onClick}>\n {action.label}\n </button>\n ) : null}\n </span>\n ),\n type: tone === \"error\" ? \"error\" : \"info\",\n isAutoHide: tone !== \"error\" && !action,\n });\n }, []);\n\n return useMemo(() => ({ toast }), [toast]);\n}\n"],"mappings":";AAiBI,cA8BI,YA9BJ;AAfJ,SAAS,aAAa,SAAS,cAA8B;AAC7D,SAAS,YAAY,gBAAgB,qBAAqB;AAYnD,SAAS,cAAc,EAAE,SAAS,GAA4B;AACnE,SACE,oBAAC,iBAAc,UAAS,aAAY,YAAY,GAC7C,UACH;AAEJ;AAkBO,SAAS,WAAqB;AACnC,QAAM,OAAO,eAAe;AAC5B,QAAM,UAAU,OAAO,IAAI;AAC3B,UAAQ,UAAU;AAElB,QAAM,QAAQ,YAA+B,CAAC,EAAE,OAAO,aAAa,OAAO,WAAW,OAAO,MAAM;AACjG,YAAQ,QAAQ;AAAA,MACd,MACE,qBAAC,UACC;AAAA,4BAAC,YAAQ,iBAAM;AAAA,QACd,cAAc,oBAAC,UAAK,OAAO,EAAE,SAAS,SAAS,SAAS,IAAI,GAAI,uBAAY,IAAU;AAAA,QACtF;AAAA;AAAA;AAAA;AAAA,UAIC,oBAAC,YAAO,MAAK,UAAS,WAAU,sBAAqB,SAAS,OAAO,SAClE,iBAAO,OACV;AAAA,YACE;AAAA,SACN;AAAA,MAEF,MAAM,SAAS,UAAU,UAAU;AAAA,MACnC,YAAY,SAAS,WAAW,CAAC;AAAA,IACnC,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAEL,SAAO,QAAQ,OAAO,EAAE,MAAM,IAAI,CAAC,KAAK,CAAC;AAC3C;","names":[]}
@@ -1,5 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { registerBuiltinSchemas } from "../../blocks/builtinSchemas.js";
4
+ registerBuiltinSchemas();
3
5
  import "@xiwebdesign/xicms-core/admin.css";
4
6
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
5
7
  import { useSiteConfig } from "../../config.js";
@@ -203,6 +205,20 @@ function VisualEditorInner({ onClose }) {
203
205
  const handleClose = useCallback(() => {
204
206
  void flushSave().finally(() => onClose());
205
207
  }, [flushSave, onClose]);
208
+ const [toonStartHint, setToonStartHint] = useState(false);
209
+ useEffect(() => {
210
+ try {
211
+ if (!localStorage.getItem("xicms-hint-bewerken-v1")) setToonStartHint(true);
212
+ } catch {
213
+ }
214
+ }, []);
215
+ const sluitStartHint = useCallback(() => {
216
+ setToonStartHint(false);
217
+ try {
218
+ localStorage.setItem("xicms-hint-bewerken-v1", "1");
219
+ } catch {
220
+ }
221
+ }, []);
206
222
  useEffect(() => {
207
223
  let active = true;
208
224
  getDraftByPath(siteId, path).then((d) => {
@@ -856,8 +872,10 @@ function VisualEditorInner({ onClose }) {
856
872
  setLiveBlocks(structuredClone(d.blocks));
857
873
  toast({
858
874
  title: "Gepubliceerd",
859
- description: demo ? "In deze demo blijft de wijziging in je browser staan." : "Zelf zie je de nieuwe tekst meteen. Voor overige bezoekers duurt het nog ongeveer 15 minuten.",
860
- tone: "success"
875
+ description: demo ? "In deze demo blijft de wijziging in je browser staan." : "Binnen enkele seconden zichtbaar voor iedereen.",
876
+ tone: "success",
877
+ // Sluit de mentale lus "staat het er nu echt?": één klik en je kijkt als bezoeker.
878
+ action: { label: "Bekijk als bezoeker \u2192", onClick: handleClose }
861
879
  });
862
880
  } catch (e) {
863
881
  toast({ title: "Publiceren mislukt", description: e.message, tone: "error" });
@@ -881,6 +899,18 @@ function VisualEditorInner({ onClose }) {
881
899
  setConflict(false);
882
900
  }, [siteId, path, mutateDraft]);
883
901
  return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "xicms-ve__shell", "data-xicms-tooling": "", role: "group", "aria-label": `Pagina bewerken: ${path}`, children: [
902
+ toonStartHint && /* @__PURE__ */ jsxs("div", { className: "xicms-ve-starthint", role: "status", children: [
903
+ /* @__PURE__ */ jsxs("span", { children: [
904
+ "\u270F\uFE0F Klik het ",
905
+ /* @__PURE__ */ jsx("strong", { children: "potlood" }),
906
+ " naast een tekst om die op de pagina zelf te bewerken; foto's wissel je met ",
907
+ /* @__PURE__ */ jsx("strong", { children: "\u{1F4F7} Foto wijzigen" }),
908
+ ". Klaar? ",
909
+ /* @__PURE__ */ jsx("strong", { children: "Publiceren" }),
910
+ " staat rechtsonder."
911
+ ] }),
912
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: sluitStartHint, children: "Begrepen" })
913
+ ] }),
884
914
  /* @__PURE__ */ jsxs("header", { className: "xicms-ve__bar", role: "toolbar", "aria-label": "Beheer", children: [
885
915
  /* @__PURE__ */ jsx("span", { className: "xicms-editbar__brand", children: isDemoActive() ? "XiCMS \xB7 demo" : "XiCMS" }),
886
916
  /* @__PURE__ */ jsx(