@xiwebdesign/xicms-core 7.23.0 → 7.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/admin/AdminApp.js +2 -1
  2. package/dist/admin/AdminApp.js.map +1 -1
  3. package/dist/admin/CoreVersion.d.ts +6 -0
  4. package/dist/admin/CoreVersion.js +42 -1
  5. package/dist/admin/CoreVersion.js.map +1 -1
  6. package/dist/admin/SuperadminPanel.js +19 -3
  7. package/dist/admin/SuperadminPanel.js.map +1 -1
  8. package/dist/admin/dashboard/DashboardPanel.js +46 -9
  9. package/dist/admin/dashboard/DashboardPanel.js.map +1 -1
  10. package/dist/admin/forms/FormsPanel.d.ts +9 -0
  11. package/dist/admin/forms/FormsPanel.js +102 -16
  12. package/dist/admin/forms/FormsPanel.js.map +1 -1
  13. package/dist/admin/forms/gezien.d.ts +3 -0
  14. package/dist/admin/forms/gezien.js +22 -0
  15. package/dist/admin/forms/gezien.js.map +1 -0
  16. package/dist/admin/lib/kleur.d.ts +32 -0
  17. package/dist/admin/lib/kleur.js +60 -0
  18. package/dist/admin/lib/kleur.js.map +1 -0
  19. package/dist/admin/lib/pagesClient.d.ts +32 -0
  20. package/dist/admin/lib/pagesClient.js +60 -1
  21. package/dist/admin/lib/pagesClient.js.map +1 -1
  22. package/dist/admin/lib/seoHulp.d.ts +28 -0
  23. package/dist/admin/lib/seoHulp.js +33 -0
  24. package/dist/admin/lib/seoHulp.js.map +1 -0
  25. package/dist/admin/media/MediaManager.js +133 -51
  26. package/dist/admin/media/MediaManager.js.map +1 -1
  27. package/dist/admin/pages/PageEditor.d.ts +14 -0
  28. package/dist/admin/pages/PageEditor.js +192 -123
  29. package/dist/admin/pages/PageEditor.js.map +1 -1
  30. package/dist/admin/pages/PagesPanel.js +88 -40
  31. package/dist/admin/pages/PagesPanel.js.map +1 -1
  32. package/dist/admin/settings/SettingsPanel.js +100 -19
  33. package/dist/admin/settings/SettingsPanel.js.map +1 -1
  34. package/dist/admin/shop/ProductsTab.js +1 -1
  35. package/dist/admin/shop/ProductsTab.js.map +1 -1
  36. package/dist/admin/shop/ShopPanel.js +5 -2
  37. package/dist/admin/shop/ShopPanel.js.map +1 -1
  38. package/dist/admin/stats/StatsPanel.js +30 -2
  39. package/dist/admin/stats/StatsPanel.js.map +1 -1
  40. package/dist/admin/stats/lib.d.ts +14 -1
  41. package/dist/admin/stats/lib.js +46 -2
  42. package/dist/admin/stats/lib.js.map +1 -1
  43. package/dist/admin/visual/VisualEditor.js +80 -4
  44. package/dist/admin/visual/VisualEditor.js.map +1 -1
  45. package/dist/admin.css +1 -1
  46. package/dist/blocks/builtins/FormBlockRender.js +8 -2
  47. package/dist/blocks/builtins/FormBlockRender.js.map +1 -1
  48. package/dist/blocks/builtins/lib/submitForm.d.ts +3 -1
  49. package/dist/blocks/builtins/lib/submitForm.js +2 -2
  50. package/dist/blocks/builtins/lib/submitForm.js.map +1 -1
  51. package/dist/components/EditBar.d.ts +3 -1
  52. package/dist/components/EditBar.js +30 -4
  53. package/dist/components/EditBar.js.map +1 -1
  54. package/dist/components/News.js +8 -4
  55. package/dist/components/News.js.map +1 -1
  56. package/dist/components/PageView.d.ts +5 -0
  57. package/dist/components/PageView.js +24 -2
  58. package/dist/components/PageView.js.map +1 -1
  59. package/dist/components/ResponsiveImage.d.ts +1 -1
  60. package/dist/components/ResponsiveImage.js +3 -1
  61. package/dist/components/ResponsiveImage.js.map +1 -1
  62. package/dist/components/site/SiteNav.d.ts +29 -0
  63. package/dist/components/site/SiteNav.js +82 -0
  64. package/dist/components/site/SiteNav.js.map +1 -0
  65. package/dist/content/types.d.ts +8 -0
  66. package/dist/index.d.ts +3 -1
  67. package/dist/index.js +8 -1
  68. package/dist/index.js.map +1 -1
  69. package/dist/media/ImageContext.d.ts +1 -6
  70. package/dist/media/ImageContext.js +6 -1
  71. package/dist/media/ImageContext.js.map +1 -1
  72. package/dist/releaseNotes.d.ts +15 -0
  73. package/dist/releaseNotes.js +28 -0
  74. package/dist/releaseNotes.js.map +1 -0
  75. package/dist/schema/settings.d.ts +105 -0
  76. package/dist/schema/settings.js +45 -5
  77. package/dist/schema/settings.js.map +1 -1
  78. package/dist/schema/siteConfig.d.ts +9 -0
  79. package/dist/server/blockData.js +6 -2
  80. package/dist/server/blockData.js.map +1 -1
  81. package/dist/server/draft.d.ts +11 -0
  82. package/dist/server/draft.js +20 -1
  83. package/dist/server/draft.js.map +1 -1
  84. package/dist/server/index.d.ts +3 -1
  85. package/dist/server/index.js +7 -1
  86. package/dist/server/index.js.map +1 -1
  87. package/dist/server/pages.js +4 -1
  88. package/dist/server/pages.js.map +1 -1
  89. package/dist/server/previewToken.d.ts +34 -0
  90. package/dist/server/previewToken.js +44 -0
  91. package/dist/server/previewToken.js.map +1 -0
  92. package/dist/server/redirects.d.ts +15 -0
  93. package/dist/server/redirects.js +15 -0
  94. package/dist/server/redirects.js.map +1 -0
  95. package/dist/styles.css +110 -0
  96. package/dist/version.js +1 -1
  97. package/package.json +1 -1
@@ -1,54 +1,65 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
3
- import { useEffect, useMemo, useRef, useState } from "react";
4
- import { useRegistry } from "../../blocks/context.js";
5
- import { buildBlockAddOptions } from "../../blocks/registry.js";
6
- import { useModules } from "../../modules/context.js";
7
- import { moduleIdForBlockType } from "../../modules/registry.js";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
8
4
  import { Button, Card, Field, Input, Textarea, Badge } from "../ui/primitives.js";
9
- import { Select } from "../ui/select.js";
5
+ import { Switch } from "../ui/switch.js";
10
6
  import { useToast } from "../ui/toast.js";
11
7
  import { useConfirm } from "../ui/ConfirmDialog.js";
12
- import { builtinBlockEditors, GenericEditor } from "../blockEditors/index.js";
13
- import { StyleEditor } from "../blockEditors/StyleEditor.js";
14
- import { PatternInserter } from "../patterns/PatternInserter.js";
15
- import { saveDraft, publishPage, subscribeVersions, restoreVersion } from "../lib/pagesClient.js";
8
+ import { MediaPickerButton } from "../media/MediaPicker.js";
9
+ import {
10
+ saveDraft,
11
+ publishPage,
12
+ unpublishPage,
13
+ duplicatePage,
14
+ trashPage,
15
+ subscribeVersions,
16
+ restoreVersion,
17
+ paginaToestand
18
+ } from "../lib/pagesClient.js";
16
19
  import { slugify } from "../lib/slugify.js";
17
- function newKey() {
18
- return (globalThis.crypto?.randomUUID?.() ?? `b${Date.now()}${Math.random()}`).slice(0, 12);
19
- }
20
+ import { titelMeter, omschrijvingMeter } from "../lib/seoHulp.js";
21
+ import { callable } from "../../firebase/functionsClient.js";
22
+ import { getFirebaseApp } from "../../firebase/client.js";
23
+ import { isDemoActive } from "../../demo/demoStore.js";
20
24
  function formatVersionDate(createdAt) {
21
25
  const d = createdAt && typeof createdAt.toDate === "function" ? createdAt.toDate() : typeof createdAt === "string" ? new Date(createdAt) : null;
22
26
  if (!d || Number.isNaN(d.getTime())) return "";
23
27
  return d.toLocaleString("nl-NL", { dateStyle: "medium", timeStyle: "short" });
24
28
  }
29
+ function MeterRegel({ m }) {
30
+ const kleur = m.toon === "goed" ? "text-emerald-700" : m.toon === "lang" || m.toon === "leeg" ? "text-amber-700" : "text-slate-500";
31
+ return /* @__PURE__ */ jsx("p", { className: `mt-1 text-xs ${kleur}`, children: m.tekst });
32
+ }
33
+ function ToestandBadge({ p }) {
34
+ const t = paginaToestand(p);
35
+ if (t === "gewijzigd") return /* @__PURE__ */ jsx(Badge, { tone: "amber", children: "gewijzigd, nog niet live" });
36
+ if (t === "live") return /* @__PURE__ */ jsx(Badge, { tone: "green", children: "live" });
37
+ if (t === "prullenbak") return /* @__PURE__ */ jsx(Badge, { children: "prullenbak" });
38
+ return /* @__PURE__ */ jsx(Badge, { tone: "amber", children: "concept" });
39
+ }
25
40
  function PageEditor({ siteId, page, allPages }) {
26
- const registry = useRegistry();
27
- const { activeIds, defs } = useModules();
28
41
  const { toast } = useToast();
29
42
  const confirm = useConfirm();
30
43
  const [draft, setDraft] = useState(page);
31
- const [busy, setBusy] = useState(false);
44
+ const [busy, setBusy] = useState(null);
32
45
  const [saveState, setSaveState] = useState("idle");
33
46
  const [versions, setVersions] = useState([]);
47
+ const [previewUrl, setPreviewUrl] = useState(null);
34
48
  const firstRender = useRef(true);
35
49
  const slugTouched = useRef(Boolean(page.slug));
36
50
  useEffect(() => {
37
51
  setDraft(page);
38
52
  firstRender.current = true;
39
53
  slugTouched.current = Boolean(page.slug);
54
+ setPreviewUrl(null);
40
55
  }, [page.id]);
56
+ useEffect(() => {
57
+ setDraft((d) => ({ ...d, published: page.published, updatedAt: page.updatedAt, lastPublishedAt: page.lastPublishedAt, trashed: page.trashed }));
58
+ }, [page.published, page.updatedAt, page.lastPublishedAt, page.trashed]);
41
59
  useEffect(() => subscribeVersions(siteId, page.id, (v) => setVersions(v)), [siteId, page.id]);
42
60
  function persist(d) {
43
61
  setSaveState("saving");
44
- return saveDraft(siteId, d.id, {
45
- title: d.title,
46
- slug: d.slug,
47
- path: d.path,
48
- seo: d.seo,
49
- blocks: d.blocks,
50
- order: d.order
51
- }).then(() => setSaveState("saved")).catch(() => {
62
+ return saveDraft(siteId, d.id, { title: d.title, slug: d.slug, path: d.path, seo: d.seo }).then(() => setSaveState("saved")).catch(() => {
52
63
  setSaveState("error");
53
64
  toast({ title: "Opslaan mislukt", description: "Wijzigingen zijn nog niet opgeslagen.", tone: "error" });
54
65
  });
@@ -60,7 +71,7 @@ function PageEditor({ siteId, page, allPages }) {
60
71
  }
61
72
  const t = setTimeout(() => void persist(draft), 800);
62
73
  return () => clearTimeout(t);
63
- }, [draft]);
74
+ }, [draft.title, draft.slug, draft.path, draft.seo]);
64
75
  useEffect(() => {
65
76
  const onBeforeUnload = (e) => {
66
77
  if (saveState === "saving" || saveState === "error") {
@@ -71,63 +82,109 @@ function PageEditor({ siteId, page, allPages }) {
71
82
  window.addEventListener("beforeunload", onBeforeUnload);
72
83
  return () => window.removeEventListener("beforeunload", onBeforeUnload);
73
84
  }, [saveState]);
74
- const blocks = draft.blocks ?? [];
75
85
  function update(next) {
76
86
  setDraft(next);
77
87
  }
78
- function setBlocks(next) {
79
- update({ ...draft, blocks: next });
80
- }
81
88
  function setTitle(title) {
82
89
  const slug = slugTouched.current ? draft.slug : slugify(title);
83
90
  update({ ...draft, title, slug });
84
91
  }
85
- function addBlock(type) {
86
- const def = registry.get(type);
87
- const base = def?.defaults?.() ?? {};
88
- setBlocks([...blocks, { _key: newKey(), _type: type, ...base }]);
89
- }
90
- function addBlocks(newBlocks) {
91
- setBlocks([...blocks, ...newBlocks]);
92
+ const seo = draft.seo ?? {};
93
+ const padBotsing = allPages?.some((p) => p.id !== draft.id && !p.trashed && p.path === draft.path) ?? false;
94
+ const padGewijzigd = Boolean(page.published && page.path !== draft.path);
95
+ function openOpSite() {
96
+ try {
97
+ sessionStorage.setItem("xicms-bewerkmodus", "bewerken");
98
+ } catch {
99
+ }
100
+ window.location.assign(draft.path || "/");
92
101
  }
93
- function updateBlock(i, next) {
94
- const copy = [...blocks];
95
- copy[i] = { ...next, _key: blocks[i]._key, _type: blocks[i]._type };
96
- setBlocks(copy);
102
+ async function onPublish() {
103
+ if (padBotsing) {
104
+ toast({ title: "Adres is al in gebruik", description: "Kies eerst een ander pad.", tone: "error" });
105
+ return;
106
+ }
107
+ const ok = await confirm({
108
+ title: "Pagina publiceren?",
109
+ description: padGewijzigd ? `De live site wordt bijgewerkt (${draft.path}). Het oude adres (${page.path}) verwijst automatisch door.` : `De live site wordt bijgewerkt (${draft.path}).`,
110
+ confirmLabel: "Publiceren"
111
+ });
112
+ if (!ok) return;
113
+ setBusy("publish");
114
+ try {
115
+ await saveDraft(siteId, draft.id, { title: draft.title, slug: draft.slug, path: draft.path, seo: draft.seo });
116
+ await publishPage(siteId, draft.id);
117
+ toast({ title: "Gepubliceerd", description: draft.path, tone: "success" });
118
+ } catch (e) {
119
+ toast({ title: "Publiceren mislukt", description: e.message, tone: "error" });
120
+ } finally {
121
+ setBusy(null);
122
+ }
97
123
  }
98
- async function removeBlock(i) {
124
+ async function onUnpublish() {
99
125
  const ok = await confirm({
100
- title: "Blok verwijderen?",
101
- description: "Dit blok wordt uit de pagina verwijderd.",
102
- confirmLabel: "Verwijderen",
126
+ title: "Pagina offline halen?",
127
+ description: "Bezoekers zien de pagina niet meer (het adres geeft een 404). De werkversie blijft bewaard; publiceren zet hem weer live.",
128
+ confirmLabel: "Offline halen",
103
129
  tone: "danger"
104
130
  });
105
131
  if (!ok) return;
106
- setBlocks(blocks.filter((_, idx) => idx !== i));
132
+ setBusy("unpublish");
133
+ try {
134
+ await unpublishPage(siteId, draft.id);
135
+ toast({ title: "Offline gehaald", description: "De live site laat de pagina los na de eerstvolgende rebuild (meestal enkele minuten)." });
136
+ } catch (e) {
137
+ toast({ title: "Offline halen mislukt", description: e.message, tone: "error" });
138
+ } finally {
139
+ setBusy(null);
140
+ }
107
141
  }
108
- function move(i, dir) {
109
- const j = i + dir;
110
- if (j < 0 || j >= blocks.length) return;
111
- const copy = [...blocks];
112
- [copy[i], copy[j]] = [copy[j], copy[i]];
113
- setBlocks(copy);
142
+ async function onDuplicate() {
143
+ setBusy("dup");
144
+ try {
145
+ await duplicatePage(siteId, draft);
146
+ toast({ title: "Pagina gedupliceerd", description: "De kopie staat als concept in de lijst.", tone: "success" });
147
+ } catch (e) {
148
+ toast({ title: "Dupliceren mislukt", description: e.message, tone: "error" });
149
+ } finally {
150
+ setBusy(null);
151
+ }
114
152
  }
115
- async function onPublish() {
153
+ async function onTrash() {
116
154
  const ok = await confirm({
117
- title: "Pagina publiceren?",
118
- description: `De gepubliceerde site wordt bijgewerkt (${draft.path}).`,
119
- confirmLabel: "Publiceren"
155
+ title: `"${draft.title}" naar de prullenbak?`,
156
+ description: page.published ? "De pagina gaat offline en staat 30 dagen in de prullenbak; daarna wordt hij definitief verwijderd. Herstellen kan al die tijd." : "De pagina staat 30 dagen in de prullenbak; daarna wordt hij definitief verwijderd. Herstellen kan al die tijd.",
157
+ confirmLabel: "Naar prullenbak",
158
+ tone: "danger"
120
159
  });
121
160
  if (!ok) return;
122
- setBusy(true);
161
+ setBusy("trash");
123
162
  try {
124
- await saveDraft(siteId, draft.id, { title: draft.title, slug: draft.slug, path: draft.path, seo: draft.seo, blocks: draft.blocks });
125
- await publishPage(siteId, draft.id);
126
- toast({ title: "Gepubliceerd", description: draft.path, tone: "success" });
163
+ await trashPage(siteId, draft);
164
+ toast({ title: "In de prullenbak", description: "Onderaan de lijst te herstellen." });
127
165
  } catch (e) {
128
- toast({ title: "Publiceren mislukt", description: e.message, tone: "error" });
166
+ toast({ title: "Mislukt", description: e.message, tone: "error" });
129
167
  } finally {
130
- setBusy(false);
168
+ setBusy(null);
169
+ }
170
+ }
171
+ async function maakPreviewLink() {
172
+ const app = getFirebaseApp();
173
+ if (!app) return;
174
+ setBusy("preview");
175
+ try {
176
+ const res = await callable(app, "createPreviewLink")({ siteId, path: draft.path });
177
+ setPreviewUrl(res.data.url);
178
+ try {
179
+ await navigator.clipboard?.writeText(res.data.url);
180
+ toast({ title: "Preview-link gekopieerd", description: "Zeven dagen geldig. Wie hem opent ziet de nieuwste stand, ook wat nog niet gepubliceerd is.", tone: "success" });
181
+ } catch {
182
+ toast({ title: "Preview-link gemaakt", description: "Kopieer hem hieronder.", tone: "success" });
183
+ }
184
+ } catch (e) {
185
+ toast({ title: "Preview-link mislukt", description: e.message, tone: "error" });
186
+ } finally {
187
+ setBusy(null);
131
188
  }
132
189
  }
133
190
  async function onRestore(versionId) {
@@ -141,42 +198,37 @@ function PageEditor({ siteId, page, allPages }) {
141
198
  const restored = await restoreVersion(siteId, draft.id, versionId);
142
199
  setDraft({ ...draft, ...restored });
143
200
  firstRender.current = true;
144
- toast({ title: "Versie hersteld", description: "Controleer en publiceer om live te zetten.", tone: "success" });
201
+ toast({ title: "Versie hersteld", description: "Controleer op de site en publiceer om live te zetten.", tone: "success" });
145
202
  } catch (e) {
146
203
  toast({ title: "Herstellen mislukt", description: e.message, tone: "error" });
147
204
  }
148
205
  }
149
- const addOptions = useMemo(() => buildBlockAddOptions(registry.values()), [registry]);
150
- const disabledBlocks = useMemo(() => {
151
- const seen = /* @__PURE__ */ new Set();
152
- const out = [];
153
- for (const b of blocks) {
154
- if (registry.get(b._type)) continue;
155
- const modId = moduleIdForBlockType(defs, b._type);
156
- if (modId && !activeIds.has(modId) && !seen.has(b._type)) {
157
- seen.add(b._type);
158
- out.push(b._type);
159
- }
160
- }
161
- return out;
162
- }, [blocks, registry, defs, activeIds]);
206
+ const inPrullenbak = Boolean(draft.trashed);
163
207
  return /* @__PURE__ */ jsxs("div", { className: "grid gap-4", children: [
164
208
  /* @__PURE__ */ jsxs(Card, { children: [
165
209
  /* @__PURE__ */ jsxs("div", { className: "mb-3 flex flex-wrap items-center justify-between gap-2", children: [
166
210
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
167
- /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold", children: "Pagina-instellingen" }),
168
- draft.published ? /* @__PURE__ */ jsx(Badge, { tone: "green", children: "gepubliceerd" }) : /* @__PURE__ */ jsx(Badge, { tone: "amber", children: "concept" })
211
+ /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold", children: "Pagina" }),
212
+ /* @__PURE__ */ jsx(ToestandBadge, { p: draft })
169
213
  ] }),
170
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
214
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
171
215
  /* @__PURE__ */ jsx("span", { className: "text-xs text-slate-500", "aria-live": "polite", children: saveState === "saving" ? "Opslaan\u2026" : saveState === "saved" ? "\u2713 Opgeslagen" : "" }),
172
216
  saveState === "error" && /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", onClick: () => void persist(draft), children: "Opnieuw opslaan" }),
173
- /* @__PURE__ */ jsx(Button, { onClick: onPublish, disabled: busy, children: busy ? "Bezig\u2026" : "Publiceren" })
217
+ !inPrullenbak && /* @__PURE__ */ jsxs(Fragment, { children: [
218
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", onClick: openOpSite, title: "De tekst en blokken bewerk je op de pagina zelf", children: "\u270E Bewerken op de site" }),
219
+ /* @__PURE__ */ jsx(Button, { size: "sm", onClick: onPublish, disabled: busy !== null, children: busy === "publish" ? "Bezig\u2026" : "Publiceren" })
220
+ ] })
174
221
  ] })
175
222
  ] }),
176
223
  saveState === "error" && /* @__PURE__ */ jsx("div", { className: "mb-3 rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700", children: "Niet alle wijzigingen zijn opgeslagen. Controleer je verbinding en klik \u201COpnieuw opslaan\u201D." }),
224
+ /* @__PURE__ */ jsxs("p", { className: "mb-3 text-xs text-slate-500", children: [
225
+ "De inhoud (teksten, foto's, blokken) bewerk je op de pagina zelf via ",
226
+ /* @__PURE__ */ jsx("em", { children: "Bewerken op de site" }),
227
+ ". Hier staan de dingen die je d\xE1\xE1r niet ziet: de naam, het adres en hoe Google de pagina toont."
228
+ ] }),
177
229
  /* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
178
230
  /* @__PURE__ */ jsx(Field, { label: "Titel", children: /* @__PURE__ */ jsx(Input, { value: draft.title, onChange: (e) => setTitle(e.target.value) }) }),
179
- /* @__PURE__ */ jsxs(Field, { label: "Pad", hint: "bv. / of /over-ons", children: [
231
+ /* @__PURE__ */ jsxs(Field, { label: "Adres (pad)", hint: "bv. / of /over-ons", children: [
180
232
  /* @__PURE__ */ jsx(
181
233
  Input,
182
234
  {
@@ -189,54 +241,56 @@ function PageEditor({ siteId, page, allPages }) {
189
241
  }
190
242
  }
191
243
  ),
192
- allPages?.some((p) => p.id !== draft.id && p.path === draft.path) && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-red-600", role: "alert", children: "\u26A0 Er bestaat al een pagina met dit pad \u2014 \xE9\xE9n van beide wordt dan onbereikbaar." })
193
- ] }),
194
- /* @__PURE__ */ jsx(Field, { label: "SEO-titel", children: /* @__PURE__ */ jsx(Input, { value: draft.seo?.title ?? "", onChange: (e) => update({ ...draft, seo: { ...draft.seo, title: e.target.value } }) }) }),
195
- /* @__PURE__ */ jsx(Field, { label: "SEO-omschrijving", children: /* @__PURE__ */ jsx(Textarea, { rows: 2, value: draft.seo?.description ?? "", onChange: (e) => update({ ...draft, seo: { ...draft.seo, description: e.target.value } }) }) })
244
+ padBotsing && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-red-600", role: "alert", children: "\u26A0 Er bestaat al een pagina met dit adres \u2014 \xE9\xE9n van beide wordt dan onbereikbaar." }),
245
+ padGewijzigd && !padBotsing && /* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-slate-500", children: [
246
+ "Na publiceren verwijst het oude adres (",
247
+ page.path,
248
+ ") automatisch door naar het nieuwe."
249
+ ] })
250
+ ] })
196
251
  ] })
197
252
  ] }),
198
- disabledBlocks.length > 0 && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-amber-300 bg-amber-50 p-3 text-sm text-amber-800", children: [
199
- "Deze pagina bevat blokken van uitgeschakelde modules (",
200
- disabledBlocks.join(", "),
201
- "). Ze worden",
202
- /* @__PURE__ */ jsx("strong", { children: " niet" }),
203
- " op de live site getoond. Zet de module aan onder Instellingen \u2192 Modules, of verwijder de blokken."
204
- ] }),
205
- /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
206
- blocks.map((b, i) => {
207
- const def = registry.get(b._type);
208
- const Editor = def?.Editor ?? builtinBlockEditors[b._type] ?? GenericEditor;
209
- return /* @__PURE__ */ jsxs(Card, { children: [
210
- /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center justify-between", children: [
211
- /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: def?.label ?? b._type }),
212
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
213
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => move(i, -1), "aria-label": "Omhoog", children: "\u2191" }),
214
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => move(i, 1), "aria-label": "Omlaag", children: "\u2193" }),
215
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => void removeBlock(i), "aria-label": "Verwijderen", children: "\u{1F5D1}" })
216
- ] })
217
- ] }),
218
- /* @__PURE__ */ jsx(Editor, { block: b, siteId, onChange: (next) => updateBlock(i, next) }),
219
- /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
220
- StyleEditor,
221
- {
222
- value: b._style,
223
- onChange: (style) => updateBlock(i, { ...b, _style: style })
224
- }
225
- ) })
226
- ] }, b._key);
227
- }),
228
- blocks.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-sm text-slate-500", children: "Nog geen blokken. Voeg er hieronder een toe." })
253
+ /* @__PURE__ */ jsxs(Card, { children: [
254
+ /* @__PURE__ */ jsx("h3", { className: "mb-1 text-base font-semibold", children: "In Google en bij het delen" }),
255
+ /* @__PURE__ */ jsx("p", { className: "mb-3 text-xs text-slate-500", children: "Zo ziet de pagina eruit in zoekresultaten en als iemand de link deelt op social media of WhatsApp." }),
256
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
257
+ /* @__PURE__ */ jsxs(Field, { label: "Titel in zoekresultaten", hint: "Leeg = de paginatitel", children: [
258
+ /* @__PURE__ */ jsx(Input, { value: seo.title ?? "", onChange: (e) => update({ ...draft, seo: { ...seo, title: e.target.value } }) }),
259
+ /* @__PURE__ */ jsx(MeterRegel, { m: titelMeter(seo.title, draft.title) })
260
+ ] }),
261
+ /* @__PURE__ */ jsxs(Field, { label: "Omschrijving", hint: "E\xE9n of twee zinnen die uitnodigen om te klikken", children: [
262
+ /* @__PURE__ */ jsx(Textarea, { rows: 2, value: seo.description ?? "", onChange: (e) => update({ ...draft, seo: { ...seo, description: e.target.value } }) }),
263
+ /* @__PURE__ */ jsx(MeterRegel, { m: omschrijvingMeter(seo.description) })
264
+ ] }),
265
+ /* @__PURE__ */ jsx(Field, { label: "Afbeelding bij het delen", hint: "Verschijnt als voorbeeld op social media en in WhatsApp. Leeg = de standaardafbeelding van de site.", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
266
+ seo.ogImage && // eslint-disable-next-line @next/next/no-img-element
267
+ /* @__PURE__ */ jsx("img", { src: seo.ogImage, alt: "", className: "h-14 w-24 rounded-md border border-slate-200 object-cover" }),
268
+ /* @__PURE__ */ jsx(MediaPickerButton, { siteId, label: seo.ogImage ? "Andere kiezen" : "Kies / upload", onSelect: (m) => update({ ...draft, seo: { ...seo, ogImage: m.url } }) }),
269
+ seo.ogImage && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => update({ ...draft, seo: { ...seo, ogImage: void 0 } }), children: "Weghalen" })
270
+ ] }) }),
271
+ /* @__PURE__ */ jsx(
272
+ Switch,
273
+ {
274
+ checked: Boolean(seo.noindex),
275
+ onCheckedChange: (v) => update({ ...draft, seo: { ...seo, noindex: v || void 0 } }),
276
+ label: "Niet opnemen in Google (bijvoorbeeld een bedankt- of actiepagina)"
277
+ }
278
+ )
279
+ ] })
229
280
  ] }),
230
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
231
- /* @__PURE__ */ jsx("span", { className: "text-sm text-slate-600", children: "Toevoegen:" }),
232
- /* @__PURE__ */ jsx("div", { className: "w-56", children: /* @__PURE__ */ jsx(Select, { value: "", placeholder: "Kies een blok\u2026", onValueChange: (v) => v && addBlock(v), options: addOptions }) }),
233
- /* @__PURE__ */ jsx(PatternInserter, { onInsert: addBlocks })
281
+ !inPrullenbak && /* @__PURE__ */ jsxs(Card, { children: [
282
+ /* @__PURE__ */ jsx("h3", { className: "mb-1 text-base font-semibold", children: "Delen v\xF3\xF3r publiceren" }),
283
+ /* @__PURE__ */ jsx("p", { className: "mb-3 text-xs text-slate-500", children: "Laat iemand zonder inlog meekijken naar de werkversie. De link werkt zeven dagen en geeft geen toegang tot het beheer." }),
284
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
285
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", disabled: busy !== null || isDemoActive(), onClick: () => void maakPreviewLink(), children: busy === "preview" ? "Bezig\u2026" : "Preview-link maken" }),
286
+ previewUrl && /* @__PURE__ */ jsx(Input, { readOnly: true, value: previewUrl, className: "min-w-0 flex-1 text-xs", onFocus: (e) => e.currentTarget.select() })
287
+ ] })
234
288
  ] }),
235
289
  versions.length > 0 && /* @__PURE__ */ jsxs(Card, { children: [
236
290
  /* @__PURE__ */ jsx("h4", { className: "mb-2 text-sm font-semibold", children: "Versiegeschiedenis" }),
237
291
  /* @__PURE__ */ jsx("ul", { className: "grid gap-1 text-sm text-slate-600", children: versions.slice(0, 10).map((v) => /* @__PURE__ */ jsxs("li", { className: "flex items-center justify-between border-b border-slate-100 py-1 last:border-0", children: [
238
292
  /* @__PURE__ */ jsxs("span", { children: [
239
- v.label ?? v.kind ?? "snapshot",
293
+ v.label ?? (v.kind === "publish" ? "Gepubliceerd" : v.kind ?? "snapshot"),
240
294
  formatVersionDate(v.createdAt) && /* @__PURE__ */ jsxs("span", { className: "text-slate-400", children: [
241
295
  " \xB7 ",
242
296
  formatVersionDate(v.createdAt)
@@ -244,10 +298,25 @@ function PageEditor({ siteId, page, allPages }) {
244
298
  ] }),
245
299
  /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => void onRestore(v.id), children: "Herstellen" })
246
300
  ] }, v.id)) })
301
+ ] }),
302
+ !inPrullenbak && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-end gap-3 border-t border-slate-200 pt-4", children: [
303
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", disabled: busy !== null, onClick: () => void onDuplicate(), children: busy === "dup" ? "Bezig\u2026" : "Dupliceren" }),
304
+ page.published && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", disabled: busy !== null, onClick: () => void onUnpublish(), children: busy === "unpublish" ? "Bezig\u2026" : "Offline halen" }),
305
+ /* @__PURE__ */ jsx(
306
+ "button",
307
+ {
308
+ type: "button",
309
+ disabled: busy !== null,
310
+ className: "rounded px-2 py-1 text-sm text-red-600 hover:underline focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none disabled:opacity-50",
311
+ onClick: () => void onTrash(),
312
+ children: "Naar prullenbak"
313
+ }
314
+ )
247
315
  ] })
248
316
  ] });
249
317
  }
250
318
  export {
251
- PageEditor
319
+ PageEditor,
320
+ ToestandBadge
252
321
  };
253
322
  //# sourceMappingURL=PageEditor.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/pages/PageEditor.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useRef, useState, type ComponentType } from \"react\";\nimport { useRegistry } from \"../../blocks/context\";\nimport { buildBlockAddOptions } from \"../../blocks/registry\";\nimport { useModules } from \"../../modules/context\";\nimport { moduleIdForBlockType } from \"../../modules/registry\";\nimport { Button, Card, Field, Input, Textarea, Badge } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { builtinBlockEditors, GenericEditor, type BlockEditorProps } from \"../blockEditors\";\nimport { StyleEditor } from \"../blockEditors/StyleEditor\";\nimport { PatternInserter } from \"../patterns/PatternInserter\";\nimport type { BlockStyle } from \"../../schema/blocks\";\nimport { saveDraft, publishPage, subscribeVersions, restoreVersion, type DraftPage } from \"../lib/pagesClient\";\nimport { slugify } from \"../lib/slugify\";\n\ntype Block = Record<string, unknown> & { _key: string; _type: string };\ntype SaveState = \"idle\" | \"saving\" | \"saved\" | \"error\";\n\ninterface VersionRow {\n id: string;\n createdAt?: unknown;\n kind?: string;\n label?: string;\n}\n\nfunction newKey() {\n return (globalThis.crypto?.randomUUID?.() ?? `b${Date.now()}${Math.random()}`).slice(0, 12);\n}\n\nfunction formatVersionDate(createdAt: unknown): string {\n const d =\n createdAt && typeof (createdAt as { toDate?: () => Date }).toDate === \"function\"\n ? (createdAt as { toDate: () => Date }).toDate()\n : typeof createdAt === \"string\"\n ? new Date(createdAt)\n : null;\n if (!d || Number.isNaN(d.getTime())) return \"\";\n return d.toLocaleString(\"nl-NL\", { dateStyle: \"medium\", timeStyle: \"short\" });\n}\n\nexport function PageEditor({ siteId, page, allPages }: { siteId: string; page: DraftPage; allPages?: DraftPage[] }) {\n const registry = useRegistry();\n const { activeIds, defs } = useModules();\n const { toast } = useToast();\n const confirm = useConfirm();\n const [draft, setDraft] = useState<DraftPage>(page);\n const [busy, setBusy] = useState(false);\n const [saveState, setSaveState] = useState<SaveState>(\"idle\");\n const [versions, setVersions] = useState<VersionRow[]>([]);\n const firstRender = useRef(true);\n const slugTouched = useRef(Boolean(page.slug));\n\n // Wissel van pagina → herlaad lokale state.\n useEffect(() => {\n setDraft(page);\n firstRender.current = true;\n slugTouched.current = Boolean(page.slug);\n }, [page.id]); // eslint-disable-line react-hooks/exhaustive-deps\n\n useEffect(() => subscribeVersions(siteId, page.id, (v) => setVersions(v)), [siteId, page.id]);\n\n function persist(d: DraftPage) {\n setSaveState(\"saving\");\n return saveDraft(siteId, d.id, {\n title: d.title,\n slug: d.slug,\n path: d.path,\n seo: d.seo,\n blocks: d.blocks,\n order: d.order,\n })\n .then(() => setSaveState(\"saved\"))\n .catch(() => {\n setSaveState(\"error\");\n toast({ title: \"Opslaan mislukt\", description: \"Wijzigingen zijn nog niet opgeslagen.\", tone: \"error\" });\n });\n }\n\n // Autosave (debounced) bij wijzigingen.\n useEffect(() => {\n if (firstRender.current) {\n firstRender.current = false;\n return;\n }\n const t = setTimeout(() => void persist(draft), 800);\n return () => clearTimeout(t);\n }, [draft]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Waarschuw bij verlaten van de pagina met niet-opgeslagen/mislukte wijzigingen.\n useEffect(() => {\n const onBeforeUnload = (e: BeforeUnloadEvent) => {\n if (saveState === \"saving\" || saveState === \"error\") {\n e.preventDefault();\n e.returnValue = \"\";\n }\n };\n window.addEventListener(\"beforeunload\", onBeforeUnload);\n return () => window.removeEventListener(\"beforeunload\", onBeforeUnload);\n }, [saveState]);\n\n const blocks = (draft.blocks ?? []) as Block[];\n\n function update(next: DraftPage) {\n setDraft(next);\n }\n function setBlocks(next: Block[]) {\n update({ ...draft, blocks: next });\n }\n function setTitle(title: string) {\n // Auto-slug zolang de gebruiker de slug niet handmatig heeft aangeraakt.\n const slug = slugTouched.current ? draft.slug : slugify(title);\n update({ ...draft, title, slug });\n }\n function addBlock(type: string) {\n const def = registry.get(type);\n const base = def?.defaults?.() ?? {};\n setBlocks([...blocks, { _key: newKey(), _type: type, ...base } as Block]);\n }\n function addBlocks(newBlocks: Array<Record<string, unknown>>) {\n setBlocks([...blocks, ...(newBlocks as Block[])]);\n }\n function updateBlock(i: number, next: Record<string, unknown>) {\n const copy = [...blocks];\n copy[i] = { ...(next as Block), _key: blocks[i]._key, _type: blocks[i]._type };\n setBlocks(copy);\n }\n async function removeBlock(i: number) {\n const ok = await confirm({\n title: \"Blok verwijderen?\",\n description: \"Dit blok wordt uit de pagina verwijderd.\",\n confirmLabel: \"Verwijderen\",\n tone: \"danger\",\n });\n if (!ok) return;\n setBlocks(blocks.filter((_, idx) => idx !== i));\n }\n function move(i: number, dir: -1 | 1) {\n const j = i + dir;\n if (j < 0 || j >= blocks.length) return;\n const copy = [...blocks];\n [copy[i], copy[j]] = [copy[j], copy[i]];\n setBlocks(copy);\n }\n\n async function onPublish() {\n const ok = await confirm({\n title: \"Pagina publiceren?\",\n description: `De gepubliceerde site wordt bijgewerkt (${draft.path}).`,\n confirmLabel: \"Publiceren\",\n });\n if (!ok) return;\n setBusy(true);\n try {\n await saveDraft(siteId, draft.id, { title: draft.title, slug: draft.slug, path: draft.path, seo: draft.seo, blocks: draft.blocks });\n await publishPage(siteId, draft.id);\n toast({ title: \"Gepubliceerd\", description: draft.path, tone: \"success\" });\n } catch (e) {\n toast({ title: \"Publiceren mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setBusy(false);\n }\n }\n\n async function onRestore(versionId: string) {\n const ok = await confirm({\n title: \"Versie herstellen?\",\n description: \"De huidige werkversie wordt overschreven met deze versie. Publiceer daarna om het live te zetten.\",\n confirmLabel: \"Herstellen\",\n });\n if (!ok) return;\n try {\n const restored = await restoreVersion(siteId, draft.id, versionId);\n setDraft({ ...draft, ...restored });\n firstRender.current = true; // herstelde versie is al opgeslagen; geen extra autosave nodig\n toast({ title: \"Versie hersteld\", description: \"Controleer en publiceer om live te zetten.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Herstellen mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n const addOptions = useMemo(() => buildBlockAddOptions(registry.values()), [registry]);\n\n // Blocks waarvan de module is uitgeschakeld → tonen niet op de live site.\n const disabledBlocks = useMemo(() => {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const b of blocks) {\n if (registry.get(b._type)) continue; // actief block-type → ok\n const modId = moduleIdForBlockType(defs, b._type);\n if (modId && !activeIds.has(modId) && !seen.has(b._type)) {\n seen.add(b._type);\n out.push(b._type);\n }\n }\n return out;\n }, [blocks, registry, defs, activeIds]);\n\n return (\n <div className=\"grid gap-4\">\n <Card>\n <div className=\"mb-3 flex flex-wrap items-center justify-between gap-2\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-base font-semibold\">Pagina-instellingen</h3>\n {draft.published ? <Badge tone=\"green\">gepubliceerd</Badge> : <Badge tone=\"amber\">concept</Badge>}\n </div>\n <div className=\"flex items-center gap-3\">\n <span className=\"text-xs text-slate-500\" aria-live=\"polite\">\n {saveState === \"saving\" ? \"Opslaan…\" : saveState === \"saved\" ? \"✓ Opgeslagen\" : \"\"}\n </span>\n {saveState === \"error\" && (\n <Button variant=\"secondary\" size=\"sm\" onClick={() => void persist(draft)}>\n Opnieuw opslaan\n </Button>\n )}\n <Button onClick={onPublish} disabled={busy}>\n {busy ? \"Bezig…\" : \"Publiceren\"}\n </Button>\n </div>\n </div>\n {saveState === \"error\" && (\n <div className=\"mb-3 rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700\">\n Niet alle wijzigingen zijn opgeslagen. Controleer je verbinding en klik “Opnieuw opslaan”.\n </div>\n )}\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Titel\">\n <Input value={draft.title} onChange={(e) => setTitle(e.target.value)} />\n </Field>\n <Field label=\"Pad\" hint=\"bv. / of /over-ons\">\n {/* Vrije tekst brak routing stil (spaties, ontbrekende /, duplicaat-pad → één van de twee\n pagina's onbereikbaar). Normaliseren op blur + waarschuwen bij een botsing. */}\n <Input\n value={draft.path}\n onChange={(e) => update({ ...draft, path: e.target.value })}\n onBlur={() => {\n const parts = draft.path.split(\"/\").map((s) => slugify(s)).filter(Boolean);\n const normalized = \"/\" + parts.join(\"/\");\n if (normalized !== draft.path) update({ ...draft, path: normalized });\n }}\n />\n {allPages?.some((p) => p.id !== draft.id && p.path === draft.path) && (\n <p className=\"mt-1 text-xs text-red-600\" role=\"alert\">\n ⚠ Er bestaat al een pagina met dit pad — één van beide wordt dan onbereikbaar.\n </p>\n )}\n </Field>\n <Field label=\"SEO-titel\">\n <Input value={draft.seo?.title ?? \"\"} onChange={(e) => update({ ...draft, seo: { ...draft.seo, title: e.target.value } })} />\n </Field>\n <Field label=\"SEO-omschrijving\">\n <Textarea rows={2} value={draft.seo?.description ?? \"\"} onChange={(e) => update({ ...draft, seo: { ...draft.seo, description: e.target.value } })} />\n </Field>\n </div>\n </Card>\n\n {disabledBlocks.length > 0 && (\n <div className=\"rounded-lg border border-amber-300 bg-amber-50 p-3 text-sm text-amber-800\">\n Deze pagina bevat blokken van uitgeschakelde modules ({disabledBlocks.join(\", \")}). Ze worden\n <strong> niet</strong> op de live site getoond. Zet de module aan onder Instellingen → Modules,\n of verwijder de blokken.\n </div>\n )}\n\n <div className=\"grid gap-3\">\n {blocks.map((b, i) => {\n const def = registry.get(b._type);\n const Editor: ComponentType<BlockEditorProps> =\n (def?.Editor as ComponentType<BlockEditorProps> | undefined) ?? builtinBlockEditors[b._type] ?? GenericEditor;\n return (\n <Card key={b._key}>\n <div className=\"mb-3 flex items-center justify-between\">\n <span className=\"text-sm font-semibold\">{def?.label ?? b._type}</span>\n <div className=\"flex items-center gap-1\">\n <Button variant=\"ghost\" size=\"sm\" onClick={() => move(i, -1)} aria-label=\"Omhoog\">↑</Button>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => move(i, 1)} aria-label=\"Omlaag\">↓</Button>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => void removeBlock(i)} aria-label=\"Verwijderen\">🗑</Button>\n </div>\n </div>\n <Editor block={b} siteId={siteId} onChange={(next) => updateBlock(i, next)} />\n <div className=\"mt-3\">\n <StyleEditor\n value={(b as Block & { _style?: BlockStyle })._style}\n onChange={(style) => updateBlock(i, { ...b, _style: style })}\n />\n </div>\n </Card>\n );\n })}\n {blocks.length === 0 && <p className=\"text-sm text-slate-500\">Nog geen blokken. Voeg er hieronder een toe.</p>}\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm text-slate-600\">Toevoegen:</span>\n <div className=\"w-56\">\n <Select value=\"\" placeholder=\"Kies een blok…\" onValueChange={(v) => v && addBlock(v)} options={addOptions} />\n </div>\n <PatternInserter onInsert={addBlocks} />\n </div>\n\n {versions.length > 0 && (\n <Card>\n <h4 className=\"mb-2 text-sm font-semibold\">Versiegeschiedenis</h4>\n <ul className=\"grid gap-1 text-sm text-slate-600\">\n {versions.slice(0, 10).map((v) => (\n <li key={v.id} className=\"flex items-center justify-between border-b border-slate-100 py-1 last:border-0\">\n <span>\n {v.label ?? v.kind ?? \"snapshot\"}\n {formatVersionDate(v.createdAt) && <span className=\"text-slate-400\"> · {formatVersionDate(v.createdAt)}</span>}\n </span>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => void onRestore(v.id)}>\n Herstellen\n </Button>\n </li>\n ))}\n </ul>\n </Card>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/pages/PageEditor.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { Button, Card, Field, Input, Textarea, Badge } from \"../ui/primitives\";\nimport { Switch } from \"../ui/switch\";\nimport { useToast } from \"../ui/toast\";\nimport { useConfirm } from \"../ui/ConfirmDialog\";\nimport { MediaPickerButton } from \"../media/MediaPicker\";\nimport {\n saveDraft,\n publishPage,\n unpublishPage,\n duplicatePage,\n trashPage,\n subscribeVersions,\n restoreVersion,\n paginaToestand,\n type DraftPage,\n} from \"../lib/pagesClient\";\nimport { slugify } from \"../lib/slugify\";\nimport { titelMeter, omschrijvingMeter, type Meter } from \"../lib/seoHulp\";\nimport { callable } from \"../../firebase/functionsClient\";\nimport { getFirebaseApp } from \"../../firebase/client\";\nimport { isDemoActive } from \"../../demo/demoStore\";\n\ntype SaveState = \"idle\" | \"saving\" | \"saved\" | \"error\";\n\ninterface VersionRow {\n id: string;\n createdAt?: unknown;\n kind?: string;\n label?: string;\n}\n\nfunction formatVersionDate(createdAt: unknown): string {\n const d =\n createdAt && typeof (createdAt as { toDate?: () => Date }).toDate === \"function\"\n ? (createdAt as { toDate: () => Date }).toDate()\n : typeof createdAt === \"string\"\n ? new Date(createdAt)\n : null;\n if (!d || Number.isNaN(d.getTime())) return \"\";\n return d.toLocaleString(\"nl-NL\", { dateStyle: \"medium\", timeStyle: \"short\" });\n}\n\n/** Lengtemeter onder een SEO-veld: één regel, kleur zegt genoeg. */\nfunction MeterRegel({ m }: { m: Meter }) {\n const kleur = m.toon === \"goed\" ? \"text-emerald-700\" : m.toon === \"lang\" || m.toon === \"leeg\" ? \"text-amber-700\" : \"text-slate-500\";\n return <p className={`mt-1 text-xs ${kleur}`}>{m.tekst}</p>;\n}\n\n/** Badge voor de toestand van een pagina — gedeeld met de lijst. */\nexport function ToestandBadge({ p }: { p: DraftPage }) {\n const t = paginaToestand(p);\n if (t === \"gewijzigd\") return <Badge tone=\"amber\">gewijzigd, nog niet live</Badge>;\n if (t === \"live\") return <Badge tone=\"green\">live</Badge>;\n if (t === \"prullenbak\") return <Badge>prullenbak</Badge>;\n return <Badge tone=\"amber\">concept</Badge>;\n}\n\n/**\n * Pagina-instellingen (7.24) — wat je NIET op de pagina zelf ziet: titel, adres, SEO, versies,\n * en de levenscyclus (publiceren, offline halen, dupliceren, prullenbak).\n *\n * Tot 7.24 stond hier een complete tweede editor: elk blok als formulier, met eigen ↑↓🗑, een\n * eigen publiceerknop zónder de \"wat is er veranderd\"-diff en zonder undo. Het dashboard stuurde\n * de klant intussen naar de site zelf (\"bewerk op de pagina\") — twee manieren voor één ding, met\n * twee gedragingen. De blokken bewerk je nu uitsluitend op de site (knop hieronder); dit scherm\n * gaat over de pagina als geheel.\n */\nexport function PageEditor({ siteId, page, allPages }: { siteId: string; page: DraftPage; allPages?: DraftPage[] }) {\n const { toast } = useToast();\n const confirm = useConfirm();\n const [draft, setDraft] = useState<DraftPage>(page);\n const [busy, setBusy] = useState<string | null>(null);\n const [saveState, setSaveState] = useState<SaveState>(\"idle\");\n const [versions, setVersions] = useState<VersionRow[]>([]);\n const [previewUrl, setPreviewUrl] = useState<string | null>(null);\n const firstRender = useRef(true);\n const slugTouched = useRef(Boolean(page.slug));\n\n // Wissel van pagina → herlaad lokale state. Bewust NIET op elke remote-update: dan zou een\n // autosave-echo je typwerk overschrijven.\n useEffect(() => {\n setDraft(page);\n firstRender.current = true;\n slugTouched.current = Boolean(page.slug);\n setPreviewUrl(null);\n }, [page.id]); // eslint-disable-line react-hooks/exhaustive-deps\n // Toestand-velden volgen wél live (publiceren/offline halen gebeurt buiten dit formulier om).\n useEffect(() => {\n setDraft((d) => ({ ...d, published: page.published, updatedAt: page.updatedAt, lastPublishedAt: page.lastPublishedAt, trashed: page.trashed }));\n }, [page.published, page.updatedAt, page.lastPublishedAt, page.trashed]);\n\n useEffect(() => subscribeVersions(siteId, page.id, (v) => setVersions(v)), [siteId, page.id]);\n\n function persist(d: DraftPage) {\n setSaveState(\"saving\");\n // Alleen de metadata: de blokken zijn van de visuele editor. Zou dit `blocks` meesturen, dan\n // kon een verouderde kopie hier een net-gemaakte inline bewerking overschrijven.\n return saveDraft(siteId, d.id, { title: d.title, slug: d.slug, path: d.path, seo: d.seo })\n .then(() => setSaveState(\"saved\"))\n .catch(() => {\n setSaveState(\"error\");\n toast({ title: \"Opslaan mislukt\", description: \"Wijzigingen zijn nog niet opgeslagen.\", tone: \"error\" });\n });\n }\n\n useEffect(() => {\n if (firstRender.current) {\n firstRender.current = false;\n return;\n }\n const t = setTimeout(() => void persist(draft), 800);\n return () => clearTimeout(t);\n }, [draft.title, draft.slug, draft.path, draft.seo]); // eslint-disable-line react-hooks/exhaustive-deps\n\n useEffect(() => {\n const onBeforeUnload = (e: BeforeUnloadEvent) => {\n if (saveState === \"saving\" || saveState === \"error\") {\n e.preventDefault();\n e.returnValue = \"\";\n }\n };\n window.addEventListener(\"beforeunload\", onBeforeUnload);\n return () => window.removeEventListener(\"beforeunload\", onBeforeUnload);\n }, [saveState]);\n\n function update(next: DraftPage) {\n setDraft(next);\n }\n function setTitle(title: string) {\n const slug = slugTouched.current ? draft.slug : slugify(title);\n update({ ...draft, title, slug });\n }\n\n const seo = draft.seo ?? {};\n const padBotsing = allPages?.some((p) => p.id !== draft.id && !p.trashed && p.path === draft.path) ?? false;\n const padGewijzigd = Boolean(page.published && page.path !== draft.path);\n\n /** Naar de pagina op de site, met de bewerkmodus alvast aan (zelfde sleutel als EditorProvider). */\n function openOpSite() {\n try {\n sessionStorage.setItem(\"xicms-bewerkmodus\", \"bewerken\");\n } catch {\n /* geblokkeerde opslag: dan opent de pagina in kijkmodus en kies je \"Bewerken\" in de balk */\n }\n window.location.assign(draft.path || \"/\");\n }\n\n async function onPublish() {\n if (padBotsing) {\n toast({ title: \"Adres is al in gebruik\", description: \"Kies eerst een ander pad.\", tone: \"error\" });\n return;\n }\n const ok = await confirm({\n title: \"Pagina publiceren?\",\n description: padGewijzigd\n ? `De live site wordt bijgewerkt (${draft.path}). Het oude adres (${page.path}) verwijst automatisch door.`\n : `De live site wordt bijgewerkt (${draft.path}).`,\n confirmLabel: \"Publiceren\",\n });\n if (!ok) return;\n setBusy(\"publish\");\n try {\n await saveDraft(siteId, draft.id, { title: draft.title, slug: draft.slug, path: draft.path, seo: draft.seo });\n await publishPage(siteId, draft.id);\n toast({ title: \"Gepubliceerd\", description: draft.path, tone: \"success\" });\n } catch (e) {\n toast({ title: \"Publiceren mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setBusy(null);\n }\n }\n\n async function onUnpublish() {\n const ok = await confirm({\n title: \"Pagina offline halen?\",\n description: \"Bezoekers zien de pagina niet meer (het adres geeft een 404). De werkversie blijft bewaard; publiceren zet hem weer live.\",\n confirmLabel: \"Offline halen\",\n tone: \"danger\",\n });\n if (!ok) return;\n setBusy(\"unpublish\");\n try {\n await unpublishPage(siteId, draft.id);\n toast({ title: \"Offline gehaald\", description: \"De live site laat de pagina los na de eerstvolgende rebuild (meestal enkele minuten).\" });\n } catch (e) {\n toast({ title: \"Offline halen mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setBusy(null);\n }\n }\n\n async function onDuplicate() {\n setBusy(\"dup\");\n try {\n await duplicatePage(siteId, draft);\n toast({ title: \"Pagina gedupliceerd\", description: \"De kopie staat als concept in de lijst.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Dupliceren mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setBusy(null);\n }\n }\n\n async function onTrash() {\n const ok = await confirm({\n title: `\"${draft.title}\" naar de prullenbak?`,\n description: page.published\n ? \"De pagina gaat offline en staat 30 dagen in de prullenbak; daarna wordt hij definitief verwijderd. Herstellen kan al die tijd.\"\n : \"De pagina staat 30 dagen in de prullenbak; daarna wordt hij definitief verwijderd. Herstellen kan al die tijd.\",\n confirmLabel: \"Naar prullenbak\",\n tone: \"danger\",\n });\n if (!ok) return;\n setBusy(\"trash\");\n try {\n await trashPage(siteId, draft);\n toast({ title: \"In de prullenbak\", description: \"Onderaan de lijst te herstellen.\" });\n } catch (e) {\n toast({ title: \"Mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setBusy(null);\n }\n }\n\n async function maakPreviewLink() {\n const app = getFirebaseApp();\n if (!app) return;\n setBusy(\"preview\");\n try {\n const res = await callable<{ siteId: string; path: string }, { url: string; expiresAt: number }>(app, \"createPreviewLink\")({ siteId, path: draft.path });\n setPreviewUrl(res.data.url);\n try {\n await navigator.clipboard?.writeText(res.data.url);\n toast({ title: \"Preview-link gekopieerd\", description: \"Zeven dagen geldig. Wie hem opent ziet de nieuwste stand, ook wat nog niet gepubliceerd is.\", tone: \"success\" });\n } catch {\n toast({ title: \"Preview-link gemaakt\", description: \"Kopieer hem hieronder.\", tone: \"success\" });\n }\n } catch (e) {\n toast({ title: \"Preview-link mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setBusy(null);\n }\n }\n\n async function onRestore(versionId: string) {\n const ok = await confirm({\n title: \"Versie herstellen?\",\n description: \"De huidige werkversie wordt overschreven met deze versie. Publiceer daarna om het live te zetten.\",\n confirmLabel: \"Herstellen\",\n });\n if (!ok) return;\n try {\n const restored = await restoreVersion(siteId, draft.id, versionId);\n setDraft({ ...draft, ...restored });\n firstRender.current = true;\n toast({ title: \"Versie hersteld\", description: \"Controleer op de site en publiceer om live te zetten.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Herstellen mislukt\", description: (e as Error).message, tone: \"error\" });\n }\n }\n\n const inPrullenbak = Boolean(draft.trashed);\n\n return (\n <div className=\"grid gap-4\">\n <Card>\n <div className=\"mb-3 flex flex-wrap items-center justify-between gap-2\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-base font-semibold\">Pagina</h3>\n <ToestandBadge p={draft} />\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-xs text-slate-500\" aria-live=\"polite\">\n {saveState === \"saving\" ? \"Opslaan…\" : saveState === \"saved\" ? \"✓ Opgeslagen\" : \"\"}\n </span>\n {saveState === \"error\" && (\n <Button variant=\"secondary\" size=\"sm\" onClick={() => void persist(draft)}>\n Opnieuw opslaan\n </Button>\n )}\n {!inPrullenbak && (\n <>\n <Button variant=\"secondary\" size=\"sm\" onClick={openOpSite} title=\"De tekst en blokken bewerk je op de pagina zelf\">\n ✎ Bewerken op de site\n </Button>\n <Button size=\"sm\" onClick={onPublish} disabled={busy !== null}>\n {busy === \"publish\" ? \"Bezig…\" : \"Publiceren\"}\n </Button>\n </>\n )}\n </div>\n </div>\n {saveState === \"error\" && (\n <div className=\"mb-3 rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700\">\n Niet alle wijzigingen zijn opgeslagen. Controleer je verbinding en klik “Opnieuw opslaan”.\n </div>\n )}\n <p className=\"mb-3 text-xs text-slate-500\">\n De inhoud (teksten, foto&apos;s, blokken) bewerk je op de pagina zelf via <em>Bewerken op de site</em>. Hier\n staan de dingen die je dáár niet ziet: de naam, het adres en hoe Google de pagina toont.\n </p>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field label=\"Titel\">\n <Input value={draft.title} onChange={(e) => setTitle(e.target.value)} />\n </Field>\n <Field label=\"Adres (pad)\" hint=\"bv. / of /over-ons\">\n <Input\n value={draft.path}\n onChange={(e) => update({ ...draft, path: e.target.value })}\n onBlur={() => {\n const parts = draft.path.split(\"/\").map((s) => slugify(s)).filter(Boolean);\n const normalized = \"/\" + parts.join(\"/\");\n if (normalized !== draft.path) update({ ...draft, path: normalized });\n }}\n />\n {padBotsing && (\n <p className=\"mt-1 text-xs text-red-600\" role=\"alert\">\n ⚠ Er bestaat al een pagina met dit adres — één van beide wordt dan onbereikbaar.\n </p>\n )}\n {padGewijzigd && !padBotsing && (\n <p className=\"mt-1 text-xs text-slate-500\">\n Na publiceren verwijst het oude adres ({page.path}) automatisch door naar het nieuwe.\n </p>\n )}\n </Field>\n </div>\n </Card>\n\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">In Google en bij het delen</h3>\n <p className=\"mb-3 text-xs text-slate-500\">Zo ziet de pagina eruit in zoekresultaten en als iemand de link deelt op social media of WhatsApp.</p>\n <div className=\"grid gap-3\">\n <Field label=\"Titel in zoekresultaten\" hint=\"Leeg = de paginatitel\">\n <Input value={seo.title ?? \"\"} onChange={(e) => update({ ...draft, seo: { ...seo, title: e.target.value } })} />\n <MeterRegel m={titelMeter(seo.title, draft.title)} />\n </Field>\n <Field label=\"Omschrijving\" hint=\"Eén of twee zinnen die uitnodigen om te klikken\">\n <Textarea rows={2} value={seo.description ?? \"\"} onChange={(e) => update({ ...draft, seo: { ...seo, description: e.target.value } })} />\n <MeterRegel m={omschrijvingMeter(seo.description)} />\n </Field>\n <Field label=\"Afbeelding bij het delen\" hint=\"Verschijnt als voorbeeld op social media en in WhatsApp. Leeg = de standaardafbeelding van de site.\">\n <div className=\"flex flex-wrap items-center gap-2\">\n {seo.ogImage && (\n // eslint-disable-next-line @next/next/no-img-element\n <img src={seo.ogImage} alt=\"\" className=\"h-14 w-24 rounded-md border border-slate-200 object-cover\" />\n )}\n <MediaPickerButton siteId={siteId} label={seo.ogImage ? \"Andere kiezen\" : \"Kies / upload\"} onSelect={(m) => update({ ...draft, seo: { ...seo, ogImage: m.url } })} />\n {seo.ogImage && (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => update({ ...draft, seo: { ...seo, ogImage: undefined } })}>\n Weghalen\n </Button>\n )}\n </div>\n </Field>\n <Switch\n checked={Boolean(seo.noindex)}\n onCheckedChange={(v) => update({ ...draft, seo: { ...seo, noindex: v || undefined } })}\n label=\"Niet opnemen in Google (bijvoorbeeld een bedankt- of actiepagina)\"\n />\n </div>\n </Card>\n\n {!inPrullenbak && (\n <Card>\n <h3 className=\"mb-1 text-base font-semibold\">Delen vóór publiceren</h3>\n <p className=\"mb-3 text-xs text-slate-500\">\n Laat iemand zonder inlog meekijken naar de werkversie. De link werkt zeven dagen en geeft geen toegang tot het beheer.\n </p>\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button variant=\"secondary\" size=\"sm\" disabled={busy !== null || isDemoActive()} onClick={() => void maakPreviewLink()}>\n {busy === \"preview\" ? \"Bezig…\" : \"Preview-link maken\"}\n </Button>\n {previewUrl && <Input readOnly value={previewUrl} className=\"min-w-0 flex-1 text-xs\" onFocus={(e) => e.currentTarget.select()} />}\n </div>\n </Card>\n )}\n\n {versions.length > 0 && (\n <Card>\n <h4 className=\"mb-2 text-sm font-semibold\">Versiegeschiedenis</h4>\n <ul className=\"grid gap-1 text-sm text-slate-600\">\n {versions.slice(0, 10).map((v) => (\n <li key={v.id} className=\"flex items-center justify-between border-b border-slate-100 py-1 last:border-0\">\n <span>\n {v.label ?? (v.kind === \"publish\" ? \"Gepubliceerd\" : v.kind ?? \"snapshot\")}\n {formatVersionDate(v.createdAt) && <span className=\"text-slate-400\"> · {formatVersionDate(v.createdAt)}</span>}\n </span>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => void onRestore(v.id)}>\n Herstellen\n </Button>\n </li>\n ))}\n </ul>\n </Card>\n )}\n\n {!inPrullenbak && (\n <div className=\"flex flex-wrap items-center justify-end gap-3 border-t border-slate-200 pt-4\">\n <Button variant=\"ghost\" size=\"sm\" disabled={busy !== null} onClick={() => void onDuplicate()}>\n {busy === \"dup\" ? \"Bezig…\" : \"Dupliceren\"}\n </Button>\n {page.published && (\n <Button variant=\"ghost\" size=\"sm\" disabled={busy !== null} onClick={() => void onUnpublish()}>\n {busy === \"unpublish\" ? \"Bezig…\" : \"Offline halen\"}\n </Button>\n )}\n <button\n type=\"button\"\n disabled={busy !== null}\n className=\"rounded px-2 py-1 text-sm text-red-600 hover:underline focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:outline-none disabled:opacity-50\"\n onClick={() => void onTrash()}\n >\n Naar prullenbak\n </button>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";AAgDS,SA4OK,UA5OL,KA8NC,YA9ND;AA9CT,SAAS,WAAW,QAAQ,gBAAgB;AAC5C,SAAS,QAAQ,MAAM,OAAO,OAAO,UAAU,aAAa;AAC5D,SAAS,cAAc;AACvB,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B,SAAS,yBAAyB;AAClC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,eAAe;AACxB,SAAS,YAAY,yBAAqC;AAC1D,SAAS,gBAAgB;AACzB,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAW7B,SAAS,kBAAkB,WAA4B;AACrD,QAAM,IACJ,aAAa,OAAQ,UAAsC,WAAW,aACjE,UAAqC,OAAO,IAC7C,OAAO,cAAc,WACnB,IAAI,KAAK,SAAS,IAClB;AACR,MAAI,CAAC,KAAK,OAAO,MAAM,EAAE,QAAQ,CAAC,EAAG,QAAO;AAC5C,SAAO,EAAE,eAAe,SAAS,EAAE,WAAW,UAAU,WAAW,QAAQ,CAAC;AAC9E;AAGA,SAAS,WAAW,EAAE,EAAE,GAAiB;AACvC,QAAM,QAAQ,EAAE,SAAS,SAAS,qBAAqB,EAAE,SAAS,UAAU,EAAE,SAAS,SAAS,mBAAmB;AACnH,SAAO,oBAAC,OAAE,WAAW,gBAAgB,KAAK,IAAK,YAAE,OAAM;AACzD;AAGO,SAAS,cAAc,EAAE,EAAE,GAAqB;AACrD,QAAM,IAAI,eAAe,CAAC;AAC1B,MAAI,MAAM,YAAa,QAAO,oBAAC,SAAM,MAAK,SAAQ,sCAAwB;AAC1E,MAAI,MAAM,OAAQ,QAAO,oBAAC,SAAM,MAAK,SAAQ,kBAAI;AACjD,MAAI,MAAM,aAAc,QAAO,oBAAC,SAAM,wBAAU;AAChD,SAAO,oBAAC,SAAM,MAAK,SAAQ,qBAAO;AACpC;AAYO,SAAS,WAAW,EAAE,QAAQ,MAAM,SAAS,GAAgE;AAClH,QAAM,EAAE,MAAM,IAAI,SAAS;AAC3B,QAAM,UAAU,WAAW;AAC3B,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAoB,IAAI;AAClD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAwB,IAAI;AACpD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAoB,MAAM;AAC5D,QAAM,CAAC,UAAU,WAAW,IAAI,SAAuB,CAAC,CAAC;AACzD,QAAM,CAAC,YAAY,aAAa,IAAI,SAAwB,IAAI;AAChE,QAAM,cAAc,OAAO,IAAI;AAC/B,QAAM,cAAc,OAAO,QAAQ,KAAK,IAAI,CAAC;AAI7C,YAAU,MAAM;AACd,aAAS,IAAI;AACb,gBAAY,UAAU;AACtB,gBAAY,UAAU,QAAQ,KAAK,IAAI;AACvC,kBAAc,IAAI;AAAA,EACpB,GAAG,CAAC,KAAK,EAAE,CAAC;AAEZ,YAAU,MAAM;AACd,aAAS,CAAC,OAAO,EAAE,GAAG,GAAG,WAAW,KAAK,WAAW,WAAW,KAAK,WAAW,iBAAiB,KAAK,iBAAiB,SAAS,KAAK,QAAQ,EAAE;AAAA,EAChJ,GAAG,CAAC,KAAK,WAAW,KAAK,WAAW,KAAK,iBAAiB,KAAK,OAAO,CAAC;AAEvE,YAAU,MAAM,kBAAkB,QAAQ,KAAK,IAAI,CAAC,MAAM,YAAY,CAAC,CAAC,GAAG,CAAC,QAAQ,KAAK,EAAE,CAAC;AAE5F,WAAS,QAAQ,GAAc;AAC7B,iBAAa,QAAQ;AAGrB,WAAO,UAAU,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,KAAK,EAAE,IAAI,CAAC,EACtF,KAAK,MAAM,aAAa,OAAO,CAAC,EAChC,MAAM,MAAM;AACX,mBAAa,OAAO;AACpB,YAAM,EAAE,OAAO,mBAAmB,aAAa,yCAAyC,MAAM,QAAQ,CAAC;AAAA,IACzG,CAAC;AAAA,EACL;AAEA,YAAU,MAAM;AACd,QAAI,YAAY,SAAS;AACvB,kBAAY,UAAU;AACtB;AAAA,IACF;AACA,UAAM,IAAI,WAAW,MAAM,KAAK,QAAQ,KAAK,GAAG,GAAG;AACnD,WAAO,MAAM,aAAa,CAAC;AAAA,EAC7B,GAAG,CAAC,MAAM,OAAO,MAAM,MAAM,MAAM,MAAM,MAAM,GAAG,CAAC;AAEnD,YAAU,MAAM;AACd,UAAM,iBAAiB,CAAC,MAAyB;AAC/C,UAAI,cAAc,YAAY,cAAc,SAAS;AACnD,UAAE,eAAe;AACjB,UAAE,cAAc;AAAA,MAClB;AAAA,IACF;AACA,WAAO,iBAAiB,gBAAgB,cAAc;AACtD,WAAO,MAAM,OAAO,oBAAoB,gBAAgB,cAAc;AAAA,EACxE,GAAG,CAAC,SAAS,CAAC;AAEd,WAAS,OAAO,MAAiB;AAC/B,aAAS,IAAI;AAAA,EACf;AACA,WAAS,SAAS,OAAe;AAC/B,UAAM,OAAO,YAAY,UAAU,MAAM,OAAO,QAAQ,KAAK;AAC7D,WAAO,EAAE,GAAG,OAAO,OAAO,KAAK,CAAC;AAAA,EAClC;AAEA,QAAM,MAAM,MAAM,OAAO,CAAC;AAC1B,QAAM,aAAa,UAAU,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM,MAAM,CAAC,EAAE,WAAW,EAAE,SAAS,MAAM,IAAI,KAAK;AACtG,QAAM,eAAe,QAAQ,KAAK,aAAa,KAAK,SAAS,MAAM,IAAI;AAGvE,WAAS,aAAa;AACpB,QAAI;AACF,qBAAe,QAAQ,qBAAqB,UAAU;AAAA,IACxD,QAAQ;AAAA,IAER;AACA,WAAO,SAAS,OAAO,MAAM,QAAQ,GAAG;AAAA,EAC1C;AAEA,iBAAe,YAAY;AACzB,QAAI,YAAY;AACd,YAAM,EAAE,OAAO,0BAA0B,aAAa,6BAA6B,MAAM,QAAQ,CAAC;AAClG;AAAA,IACF;AACA,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,OAAO;AAAA,MACP,aAAa,eACT,kCAAkC,MAAM,IAAI,sBAAsB,KAAK,IAAI,iCAC3E,kCAAkC,MAAM,IAAI;AAAA,MAChD,cAAc;AAAA,IAChB,CAAC;AACD,QAAI,CAAC,GAAI;AACT,YAAQ,SAAS;AACjB,QAAI;AACF,YAAM,UAAU,QAAQ,MAAM,IAAI,EAAE,OAAO,MAAM,OAAO,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,KAAK,MAAM,IAAI,CAAC;AAC5G,YAAM,YAAY,QAAQ,MAAM,EAAE;AAClC,YAAM,EAAE,OAAO,gBAAgB,aAAa,MAAM,MAAM,MAAM,UAAU,CAAC;AAAA,IAC3E,SAAS,GAAG;AACV,YAAM,EAAE,OAAO,sBAAsB,aAAc,EAAY,SAAS,MAAM,QAAQ,CAAC;AAAA,IACzF,UAAE;AACA,cAAQ,IAAI;AAAA,IACd;AAAA,EACF;AAEA,iBAAe,cAAc;AAC3B,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,OAAO;AAAA,MACP,aAAa;AAAA,MACb,cAAc;AAAA,MACd,MAAM;AAAA,IACR,CAAC;AACD,QAAI,CAAC,GAAI;AACT,YAAQ,WAAW;AACnB,QAAI;AACF,YAAM,cAAc,QAAQ,MAAM,EAAE;AACpC,YAAM,EAAE,OAAO,mBAAmB,aAAa,wFAAwF,CAAC;AAAA,IAC1I,SAAS,GAAG;AACV,YAAM,EAAE,OAAO,yBAAyB,aAAc,EAAY,SAAS,MAAM,QAAQ,CAAC;AAAA,IAC5F,UAAE;AACA,cAAQ,IAAI;AAAA,IACd;AAAA,EACF;AAEA,iBAAe,cAAc;AAC3B,YAAQ,KAAK;AACb,QAAI;AACF,YAAM,cAAc,QAAQ,KAAK;AACjC,YAAM,EAAE,OAAO,uBAAuB,aAAa,2CAA2C,MAAM,UAAU,CAAC;AAAA,IACjH,SAAS,GAAG;AACV,YAAM,EAAE,OAAO,sBAAsB,aAAc,EAAY,SAAS,MAAM,QAAQ,CAAC;AAAA,IACzF,UAAE;AACA,cAAQ,IAAI;AAAA,IACd;AAAA,EACF;AAEA,iBAAe,UAAU;AACvB,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,OAAO,IAAI,MAAM,KAAK;AAAA,MACtB,aAAa,KAAK,YACd,mIACA;AAAA,MACJ,cAAc;AAAA,MACd,MAAM;AAAA,IACR,CAAC;AACD,QAAI,CAAC,GAAI;AACT,YAAQ,OAAO;AACf,QAAI;AACF,YAAM,UAAU,QAAQ,KAAK;AAC7B,YAAM,EAAE,OAAO,oBAAoB,aAAa,mCAAmC,CAAC;AAAA,IACtF,SAAS,GAAG;AACV,YAAM,EAAE,OAAO,WAAW,aAAc,EAAY,SAAS,MAAM,QAAQ,CAAC;AAAA,IAC9E,UAAE;AACA,cAAQ,IAAI;AAAA,IACd;AAAA,EACF;AAEA,iBAAe,kBAAkB;AAC/B,UAAM,MAAM,eAAe;AAC3B,QAAI,CAAC,IAAK;AACV,YAAQ,SAAS;AACjB,QAAI;AACF,YAAM,MAAM,MAAM,SAA+E,KAAK,mBAAmB,EAAE,EAAE,QAAQ,MAAM,MAAM,KAAK,CAAC;AACvJ,oBAAc,IAAI,KAAK,GAAG;AAC1B,UAAI;AACF,cAAM,UAAU,WAAW,UAAU,IAAI,KAAK,GAAG;AACjD,cAAM,EAAE,OAAO,2BAA2B,aAAa,+FAA+F,MAAM,UAAU,CAAC;AAAA,MACzK,QAAQ;AACN,cAAM,EAAE,OAAO,wBAAwB,aAAa,0BAA0B,MAAM,UAAU,CAAC;AAAA,MACjG;AAAA,IACF,SAAS,GAAG;AACV,YAAM,EAAE,OAAO,wBAAwB,aAAc,EAAY,SAAS,MAAM,QAAQ,CAAC;AAAA,IAC3F,UAAE;AACA,cAAQ,IAAI;AAAA,IACd;AAAA,EACF;AAEA,iBAAe,UAAU,WAAmB;AAC1C,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,OAAO;AAAA,MACP,aAAa;AAAA,MACb,cAAc;AAAA,IAChB,CAAC;AACD,QAAI,CAAC,GAAI;AACT,QAAI;AACF,YAAM,WAAW,MAAM,eAAe,QAAQ,MAAM,IAAI,SAAS;AACjE,eAAS,EAAE,GAAG,OAAO,GAAG,SAAS,CAAC;AAClC,kBAAY,UAAU;AACtB,YAAM,EAAE,OAAO,mBAAmB,aAAa,yDAAyD,MAAM,UAAU,CAAC;AAAA,IAC3H,SAAS,GAAG;AACV,YAAM,EAAE,OAAO,sBAAsB,aAAc,EAAY,SAAS,MAAM,QAAQ,CAAC;AAAA,IACzF;AAAA,EACF;AAEA,QAAM,eAAe,QAAQ,MAAM,OAAO;AAE1C,SACE,qBAAC,SAAI,WAAU,cACb;AAAA,yBAAC,QACC;AAAA,2BAAC,SAAI,WAAU,0DACb;AAAA,6BAAC,SAAI,WAAU,2BACb;AAAA,8BAAC,QAAG,WAAU,2BAA0B,oBAAM;AAAA,UAC9C,oBAAC,iBAAc,GAAG,OAAO;AAAA,WAC3B;AAAA,QACA,qBAAC,SAAI,WAAU,qCACb;AAAA,8BAAC,UAAK,WAAU,0BAAyB,aAAU,UAChD,wBAAc,WAAW,kBAAa,cAAc,UAAU,sBAAiB,IAClF;AAAA,UACC,cAAc,WACb,oBAAC,UAAO,SAAQ,aAAY,MAAK,MAAK,SAAS,MAAM,KAAK,QAAQ,KAAK,GAAG,6BAE1E;AAAA,UAED,CAAC,gBACA,iCACE;AAAA,gCAAC,UAAO,SAAQ,aAAY,MAAK,MAAK,SAAS,YAAY,OAAM,mDAAkD,wCAEnH;AAAA,YACA,oBAAC,UAAO,MAAK,MAAK,SAAS,WAAW,UAAU,SAAS,MACtD,mBAAS,YAAY,gBAAW,cACnC;AAAA,aACF;AAAA,WAEJ;AAAA,SACF;AAAA,MACC,cAAc,WACb,oBAAC,SAAI,WAAU,4EAA2E,kHAE1F;AAAA,MAEF,qBAAC,OAAE,WAAU,+BAA8B;AAAA;AAAA,QACiC,oBAAC,QAAG,iCAAmB;AAAA,QAAK;AAAA,SAExG;AAAA,MACA,qBAAC,SAAI,WAAU,6BACb;AAAA,4BAAC,SAAM,OAAM,SACX,8BAAC,SAAM,OAAO,MAAM,OAAO,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK,GAAG,GACxE;AAAA,QACA,qBAAC,SAAM,OAAM,eAAc,MAAK,sBAC9B;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,OAAO,MAAM;AAAA,cACb,UAAU,CAAC,MAAM,OAAO,EAAE,GAAG,OAAO,MAAM,EAAE,OAAO,MAAM,CAAC;AAAA,cAC1D,QAAQ,MAAM;AACZ,sBAAM,QAAQ,MAAM,KAAK,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,QAAQ,CAAC,CAAC,EAAE,OAAO,OAAO;AACzE,sBAAM,aAAa,MAAM,MAAM,KAAK,GAAG;AACvC,oBAAI,eAAe,MAAM,KAAM,QAAO,EAAE,GAAG,OAAO,MAAM,WAAW,CAAC;AAAA,cACtE;AAAA;AAAA,UACF;AAAA,UACC,cACC,oBAAC,OAAE,WAAU,6BAA4B,MAAK,SAAQ,8GAEtD;AAAA,UAED,gBAAgB,CAAC,cAChB,qBAAC,OAAE,WAAU,+BAA8B;AAAA;AAAA,YACD,KAAK;AAAA,YAAK;AAAA,aACpD;AAAA,WAEJ;AAAA,SACF;AAAA,OACF;AAAA,IAEA,qBAAC,QACC;AAAA,0BAAC,QAAG,WAAU,gCAA+B,wCAA0B;AAAA,MACvE,oBAAC,OAAE,WAAU,+BAA8B,gHAAkG;AAAA,MAC7I,qBAAC,SAAI,WAAU,cACb;AAAA,6BAAC,SAAM,OAAM,2BAA0B,MAAK,yBAC1C;AAAA,8BAAC,SAAM,OAAO,IAAI,SAAS,IAAI,UAAU,CAAC,MAAM,OAAO,EAAE,GAAG,OAAO,KAAK,EAAE,GAAG,KAAK,OAAO,EAAE,OAAO,MAAM,EAAE,CAAC,GAAG;AAAA,UAC9G,oBAAC,cAAW,GAAG,WAAW,IAAI,OAAO,MAAM,KAAK,GAAG;AAAA,WACrD;AAAA,QACA,qBAAC,SAAM,OAAM,gBAAe,MAAK,sDAC/B;AAAA,8BAAC,YAAS,MAAM,GAAG,OAAO,IAAI,eAAe,IAAI,UAAU,CAAC,MAAM,OAAO,EAAE,GAAG,OAAO,KAAK,EAAE,GAAG,KAAK,aAAa,EAAE,OAAO,MAAM,EAAE,CAAC,GAAG;AAAA,UACtI,oBAAC,cAAW,GAAG,kBAAkB,IAAI,WAAW,GAAG;AAAA,WACrD;AAAA,QACA,oBAAC,SAAM,OAAM,4BAA2B,MAAK,uGAC3C,+BAAC,SAAI,WAAU,qCACZ;AAAA,cAAI;AAAA,UAEH,oBAAC,SAAI,KAAK,IAAI,SAAS,KAAI,IAAG,WAAU,6DAA4D;AAAA,UAEtG,oBAAC,qBAAkB,QAAgB,OAAO,IAAI,UAAU,kBAAkB,iBAAiB,UAAU,CAAC,MAAM,OAAO,EAAE,GAAG,OAAO,KAAK,EAAE,GAAG,KAAK,SAAS,EAAE,IAAI,EAAE,CAAC,GAAG;AAAA,UAClK,IAAI,WACH,oBAAC,UAAO,SAAQ,SAAQ,MAAK,MAAK,SAAS,MAAM,OAAO,EAAE,GAAG,OAAO,KAAK,EAAE,GAAG,KAAK,SAAS,OAAU,EAAE,CAAC,GAAG,sBAE5G;AAAA,WAEJ,GACF;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,SAAS,QAAQ,IAAI,OAAO;AAAA,YAC5B,iBAAiB,CAAC,MAAM,OAAO,EAAE,GAAG,OAAO,KAAK,EAAE,GAAG,KAAK,SAAS,KAAK,OAAU,EAAE,CAAC;AAAA,YACrF,OAAM;AAAA;AAAA,QACR;AAAA,SACF;AAAA,OACF;AAAA,IAEC,CAAC,gBACA,qBAAC,QACC;AAAA,0BAAC,QAAG,WAAU,gCAA+B,yCAAqB;AAAA,MAClE,oBAAC,OAAE,WAAU,+BAA8B,oIAE3C;AAAA,MACA,qBAAC,SAAI,WAAU,qCACb;AAAA,4BAAC,UAAO,SAAQ,aAAY,MAAK,MAAK,UAAU,SAAS,QAAQ,aAAa,GAAG,SAAS,MAAM,KAAK,gBAAgB,GAClH,mBAAS,YAAY,gBAAW,sBACnC;AAAA,QACC,cAAc,oBAAC,SAAM,UAAQ,MAAC,OAAO,YAAY,WAAU,0BAAyB,SAAS,CAAC,MAAM,EAAE,cAAc,OAAO,GAAG;AAAA,SACjI;AAAA,OACF;AAAA,IAGD,SAAS,SAAS,KACjB,qBAAC,QACC;AAAA,0BAAC,QAAG,WAAU,8BAA6B,gCAAkB;AAAA,MAC7D,oBAAC,QAAG,WAAU,qCACX,mBAAS,MAAM,GAAG,EAAE,EAAE,IAAI,CAAC,MAC1B,qBAAC,QAAc,WAAU,kFACvB;AAAA,6BAAC,UACE;AAAA,YAAE,UAAU,EAAE,SAAS,YAAY,iBAAiB,EAAE,QAAQ;AAAA,UAC9D,kBAAkB,EAAE,SAAS,KAAK,qBAAC,UAAK,WAAU,kBAAiB;AAAA;AAAA,YAAI,kBAAkB,EAAE,SAAS;AAAA,aAAE;AAAA,WACzG;AAAA,QACA,oBAAC,UAAO,SAAQ,SAAQ,MAAK,MAAK,SAAS,MAAM,KAAK,UAAU,EAAE,EAAE,GAAG,wBAEvE;AAAA,WAPO,EAAE,EAQX,CACD,GACH;AAAA,OACF;AAAA,IAGD,CAAC,gBACA,qBAAC,SAAI,WAAU,gFACb;AAAA,0BAAC,UAAO,SAAQ,SAAQ,MAAK,MAAK,UAAU,SAAS,MAAM,SAAS,MAAM,KAAK,YAAY,GACxF,mBAAS,QAAQ,gBAAW,cAC/B;AAAA,MACC,KAAK,aACJ,oBAAC,UAAO,SAAQ,SAAQ,MAAK,MAAK,UAAU,SAAS,MAAM,SAAS,MAAM,KAAK,YAAY,GACxF,mBAAS,cAAc,gBAAW,iBACrC;AAAA,MAEF;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,UAAU,SAAS;AAAA,UACnB,WAAU;AAAA,UACV,SAAS,MAAM,KAAK,QAAQ;AAAA,UAC7B;AAAA;AAAA,MAED;AAAA,OACF;AAAA,KAEJ;AAEJ;","names":[]}