@xiwebdesign/xicms-core 7.29.0 → 7.31.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.
@@ -53,59 +53,88 @@ function AdminApp({
53
53
  /* @__PURE__ */ jsx("strong", { children: user.email }),
54
54
  ", maar hebt geen beheerrechten voor deze site.",
55
55
  /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", onClick: () => void signOut(), children: "Uitloggen" }) })
56
- ] }) : panels.length === 0 ? /* @__PURE__ */ jsx(Melding, { children: "Geen actieve beheer-modules voor deze site." }) : /* @__PURE__ */ jsx(AdminNavContext.Provider, { value: setActief, children: /* @__PURE__ */ jsxs(Tabs, { value: actief ?? firstTab, onValueChange: setActief, orientation: "vertical", className: "xicms-admin__raster md:grid md:grid-cols-[248px_1fr]", children: [
57
- /* @__PURE__ */ jsxs(
58
- "div",
59
- {
60
- 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",
61
- style: { background: "var(--admin-sidebar)" },
62
- children: [
63
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 px-2 pt-1", children: [
64
- /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-semibold", children: name }),
65
- /* @__PURE__ */ jsx("p", { className: "text-xs text-slate-500", children: "Beheer" })
66
- ] }),
67
- /* @__PURE__ */ jsx(TabsPrimitive.List, { "aria-label": "Beheer-secties", className: "xicms-admin__lijst -mx-1 flex gap-1 overflow-x-auto md:flex-col md:overflow-visible", children: panels.map((p) => /* @__PURE__ */ jsxs(
68
- TabsPrimitive.Trigger,
56
+ ] }) : panels.length === 0 ? /* @__PURE__ */ jsx(Melding, { children: "Geen actieve beheer-modules voor deze site." }) : /* @__PURE__ */ jsx(AdminNavContext.Provider, { value: setActief, children: /* @__PURE__ */ jsxs(
57
+ Tabs,
58
+ {
59
+ value: actief ?? firstTab,
60
+ onValueChange: setActief,
61
+ orientation: "vertical",
62
+ className: embedded ? "xicms-admin__raster" : "xicms-admin__raster md:grid md:grid-cols-[248px_1fr]",
63
+ children: [
64
+ /* @__PURE__ */ jsxs(
65
+ "div",
66
+ {
67
+ className: embedded ? "xicms-admin__rail flex flex-row flex-wrap items-center gap-4 border-b border-slate-200 p-2" : "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",
68
+ style: { background: "var(--admin-sidebar)" },
69
+ children: [
70
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 px-2 pt-1", children: [
71
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-semibold", children: name }),
72
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-slate-500", children: "Beheer" })
73
+ ] }),
74
+ /* @__PURE__ */ jsx(
75
+ TabsPrimitive.List,
76
+ {
77
+ "aria-label": "Beheer-secties",
78
+ className: embedded ? "xicms-admin__lijst -mx-1 flex flex-row gap-1 overflow-x-auto" : "xicms-admin__lijst -mx-1 flex gap-1 overflow-x-auto md:flex-col md:overflow-visible",
79
+ children: panels.map((p) => /* @__PURE__ */ jsxs(
80
+ TabsPrimitive.Trigger,
81
+ {
82
+ value: sleutel(p),
83
+ 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)]",
84
+ children: [
85
+ /* @__PURE__ */ jsx(PanelIcon, { id: sleutel(p) }),
86
+ p.label
87
+ ]
88
+ },
89
+ sleutel(p)
90
+ ))
91
+ }
92
+ ),
93
+ /* @__PURE__ */ jsxs(
94
+ "div",
95
+ {
96
+ className: embedded ? "xicms-admin__voet ml-auto flex min-w-0 flex-row items-center gap-3 border-t-0 pt-0" : "xicms-admin__voet mt-auto hidden min-w-0 flex-col gap-3 border-t border-slate-200 pt-3 md:flex",
97
+ children: [
98
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
99
+ /* @__PURE__ */ jsx("p", { className: "truncate text-xs font-medium text-slate-600", title: user.email ?? void 0, children: user.email }),
100
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-slate-400", children: role })
101
+ ] }),
102
+ /* @__PURE__ */ jsx(CoreVersion, {}),
103
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", onClick: () => void signOut(), children: "Uitloggen" })
104
+ ]
105
+ }
106
+ )
107
+ ]
108
+ }
109
+ ),
110
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
111
+ /* @__PURE__ */ jsxs(
112
+ "header",
69
113
  {
70
- value: sleutel(p),
71
- 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)]",
114
+ className: embedded ? "xicms-admin__kop static z-10 flex items-center justify-between gap-4 border-b border-slate-200 px-4 py-3 md:px-8" : "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",
115
+ style: { background: "var(--admin-canvas)" },
72
116
  children: [
73
- /* @__PURE__ */ jsx(PanelIcon, { id: sleutel(p) }),
74
- p.label
117
+ /* @__PURE__ */ jsx("h1", { className: "truncate text-base font-semibold", children: actievePanel?.label ?? "Beheer" }),
118
+ /* @__PURE__ */ jsx(
119
+ "a",
120
+ {
121
+ 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",
122
+ href: "/",
123
+ children: "Bekijk site \u2197"
124
+ }
125
+ )
75
126
  ]
76
- },
77
- sleutel(p)
78
- )) }),
79
- /* @__PURE__ */ jsxs("div", { className: "xicms-admin__voet mt-auto hidden min-w-0 flex-col gap-3 border-t border-slate-200 pt-3 md:flex", children: [
80
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
81
- /* @__PURE__ */ jsx("p", { className: "truncate text-xs font-medium text-slate-600", title: user.email ?? void 0, children: user.email }),
82
- /* @__PURE__ */ jsx("p", { className: "text-xs text-slate-400", children: role })
83
- ] }),
84
- /* @__PURE__ */ jsx(CoreVersion, {}),
85
- /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", onClick: () => void signOut(), children: "Uitloggen" })
127
+ }
128
+ ),
129
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-6 p-4 md:p-8", children: [
130
+ /* @__PURE__ */ jsx(WatIsNieuw, {}),
131
+ panels.map((p) => /* @__PURE__ */ jsx(TabsContent, { value: sleutel(p), className: "pt-0", children: /* @__PURE__ */ jsx(p.Panel, { siteId }) }, sleutel(p))),
132
+ /* @__PURE__ */ jsx(AdminHelp, {})
86
133
  ] })
87
- ]
88
- }
89
- ),
90
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
91
- /* @__PURE__ */ jsxs("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)" }, children: [
92
- /* @__PURE__ */ jsx("h1", { className: "truncate text-base font-semibold", children: actievePanel?.label ?? "Beheer" }),
93
- /* @__PURE__ */ jsx(
94
- "a",
95
- {
96
- 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",
97
- href: "/",
98
- children: "Bekijk site \u2197"
99
- }
100
- )
101
- ] }),
102
- /* @__PURE__ */ jsxs("div", { className: "grid gap-6 p-4 md:p-8", children: [
103
- /* @__PURE__ */ jsx(WatIsNieuw, {}),
104
- panels.map((p) => /* @__PURE__ */ jsx(TabsContent, { value: sleutel(p), className: "pt-0", children: /* @__PURE__ */ jsx(p.Panel, { siteId }) }, sleutel(p))),
105
- /* @__PURE__ */ jsx(AdminHelp, {})
106
- ] })
107
- ] })
108
- ] }) }) }) }) });
134
+ ] })
135
+ ]
136
+ }
137
+ ) }) }) }) });
109
138
  }
110
139
  export {
111
140
  AdminApp
@@ -1 +1 @@
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 { AdminNavContext } from \"./navContext\";\nimport { AdminHelp } from \"./AdminHelp\";\nimport { CoreVersion, WatIsNieuw } 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 <AdminNavContext.Provider value={setActief}>\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 {/* Op paneel-sleutel, niet module-id: Menu, Bouwstenen en Start delen de\n content-module maar zijn drie verschillende plekken met drie vormen. */}\n <PanelIcon id={sleutel(p)} />\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 <WatIsNieuw />\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 </AdminNavContext.Provider>\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 { AdminNavContext } from \"./navContext\";\nimport { AdminHelp } from \"./AdminHelp\";\nimport { CoreVersion, WatIsNieuw } 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 <AdminNavContext.Provider value={setActief}>\n {/*\n * Ingebed vs. losstaand kiest hier al TUSSEN de Tailwind-classnamen, niet erna via CSS.\n *\n * De eerdere aanpak (7.28/7.29) probeerde deze layout via CSS te overrulen — een\n * `.xicms-admin--ingebed`-selector met `!important` tegen Tailwinds eigen, ook al\n * `!important` gemaakte utilities (de `@scope`-import bovenin admin.css). Op de echte\n * klantsite bleek dat na TWEE releases nog steeds niet te werken: `getComputedStyle`\n * liet zien dat `md:grid`/`md:sticky`/`flex-col` het bleven winnen, wat een CSS-\n * cascade-gevecht is dat theoretisch nooit betrouwbaar te winnen is (laag-volgorde\n * en @scope-proximiteit gedragen zich niet zoals de commentaren daar veronderstelden).\n * De enige robuuste oplossing: de conflicterende classnamen helemaal niet uitzenden\n * wanneer ingebed, zodat er geen cascade-gevecht kán ontstaan.\n */}\n <Tabs\n value={actief ?? firstTab}\n onValueChange={setActief}\n orientation=\"vertical\"\n className={embedded ? \"xicms-admin__raster\" : \"xicms-admin__raster md:grid md:grid-cols-[248px_1fr]\"}\n >\n {/* ── Zijbalk ─────────────────────────────────────────────────────────── */}\n <div\n className={\n embedded\n ? \"xicms-admin__rail flex flex-row flex-wrap items-center gap-4 border-b border-slate-200 p-2\"\n : \"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 }\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\n aria-label=\"Beheer-secties\"\n className={\n embedded\n ? \"xicms-admin__lijst -mx-1 flex flex-row gap-1 overflow-x-auto\"\n : \"xicms-admin__lijst -mx-1 flex gap-1 overflow-x-auto md:flex-col md:overflow-visible\"\n }\n >\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 {/* Op paneel-sleutel, niet module-id: Menu, Bouwstenen en Start delen de\n content-module maar zijn drie verschillende plekken met drie vormen. */}\n <PanelIcon id={sleutel(p)} />\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\n className={\n embedded\n ? \"xicms-admin__voet ml-auto flex min-w-0 flex-row items-center gap-3 border-t-0 pt-0\"\n : \"xicms-admin__voet mt-auto hidden min-w-0 flex-col gap-3 border-t border-slate-200 pt-3 md:flex\"\n }\n >\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\n className={\n embedded\n ? \"xicms-admin__kop static z-10 flex items-center justify-between gap-4 border-b border-slate-200 px-4 py-3 md:px-8\"\n : \"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\"\n }\n style={{ background: \"var(--admin-canvas)\" }}\n >\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 <WatIsNieuw />\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 </AdminNavContext.Provider>\n )}\n </ConfirmHost>\n </ToastProvider>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -2,6 +2,7 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEditor, EditorContent } from "@tiptap/react";
4
4
  import StarterKit from "@tiptap/starter-kit";
5
+ import { TableKit } from "@tiptap/extension-table";
5
6
  import Link from "@tiptap/extension-link";
6
7
  import Image from "@tiptap/extension-image";
7
8
  import DOMPurify from "dompurify";
@@ -22,6 +23,9 @@ function RichTextEditor({
22
23
  // zetten we uit omdat we die hieronder zelf configureren (geen open-on-click, eigen rel).
23
24
  StarterKit.configure({ heading: { levels: [2, 3, 4] }, link: false }),
24
25
  FontSize,
26
+ // Zonder deze extensie gooit TipTap een <table> bij het inladen weg (onbekende node) — een
27
+ // tabel uit de seed of een import overleefde dan geen enkele bewerking van dat blok.
28
+ TableKit.configure({ table: { resizable: false } }),
25
29
  Link.configure({ openOnClick: false, autolink: true, HTMLAttributes: { rel: "noopener noreferrer nofollow" } }),
26
30
  Image.configure({ inline: false, allowBase64: false, HTMLAttributes: { loading: "lazy", decoding: "async" } })
27
31
  ],
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/editors/RichTextEditor.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEditor, EditorContent } from \"@tiptap/react\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport Link from \"@tiptap/extension-link\";\nimport Image from \"@tiptap/extension-image\";\nimport DOMPurify from \"dompurify\";\nimport { useCallback, useEffect, type ReactNode } from \"react\";\nimport { cn } from \"../ui/cn\";\nimport { MediaPickerButton } from \"../media/MediaPicker\";\nimport { FontSize, FONT_SIZES } from \"./fontSizeExtension\";\n\n/**\n * WYSIWYG-editor (TipTap) met opmaak én afbeeldingen. Output wordt client-side met DOMPurify\n * gesanitized voor preview/opslag; de server sanitized nogmaals bij publiceren (server/sanitize.ts)\n * — nooit op de client vertrouwen. Geef `siteId` mee om afbeeldingen te kunnen uploaden/invoegen.\n */\nexport function RichTextEditor({\n value,\n onChange,\n siteId,\n}: {\n value: string;\n onChange: (html: string) => void;\n siteId?: string;\n}) {\n\n const editor = useEditor({\n immediatelyRender: false,\n extensions: [\n // Tiptap 3 bundelt Link en Underline IN StarterKit. Los toevoegen geeft dubbele\n // extensienamen (en stille gedragsfouten), dus: Underline komt uit StarterKit, en zijn Link\n // zetten we uit omdat we die hieronder zelf configureren (geen open-on-click, eigen rel).\n StarterKit.configure({ heading: { levels: [2, 3, 4] }, link: false }),\n FontSize,\n Link.configure({ openOnClick: false, autolink: true, HTMLAttributes: { rel: \"noopener noreferrer nofollow\" } }),\n Image.configure({ inline: false, allowBase64: false, HTMLAttributes: { loading: \"lazy\", decoding: \"async\" } }),\n ],\n content: value || \"<p></p>\",\n onUpdate: ({ editor }) => onChange(DOMPurify.sanitize(editor.getHTML())),\n editorProps: {\n attributes: { class: \"min-h-[8rem] rounded-b-lg border border-t-0 border-slate-300 bg-white p-3 text-sm\" },\n },\n });\n\n // Externe waarde-wijzigingen (undo/redo in de VisualEditor, taal-wissel in het vertaalpaneel)\n // in de editor doorvoeren: TipTap leest `content` alleen bij init. Zonder deze sync blijft de\n // zichtbare HTML stale terwijl de opgeslagen waarde al veranderd is — data-integriteitsval.\n // Niet syncen terwijl de gebruiker zelf typt (editor.isFocused) — dan is de editor de bron.\n useEffect(() => {\n if (!editor || editor.isFocused) return;\n const current = editor.getHTML();\n const next = value || \"<p></p>\";\n // Tiptap 3: de tweede parameter is een options-object i.p.v. een boolean.\n if (current !== next) editor.commands.setContent(next, { emitUpdate: false });\n }, [value, editor]);\n\n const setLink = useCallback(() => {\n if (!editor) return;\n const prev = editor.getAttributes(\"link\").href as string | undefined;\n const url = window.prompt(\"Link-URL (leeg = verwijderen)\", prev ?? \"https://\");\n if (url === null) return;\n if (url === \"\") editor.chain().focus().extendMarkRange(\"link\").unsetLink().run();\n else editor.chain().focus().extendMarkRange(\"link\").setLink({ href: url }).run();\n }, [editor]);\n\n const insertImageByUrl = useCallback(() => {\n if (!editor) return;\n const url = window.prompt(\"Afbeeldings-URL (https)\");\n if (!url) return;\n const alt = window.prompt(\"Beschrijving (alt-tekst, voor toegankelijkheid)\") ?? \"\";\n editor.chain().focus().setImage({ src: url, alt }).run();\n }, [editor]);\n\n const setFontSize = useCallback(\n (value: string) => {\n if (!editor) return;\n editor.chain().focus().setMark(\"textStyle\", { fontSize: value || null }).run();\n },\n [editor],\n );\n\n if (!editor) return null;\n\n const Btn = ({ on, active, label, title }: { on: () => void; active?: boolean; label: ReactNode; title?: string }) => (\n <button\n type=\"button\"\n title={title}\n onMouseDown={(e) => e.preventDefault()}\n onClick={on}\n className={cn(\n \"h-8 min-w-8 rounded px-2 text-sm hover:bg-slate-100\",\n active && \"bg-slate-200 font-semibold\",\n )}\n >\n {label}\n </button>\n );\n\n return (\n <div>\n <div className=\"flex flex-wrap gap-1 rounded-t-lg border border-slate-300 bg-slate-50 p-1\">\n <Btn on={() => editor.chain().focus().toggleBold().run()} active={editor.isActive(\"bold\")} label=\"B\" title=\"Vet\" />\n <Btn on={() => editor.chain().focus().toggleItalic().run()} active={editor.isActive(\"italic\")} label=\"I\" title=\"Cursief\" />\n <Btn on={() => editor.chain().focus().toggleUnderline().run()} active={editor.isActive(\"underline\")} label={<span style={{ textDecoration: \"underline\" }}>U</span>} title=\"Onderstrepen\" />\n <Btn on={() => editor.chain().focus().toggleStrike().run()} active={editor.isActive(\"strike\")} label={<span style={{ textDecoration: \"line-through\" }}>S</span>} title=\"Doorstrepen\" />\n <select\n className=\"h-8 rounded border border-slate-300 bg-white px-1 text-sm\"\n value={(editor.getAttributes(\"textStyle\").fontSize as string | undefined) ?? \"\"}\n onChange={(e) => setFontSize(e.target.value)}\n title=\"Lettergrootte\"\n aria-label=\"Lettergrootte\"\n >\n {FONT_SIZES.map((s) => (\n <option key={s.label} value={s.value}>\n {s.label}\n </option>\n ))}\n </select>\n <Btn on={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} active={editor.isActive(\"heading\", { level: 2 })} label=\"H2\" title=\"Kop\" />\n <Btn on={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} active={editor.isActive(\"heading\", { level: 3 })} label=\"H3\" title=\"Subkop\" />\n <Btn on={() => editor.chain().focus().toggleBulletList().run()} active={editor.isActive(\"bulletList\")} label=\"• Lijst\" title=\"Opsomming\" />\n <Btn on={() => editor.chain().focus().toggleOrderedList().run()} active={editor.isActive(\"orderedList\")} label=\"1. Lijst\" title=\"Genummerde lijst\" />\n <Btn on={() => editor.chain().focus().toggleBlockquote().run()} active={editor.isActive(\"blockquote\")} label=\"❝\" title=\"Citaat\" />\n <Btn on={setLink} active={editor.isActive(\"link\")} label=\"🔗\" title=\"Link\" />\n {siteId && (\n <MediaPickerButton\n siteId={siteId}\n label=\"🖼 Afbeelding\"\n variant=\"ghost\"\n onSelect={(m) => editor.chain().focus().setImage({ src: m.url, alt: m.alt ?? \"\" }).run()}\n />\n )}\n <Btn on={insertImageByUrl} label=\"🖼 URL\" title=\"Externe afbeelding via URL invoegen\" />\n </div>\n <EditorContent editor={editor} />\n </div>\n );\n}\n"],"mappings":";AAqFI,cAgBE,YAhBF;AAnFJ,SAAS,WAAW,qBAAqB;AACzC,OAAO,gBAAgB;AACvB,OAAO,UAAU;AACjB,OAAO,WAAW;AAClB,OAAO,eAAe;AACtB,SAAS,aAAa,iBAAiC;AACvD,SAAS,UAAU;AACnB,SAAS,yBAAyB;AAClC,SAAS,UAAU,kBAAkB;AAO9B,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AAED,QAAM,SAAS,UAAU;AAAA,IACvB,mBAAmB;AAAA,IACnB,YAAY;AAAA;AAAA;AAAA;AAAA,MAIV,WAAW,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC,GAAG,GAAG,CAAC,EAAE,GAAG,MAAM,MAAM,CAAC;AAAA,MACpE;AAAA,MACA,KAAK,UAAU,EAAE,aAAa,OAAO,UAAU,MAAM,gBAAgB,EAAE,KAAK,+BAA+B,EAAE,CAAC;AAAA,MAC9G,MAAM,UAAU,EAAE,QAAQ,OAAO,aAAa,OAAO,gBAAgB,EAAE,SAAS,QAAQ,UAAU,QAAQ,EAAE,CAAC;AAAA,IAC/G;AAAA,IACA,SAAS,SAAS;AAAA,IAClB,UAAU,CAAC,EAAE,QAAAA,QAAO,MAAM,SAAS,UAAU,SAASA,QAAO,QAAQ,CAAC,CAAC;AAAA,IACvE,aAAa;AAAA,MACX,YAAY,EAAE,OAAO,oFAAoF;AAAA,IAC3G;AAAA,EACF,CAAC;AAMD,YAAU,MAAM;AACd,QAAI,CAAC,UAAU,OAAO,UAAW;AACjC,UAAM,UAAU,OAAO,QAAQ;AAC/B,UAAM,OAAO,SAAS;AAEtB,QAAI,YAAY,KAAM,QAAO,SAAS,WAAW,MAAM,EAAE,YAAY,MAAM,CAAC;AAAA,EAC9E,GAAG,CAAC,OAAO,MAAM,CAAC;AAElB,QAAM,UAAU,YAAY,MAAM;AAChC,QAAI,CAAC,OAAQ;AACb,UAAM,OAAO,OAAO,cAAc,MAAM,EAAE;AAC1C,UAAM,MAAM,OAAO,OAAO,iCAAiC,QAAQ,UAAU;AAC7E,QAAI,QAAQ,KAAM;AAClB,QAAI,QAAQ,GAAI,QAAO,MAAM,EAAE,MAAM,EAAE,gBAAgB,MAAM,EAAE,UAAU,EAAE,IAAI;AAAA,QAC1E,QAAO,MAAM,EAAE,MAAM,EAAE,gBAAgB,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC,EAAE,IAAI;AAAA,EACjF,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,mBAAmB,YAAY,MAAM;AACzC,QAAI,CAAC,OAAQ;AACb,UAAM,MAAM,OAAO,OAAO,yBAAyB;AACnD,QAAI,CAAC,IAAK;AACV,UAAM,MAAM,OAAO,OAAO,iDAAiD,KAAK;AAChF,WAAO,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,KAAK,IAAI,CAAC,EAAE,IAAI;AAAA,EACzD,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,cAAc;AAAA,IAClB,CAACC,WAAkB;AACjB,UAAI,CAAC,OAAQ;AACb,aAAO,MAAM,EAAE,MAAM,EAAE,QAAQ,aAAa,EAAE,UAAUA,UAAS,KAAK,CAAC,EAAE,IAAI;AAAA,IAC/E;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAEA,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,MAAM,CAAC,EAAE,IAAI,QAAQ,OAAO,MAAM,MACtC;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL;AAAA,MACA,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,MACrC,SAAS;AAAA,MACT,WAAW;AAAA,QACT;AAAA,QACA,UAAU;AAAA,MACZ;AAAA,MAEC;AAAA;AAAA,EACH;AAGF,SACE,qBAAC,SACC;AAAA,yBAAC,SAAI,WAAU,6EACb;AAAA,0BAAC,OAAI,IAAI,MAAM,OAAO,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,GAAG,QAAQ,OAAO,SAAS,MAAM,GAAG,OAAM,KAAI,OAAM,OAAM;AAAA,MACjH,oBAAC,OAAI,IAAI,MAAM,OAAO,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,GAAG,QAAQ,OAAO,SAAS,QAAQ,GAAG,OAAM,KAAI,OAAM,WAAU;AAAA,MACzH,oBAAC,OAAI,IAAI,MAAM,OAAO,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,IAAI,GAAG,QAAQ,OAAO,SAAS,WAAW,GAAG,OAAO,oBAAC,UAAK,OAAO,EAAE,gBAAgB,YAAY,GAAG,eAAC,GAAS,OAAM,gBAAe;AAAA,MACzL,oBAAC,OAAI,IAAI,MAAM,OAAO,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,GAAG,QAAQ,OAAO,SAAS,QAAQ,GAAG,OAAO,oBAAC,UAAK,OAAO,EAAE,gBAAgB,eAAe,GAAG,eAAC,GAAS,OAAM,eAAc;AAAA,MACrL;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,OAAQ,OAAO,cAAc,WAAW,EAAE,YAAmC;AAAA,UAC7E,UAAU,CAAC,MAAM,YAAY,EAAE,OAAO,KAAK;AAAA,UAC3C,OAAM;AAAA,UACN,cAAW;AAAA,UAEV,qBAAW,IAAI,CAAC,MACf,oBAAC,YAAqB,OAAO,EAAE,OAC5B,YAAE,SADQ,EAAE,KAEf,CACD;AAAA;AAAA,MACH;AAAA,MACA,oBAAC,OAAI,IAAI,MAAM,OAAO,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,GAAG,QAAQ,OAAO,SAAS,WAAW,EAAE,OAAO,EAAE,CAAC,GAAG,OAAM,MAAK,OAAM,OAAM;AAAA,MAClJ,oBAAC,OAAI,IAAI,MAAM,OAAO,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,GAAG,QAAQ,OAAO,SAAS,WAAW,EAAE,OAAO,EAAE,CAAC,GAAG,OAAM,MAAK,OAAM,UAAS;AAAA,MACrJ,oBAAC,OAAI,IAAI,MAAM,OAAO,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,IAAI,GAAG,QAAQ,OAAO,SAAS,YAAY,GAAG,OAAM,gBAAU,OAAM,aAAY;AAAA,MACzI,oBAAC,OAAI,IAAI,MAAM,OAAO,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,IAAI,GAAG,QAAQ,OAAO,SAAS,aAAa,GAAG,OAAM,YAAW,OAAM,oBAAmB;AAAA,MACnJ,oBAAC,OAAI,IAAI,MAAM,OAAO,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,IAAI,GAAG,QAAQ,OAAO,SAAS,YAAY,GAAG,OAAM,UAAI,OAAM,UAAS;AAAA,MAChI,oBAAC,OAAI,IAAI,SAAS,QAAQ,OAAO,SAAS,MAAM,GAAG,OAAM,aAAK,OAAM,QAAO;AAAA,MAC1E,UACC;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,OAAM;AAAA,UACN,SAAQ;AAAA,UACR,UAAU,CAAC,MAAM,OAAO,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,KAAK,EAAE,OAAO,GAAG,CAAC,EAAE,IAAI;AAAA;AAAA,MACzF;AAAA,MAEF,oBAAC,OAAI,IAAI,kBAAkB,OAAM,iBAAS,OAAM,uCAAsC;AAAA,OACxF;AAAA,IACA,oBAAC,iBAAc,QAAgB;AAAA,KACjC;AAEJ;","names":["editor","value"]}
1
+ {"version":3,"sources":["../../../src/admin/editors/RichTextEditor.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEditor, EditorContent } from \"@tiptap/react\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport { TableKit } from \"@tiptap/extension-table\";\nimport Link from \"@tiptap/extension-link\";\nimport Image from \"@tiptap/extension-image\";\nimport DOMPurify from \"dompurify\";\nimport { useCallback, useEffect, type ReactNode } from \"react\";\nimport { cn } from \"../ui/cn\";\nimport { MediaPickerButton } from \"../media/MediaPicker\";\nimport { FontSize, FONT_SIZES } from \"./fontSizeExtension\";\n\n/**\n * WYSIWYG-editor (TipTap) met opmaak én afbeeldingen. Output wordt client-side met DOMPurify\n * gesanitized voor preview/opslag; de server sanitized nogmaals bij publiceren (server/sanitize.ts)\n * — nooit op de client vertrouwen. Geef `siteId` mee om afbeeldingen te kunnen uploaden/invoegen.\n */\nexport function RichTextEditor({\n value,\n onChange,\n siteId,\n}: {\n value: string;\n onChange: (html: string) => void;\n siteId?: string;\n}) {\n\n const editor = useEditor({\n immediatelyRender: false,\n extensions: [\n // Tiptap 3 bundelt Link en Underline IN StarterKit. Los toevoegen geeft dubbele\n // extensienamen (en stille gedragsfouten), dus: Underline komt uit StarterKit, en zijn Link\n // zetten we uit omdat we die hieronder zelf configureren (geen open-on-click, eigen rel).\n StarterKit.configure({ heading: { levels: [2, 3, 4] }, link: false }),\n FontSize,\n // Zonder deze extensie gooit TipTap een <table> bij het inladen weg (onbekende node) — een\n // tabel uit de seed of een import overleefde dan geen enkele bewerking van dat blok.\n TableKit.configure({ table: { resizable: false } }),\n Link.configure({ openOnClick: false, autolink: true, HTMLAttributes: { rel: \"noopener noreferrer nofollow\" } }),\n Image.configure({ inline: false, allowBase64: false, HTMLAttributes: { loading: \"lazy\", decoding: \"async\" } }),\n ],\n content: value || \"<p></p>\",\n onUpdate: ({ editor }) => onChange(DOMPurify.sanitize(editor.getHTML())),\n editorProps: {\n attributes: { class: \"min-h-[8rem] rounded-b-lg border border-t-0 border-slate-300 bg-white p-3 text-sm\" },\n },\n });\n\n // Externe waarde-wijzigingen (undo/redo in de VisualEditor, taal-wissel in het vertaalpaneel)\n // in de editor doorvoeren: TipTap leest `content` alleen bij init. Zonder deze sync blijft de\n // zichtbare HTML stale terwijl de opgeslagen waarde al veranderd is — data-integriteitsval.\n // Niet syncen terwijl de gebruiker zelf typt (editor.isFocused) — dan is de editor de bron.\n useEffect(() => {\n if (!editor || editor.isFocused) return;\n const current = editor.getHTML();\n const next = value || \"<p></p>\";\n // Tiptap 3: de tweede parameter is een options-object i.p.v. een boolean.\n if (current !== next) editor.commands.setContent(next, { emitUpdate: false });\n }, [value, editor]);\n\n const setLink = useCallback(() => {\n if (!editor) return;\n const prev = editor.getAttributes(\"link\").href as string | undefined;\n const url = window.prompt(\"Link-URL (leeg = verwijderen)\", prev ?? \"https://\");\n if (url === null) return;\n if (url === \"\") editor.chain().focus().extendMarkRange(\"link\").unsetLink().run();\n else editor.chain().focus().extendMarkRange(\"link\").setLink({ href: url }).run();\n }, [editor]);\n\n const insertImageByUrl = useCallback(() => {\n if (!editor) return;\n const url = window.prompt(\"Afbeeldings-URL (https)\");\n if (!url) return;\n const alt = window.prompt(\"Beschrijving (alt-tekst, voor toegankelijkheid)\") ?? \"\";\n editor.chain().focus().setImage({ src: url, alt }).run();\n }, [editor]);\n\n const setFontSize = useCallback(\n (value: string) => {\n if (!editor) return;\n editor.chain().focus().setMark(\"textStyle\", { fontSize: value || null }).run();\n },\n [editor],\n );\n\n if (!editor) return null;\n\n const Btn = ({ on, active, label, title }: { on: () => void; active?: boolean; label: ReactNode; title?: string }) => (\n <button\n type=\"button\"\n title={title}\n onMouseDown={(e) => e.preventDefault()}\n onClick={on}\n className={cn(\n \"h-8 min-w-8 rounded px-2 text-sm hover:bg-slate-100\",\n active && \"bg-slate-200 font-semibold\",\n )}\n >\n {label}\n </button>\n );\n\n return (\n <div>\n <div className=\"flex flex-wrap gap-1 rounded-t-lg border border-slate-300 bg-slate-50 p-1\">\n <Btn on={() => editor.chain().focus().toggleBold().run()} active={editor.isActive(\"bold\")} label=\"B\" title=\"Vet\" />\n <Btn on={() => editor.chain().focus().toggleItalic().run()} active={editor.isActive(\"italic\")} label=\"I\" title=\"Cursief\" />\n <Btn on={() => editor.chain().focus().toggleUnderline().run()} active={editor.isActive(\"underline\")} label={<span style={{ textDecoration: \"underline\" }}>U</span>} title=\"Onderstrepen\" />\n <Btn on={() => editor.chain().focus().toggleStrike().run()} active={editor.isActive(\"strike\")} label={<span style={{ textDecoration: \"line-through\" }}>S</span>} title=\"Doorstrepen\" />\n <select\n className=\"h-8 rounded border border-slate-300 bg-white px-1 text-sm\"\n value={(editor.getAttributes(\"textStyle\").fontSize as string | undefined) ?? \"\"}\n onChange={(e) => setFontSize(e.target.value)}\n title=\"Lettergrootte\"\n aria-label=\"Lettergrootte\"\n >\n {FONT_SIZES.map((s) => (\n <option key={s.label} value={s.value}>\n {s.label}\n </option>\n ))}\n </select>\n <Btn on={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} active={editor.isActive(\"heading\", { level: 2 })} label=\"H2\" title=\"Kop\" />\n <Btn on={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} active={editor.isActive(\"heading\", { level: 3 })} label=\"H3\" title=\"Subkop\" />\n <Btn on={() => editor.chain().focus().toggleBulletList().run()} active={editor.isActive(\"bulletList\")} label=\"• Lijst\" title=\"Opsomming\" />\n <Btn on={() => editor.chain().focus().toggleOrderedList().run()} active={editor.isActive(\"orderedList\")} label=\"1. Lijst\" title=\"Genummerde lijst\" />\n <Btn on={() => editor.chain().focus().toggleBlockquote().run()} active={editor.isActive(\"blockquote\")} label=\"❝\" title=\"Citaat\" />\n <Btn on={setLink} active={editor.isActive(\"link\")} label=\"🔗\" title=\"Link\" />\n {siteId && (\n <MediaPickerButton\n siteId={siteId}\n label=\"🖼 Afbeelding\"\n variant=\"ghost\"\n onSelect={(m) => editor.chain().focus().setImage({ src: m.url, alt: m.alt ?? \"\" }).run()}\n />\n )}\n <Btn on={insertImageByUrl} label=\"🖼 URL\" title=\"Externe afbeelding via URL invoegen\" />\n </div>\n <EditorContent editor={editor} />\n </div>\n );\n}\n"],"mappings":"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","names":["editor","value"]}
@@ -32,7 +32,7 @@ function Segmented({
32
32
  )) });
33
33
  }
34
34
  function mailProviderLabel(p) {
35
- return p === "smtp" ? "Eigen SMTP" : p === "resend" ? "Resend" : "Postmark";
35
+ return p === "smtp" ? "Eigen SMTP" : p === "resend" ? "Resend" : p === "ses" ? "Amazon SES" : "Postmark";
36
36
  }
37
37
  function MailSettings({ siteId }) {
38
38
  const { toast } = useToast();
@@ -112,7 +112,7 @@ function MailSettings({ siteId }) {
112
112
  /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold", children: "E-mail (formulieren & shop)" }),
113
113
  /* @__PURE__ */ jsx("p", { className: "text-xs text-slate-500", children: "De provider waarmee deze site e-mails verstuurt \u2014 formulier-notificaties \xE9n bestelbevestigingen. Een provider verstuurt alleen vanaf een geverifieerd domein." })
114
114
  ] }),
115
- status && /* @__PURE__ */ jsx(Badge, { tone: status.configured ? "green" : "amber", children: status.configured ? `${mailProviderLabel(mailProvider)} actief` : "Nog niet ingesteld" })
115
+ status && /* @__PURE__ */ jsx(Badge, { tone: status.configured ? "green" : "amber", children: status.configured ? `${mailProviderLabel(status.provider ?? mailProvider)} actief` : "Nog niet ingesteld" })
116
116
  ] }),
117
117
  /* @__PURE__ */ jsx(
118
118
  Segmented,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/settings/MailSettings.tsx"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useState } from \"react\";\nimport { callable } from \"../../firebase/functionsClient\";\nimport { getFirebaseApp } from \"../../firebase/client\";\nimport { Badge, Button, Card, Field, Input } from \"../ui/primitives\";\nimport { Switch } from \"../ui/switch\";\nimport { useToast } from \"../ui/toast\";\n\ntype MailProvider = \"resend\" | \"postmark\" | \"smtp\" | \"ses\";\n\ninterface MailStatus {\n provider?: MailProvider;\n configured: boolean;\n /** provider \"ses\": AWS-regio waar het domein geverifieerd is. */\n sesRegion?: string;\n host?: string;\n port?: number;\n secure?: boolean;\n user?: string;\n fromName?: string;\n fromEmail?: string;\n notifyTo?: string;\n hasResendKey?: boolean;\n hasPostmarkToken?: boolean;\n hasSmtpPass?: boolean;\n}\n\n/**\n * Generieke callable-helper (niet shop-gebonden): roept een functie op naam aan via de\n * client-Firebase. Bewust hier lokaal zodat de algemene instellingen niet van de opt-in\n * shop-module afhangen — mail is een site-brede instelling (zie docs/SHARED-SETTINGS.md).\n */\nfunction callFunction<TIn, TOut>(name: string) {\n return async (data: TIn): Promise<TOut> => {\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase niet beschikbaar.\");\n const fn = callable(app, name);\n const res = await fn(data as Record<string, unknown>);\n return res.data as TOut;\n };\n}\n\n/** Segmented control voor providerkeuze. */\nfunction Segmented<T extends string>({\n value,\n options,\n onChange,\n}: {\n value: T;\n options: { value: T; label: string }[];\n onChange: (v: T) => void;\n}) {\n return (\n <div className=\"inline-flex rounded-lg border border-slate-200 bg-slate-50 p-0.5\">\n {options.map((o) => (\n <button\n key={o.value}\n type=\"button\"\n onClick={() => onChange(o.value)}\n className={`rounded-md px-3 py-1.5 text-sm font-medium transition ${\n value === o.value ? \"bg-white text-brand-700 shadow-sm\" : \"text-slate-500 hover:text-slate-700\"\n }`}\n >\n {o.label}\n </button>\n ))}\n </div>\n );\n}\n\nfunction mailProviderLabel(p: MailProvider): string {\n return p === \"smtp\" ? \"Eigen SMTP\" : p === \"resend\" ? \"Resend\" : \"Postmark\";\n}\n\n/**\n * Site-brede mailprovider-configuratie (Resend / Postmark / eigen SMTP) + afzender-identiteit.\n * Deze instelling wordt door MEERDERE modules gebruikt (formulier-notificaties én de shop), dus\n * hoort site-breed thuis in de algemene instellingen — niet in een module-paneel. Geheimen gaan\n * uitsluitend via callables naar `sites/{siteId}/private/mail` (deny-all rules); dit paneel toont\n * alleen gemaskeerde status. Alleen een siteadmin kan opslaan.\n */\nexport function MailSettings({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const [status, setStatus] = useState<MailStatus | null>(null);\n const [busy, setBusy] = useState(false);\n\n const [mailProvider, setMailProvider] = useState<MailProvider>(\"smtp\");\n const [resendKey, setResendKey] = useState(\"\");\n const [postmarkToken, setPostmarkToken] = useState(\"\");\n const [smtp, setSmtp] = useState({ host: \"\", port: \"465\", secure: true, user: \"\", pass: \"\" });\n const [sesRegion, setSesRegion] = useState(\"eu-west-1\");\n const [fromName, setFromName] = useState(\"\");\n const [fromEmail, setFromEmail] = useState(\"\");\n const [notifyTo, setNotifyTo] = useState(\"\");\n\n const refresh = useCallback(async () => {\n try {\n const m = await callFunction<{ siteId: string }, MailStatus>(\"getMailStatus\")({ siteId });\n setStatus(m);\n setMailProvider(m.provider ?? (m.host ? \"smtp\" : m.hasPostmarkToken ? \"postmark\" : m.hasResendKey ? \"resend\" : \"smtp\"));\n setSmtp((p) => ({ ...p, host: m.host ?? p.host, port: String(m.port ?? 465), secure: m.secure !== false, user: m.user ?? p.user }));\n setSesRegion(m.sesRegion ?? \"eu-west-1\");\n setFromName(m.fromName ?? \"\");\n setFromEmail(m.fromEmail ?? \"\");\n setNotifyTo(m.notifyTo ?? \"\");\n } catch {\n setStatus(null);\n }\n }, [siteId]);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n async function saveMail() {\n setBusy(true);\n try {\n const mail: Record<string, unknown> = { provider: mailProvider, fromName, fromEmail, notifyTo };\n if (mailProvider === \"resend\") mail.resendApiKey = resendKey.trim() || undefined;\n if (mailProvider === \"postmark\") mail.postmarkToken = postmarkToken.trim() || undefined;\n if (mailProvider === \"ses\") {\n // Host leidt de server af uit de regio; hier alleen de SES-SMTP-inloggegevens.\n mail.sesRegion = sesRegion.trim();\n mail.port = Number(smtp.port) || 465;\n mail.secure = smtp.secure;\n mail.user = smtp.user.trim();\n if (smtp.pass) mail.pass = smtp.pass;\n }\n if (mailProvider === \"smtp\") {\n mail.host = smtp.host.trim();\n mail.port = Number(smtp.port) || 465;\n mail.secure = smtp.secure;\n mail.user = smtp.user.trim();\n if (smtp.pass) mail.pass = smtp.pass;\n }\n await callFunction<{ siteId: string; mail: Record<string, unknown> }, { ok: boolean }>(\"saveMailSettings\")({ siteId, mail });\n toast({ title: \"E-mailinstellingen opgeslagen\", tone: \"success\" });\n setResendKey(\"\");\n setPostmarkToken(\"\");\n setSmtp((p) => ({ ...p, pass: \"\" }));\n await refresh();\n } catch (e) {\n toast({ title: \"Opslaan mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n async function sendTest() {\n setBusy(true);\n try {\n // `sendShopTestMail` is een generieke testverzending (naam ongewijzigd voor compat).\n const res = await callFunction<{ siteId: string }, { to: string }>(\"sendShopTestMail\")({ siteId });\n toast({ title: \"Testmail verstuurd\", description: `Naar ${res.to} — check de inbox (en spam).`, tone: \"success\" });\n } catch (e) {\n toast({ title: \"Testmail mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n return (\n <Card>\n <div className=\"grid gap-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div>\n <h3 className=\"text-base font-semibold\">E-mail (formulieren &amp; shop)</h3>\n <p className=\"text-xs text-slate-500\">\n De provider waarmee deze site e-mails verstuurt — formulier-notificaties én\n bestelbevestigingen. Een provider verstuurt alleen vanaf een geverifieerd domein.\n </p>\n </div>\n {status && (\n <Badge tone={status.configured ? \"green\" : \"amber\"}>\n {status.configured ? `${mailProviderLabel(mailProvider)} actief` : \"Nog niet ingesteld\"}\n </Badge>\n )}\n </div>\n\n <Segmented\n value={mailProvider}\n onChange={setMailProvider}\n options={[\n { value: \"smtp\", label: \"Eigen SMTP\" },\n { value: \"ses\", label: \"Amazon SES\" },\n { value: \"resend\", label: \"Resend\" },\n { value: \"postmark\", label: \"Postmark\" },\n ]}\n />\n\n {mailProvider === \"smtp\" && (\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-slate-600\">\n Via je eigen mailbox: bv. het adres bij je hostingpartij, of Gmail (smtp.gmail.com, poort 465, met een{\" \"}\n <a className=\"underline\" href=\"https://myaccount.google.com/apppasswords\" target=\"_blank\" rel=\"noreferrer\">app-wachtwoord</a>).\n </p>\n <div className=\"grid gap-3 md:grid-cols-[1fr_110px]\">\n <Field label=\"SMTP-server\"><Input value={smtp.host} onChange={(e) => setSmtp((p) => ({ ...p, host: e.target.value }))} placeholder=\"smtp.gmail.com\" /></Field>\n <Field label=\"Poort\"><Input value={smtp.port} onChange={(e) => setSmtp((p) => ({ ...p, port: e.target.value }))} placeholder=\"465\" /></Field>\n </div>\n <Switch checked={smtp.secure} onCheckedChange={(v) => setSmtp((p) => ({ ...p, secure: v }))} label=\"SSL (poort 465). Uit = STARTTLS (poort 587)\" />\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Gebruikersnaam\"><Input value={smtp.user} onChange={(e) => setSmtp((p) => ({ ...p, user: e.target.value }))} placeholder=\"info@jouwdomein.nl\" /></Field>\n <Field label=\"Wachtwoord\" hint={status?.hasSmtpPass ? \"Leeg laten = ongewijzigd\" : undefined}>\n <Input type=\"password\" value={smtp.pass} onChange={(e) => setSmtp((p) => ({ ...p, pass: e.target.value }))} />\n </Field>\n </div>\n </div>\n )}\n\n {mailProvider === \"ses\" && (\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-slate-600\">\n <strong>Amazon SES</strong> verstuurt vanaf je eigen domein en is goedkoop bij grotere\n aantallen. Let op drie dingen: de <em>regio</em> moet dezelfde zijn als waar je domein\n geverifieerd is, de inloggegevens hieronder zijn{\" \"}\n <a className=\"underline\" href=\"https://docs.aws.amazon.com/ses/latest/dg/smtp-credentials.html\" target=\"_blank\" rel=\"noreferrer\">\n SES-SMTP-inloggegevens\n </a>{\" \"}\n (niet je gewone AWS-sleutels), en een nieuw SES-account staat in de <em>sandbox</em> —\n daar mag je alleen naar geverifieerde adressen mailen tot je productietoegang aanvraagt.\n </p>\n <div className=\"grid gap-3 md:grid-cols-[1fr_110px]\">\n <Field label=\"Regio\" hint=\"Bijvoorbeeld eu-west-1 (Ierland) of eu-central-1 (Frankfurt)\">\n <Input value={sesRegion} onChange={(e) => setSesRegion(e.target.value)} placeholder=\"eu-west-1\" />\n </Field>\n <Field label=\"Poort\"><Input value={smtp.port} onChange={(e) => setSmtp((p) => ({ ...p, port: e.target.value }))} placeholder=\"465\" /></Field>\n </div>\n <p className=\"text-xs text-slate-500\">\n Server: <code>email-smtp.{sesRegion.trim() || \"eu-west-1\"}.amazonaws.com</code>\n </p>\n <Switch checked={smtp.secure} onCheckedChange={(v) => setSmtp((p) => ({ ...p, secure: v }))} label=\"SSL (poort 465). Uit = STARTTLS (poort 587)\" />\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"SMTP-gebruikersnaam\"><Input value={smtp.user} onChange={(e) => setSmtp((p) => ({ ...p, user: e.target.value }))} placeholder=\"AKIA…\" /></Field>\n <Field label=\"SMTP-wachtwoord\" hint={status?.hasSmtpPass ? \"Leeg laten = ongewijzigd\" : undefined}>\n <Input type=\"password\" value={smtp.pass} onChange={(e) => setSmtp((p) => ({ ...p, pass: e.target.value }))} />\n </Field>\n </div>\n </div>\n )}\n\n {mailProvider === \"resend\" && (\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-slate-600\">\n <a className=\"underline\" href=\"https://resend.com\" target=\"_blank\" rel=\"noreferrer\">Resend</a> verstuurt vanaf een geverifieerd domein.\n Maak een API-sleutel (re_…) en verifieer je afzenderdomein in het Resend-dashboard.\n </p>\n <Field label=\"Resend API-sleutel (re_…)\" hint={status?.hasResendKey ? \"Leeg laten = ongewijzigd\" : undefined}>\n <Input type=\"password\" value={resendKey} onChange={(e) => setResendKey(e.target.value)} placeholder=\"re_…\" />\n </Field>\n </div>\n )}\n\n {mailProvider === \"postmark\" && (\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-slate-600\">\n <a className=\"underline\" href=\"https://postmarkapp.com\" target=\"_blank\" rel=\"noreferrer\">Postmark</a> verstuurt vanaf een geverifieerde afzender/domein (Sender Signature).\n Kopieer het Server-token.\n </p>\n <Field label=\"Postmark server-token\" hint={status?.hasPostmarkToken ? \"Leeg laten = ongewijzigd\" : undefined}>\n <Input type=\"password\" value={postmarkToken} onChange={(e) => setPostmarkToken(e.target.value)} placeholder=\"xxxxxxxx-xxxx-…\" />\n </Field>\n </div>\n )}\n\n {/* Gedeelde afzender-velden */}\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Afzendernaam\"><Input value={fromName} onChange={(e) => setFromName(e.target.value)} placeholder=\"Mijn Site\" /></Field>\n <Field label=\"Afzenderadres\"><Input value={fromEmail} onChange={(e) => setFromEmail(e.target.value)} placeholder=\"info@jouwdomein.nl\" /></Field>\n </div>\n <Field label=\"Notificatie-adres\" hint=\"Hier ontvang je zelf een mail bij elk formulier/elke bestelling; ook het doel van de testmail\">\n <Input value={notifyTo} onChange={(e) => setNotifyTo(e.target.value)} placeholder=\"notificaties@jouwdomein.nl\" />\n </Field>\n\n <div className=\"flex items-center gap-2\">\n <Button onClick={saveMail} disabled={busy}>Opslaan</Button>\n <Button variant=\"secondary\" onClick={sendTest} disabled={busy || !status?.configured}>Testmail versturen</Button>\n </div>\n </div>\n </Card>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/settings/MailSettings.tsx"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useState } from \"react\";\nimport { callable } from \"../../firebase/functionsClient\";\nimport { getFirebaseApp } from \"../../firebase/client\";\nimport { Badge, Button, Card, Field, Input } from \"../ui/primitives\";\nimport { Switch } from \"../ui/switch\";\nimport { useToast } from \"../ui/toast\";\n\ntype MailProvider = \"resend\" | \"postmark\" | \"smtp\" | \"ses\";\n\ninterface MailStatus {\n provider?: MailProvider;\n configured: boolean;\n /** provider \"ses\": AWS-regio waar het domein geverifieerd is. */\n sesRegion?: string;\n host?: string;\n port?: number;\n secure?: boolean;\n user?: string;\n fromName?: string;\n fromEmail?: string;\n notifyTo?: string;\n hasResendKey?: boolean;\n hasPostmarkToken?: boolean;\n hasSmtpPass?: boolean;\n}\n\n/**\n * Generieke callable-helper (niet shop-gebonden): roept een functie op naam aan via de\n * client-Firebase. Bewust hier lokaal zodat de algemene instellingen niet van de opt-in\n * shop-module afhangen — mail is een site-brede instelling (zie docs/SHARED-SETTINGS.md).\n */\nfunction callFunction<TIn, TOut>(name: string) {\n return async (data: TIn): Promise<TOut> => {\n const app = getFirebaseApp();\n if (!app) throw new Error(\"Firebase niet beschikbaar.\");\n const fn = callable(app, name);\n const res = await fn(data as Record<string, unknown>);\n return res.data as TOut;\n };\n}\n\n/** Segmented control voor providerkeuze. */\nfunction Segmented<T extends string>({\n value,\n options,\n onChange,\n}: {\n value: T;\n options: { value: T; label: string }[];\n onChange: (v: T) => void;\n}) {\n return (\n <div className=\"inline-flex rounded-lg border border-slate-200 bg-slate-50 p-0.5\">\n {options.map((o) => (\n <button\n key={o.value}\n type=\"button\"\n onClick={() => onChange(o.value)}\n className={`rounded-md px-3 py-1.5 text-sm font-medium transition ${\n value === o.value ? \"bg-white text-brand-700 shadow-sm\" : \"text-slate-500 hover:text-slate-700\"\n }`}\n >\n {o.label}\n </button>\n ))}\n </div>\n );\n}\n\nfunction mailProviderLabel(p: MailProvider): string {\n return p === \"smtp\" ? \"Eigen SMTP\" : p === \"resend\" ? \"Resend\" : p === \"ses\" ? \"Amazon SES\" : \"Postmark\";\n}\n\n/**\n * Site-brede mailprovider-configuratie (Resend / Postmark / eigen SMTP) + afzender-identiteit.\n * Deze instelling wordt door MEERDERE modules gebruikt (formulier-notificaties én de shop), dus\n * hoort site-breed thuis in de algemene instellingen — niet in een module-paneel. Geheimen gaan\n * uitsluitend via callables naar `sites/{siteId}/private/mail` (deny-all rules); dit paneel toont\n * alleen gemaskeerde status. Alleen een siteadmin kan opslaan.\n */\nexport function MailSettings({ siteId }: { siteId: string }) {\n const { toast } = useToast();\n const [status, setStatus] = useState<MailStatus | null>(null);\n const [busy, setBusy] = useState(false);\n\n const [mailProvider, setMailProvider] = useState<MailProvider>(\"smtp\");\n const [resendKey, setResendKey] = useState(\"\");\n const [postmarkToken, setPostmarkToken] = useState(\"\");\n const [smtp, setSmtp] = useState({ host: \"\", port: \"465\", secure: true, user: \"\", pass: \"\" });\n const [sesRegion, setSesRegion] = useState(\"eu-west-1\");\n const [fromName, setFromName] = useState(\"\");\n const [fromEmail, setFromEmail] = useState(\"\");\n const [notifyTo, setNotifyTo] = useState(\"\");\n\n const refresh = useCallback(async () => {\n try {\n const m = await callFunction<{ siteId: string }, MailStatus>(\"getMailStatus\")({ siteId });\n setStatus(m);\n setMailProvider(m.provider ?? (m.host ? \"smtp\" : m.hasPostmarkToken ? \"postmark\" : m.hasResendKey ? \"resend\" : \"smtp\"));\n setSmtp((p) => ({ ...p, host: m.host ?? p.host, port: String(m.port ?? 465), secure: m.secure !== false, user: m.user ?? p.user }));\n setSesRegion(m.sesRegion ?? \"eu-west-1\");\n setFromName(m.fromName ?? \"\");\n setFromEmail(m.fromEmail ?? \"\");\n setNotifyTo(m.notifyTo ?? \"\");\n } catch {\n setStatus(null);\n }\n }, [siteId]);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n async function saveMail() {\n setBusy(true);\n try {\n const mail: Record<string, unknown> = { provider: mailProvider, fromName, fromEmail, notifyTo };\n if (mailProvider === \"resend\") mail.resendApiKey = resendKey.trim() || undefined;\n if (mailProvider === \"postmark\") mail.postmarkToken = postmarkToken.trim() || undefined;\n if (mailProvider === \"ses\") {\n // Host leidt de server af uit de regio; hier alleen de SES-SMTP-inloggegevens.\n mail.sesRegion = sesRegion.trim();\n mail.port = Number(smtp.port) || 465;\n mail.secure = smtp.secure;\n mail.user = smtp.user.trim();\n if (smtp.pass) mail.pass = smtp.pass;\n }\n if (mailProvider === \"smtp\") {\n mail.host = smtp.host.trim();\n mail.port = Number(smtp.port) || 465;\n mail.secure = smtp.secure;\n mail.user = smtp.user.trim();\n if (smtp.pass) mail.pass = smtp.pass;\n }\n await callFunction<{ siteId: string; mail: Record<string, unknown> }, { ok: boolean }>(\"saveMailSettings\")({ siteId, mail });\n toast({ title: \"E-mailinstellingen opgeslagen\", tone: \"success\" });\n setResendKey(\"\");\n setPostmarkToken(\"\");\n setSmtp((p) => ({ ...p, pass: \"\" }));\n await refresh();\n } catch (e) {\n toast({ title: \"Opslaan mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n async function sendTest() {\n setBusy(true);\n try {\n // `sendShopTestMail` is een generieke testverzending (naam ongewijzigd voor compat).\n const res = await callFunction<{ siteId: string }, { to: string }>(\"sendShopTestMail\")({ siteId });\n toast({ title: \"Testmail verstuurd\", description: `Naar ${res.to} — check de inbox (en spam).`, tone: \"success\" });\n } catch (e) {\n toast({ title: \"Testmail mislukt\", description: e instanceof Error ? e.message : undefined, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n return (\n <Card>\n <div className=\"grid gap-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div>\n <h3 className=\"text-base font-semibold\">E-mail (formulieren &amp; shop)</h3>\n <p className=\"text-xs text-slate-500\">\n De provider waarmee deze site e-mails verstuurt — formulier-notificaties én\n bestelbevestigingen. Een provider verstuurt alleen vanaf een geverifieerd domein.\n </p>\n </div>\n {status && (\n <Badge tone={status.configured ? \"green\" : \"amber\"}>\n {status.configured ? `${mailProviderLabel(status.provider ?? mailProvider)} actief` : \"Nog niet ingesteld\"}\n </Badge>\n )}\n </div>\n\n <Segmented\n value={mailProvider}\n onChange={setMailProvider}\n options={[\n { value: \"smtp\", label: \"Eigen SMTP\" },\n { value: \"ses\", label: \"Amazon SES\" },\n { value: \"resend\", label: \"Resend\" },\n { value: \"postmark\", label: \"Postmark\" },\n ]}\n />\n\n {mailProvider === \"smtp\" && (\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-slate-600\">\n Via je eigen mailbox: bv. het adres bij je hostingpartij, of Gmail (smtp.gmail.com, poort 465, met een{\" \"}\n <a className=\"underline\" href=\"https://myaccount.google.com/apppasswords\" target=\"_blank\" rel=\"noreferrer\">app-wachtwoord</a>).\n </p>\n <div className=\"grid gap-3 md:grid-cols-[1fr_110px]\">\n <Field label=\"SMTP-server\"><Input value={smtp.host} onChange={(e) => setSmtp((p) => ({ ...p, host: e.target.value }))} placeholder=\"smtp.gmail.com\" /></Field>\n <Field label=\"Poort\"><Input value={smtp.port} onChange={(e) => setSmtp((p) => ({ ...p, port: e.target.value }))} placeholder=\"465\" /></Field>\n </div>\n <Switch checked={smtp.secure} onCheckedChange={(v) => setSmtp((p) => ({ ...p, secure: v }))} label=\"SSL (poort 465). Uit = STARTTLS (poort 587)\" />\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Gebruikersnaam\"><Input value={smtp.user} onChange={(e) => setSmtp((p) => ({ ...p, user: e.target.value }))} placeholder=\"info@jouwdomein.nl\" /></Field>\n <Field label=\"Wachtwoord\" hint={status?.hasSmtpPass ? \"Leeg laten = ongewijzigd\" : undefined}>\n <Input type=\"password\" value={smtp.pass} onChange={(e) => setSmtp((p) => ({ ...p, pass: e.target.value }))} />\n </Field>\n </div>\n </div>\n )}\n\n {mailProvider === \"ses\" && (\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-slate-600\">\n <strong>Amazon SES</strong> verstuurt vanaf je eigen domein en is goedkoop bij grotere\n aantallen. Let op drie dingen: de <em>regio</em> moet dezelfde zijn als waar je domein\n geverifieerd is, de inloggegevens hieronder zijn{\" \"}\n <a className=\"underline\" href=\"https://docs.aws.amazon.com/ses/latest/dg/smtp-credentials.html\" target=\"_blank\" rel=\"noreferrer\">\n SES-SMTP-inloggegevens\n </a>{\" \"}\n (niet je gewone AWS-sleutels), en een nieuw SES-account staat in de <em>sandbox</em> —\n daar mag je alleen naar geverifieerde adressen mailen tot je productietoegang aanvraagt.\n </p>\n <div className=\"grid gap-3 md:grid-cols-[1fr_110px]\">\n <Field label=\"Regio\" hint=\"Bijvoorbeeld eu-west-1 (Ierland) of eu-central-1 (Frankfurt)\">\n <Input value={sesRegion} onChange={(e) => setSesRegion(e.target.value)} placeholder=\"eu-west-1\" />\n </Field>\n <Field label=\"Poort\"><Input value={smtp.port} onChange={(e) => setSmtp((p) => ({ ...p, port: e.target.value }))} placeholder=\"465\" /></Field>\n </div>\n <p className=\"text-xs text-slate-500\">\n Server: <code>email-smtp.{sesRegion.trim() || \"eu-west-1\"}.amazonaws.com</code>\n </p>\n <Switch checked={smtp.secure} onCheckedChange={(v) => setSmtp((p) => ({ ...p, secure: v }))} label=\"SSL (poort 465). Uit = STARTTLS (poort 587)\" />\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"SMTP-gebruikersnaam\"><Input value={smtp.user} onChange={(e) => setSmtp((p) => ({ ...p, user: e.target.value }))} placeholder=\"AKIA…\" /></Field>\n <Field label=\"SMTP-wachtwoord\" hint={status?.hasSmtpPass ? \"Leeg laten = ongewijzigd\" : undefined}>\n <Input type=\"password\" value={smtp.pass} onChange={(e) => setSmtp((p) => ({ ...p, pass: e.target.value }))} />\n </Field>\n </div>\n </div>\n )}\n\n {mailProvider === \"resend\" && (\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-slate-600\">\n <a className=\"underline\" href=\"https://resend.com\" target=\"_blank\" rel=\"noreferrer\">Resend</a> verstuurt vanaf een geverifieerd domein.\n Maak een API-sleutel (re_…) en verifieer je afzenderdomein in het Resend-dashboard.\n </p>\n <Field label=\"Resend API-sleutel (re_…)\" hint={status?.hasResendKey ? \"Leeg laten = ongewijzigd\" : undefined}>\n <Input type=\"password\" value={resendKey} onChange={(e) => setResendKey(e.target.value)} placeholder=\"re_…\" />\n </Field>\n </div>\n )}\n\n {mailProvider === \"postmark\" && (\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-slate-600\">\n <a className=\"underline\" href=\"https://postmarkapp.com\" target=\"_blank\" rel=\"noreferrer\">Postmark</a> verstuurt vanaf een geverifieerde afzender/domein (Sender Signature).\n Kopieer het Server-token.\n </p>\n <Field label=\"Postmark server-token\" hint={status?.hasPostmarkToken ? \"Leeg laten = ongewijzigd\" : undefined}>\n <Input type=\"password\" value={postmarkToken} onChange={(e) => setPostmarkToken(e.target.value)} placeholder=\"xxxxxxxx-xxxx-…\" />\n </Field>\n </div>\n )}\n\n {/* Gedeelde afzender-velden */}\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Afzendernaam\"><Input value={fromName} onChange={(e) => setFromName(e.target.value)} placeholder=\"Mijn Site\" /></Field>\n <Field label=\"Afzenderadres\"><Input value={fromEmail} onChange={(e) => setFromEmail(e.target.value)} placeholder=\"info@jouwdomein.nl\" /></Field>\n </div>\n <Field label=\"Notificatie-adres\" hint=\"Hier ontvang je zelf een mail bij elk formulier/elke bestelling; ook het doel van de testmail\">\n <Input value={notifyTo} onChange={(e) => setNotifyTo(e.target.value)} placeholder=\"notificaties@jouwdomein.nl\" />\n </Field>\n\n <div className=\"flex items-center gap-2\">\n <Button onClick={saveMail} disabled={busy}>Opslaan</Button>\n <Button variant=\"secondary\" onClick={sendTest} disabled={busy || !status?.configured}>Testmail versturen</Button>\n </div>\n </div>\n </Card>\n );\n}\n"],"mappings":"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","names":[]}
package/dist/admin.css CHANGED
@@ -1,2 +1,2 @@
1
1
  /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
2
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-emerald-700:oklch(50.8% .118 165.612);--color-blue-600:oklch(54.6% .245 262.881);--color-indigo-500:oklch(58.5% .233 277.117);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-slate-900:oklch(20.8% .042 265.755);--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-3xl:48rem;--container-4xl:56rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wide:.025em;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-out:cubic-bezier(0, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--color-brand:#4f46e5;--color-brand-50:#eef2ff;--color-brand-500:#4f46e5;--color-brand-600:#4f46e5;--color-brand-700:#3730a3}}@scope(.xicms-admin){@layer utilities{.pointer-events-none{pointer-events:none!important}.visible{visibility:visible!important}.sr-only{clip-path:inset(50%)!important;white-space:nowrap!important;border-width:0!important;width:1px!important;height:1px!important;margin:-1px!important;padding:0!important;position:absolute!important;overflow:hidden!important}.absolute{position:absolute!important}.fixed{position:fixed!important}.relative{position:relative!important}.static{position:static!important}.sticky{position:sticky!important}.inset-x-0{inset-inline:0!important}.-top-0\.5{top:calc(var(--spacing) * -.5)!important}.-top-2{top:calc(var(--spacing) * -2)!important}.top-0{top:0!important}.-right-2{right:calc(var(--spacing) * -2)!important}.right-0{right:0!important}.-bottom-2{bottom:calc(var(--spacing) * -2)!important}.bottom-0{bottom:0!important}.-left-2{left:calc(var(--spacing) * -2)!important}.left-1\/2{left:50%!important}.z-10{z-index:10!important}.col-span-2{grid-column:span 2/span 2!important}.col-span-full{grid-column:1/-1!important}.container{width:100%!important}@media (min-width:40rem){.container{max-width:40rem!important}}@media (min-width:48rem){.container{max-width:48rem!important}}@media (min-width:64rem){.container{max-width:64rem!important}}@media (min-width:80rem){.container{max-width:80rem!important}}@media (min-width:96rem){.container{max-width:96rem!important}}.-mx-1{margin-inline:calc(var(--spacing) * -1)!important}.-mx-4{margin-inline:calc(var(--spacing) * -4)!important}.mt-0\.5{margin-top:calc(var(--spacing) * .5)!important}.mt-1{margin-top:var(--spacing)!important}.mt-2{margin-top:calc(var(--spacing) * 2)!important}.mt-3{margin-top:calc(var(--spacing) * 3)!important}.mt-4{margin-top:calc(var(--spacing) * 4)!important}.mt-5{margin-top:calc(var(--spacing) * 5)!important}.mt-6{margin-top:calc(var(--spacing) * 6)!important}.mt-auto{margin-top:auto!important}.mr-1{margin-right:var(--spacing)!important}.mr-2{margin-right:calc(var(--spacing) * 2)!important}.mb-1{margin-bottom:var(--spacing)!important}.mb-2{margin-bottom:calc(var(--spacing) * 2)!important}.mb-3{margin-bottom:calc(var(--spacing) * 3)!important}.mb-4{margin-bottom:calc(var(--spacing) * 4)!important}.ml-1{margin-left:var(--spacing)!important}.ml-1\.5{margin-left:calc(var(--spacing) * 1.5)!important}.ml-auto{margin-left:auto!important}.block{display:block!important}.contents{display:contents!important}.flex{display:flex!important}.grid{display:grid!important}.hidden{display:none!important}.inline{display:inline!important}.inline-block{display:inline-block!important}.inline-flex{display:inline-flex!important}.table{display:table!important}.aspect-square{aspect-ratio:1!important}.h-1\.5{height:calc(var(--spacing) * 1.5)!important}.h-2{height:calc(var(--spacing) * 2)!important}.h-4{height:calc(var(--spacing) * 4)!important}.h-5{height:calc(var(--spacing) * 5)!important}.h-6{height:calc(var(--spacing) * 6)!important}.h-8{height:calc(var(--spacing) * 8)!important}.h-9{height:calc(var(--spacing) * 9)!important}.h-10{height:calc(var(--spacing) * 10)!important}.h-12{height:calc(var(--spacing) * 12)!important}.h-14{height:calc(var(--spacing) * 14)!important}.h-16{height:calc(var(--spacing) * 16)!important}.h-28{height:calc(var(--spacing) * 28)!important}.h-32{height:calc(var(--spacing) * 32)!important}.h-\[70vh\]{height:70vh!important}.h-fit{height:fit-content!important}.h-full{height:100%!important}.max-h-\[55vh\]{max-height:55vh!important}.max-h-\[60vh\]{max-height:60vh!important}.min-h-\[8rem\]{min-height:8rem!important}.min-h-screen{min-height:100vh!important}.w-2{width:calc(var(--spacing) * 2)!important}.w-4{width:calc(var(--spacing) * 4)!important}.w-5{width:calc(var(--spacing) * 5)!important}.w-6{width:calc(var(--spacing) * 6)!important}.w-8{width:calc(var(--spacing) * 8)!important}.w-11{width:calc(var(--spacing) * 11)!important}.w-14{width:calc(var(--spacing) * 14)!important}.w-16{width:calc(var(--spacing) * 16)!important}.w-20{width:calc(var(--spacing) * 20)!important}.w-24{width:calc(var(--spacing) * 24)!important}.w-28{width:calc(var(--spacing) * 28)!important}.w-32{width:calc(var(--spacing) * 32)!important}.w-36{width:calc(var(--spacing) * 36)!important}.w-44{width:calc(var(--spacing) * 44)!important}.w-fit{width:fit-content!important}.w-full{width:100%!important}.max-w-3xl{max-width:var(--container-3xl)!important}.max-w-4xl{max-width:var(--container-4xl)!important}.max-w-40{max-width:calc(var(--spacing) * 40)!important}.max-w-md{max-width:var(--container-md)!important}.max-w-sm{max-width:var(--container-sm)!important}.max-w-xs{max-width:var(--container-xs)!important}.min-w-0{min-width:0!important}.min-w-8{min-width:calc(var(--spacing) * 8)!important}.flex-1{flex:1!important}.shrink-0{flex-shrink:0!important}.-translate-x-1\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1)!important;translate:var(--tw-translate-x) var(--tw-translate-y)!important}.-translate-y-1\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1)!important;translate:var(--tw-translate-x) var(--tw-translate-y)!important}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)!important}.cursor-crosshair{cursor:crosshair!important}.cursor-default{cursor:default!important}.cursor-pointer{cursor:pointer!important}.resize{resize:both!important}.list-decimal{list-style-type:decimal!important}.list-disc{list-style-type:disc!important}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))!important}.grid-cols-\[1fr_1\.4fr_auto\]{grid-template-columns:1fr 1.4fr auto!important}.grid-cols-\[1fr_1fr_90px_90px_auto\]{grid-template-columns:1fr 1fr 90px 90px auto!important}.grid-cols-\[1fr_150px_70px_auto\]{grid-template-columns:1fr 150px 70px auto!important}.grid-cols-\[120px_1fr\]{grid-template-columns:120px 1fr!important}.grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto!important}.flex-col{flex-direction:column!important}.flex-wrap{flex-wrap:wrap!important}.place-items-center{place-items:center!important}.items-baseline{align-items:baseline!important}.items-center{align-items:center!important}.items-end{align-items:flex-end!important}.items-start{align-items:flex-start!important}.justify-between{justify-content:space-between!important}.justify-center{justify-content:center!important}.justify-end{justify-content:flex-end!important}.gap-0\.5{gap:calc(var(--spacing) * .5)!important}.gap-1{gap:var(--spacing)!important}.gap-1\.5{gap:calc(var(--spacing) * 1.5)!important}.gap-2{gap:calc(var(--spacing) * 2)!important}.gap-2\.5{gap:calc(var(--spacing) * 2.5)!important}.gap-3{gap:calc(var(--spacing) * 3)!important}.gap-4{gap:calc(var(--spacing) * 4)!important}.gap-6{gap:calc(var(--spacing) * 6)!important}.gap-x-3{column-gap:calc(var(--spacing) * 3)!important}.gap-x-5{column-gap:calc(var(--spacing) * 5)!important}.gap-y-1{row-gap:var(--spacing)!important}.self-center{align-self:center!important}.self-end{align-self:flex-end!important}.truncate{text-overflow:ellipsis!important;white-space:nowrap!important;overflow:hidden!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-x-auto{overflow-x:auto!important}.overflow-y-auto{overflow-y:auto!important}.rounded{border-radius:.25rem!important}.rounded-full{border-radius:3.40282e38px!important}.rounded-lg{border-radius:var(--radius-lg)!important}.rounded-md{border-radius:var(--radius-md)!important}.rounded-xl{border-radius:var(--radius-xl)!important}.rounded-t-lg{border-top-left-radius:var(--radius-lg)!important;border-top-right-radius:var(--radius-lg)!important}.rounded-b-lg{border-bottom-right-radius:var(--radius-lg)!important;border-bottom-left-radius:var(--radius-lg)!important}.border{border-style:var(--tw-border-style)!important;border-width:1px!important}.border-2{border-style:var(--tw-border-style)!important;border-width:2px!important}.border-t{border-top-style:var(--tw-border-style)!important;border-top-width:1px!important}.border-t-0{border-top-style:var(--tw-border-style)!important;border-top-width:0!important}.border-b{border-bottom-style:var(--tw-border-style)!important;border-bottom-width:1px!important}.border-amber-200{border-color:var(--color-amber-200)!important}.border-amber-300{border-color:var(--color-amber-300)!important}.border-brand-500{border-color:var(--color-brand-500)!important}.border-red-200{border-color:var(--color-red-200)!important}.border-slate-100{border-color:var(--color-slate-100)!important}.border-slate-200{border-color:var(--color-slate-200)!important}.border-slate-300{border-color:var(--color-slate-300)!important}.border-white{border-color:var(--color-white)!important}.bg-amber-50{background-color:var(--color-amber-50)!important}.bg-amber-50\/40{background-color:#fffbeb66!important}@supports (color:color-mix(in lab, red, red)){.bg-amber-50\/40{background-color:color-mix(in oklab, var(--color-amber-50) 40%, transparent)!important}}.bg-brand-50{background-color:var(--color-brand-50)!important}.bg-brand-500{background-color:var(--color-brand-500)!important}.bg-red-50{background-color:var(--color-red-50)!important}.bg-slate-50{background-color:var(--color-slate-50)!important}.bg-slate-100{background-color:var(--color-slate-100)!important}.bg-slate-200{background-color:var(--color-slate-200)!important}.bg-slate-700{background-color:var(--color-slate-700)!important}.bg-transparent{background-color:#0000!important}.bg-white{background-color:var(--color-white)!important}.object-contain{object-fit:contain!important}.object-cover{object-fit:cover!important}.p-0\.5{padding:calc(var(--spacing) * .5)!important}.p-1{padding:var(--spacing)!important}.p-2{padding:calc(var(--spacing) * 2)!important}.p-3{padding:calc(var(--spacing) * 3)!important}.p-4{padding:calc(var(--spacing) * 4)!important}.p-5{padding:calc(var(--spacing) * 5)!important}.p-6{padding:calc(var(--spacing) * 6)!important}.px-1{padding-inline:var(--spacing)!important}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)!important}.px-2{padding-inline:calc(var(--spacing) * 2)!important}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)!important}.px-3{padding-inline:calc(var(--spacing) * 3)!important}.px-4{padding-inline:calc(var(--spacing) * 4)!important}.py-0\.5{padding-block:calc(var(--spacing) * .5)!important}.py-1{padding-block:var(--spacing)!important}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)!important}.py-2{padding-block:calc(var(--spacing) * 2)!important}.py-3{padding-block:calc(var(--spacing) * 3)!important}.py-8{padding-block:calc(var(--spacing) * 8)!important}.py-16{padding-block:calc(var(--spacing) * 16)!important}.pt-0{padding-top:0!important}.pt-1{padding-top:var(--spacing)!important}.pt-3{padding-top:calc(var(--spacing) * 3)!important}.pt-4{padding-top:calc(var(--spacing) * 4)!important}.pt-5{padding-top:calc(var(--spacing) * 5)!important}.pr-2{padding-right:calc(var(--spacing) * 2)!important}.pb-1{padding-bottom:var(--spacing)!important}.pb-2{padding-bottom:calc(var(--spacing) * 2)!important}.pl-1{padding-left:var(--spacing)!important}.pl-5{padding-left:calc(var(--spacing) * 5)!important}.text-center{text-align:center!important}.text-left{text-align:left!important}.text-right{text-align:right!important}.align-top{vertical-align:top!important}.font-mono{font-family:var(--font-mono)!important}.text-3xl{font-size:var(--text-3xl)!important;line-height:var(--tw-leading,var(--text-3xl--line-height))!important}.text-base{font-size:var(--text-base)!important;line-height:var(--tw-leading,var(--text-base--line-height))!important}.text-lg{font-size:var(--text-lg)!important;line-height:var(--tw-leading,var(--text-lg--line-height))!important}.text-sm{font-size:var(--text-sm)!important;line-height:var(--tw-leading,var(--text-sm--line-height))!important}.text-xl{font-size:var(--text-xl)!important;line-height:var(--tw-leading,var(--text-xl--line-height))!important}.text-xs{font-size:var(--text-xs)!important;line-height:var(--tw-leading,var(--text-xs--line-height))!important}.text-\[10px\]{font-size:10px!important}.text-\[11px\]{font-size:11px!important}.font-bold{--tw-font-weight:var(--font-weight-bold)!important;font-weight:var(--font-weight-bold)!important}.font-medium{--tw-font-weight:var(--font-weight-medium)!important;font-weight:var(--font-weight-medium)!important}.font-normal{--tw-font-weight:var(--font-weight-normal)!important;font-weight:var(--font-weight-normal)!important}.font-semibold{--tw-font-weight:var(--font-weight-semibold)!important;font-weight:var(--font-weight-semibold)!important}.tracking-wide{--tw-tracking:var(--tracking-wide)!important;letter-spacing:var(--tracking-wide)!important}.whitespace-nowrap{white-space:nowrap!important}.whitespace-pre-wrap{white-space:pre-wrap!important}.text-amber-600{color:var(--color-amber-600)!important}.text-amber-700{color:var(--color-amber-700)!important}.text-amber-800{color:var(--color-amber-800)!important}.text-amber-900{color:var(--color-amber-900)!important}.text-brand-700{color:var(--color-brand-700)!important}.text-brand-700\/70{color:#3730a3b3!important}@supports (color:color-mix(in lab, red, red)){.text-brand-700\/70{color:color-mix(in oklab, var(--color-brand-700) 70%, transparent)!important}}.text-emerald-700{color:var(--color-emerald-700)!important}.text-red-600{color:var(--color-red-600)!important}.text-red-700{color:var(--color-red-700)!important}.text-slate-400{color:var(--color-slate-400)!important}.text-slate-500{color:var(--color-slate-500)!important}.text-slate-600{color:var(--color-slate-600)!important}.text-slate-700{color:var(--color-slate-700)!important}.text-slate-800{color:var(--color-slate-800)!important}.text-slate-900{color:var(--color-slate-900)!important}.text-white{color:var(--color-white)!important}.uppercase{text-transform:uppercase!important}.italic{font-style:italic!important}.tabular-nums{--tw-numeric-spacing:tabular-nums!important;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)!important}.line-through{text-decoration-line:line-through!important}.underline{text-decoration-line:underline!important}.opacity-70{opacity:.7!important}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.ring-brand-500{--tw-ring-color:var(--color-brand-500)!important}.outline{outline-style:var(--tw-outline-style)!important;outline-width:1px!important}.outline-2{outline-style:var(--tw-outline-style)!important;outline-width:2px!important}.outline-offset-4{outline-offset:4px!important}.outline-brand-500{outline-color:var(--color-brand-500)!important}.blur{--tw-blur:blur(8px)!important;filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)!important}.grayscale{--tw-grayscale:grayscale(100%)!important;filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)!important}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)!important}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events!important;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))!important;transition-duration:var(--tw-duration,var(--default-transition-duration))!important}.transition-\[filter\]{transition-property:filter!important;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))!important;transition-duration:var(--tw-duration,var(--default-transition-duration))!important}.ease-out{--tw-ease:var(--ease-out)!important;transition-timing-function:var(--ease-out)!important}.outline-dashed{--tw-outline-style:dashed!important;outline-style:dashed!important}.outline-none{--tw-outline-style:none!important;outline-style:none!important}@media (hover:hover){.group-hover\:opacity-80:is(:where(.group):hover *){opacity:.8!important}}.last\:border-0:last-child{border-style:var(--tw-border-style)!important;border-width:0!important}@media (hover:hover){.hover\:border-brand-500:hover{border-color:var(--color-brand-500)!important}.hover\:bg-slate-50:hover{background-color:var(--color-slate-50)!important}.hover\:bg-slate-100:hover{background-color:var(--color-slate-100)!important}.hover\:bg-slate-200:hover{background-color:var(--color-slate-200)!important}.hover\:text-slate-600:hover{color:var(--color-slate-600)!important}.hover\:text-slate-700:hover{color:var(--color-slate-700)!important}.hover\:text-slate-900:hover{color:var(--color-slate-900)!important}.hover\:underline:hover{text-decoration-line:underline!important}.hover\:brightness-95:hover{--tw-brightness:brightness(95%)!important;filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)!important}}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.focus\:ring-indigo-500:focus{--tw-ring-color:var(--color-indigo-500)!important}.focus\:outline-none:focus{--tw-outline-style:none!important;outline-style:none!important}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.focus-visible\:ring-brand-500:focus-visible{--tw-ring-color:var(--color-brand-500)!important}.focus-visible\:outline-2:focus-visible{outline-style:var(--tw-outline-style)!important;outline-width:2px!important}.focus-visible\:outline-offset-2:focus-visible{outline-offset:2px!important}.focus-visible\:outline-blue-600:focus-visible{outline-color:var(--color-blue-600)!important}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none!important;outline-style:none!important}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed!important}.disabled\:opacity-50:disabled{opacity:.5!important}.aria-\[current\=page\]\:bg-slate-200[aria-current=page]{background-color:var(--color-slate-200)!important}.aria-\[current\=page\]\:text-slate-900[aria-current=page]{color:var(--color-slate-900)!important}.data-\[state\=active\]\:border-b-2[data-state=active]{border-bottom-style:var(--tw-border-style)!important;border-bottom-width:2px!important}.data-\[state\=active\]\:border-brand-600[data-state=active]{border-color:var(--color-brand-600)!important}.data-\[state\=active\]\:bg-slate-100[data-state=active]{background-color:var(--color-slate-100)!important}.data-\[state\=active\]\:text-brand-700[data-state=active]{color:var(--color-brand-700)!important}.data-\[state\=active\]\:text-slate-900[data-state=active]{color:var(--color-slate-900)!important}.data-\[state\=active\]\:shadow-\[inset_2px_0_0_0_var\(--color-brand-500\)\][data-state=active]{--tw-shadow:inset 2px 0 0 0 var(--tw-shadow-color,var(--color-brand-500))!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}@media (min-width:40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))!important}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))!important}}@media (min-width:48rem){.md\:sticky{position:sticky!important}.md\:top-0{top:0!important}.md\:col-span-5{grid-column:span 5/span 5!important}.md\:block{display:block!important}.md\:flex{display:flex!important}.md\:grid{display:grid!important}.md\:h-screen{height:100vh!important}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))!important}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))!important}.md\:grid-cols-\[1fr_1fr_140px_auto_auto\]{grid-template-columns:1fr 1fr 140px auto auto!important}.md\:grid-cols-\[1fr_110px\]{grid-template-columns:1fr 110px!important}.md\:grid-cols-\[1fr_130px_90px_110px_auto\]{grid-template-columns:1fr 130px 90px 110px auto!important}.md\:grid-cols-\[1fr_140px_1fr_150px\]{grid-template-columns:1fr 140px 1fr 150px!important}.md\:grid-cols-\[1fr_auto_1fr\]{grid-template-columns:1fr auto 1fr!important}.md\:grid-cols-\[248px_1fr\]{grid-template-columns:248px 1fr!important}.md\:grid-cols-\[260px_1fr\]{grid-template-columns:260px 1fr!important}.md\:grid-cols-\[280px_1fr\]{grid-template-columns:280px 1fr!important}.md\:grid-cols-\[300px_1fr\]{grid-template-columns:300px 1fr!important}.md\:grid-cols-\[340px_1fr\]{grid-template-columns:340px 1fr!important}.md\:flex-col{flex-direction:column!important}.md\:items-end{align-items:flex-end!important}.md\:overflow-visible{overflow:visible!important}.md\:border-r{border-right-style:var(--tw-border-style)!important;border-right-width:1px!important}.md\:border-b-0{border-bottom-style:var(--tw-border-style)!important;border-bottom-width:0!important}.md\:p-8{padding:calc(var(--spacing) * 8)!important}.md\:px-8{padding-inline:calc(var(--spacing) * 8)!important}}@media (min-width:64rem){.lg\:col-span-2{grid-column:span 2/span 2!important}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))!important}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))!important}}}}.xicms-admin,.xicms-admin *,.xicms-admin :before,.xicms-admin :after{box-sizing:border-box}.xicms-admin{-webkit-font-smoothing:antialiased;--color-white:#fff;--color-slate-50:#faf9f6;--color-slate-100:#f3f1ec;--color-slate-200:#e5e2da;--color-slate-300:#d5d1c7;--color-slate-400:#a8a399;--color-slate-500:#6e6a62;--color-slate-600:#57534b;--color-slate-700:#403c36;--color-slate-900:#1b1a17;--color-brand:var(--xicms-primary,#4f46e5);--color-brand-500:var(--xicms-primary,#4f46e5);--color-brand-600:var(--xicms-primary,#4f46e5);--color-brand-700:var(--xicms-primary,#4f46e5);font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;line-height:1.5}@supports (color:color-mix(in lab, red, red)){.xicms-admin{--color-brand-700:color-mix(in srgb, var(--xicms-primary,#4f46e5) 78%, #1b1a17)}}.xicms-admin{--color-brand-50:var(--xicms-primary,#4f46e5)}@supports (color:color-mix(in lab, red, red)){.xicms-admin{--color-brand-50:color-mix(in srgb, var(--xicms-primary,#4f46e5) 10%, #fff)}}.xicms-admin{--admin-canvas:#f6f5f2;--admin-sidebar:#f0eee9;--color-background-body:var(--color-white);--color-background-surface:var(--color-white);--color-background-card:var(--color-white);--color-background-muted:var(--color-slate-100);--color-text-primary:var(--color-slate-900);--color-text-secondary:var(--color-slate-500);--color-border:var(--color-slate-200);--color-accent:var(--color-brand-500);--color-on-accent:#fff;--color-error:#dc2626;color-scheme:light;color:var(--color-text-primary);background:var(--admin-canvas)}.xicms-admin h1,.xicms-admin h2,.xicms-admin h3,.xicms-admin p,.xicms-admin figure{margin:0}.xicms-admin button{font:inherit;cursor:pointer}.xicms-admin input,.xicms-admin textarea,.xicms-admin select{font:inherit;color:inherit}.xicms-admin .ProseMirror{outline:none;min-height:8rem}.xicms-admin .ProseMirror p{margin:0 0 .6rem}.xicms-admin .ProseMirror:focus{outline:none}@media (prefers-reduced-motion:reduce){.xicms-admin *,.xicms-admin :before,.xicms-admin :after,.xicms-ve *,.xicms-ve :before,.xicms-ve :after{transition-duration:.001ms!important;animation-duration:.001ms!important;animation-iteration-count:1!important}}.xicms-admin__midden,.xicms-admin__raster{min-height:100vh}@layer utilities{@scope(.xicms-admin){.xicms-admin--ingebed .xicms-admin__midden{min-height:50vh}.xicms-admin--ingebed .xicms-admin__raster{min-height:60vh;display:block!important}.xicms-admin--ingebed .xicms-admin__rail{border-bottom-width:1px;border-right:0;flex-wrap:wrap;align-items:center;gap:1rem;padding:.5rem 1rem;flex-direction:row!important;height:auto!important;position:static!important}}@scope(.xicms-admin){.xicms-admin--ingebed .xicms-admin__lijst{-webkit-overflow-scrolling:touch;flex:100%;min-width:0;overflow-x:auto;flex-direction:row!important}}}.xicms-admin--ingebed .xicms-admin__voet{border-top:0;flex-direction:row;align-items:center;gap:.75rem;margin-top:0;margin-left:auto;padding-top:0;display:flex}.xicms-admin--ingebed .xicms-admin__kop{position:static}.xicms-admin--ingebed{width:100vw;max-width:100vw;margin-left:calc(50% - 50vw);margin-right:calc(50% - 50vw)}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-emerald-700:oklch(50.8% .118 165.612);--color-blue-600:oklch(54.6% .245 262.881);--color-indigo-500:oklch(58.5% .233 277.117);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-slate-900:oklch(20.8% .042 265.755);--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-3xl:48rem;--container-4xl:56rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wide:.025em;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-out:cubic-bezier(0, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--color-brand:#4f46e5;--color-brand-50:#eef2ff;--color-brand-500:#4f46e5;--color-brand-600:#4f46e5;--color-brand-700:#3730a3}}@scope(.xicms-admin){@layer utilities{.pointer-events-none{pointer-events:none!important}.visible{visibility:visible!important}.sr-only{clip-path:inset(50%)!important;white-space:nowrap!important;border-width:0!important;width:1px!important;height:1px!important;margin:-1px!important;padding:0!important;position:absolute!important;overflow:hidden!important}.absolute{position:absolute!important}.fixed{position:fixed!important}.relative{position:relative!important}.static{position:static!important}.sticky{position:sticky!important}.inset-x-0{inset-inline:0!important}.-top-0\.5{top:calc(var(--spacing) * -.5)!important}.-top-2{top:calc(var(--spacing) * -2)!important}.top-0{top:0!important}.-right-2{right:calc(var(--spacing) * -2)!important}.right-0{right:0!important}.-bottom-2{bottom:calc(var(--spacing) * -2)!important}.bottom-0{bottom:0!important}.-left-2{left:calc(var(--spacing) * -2)!important}.left-1\/2{left:50%!important}.z-10{z-index:10!important}.col-span-2{grid-column:span 2/span 2!important}.col-span-full{grid-column:1/-1!important}.container{width:100%!important}@media (min-width:40rem){.container{max-width:40rem!important}}@media (min-width:48rem){.container{max-width:48rem!important}}@media (min-width:64rem){.container{max-width:64rem!important}}@media (min-width:80rem){.container{max-width:80rem!important}}@media (min-width:96rem){.container{max-width:96rem!important}}.-mx-1{margin-inline:calc(var(--spacing) * -1)!important}.-mx-4{margin-inline:calc(var(--spacing) * -4)!important}.mt-0\.5{margin-top:calc(var(--spacing) * .5)!important}.mt-1{margin-top:var(--spacing)!important}.mt-2{margin-top:calc(var(--spacing) * 2)!important}.mt-3{margin-top:calc(var(--spacing) * 3)!important}.mt-4{margin-top:calc(var(--spacing) * 4)!important}.mt-5{margin-top:calc(var(--spacing) * 5)!important}.mt-6{margin-top:calc(var(--spacing) * 6)!important}.mt-auto{margin-top:auto!important}.mr-1{margin-right:var(--spacing)!important}.mr-2{margin-right:calc(var(--spacing) * 2)!important}.mb-1{margin-bottom:var(--spacing)!important}.mb-2{margin-bottom:calc(var(--spacing) * 2)!important}.mb-3{margin-bottom:calc(var(--spacing) * 3)!important}.mb-4{margin-bottom:calc(var(--spacing) * 4)!important}.ml-1{margin-left:var(--spacing)!important}.ml-1\.5{margin-left:calc(var(--spacing) * 1.5)!important}.ml-auto{margin-left:auto!important}.block{display:block!important}.contents{display:contents!important}.flex{display:flex!important}.grid{display:grid!important}.hidden{display:none!important}.inline{display:inline!important}.inline-block{display:inline-block!important}.inline-flex{display:inline-flex!important}.table{display:table!important}.aspect-square{aspect-ratio:1!important}.h-1\.5{height:calc(var(--spacing) * 1.5)!important}.h-2{height:calc(var(--spacing) * 2)!important}.h-4{height:calc(var(--spacing) * 4)!important}.h-5{height:calc(var(--spacing) * 5)!important}.h-6{height:calc(var(--spacing) * 6)!important}.h-8{height:calc(var(--spacing) * 8)!important}.h-9{height:calc(var(--spacing) * 9)!important}.h-10{height:calc(var(--spacing) * 10)!important}.h-12{height:calc(var(--spacing) * 12)!important}.h-14{height:calc(var(--spacing) * 14)!important}.h-16{height:calc(var(--spacing) * 16)!important}.h-28{height:calc(var(--spacing) * 28)!important}.h-32{height:calc(var(--spacing) * 32)!important}.h-\[70vh\]{height:70vh!important}.h-fit{height:fit-content!important}.h-full{height:100%!important}.max-h-\[55vh\]{max-height:55vh!important}.max-h-\[60vh\]{max-height:60vh!important}.min-h-\[8rem\]{min-height:8rem!important}.min-h-screen{min-height:100vh!important}.w-2{width:calc(var(--spacing) * 2)!important}.w-4{width:calc(var(--spacing) * 4)!important}.w-5{width:calc(var(--spacing) * 5)!important}.w-6{width:calc(var(--spacing) * 6)!important}.w-8{width:calc(var(--spacing) * 8)!important}.w-11{width:calc(var(--spacing) * 11)!important}.w-14{width:calc(var(--spacing) * 14)!important}.w-16{width:calc(var(--spacing) * 16)!important}.w-20{width:calc(var(--spacing) * 20)!important}.w-24{width:calc(var(--spacing) * 24)!important}.w-28{width:calc(var(--spacing) * 28)!important}.w-32{width:calc(var(--spacing) * 32)!important}.w-36{width:calc(var(--spacing) * 36)!important}.w-44{width:calc(var(--spacing) * 44)!important}.w-fit{width:fit-content!important}.w-full{width:100%!important}.max-w-3xl{max-width:var(--container-3xl)!important}.max-w-4xl{max-width:var(--container-4xl)!important}.max-w-40{max-width:calc(var(--spacing) * 40)!important}.max-w-md{max-width:var(--container-md)!important}.max-w-sm{max-width:var(--container-sm)!important}.max-w-xs{max-width:var(--container-xs)!important}.min-w-0{min-width:0!important}.min-w-8{min-width:calc(var(--spacing) * 8)!important}.flex-1{flex:1!important}.shrink-0{flex-shrink:0!important}.-translate-x-1\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1)!important;translate:var(--tw-translate-x) var(--tw-translate-y)!important}.-translate-y-1\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1)!important;translate:var(--tw-translate-x) var(--tw-translate-y)!important}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)!important}.cursor-crosshair{cursor:crosshair!important}.cursor-default{cursor:default!important}.cursor-pointer{cursor:pointer!important}.resize{resize:both!important}.list-decimal{list-style-type:decimal!important}.list-disc{list-style-type:disc!important}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))!important}.grid-cols-\[1fr_1\.4fr_auto\]{grid-template-columns:1fr 1.4fr auto!important}.grid-cols-\[1fr_1fr_90px_90px_auto\]{grid-template-columns:1fr 1fr 90px 90px auto!important}.grid-cols-\[1fr_150px_70px_auto\]{grid-template-columns:1fr 150px 70px auto!important}.grid-cols-\[120px_1fr\]{grid-template-columns:120px 1fr!important}.grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto!important}.flex-col{flex-direction:column!important}.flex-row{flex-direction:row!important}.flex-wrap{flex-wrap:wrap!important}.place-items-center{place-items:center!important}.items-baseline{align-items:baseline!important}.items-center{align-items:center!important}.items-end{align-items:flex-end!important}.items-start{align-items:flex-start!important}.justify-between{justify-content:space-between!important}.justify-center{justify-content:center!important}.justify-end{justify-content:flex-end!important}.gap-0\.5{gap:calc(var(--spacing) * .5)!important}.gap-1{gap:var(--spacing)!important}.gap-1\.5{gap:calc(var(--spacing) * 1.5)!important}.gap-2{gap:calc(var(--spacing) * 2)!important}.gap-2\.5{gap:calc(var(--spacing) * 2.5)!important}.gap-3{gap:calc(var(--spacing) * 3)!important}.gap-4{gap:calc(var(--spacing) * 4)!important}.gap-6{gap:calc(var(--spacing) * 6)!important}.gap-x-3{column-gap:calc(var(--spacing) * 3)!important}.gap-x-5{column-gap:calc(var(--spacing) * 5)!important}.gap-y-1{row-gap:var(--spacing)!important}.self-center{align-self:center!important}.self-end{align-self:flex-end!important}.truncate{text-overflow:ellipsis!important;white-space:nowrap!important;overflow:hidden!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-x-auto{overflow-x:auto!important}.overflow-y-auto{overflow-y:auto!important}.rounded{border-radius:.25rem!important}.rounded-full{border-radius:3.40282e38px!important}.rounded-lg{border-radius:var(--radius-lg)!important}.rounded-md{border-radius:var(--radius-md)!important}.rounded-xl{border-radius:var(--radius-xl)!important}.rounded-t-lg{border-top-left-radius:var(--radius-lg)!important;border-top-right-radius:var(--radius-lg)!important}.rounded-b-lg{border-bottom-right-radius:var(--radius-lg)!important;border-bottom-left-radius:var(--radius-lg)!important}.border{border-style:var(--tw-border-style)!important;border-width:1px!important}.border-2{border-style:var(--tw-border-style)!important;border-width:2px!important}.border-t{border-top-style:var(--tw-border-style)!important;border-top-width:1px!important}.border-t-0{border-top-style:var(--tw-border-style)!important;border-top-width:0!important}.border-b{border-bottom-style:var(--tw-border-style)!important;border-bottom-width:1px!important}.border-amber-200{border-color:var(--color-amber-200)!important}.border-amber-300{border-color:var(--color-amber-300)!important}.border-brand-500{border-color:var(--color-brand-500)!important}.border-red-200{border-color:var(--color-red-200)!important}.border-slate-100{border-color:var(--color-slate-100)!important}.border-slate-200{border-color:var(--color-slate-200)!important}.border-slate-300{border-color:var(--color-slate-300)!important}.border-white{border-color:var(--color-white)!important}.bg-amber-50{background-color:var(--color-amber-50)!important}.bg-amber-50\/40{background-color:#fffbeb66!important}@supports (color:color-mix(in lab, red, red)){.bg-amber-50\/40{background-color:color-mix(in oklab, var(--color-amber-50) 40%, transparent)!important}}.bg-brand-50{background-color:var(--color-brand-50)!important}.bg-brand-500{background-color:var(--color-brand-500)!important}.bg-red-50{background-color:var(--color-red-50)!important}.bg-slate-50{background-color:var(--color-slate-50)!important}.bg-slate-100{background-color:var(--color-slate-100)!important}.bg-slate-200{background-color:var(--color-slate-200)!important}.bg-slate-700{background-color:var(--color-slate-700)!important}.bg-transparent{background-color:#0000!important}.bg-white{background-color:var(--color-white)!important}.object-contain{object-fit:contain!important}.object-cover{object-fit:cover!important}.p-0\.5{padding:calc(var(--spacing) * .5)!important}.p-1{padding:var(--spacing)!important}.p-2{padding:calc(var(--spacing) * 2)!important}.p-3{padding:calc(var(--spacing) * 3)!important}.p-4{padding:calc(var(--spacing) * 4)!important}.p-5{padding:calc(var(--spacing) * 5)!important}.p-6{padding:calc(var(--spacing) * 6)!important}.px-1{padding-inline:var(--spacing)!important}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)!important}.px-2{padding-inline:calc(var(--spacing) * 2)!important}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)!important}.px-3{padding-inline:calc(var(--spacing) * 3)!important}.px-4{padding-inline:calc(var(--spacing) * 4)!important}.py-0\.5{padding-block:calc(var(--spacing) * .5)!important}.py-1{padding-block:var(--spacing)!important}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)!important}.py-2{padding-block:calc(var(--spacing) * 2)!important}.py-3{padding-block:calc(var(--spacing) * 3)!important}.py-8{padding-block:calc(var(--spacing) * 8)!important}.py-16{padding-block:calc(var(--spacing) * 16)!important}.pt-0{padding-top:0!important}.pt-1{padding-top:var(--spacing)!important}.pt-3{padding-top:calc(var(--spacing) * 3)!important}.pt-4{padding-top:calc(var(--spacing) * 4)!important}.pt-5{padding-top:calc(var(--spacing) * 5)!important}.pr-2{padding-right:calc(var(--spacing) * 2)!important}.pb-1{padding-bottom:var(--spacing)!important}.pb-2{padding-bottom:calc(var(--spacing) * 2)!important}.pl-1{padding-left:var(--spacing)!important}.pl-5{padding-left:calc(var(--spacing) * 5)!important}.text-center{text-align:center!important}.text-left{text-align:left!important}.text-right{text-align:right!important}.align-top{vertical-align:top!important}.font-mono{font-family:var(--font-mono)!important}.text-3xl{font-size:var(--text-3xl)!important;line-height:var(--tw-leading,var(--text-3xl--line-height))!important}.text-base{font-size:var(--text-base)!important;line-height:var(--tw-leading,var(--text-base--line-height))!important}.text-lg{font-size:var(--text-lg)!important;line-height:var(--tw-leading,var(--text-lg--line-height))!important}.text-sm{font-size:var(--text-sm)!important;line-height:var(--tw-leading,var(--text-sm--line-height))!important}.text-xl{font-size:var(--text-xl)!important;line-height:var(--tw-leading,var(--text-xl--line-height))!important}.text-xs{font-size:var(--text-xs)!important;line-height:var(--tw-leading,var(--text-xs--line-height))!important}.text-\[10px\]{font-size:10px!important}.text-\[11px\]{font-size:11px!important}.font-bold{--tw-font-weight:var(--font-weight-bold)!important;font-weight:var(--font-weight-bold)!important}.font-medium{--tw-font-weight:var(--font-weight-medium)!important;font-weight:var(--font-weight-medium)!important}.font-normal{--tw-font-weight:var(--font-weight-normal)!important;font-weight:var(--font-weight-normal)!important}.font-semibold{--tw-font-weight:var(--font-weight-semibold)!important;font-weight:var(--font-weight-semibold)!important}.tracking-wide{--tw-tracking:var(--tracking-wide)!important;letter-spacing:var(--tracking-wide)!important}.whitespace-nowrap{white-space:nowrap!important}.whitespace-pre-wrap{white-space:pre-wrap!important}.text-amber-600{color:var(--color-amber-600)!important}.text-amber-700{color:var(--color-amber-700)!important}.text-amber-800{color:var(--color-amber-800)!important}.text-amber-900{color:var(--color-amber-900)!important}.text-brand-700{color:var(--color-brand-700)!important}.text-brand-700\/70{color:#3730a3b3!important}@supports (color:color-mix(in lab, red, red)){.text-brand-700\/70{color:color-mix(in oklab, var(--color-brand-700) 70%, transparent)!important}}.text-emerald-700{color:var(--color-emerald-700)!important}.text-red-600{color:var(--color-red-600)!important}.text-red-700{color:var(--color-red-700)!important}.text-slate-400{color:var(--color-slate-400)!important}.text-slate-500{color:var(--color-slate-500)!important}.text-slate-600{color:var(--color-slate-600)!important}.text-slate-700{color:var(--color-slate-700)!important}.text-slate-800{color:var(--color-slate-800)!important}.text-slate-900{color:var(--color-slate-900)!important}.text-white{color:var(--color-white)!important}.uppercase{text-transform:uppercase!important}.italic{font-style:italic!important}.tabular-nums{--tw-numeric-spacing:tabular-nums!important;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)!important}.line-through{text-decoration-line:line-through!important}.underline{text-decoration-line:underline!important}.opacity-70{opacity:.7!important}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.ring-brand-500{--tw-ring-color:var(--color-brand-500)!important}.outline{outline-style:var(--tw-outline-style)!important;outline-width:1px!important}.outline-2{outline-style:var(--tw-outline-style)!important;outline-width:2px!important}.outline-offset-4{outline-offset:4px!important}.outline-brand-500{outline-color:var(--color-brand-500)!important}.blur{--tw-blur:blur(8px)!important;filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)!important}.grayscale{--tw-grayscale:grayscale(100%)!important;filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)!important}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)!important}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events!important;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))!important;transition-duration:var(--tw-duration,var(--default-transition-duration))!important}.transition-\[filter\]{transition-property:filter!important;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))!important;transition-duration:var(--tw-duration,var(--default-transition-duration))!important}.ease-out{--tw-ease:var(--ease-out)!important;transition-timing-function:var(--ease-out)!important}.outline-dashed{--tw-outline-style:dashed!important;outline-style:dashed!important}.outline-none{--tw-outline-style:none!important;outline-style:none!important}@media (hover:hover){.group-hover\:opacity-80:is(:where(.group):hover *){opacity:.8!important}}.last\:border-0:last-child{border-style:var(--tw-border-style)!important;border-width:0!important}@media (hover:hover){.hover\:border-brand-500:hover{border-color:var(--color-brand-500)!important}.hover\:bg-slate-50:hover{background-color:var(--color-slate-50)!important}.hover\:bg-slate-100:hover{background-color:var(--color-slate-100)!important}.hover\:bg-slate-200:hover{background-color:var(--color-slate-200)!important}.hover\:text-slate-600:hover{color:var(--color-slate-600)!important}.hover\:text-slate-700:hover{color:var(--color-slate-700)!important}.hover\:text-slate-900:hover{color:var(--color-slate-900)!important}.hover\:underline:hover{text-decoration-line:underline!important}.hover\:brightness-95:hover{--tw-brightness:brightness(95%)!important;filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)!important}}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.focus\:ring-indigo-500:focus{--tw-ring-color:var(--color-indigo-500)!important}.focus\:outline-none:focus{--tw-outline-style:none!important;outline-style:none!important}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor)!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.focus-visible\:ring-brand-500:focus-visible{--tw-ring-color:var(--color-brand-500)!important}.focus-visible\:outline-2:focus-visible{outline-style:var(--tw-outline-style)!important;outline-width:2px!important}.focus-visible\:outline-offset-2:focus-visible{outline-offset:2px!important}.focus-visible\:outline-blue-600:focus-visible{outline-color:var(--color-blue-600)!important}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none!important;outline-style:none!important}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed!important}.disabled\:opacity-50:disabled{opacity:.5!important}.aria-\[current\=page\]\:bg-slate-200[aria-current=page]{background-color:var(--color-slate-200)!important}.aria-\[current\=page\]\:text-slate-900[aria-current=page]{color:var(--color-slate-900)!important}.data-\[state\=active\]\:border-b-2[data-state=active]{border-bottom-style:var(--tw-border-style)!important;border-bottom-width:2px!important}.data-\[state\=active\]\:border-brand-600[data-state=active]{border-color:var(--color-brand-600)!important}.data-\[state\=active\]\:bg-slate-100[data-state=active]{background-color:var(--color-slate-100)!important}.data-\[state\=active\]\:text-brand-700[data-state=active]{color:var(--color-brand-700)!important}.data-\[state\=active\]\:text-slate-900[data-state=active]{color:var(--color-slate-900)!important}.data-\[state\=active\]\:shadow-\[inset_2px_0_0_0_var\(--color-brand-500\)\][data-state=active]{--tw-shadow:inset 2px 0 0 0 var(--tw-shadow-color,var(--color-brand-500))!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}@media (min-width:40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))!important}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))!important}}@media (min-width:48rem){.md\:sticky{position:sticky!important}.md\:top-0{top:0!important}.md\:col-span-5{grid-column:span 5/span 5!important}.md\:block{display:block!important}.md\:flex{display:flex!important}.md\:grid{display:grid!important}.md\:h-screen{height:100vh!important}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))!important}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))!important}.md\:grid-cols-\[1fr_1fr_140px_auto_auto\]{grid-template-columns:1fr 1fr 140px auto auto!important}.md\:grid-cols-\[1fr_110px\]{grid-template-columns:1fr 110px!important}.md\:grid-cols-\[1fr_130px_90px_110px_auto\]{grid-template-columns:1fr 130px 90px 110px auto!important}.md\:grid-cols-\[1fr_140px_1fr_150px\]{grid-template-columns:1fr 140px 1fr 150px!important}.md\:grid-cols-\[1fr_auto_1fr\]{grid-template-columns:1fr auto 1fr!important}.md\:grid-cols-\[248px_1fr\]{grid-template-columns:248px 1fr!important}.md\:grid-cols-\[260px_1fr\]{grid-template-columns:260px 1fr!important}.md\:grid-cols-\[280px_1fr\]{grid-template-columns:280px 1fr!important}.md\:grid-cols-\[300px_1fr\]{grid-template-columns:300px 1fr!important}.md\:grid-cols-\[340px_1fr\]{grid-template-columns:340px 1fr!important}.md\:flex-col{flex-direction:column!important}.md\:items-end{align-items:flex-end!important}.md\:overflow-visible{overflow:visible!important}.md\:border-r{border-right-style:var(--tw-border-style)!important;border-right-width:1px!important}.md\:border-b-0{border-bottom-style:var(--tw-border-style)!important;border-bottom-width:0!important}.md\:p-8{padding:calc(var(--spacing) * 8)!important}.md\:px-8{padding-inline:calc(var(--spacing) * 8)!important}}@media (min-width:64rem){.lg\:col-span-2{grid-column:span 2/span 2!important}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))!important}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))!important}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))!important}}}}.xicms-admin,.xicms-admin *,.xicms-admin :before,.xicms-admin :after{box-sizing:border-box}.xicms-admin{-webkit-font-smoothing:antialiased;--color-white:#fff;--color-slate-50:#faf9f6;--color-slate-100:#f3f1ec;--color-slate-200:#e5e2da;--color-slate-300:#d5d1c7;--color-slate-400:#a8a399;--color-slate-500:#6e6a62;--color-slate-600:#57534b;--color-slate-700:#403c36;--color-slate-900:#1b1a17;--color-brand:var(--xicms-primary,#4f46e5);--color-brand-500:var(--xicms-primary,#4f46e5);--color-brand-600:var(--xicms-primary,#4f46e5);--color-brand-700:var(--xicms-primary,#4f46e5);font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;line-height:1.5}@supports (color:color-mix(in lab, red, red)){.xicms-admin{--color-brand-700:color-mix(in srgb, var(--xicms-primary,#4f46e5) 78%, #1b1a17)}}.xicms-admin{--color-brand-50:var(--xicms-primary,#4f46e5)}@supports (color:color-mix(in lab, red, red)){.xicms-admin{--color-brand-50:color-mix(in srgb, var(--xicms-primary,#4f46e5) 10%, #fff)}}.xicms-admin{--admin-canvas:#f6f5f2;--admin-sidebar:#f0eee9;--color-background-body:var(--color-white);--color-background-surface:var(--color-white);--color-background-card:var(--color-white);--color-background-muted:var(--color-slate-100);--color-text-primary:var(--color-slate-900);--color-text-secondary:var(--color-slate-500);--color-border:var(--color-slate-200);--color-accent:var(--color-brand-500);--color-on-accent:#fff;--color-error:#dc2626;color-scheme:light;color:var(--color-text-primary);background:var(--admin-canvas)}.xicms-admin h1,.xicms-admin h2,.xicms-admin h3,.xicms-admin p,.xicms-admin figure{margin:0}.xicms-admin button{font:inherit;cursor:pointer}.xicms-admin input,.xicms-admin textarea,.xicms-admin select{font:inherit;color:inherit}.xicms-admin .ProseMirror{outline:none;min-height:8rem}.xicms-admin .ProseMirror p{margin:0 0 .6rem}.xicms-admin .ProseMirror:focus{outline:none}@media (prefers-reduced-motion:reduce){.xicms-admin *,.xicms-admin :before,.xicms-admin :after,.xicms-ve *,.xicms-ve :before,.xicms-ve :after{transition-duration:.001ms!important;animation-duration:.001ms!important;animation-iteration-count:1!important}}.xicms-admin__midden,.xicms-admin__raster{min-height:100vh}.xicms-admin--ingebed .xicms-admin__midden{min-height:50vh}.xicms-admin--ingebed .xicms-admin__raster{min-height:60vh}.xicms-admin--ingebed .xicms-admin__lijst{flex:100%;min-width:0}.xicms-admin--ingebed{width:100vw;max-width:100vw;margin-left:calc(50% - 50vw);margin-right:calc(50% - 50vw)}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}
@@ -23,7 +23,15 @@ const OPTIONS = {
23
23
  "h4",
24
24
  "code",
25
25
  "pre",
26
- "hr"
26
+ "hr",
27
+ "table",
28
+ "thead",
29
+ "tbody",
30
+ "tfoot",
31
+ "tr",
32
+ "th",
33
+ "td",
34
+ "caption"
27
35
  ],
28
36
  allowedAttributes: {
29
37
  a: ["href", "target", "rel"],
@@ -31,7 +39,10 @@ const OPTIONS = {
31
39
  img: ["src", "alt", "title", "width", "height", "loading", "decoding"],
32
40
  // `span` mag uitsluitend `style` dragen — en zelfs dat alleen voor de font-size-preset (zie
33
41
  // `allowedStyles` hieronder). Geen andere attributen (geen class/id/data-*).
34
- span: ["style"]
42
+ span: ["style"],
43
+ // Tabellen: alleen celspanning, geen style/class (opmaak komt uit .xicms-richtext in styles.css).
44
+ th: ["colspan", "rowspan"],
45
+ td: ["colspan", "rowspan"]
35
46
  },
36
47
  allowedStyles: {
37
48
  span: { "font-size": [ALLOWED_FONT_SIZES] }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/sanitize.ts"],"sourcesContent":["import \"server-only\";\n\nimport sanitizeHtml from \"sanitize-html\";\n\n/**\n * Server-side sanitization van rich text (TipTap-output) met een strikte allowlist.\n * Dit draait bij elke write/publish, zodat opgeslagen HTML gegarandeerd veilig te renderen is\n * (de RichText-block rendert met dangerouslySetInnerHTML). Nooit op vertrouwen van de client.\n */\n// Lettergrootte (RichTextEditor.tsx, FONT_SIZES): vaste preset-waarden, geen vrij invoerveld — de\n// regex staat UITSLUITEND deze exacte strings toe (geen url()/expression()/willekeurige CSS).\nconst ALLOWED_FONT_SIZES = /^(0\\.85|1\\.25|1\\.5)rem$/;\n\nconst OPTIONS: sanitizeHtml.IOptions = {\n allowedTags: [\n \"p\", \"br\", \"strong\", \"b\", \"em\", \"i\", \"u\", \"s\", \"a\", \"img\", \"span\",\n \"ul\", \"ol\", \"li\", \"blockquote\", \"h2\", \"h3\", \"h4\", \"code\", \"pre\", \"hr\",\n ],\n allowedAttributes: {\n a: [\"href\", \"target\", \"rel\"],\n // Afbeeldingen in rich text (WYSIWYG): alleen veilige presentatie-attributen, geen on*-handlers.\n img: [\"src\", \"alt\", \"title\", \"width\", \"height\", \"loading\", \"decoding\"],\n // `span` mag uitsluitend `style` dragen — en zelfs dat alleen voor de font-size-preset (zie\n // `allowedStyles` hieronder). Geen andere attributen (geen class/id/data-*).\n span: [\"style\"],\n },\n allowedStyles: {\n span: { \"font-size\": [ALLOWED_FONT_SIZES] },\n },\n // Geen data:-URI's voor afbeeldingen (alleen http/https + relatieve paden naar onze eigen Storage).\n allowedSchemesByTag: { img: [\"http\", \"https\"] },\n allowedSchemes: [\"http\", \"https\", \"mailto\", \"tel\"],\n transformTags: {\n // Externe links altijd veilig openen.\n a: (tagName, attribs) => ({\n tagName,\n attribs: {\n ...attribs,\n rel: \"noopener noreferrer nofollow\",\n ...(attribs.target === \"_blank\" ? { target: \"_blank\" } : {}),\n },\n }),\n // Afbeeldingen lazy + async laten laden voor performance.\n img: (tagName, attribs) => ({ tagName, attribs: { ...attribs, loading: \"lazy\", decoding: \"async\" } }),\n },\n disallowedTagsMode: \"discard\",\n};\n\nexport function sanitizeRichText(html: string): string {\n return sanitizeHtml(html ?? \"\", OPTIONS);\n}\n"],"mappings":"AAAA,OAAO;AAEP,OAAO,kBAAkB;AASzB,MAAM,qBAAqB;AAE3B,MAAM,UAAiC;AAAA,EACrC,aAAa;AAAA,IACX;AAAA,IAAK;AAAA,IAAM;AAAA,IAAU;AAAA,IAAK;AAAA,IAAM;AAAA,IAAK;AAAA,IAAK;AAAA,IAAK;AAAA,IAAK;AAAA,IAAO;AAAA,IAC3D;AAAA,IAAM;AAAA,IAAM;AAAA,IAAM;AAAA,IAAc;AAAA,IAAM;AAAA,IAAM;AAAA,IAAM;AAAA,IAAQ;AAAA,IAAO;AAAA,EACnE;AAAA,EACA,mBAAmB;AAAA,IACjB,GAAG,CAAC,QAAQ,UAAU,KAAK;AAAA;AAAA,IAE3B,KAAK,CAAC,OAAO,OAAO,SAAS,SAAS,UAAU,WAAW,UAAU;AAAA;AAAA;AAAA,IAGrE,MAAM,CAAC,OAAO;AAAA,EAChB;AAAA,EACA,eAAe;AAAA,IACb,MAAM,EAAE,aAAa,CAAC,kBAAkB,EAAE;AAAA,EAC5C;AAAA;AAAA,EAEA,qBAAqB,EAAE,KAAK,CAAC,QAAQ,OAAO,EAAE;AAAA,EAC9C,gBAAgB,CAAC,QAAQ,SAAS,UAAU,KAAK;AAAA,EACjD,eAAe;AAAA;AAAA,IAEb,GAAG,CAAC,SAAS,aAAa;AAAA,MACxB;AAAA,MACA,SAAS;AAAA,QACP,GAAG;AAAA,QACH,KAAK;AAAA,QACL,GAAI,QAAQ,WAAW,WAAW,EAAE,QAAQ,SAAS,IAAI,CAAC;AAAA,MAC5D;AAAA,IACF;AAAA;AAAA,IAEA,KAAK,CAAC,SAAS,aAAa,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,SAAS,QAAQ,UAAU,QAAQ,EAAE;AAAA,EACrG;AAAA,EACA,oBAAoB;AACtB;AAEO,SAAS,iBAAiB,MAAsB;AACrD,SAAO,aAAa,QAAQ,IAAI,OAAO;AACzC;","names":[]}
1
+ {"version":3,"sources":["../../src/server/sanitize.ts"],"sourcesContent":["import \"server-only\";\n\nimport sanitizeHtml from \"sanitize-html\";\n\n/**\n * Server-side sanitization van rich text (TipTap-output) met een strikte allowlist.\n * Dit draait bij elke write/publish, zodat opgeslagen HTML gegarandeerd veilig te renderen is\n * (de RichText-block rendert met dangerouslySetInnerHTML). Nooit op vertrouwen van de client.\n */\n// Lettergrootte (RichTextEditor.tsx, FONT_SIZES): vaste preset-waarden, geen vrij invoerveld — de\n// regex staat UITSLUITEND deze exacte strings toe (geen url()/expression()/willekeurige CSS).\nconst ALLOWED_FONT_SIZES = /^(0\\.85|1\\.25|1\\.5)rem$/;\n\nconst OPTIONS: sanitizeHtml.IOptions = {\n allowedTags: [\n \"p\", \"br\", \"strong\", \"b\", \"em\", \"i\", \"u\", \"s\", \"a\", \"img\", \"span\",\n \"ul\", \"ol\", \"li\", \"blockquote\", \"h2\", \"h3\", \"h4\", \"code\", \"pre\", \"hr\",\n \"table\", \"thead\", \"tbody\", \"tfoot\", \"tr\", \"th\", \"td\", \"caption\",\n ],\n allowedAttributes: {\n a: [\"href\", \"target\", \"rel\"],\n // Afbeeldingen in rich text (WYSIWYG): alleen veilige presentatie-attributen, geen on*-handlers.\n img: [\"src\", \"alt\", \"title\", \"width\", \"height\", \"loading\", \"decoding\"],\n // `span` mag uitsluitend `style` dragen — en zelfs dat alleen voor de font-size-preset (zie\n // `allowedStyles` hieronder). Geen andere attributen (geen class/id/data-*).\n span: [\"style\"],\n // Tabellen: alleen celspanning, geen style/class (opmaak komt uit .xicms-richtext in styles.css).\n th: [\"colspan\", \"rowspan\"],\n td: [\"colspan\", \"rowspan\"],\n },\n allowedStyles: {\n span: { \"font-size\": [ALLOWED_FONT_SIZES] },\n },\n // Geen data:-URI's voor afbeeldingen (alleen http/https + relatieve paden naar onze eigen Storage).\n allowedSchemesByTag: { img: [\"http\", \"https\"] },\n allowedSchemes: [\"http\", \"https\", \"mailto\", \"tel\"],\n transformTags: {\n // Externe links altijd veilig openen.\n a: (tagName, attribs) => ({\n tagName,\n attribs: {\n ...attribs,\n rel: \"noopener noreferrer nofollow\",\n ...(attribs.target === \"_blank\" ? { target: \"_blank\" } : {}),\n },\n }),\n // Afbeeldingen lazy + async laten laden voor performance.\n img: (tagName, attribs) => ({ tagName, attribs: { ...attribs, loading: \"lazy\", decoding: \"async\" } }),\n },\n disallowedTagsMode: \"discard\",\n};\n\nexport function sanitizeRichText(html: string): string {\n return sanitizeHtml(html ?? \"\", OPTIONS);\n}\n"],"mappings":"AAAA,OAAO;AAEP,OAAO,kBAAkB;AASzB,MAAM,qBAAqB;AAE3B,MAAM,UAAiC;AAAA,EACrC,aAAa;AAAA,IACX;AAAA,IAAK;AAAA,IAAM;AAAA,IAAU;AAAA,IAAK;AAAA,IAAM;AAAA,IAAK;AAAA,IAAK;AAAA,IAAK;AAAA,IAAK;AAAA,IAAO;AAAA,IAC3D;AAAA,IAAM;AAAA,IAAM;AAAA,IAAM;AAAA,IAAc;AAAA,IAAM;AAAA,IAAM;AAAA,IAAM;AAAA,IAAQ;AAAA,IAAO;AAAA,IACjE;AAAA,IAAS;AAAA,IAAS;AAAA,IAAS;AAAA,IAAS;AAAA,IAAM;AAAA,IAAM;AAAA,IAAM;AAAA,EACxD;AAAA,EACA,mBAAmB;AAAA,IACjB,GAAG,CAAC,QAAQ,UAAU,KAAK;AAAA;AAAA,IAE3B,KAAK,CAAC,OAAO,OAAO,SAAS,SAAS,UAAU,WAAW,UAAU;AAAA;AAAA;AAAA,IAGrE,MAAM,CAAC,OAAO;AAAA;AAAA,IAEd,IAAI,CAAC,WAAW,SAAS;AAAA,IACzB,IAAI,CAAC,WAAW,SAAS;AAAA,EAC3B;AAAA,EACA,eAAe;AAAA,IACb,MAAM,EAAE,aAAa,CAAC,kBAAkB,EAAE;AAAA,EAC5C;AAAA;AAAA,EAEA,qBAAqB,EAAE,KAAK,CAAC,QAAQ,OAAO,EAAE;AAAA,EAC9C,gBAAgB,CAAC,QAAQ,SAAS,UAAU,KAAK;AAAA,EACjD,eAAe;AAAA;AAAA,IAEb,GAAG,CAAC,SAAS,aAAa;AAAA,MACxB;AAAA,MACA,SAAS;AAAA,QACP,GAAG;AAAA,QACH,KAAK;AAAA,QACL,GAAI,QAAQ,WAAW,WAAW,EAAE,QAAQ,SAAS,IAAI,CAAC;AAAA,MAC5D;AAAA,IACF;AAAA;AAAA,IAEA,KAAK,CAAC,SAAS,aAAa,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,SAAS,QAAQ,UAAU,QAAQ,EAAE;AAAA,EACrG;AAAA,EACA,oBAAoB;AACtB;AAEO,SAAS,iBAAiB,MAAsB;AACrD,SAAO,aAAa,QAAQ,IAAI,OAAO;AACzC;","names":[]}
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
- const CORE_VERSION = true ? "7.29.0" : "0.0.0-dev";
1
+ const CORE_VERSION = true ? "7.31.0" : "0.0.0-dev";
2
2
  const CORE_PACKAGE = "@xiwebdesign/xicms-core";
3
3
  function isNieuwer(beschikbaar, huidig) {
4
4
  const delen = (v) => v.split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiwebdesign/xicms-core",
3
- "version": "7.29.0",
3
+ "version": "7.31.0",
4
4
  "description": "XiCMS engine: gestructureerde content, pagina's, draft/publish, admin (TipTap+Radix), SEO en beveiliging voor klantsites.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -107,6 +107,7 @@
107
107
  "@radix-ui/react-tabs": "^1.1.21",
108
108
  "@tiptap/extension-image": "^3.30.1",
109
109
  "@tiptap/extension-link": "^3.30.1",
110
+ "@tiptap/extension-table": "^3.30.1",
110
111
  "@tiptap/extension-text-style": "^3.30.1",
111
112
  "@tiptap/pm": "^3.30.1",
112
113
  "@tiptap/react": "^3.30.1",