@xiwebdesign/xicms-core 6.2.0 → 6.4.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.
- package/dist/XiCMSProvider.js +2 -0
- package/dist/XiCMSProvider.js.map +1 -1
- package/dist/admin/forms/FormsPanel.js +59 -27
- package/dist/admin/forms/FormsPanel.js.map +1 -1
- package/dist/admin/settings/MailTemplates.d.ts +8 -3
- package/dist/admin/settings/MailTemplates.js +2 -7
- package/dist/admin/settings/MailTemplates.js.map +1 -1
- package/dist/admin/settings/SettingsPanel.js +10 -4
- package/dist/admin/settings/SettingsPanel.js.map +1 -1
- package/dist/admin/shop/ShopPanel.d.ts +3 -3
- package/dist/admin/shop/ShopPanel.js +4 -0
- package/dist/admin/shop/ShopPanel.js.map +1 -1
- package/dist/admin.css +1 -1
- package/dist/blocks/BlockRenderer.js +12 -1
- package/dist/blocks/BlockRenderer.js.map +1 -1
- package/dist/components/FeedbackWidget.d.ts +3 -0
- package/dist/components/FeedbackWidget.js +275 -0
- package/dist/components/FeedbackWidget.js.map +1 -0
- package/dist/firebase/feedback.d.ts +19 -0
- package/dist/firebase/feedback.js +23 -0
- package/dist/firebase/feedback.js.map +1 -0
- package/dist/schema/forms.d.ts +8 -1
- package/dist/schema/forms.js +7 -2
- package/dist/schema/forms.js.map +1 -1
- package/dist/schema/mail.d.ts +4 -0
- package/dist/schema/mail.js +5 -1
- package/dist/schema/mail.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/FeedbackWidget.tsx"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useAuth } from \"../auth/AuthProvider\";\nimport { canEdit } from \"../auth/roles\";\nimport { useEditor } from \"../editor/EditorProvider\";\nimport { addFeedback, uploadFeedbackShot } from \"../firebase/feedback\";\n\n/**\n * Feedback per onderdeel — zichtbaar voor ingelogde editors/siteadmins (vaak de klant zelf) én\n * wanneer de editor open staat (preview). Zelfde zichtbaarheidslogica als de EditBar. De gebruiker\n * kiest een onderdeel op de pagina (hover-highlight + klik → het dichtstbijzijnde block met\n * `data-xicms-block`), schrijft feedback en stuurt optioneel een screenshot van dat onderdeel mee.\n * Alles landt in `sites/{siteId}/feedback` en wordt verzameld in het agency-dashboard.\n *\n * Losstaand: inline styles (geen afhankelijkheid van de huisstijl van de site), alle eigen nodes\n * dragen `data-xicms-feedback-ignore` zodat ze buiten screenshots blijven.\n */\ntype Mode = \"idle\" | \"picking\" | \"form\" | \"sent\";\ntype Picked = { key: string; type: string; el: HTMLElement };\ntype Rect = { top: number; left: number; width: number; height: number };\n\nconst ACCENT = \"#4f46e5\";\n\nexport function FeedbackWidget({ siteId }: { siteId: string }) {\n const { user, role } = useAuth();\n const { open } = useEditor();\n\n const [mode, setMode] = useState<Mode>(\"idle\");\n const [picked, setPicked] = useState<Picked | null>(null);\n const [rect, setRect] = useState<Rect | null>(null);\n const [message, setMessage] = useState(\"\");\n const [withShot, setWithShot] = useState(true);\n const [busy, setBusy] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const pickedRef = useRef<Picked | null>(null);\n\n const active = (Boolean(user) && canEdit(role)) || open;\n\n const reset = useCallback(() => {\n setMode(\"idle\");\n setPicked(null);\n pickedRef.current = null;\n setRect(null);\n setMessage(\"\");\n setError(null);\n }, []);\n\n // Pick-modus: volg de muis, teken een highlight om het dichtstbijzijnde block, en leg bij klik\n // het doelblock vast. Widget-eigen elementen (data-xicms-feedback-ignore) negeren we.\n useEffect(() => {\n if (mode !== \"picking\") return;\n\n const blockAt = (x: number, y: number): HTMLElement | null => {\n const el = document.elementFromPoint(x, y) as HTMLElement | null;\n if (!el || el.closest(\"[data-xicms-feedback-ignore]\")) return null;\n return el.closest<HTMLElement>(\"[data-xicms-block]\");\n };\n\n const onMove = (e: PointerEvent) => {\n const block = blockAt(e.clientX, e.clientY);\n if (!block) {\n setRect(null);\n return;\n }\n const r = block.getBoundingClientRect();\n setRect({ top: r.top, left: r.left, width: r.width, height: r.height });\n };\n\n const onClick = (e: MouseEvent) => {\n const block = blockAt(e.clientX, e.clientY);\n if (!block) return;\n e.preventDefault();\n e.stopPropagation();\n const p: Picked = {\n key: block.dataset.xicmsBlock ?? \"\",\n type: block.dataset.xicmsBlockType ?? \"\",\n el: block,\n };\n pickedRef.current = p;\n setPicked(p);\n setMode(\"form\");\n };\n\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") reset();\n };\n\n document.addEventListener(\"pointermove\", onMove, true);\n document.addEventListener(\"click\", onClick, true);\n document.addEventListener(\"keydown\", onKey, true);\n return () => {\n document.removeEventListener(\"pointermove\", onMove, true);\n document.removeEventListener(\"click\", onClick, true);\n document.removeEventListener(\"keydown\", onKey, true);\n };\n }, [mode, reset]);\n\n async function captureShot(el: HTMLElement): Promise<Blob | null> {\n try {\n const mod = await import(\"html2canvas-pro\");\n const html2canvas = mod.default;\n const canvas = await html2canvas(el, {\n useCORS: true,\n backgroundColor: null,\n scale: Math.min(1.5, window.devicePixelRatio || 1),\n logging: false,\n ignoreElements: (node) => node.hasAttribute?.(\"data-xicms-feedback-ignore\"),\n });\n return await new Promise<Blob | null>((res) => canvas.toBlob((b) => res(b), \"image/png\"));\n } catch {\n return null; // screenshot is best-effort; feedback gaat alsnog door\n }\n }\n\n async function submit() {\n if (!picked || !message.trim()) return;\n setBusy(true);\n setError(null);\n try {\n let screenshotPath: string | undefined;\n if (withShot) {\n const blob = await captureShot(picked.el);\n if (blob) screenshotPath = await uploadFeedbackShot(siteId, blob).catch(() => undefined);\n }\n await addFeedback(siteId, {\n message: message.trim(),\n blockKey: picked.key,\n blockType: picked.type || undefined,\n pageRoute: typeof window !== \"undefined\" ? window.location.pathname : undefined,\n authorUid: user?.uid,\n authorEmail: user?.email ?? undefined,\n screenshotPath,\n });\n setMode(\"sent\");\n setTimeout(reset, 2200);\n } catch (e) {\n setError(e instanceof Error ? e.message : \"Versturen mislukt.\");\n } finally {\n setBusy(false);\n }\n }\n\n if (!active) return null;\n\n return (\n <div data-xicms-feedback-ignore=\"\">\n {/* Highlight-overlay tijdens het kiezen (pointer-events uit zodat klikken doorgaat). */}\n {mode === \"picking\" && rect && (\n <div\n data-xicms-feedback-ignore=\"\"\n style={{\n position: \"fixed\",\n top: rect.top,\n left: rect.left,\n width: rect.width,\n height: rect.height,\n border: `2px solid ${ACCENT}`,\n background: \"rgba(79,70,229,0.08)\",\n borderRadius: 6,\n zIndex: 2147483000,\n pointerEvents: \"none\",\n boxShadow: \"0 0 0 9999px rgba(15,15,25,0.04)\",\n transition: \"all 60ms ease-out\",\n }}\n />\n )}\n\n {/* Instructiebalk tijdens het kiezen. */}\n {mode === \"picking\" && (\n <div\n data-xicms-feedback-ignore=\"\"\n style={{ ...bannerStyle, pointerEvents: \"none\" }}\n >\n Klik op een onderdeel om er feedback op te geven · <b>Esc</b> om te annuleren\n </div>\n )}\n\n {/* Zwevende knop (linksonder — EditBar zit rechtsonder). */}\n {mode === \"idle\" && (\n <button\n type=\"button\"\n data-xicms-feedback-ignore=\"\"\n onClick={() => setMode(\"picking\")}\n style={fabStyle}\n title=\"Feedback geven op een onderdeel\"\n >\n <span style={{ fontSize: 15 }}>💬</span> Feedback\n </button>\n )}\n\n {/* Formulier na het kiezen van een onderdeel. */}\n {mode === \"form\" && picked && (\n <div data-xicms-feedback-ignore=\"\" style={cardStyle}>\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", marginBottom: 8 }}>\n <strong style={{ fontSize: 13 }}>Feedback op dit onderdeel</strong>\n <span style={{ fontSize: 11, color: \"#8a8f98\", fontFamily: \"monospace\" }}>{picked.type || \"block\"}</span>\n </div>\n <textarea\n autoFocus\n value={message}\n onChange={(e) => setMessage(e.target.value)}\n placeholder=\"Wat moet er anders aan dit onderdeel?\"\n rows={4}\n style={textareaStyle}\n />\n <label style={{ display: \"flex\", alignItems: \"center\", gap: 6, fontSize: 12, color: \"#4b5563\", margin: \"8px 0\" }}>\n <input type=\"checkbox\" checked={withShot} onChange={(e) => setWithShot(e.target.checked)} />\n Screenshot van dit onderdeel meesturen\n </label>\n {error && <p style={{ color: \"#dc2626\", fontSize: 12, margin: \"0 0 8px\" }}>{error}</p>}\n <div style={{ display: \"flex\", gap: 8, justifyContent: \"flex-end\" }}>\n <button type=\"button\" onClick={reset} disabled={busy} style={ghostBtn}>Annuleren</button>\n <button type=\"button\" onClick={submit} disabled={busy || !message.trim()} style={primaryBtn}>\n {busy ? \"Versturen…\" : \"Versturen\"}\n </button>\n </div>\n </div>\n )}\n\n {/* Bevestiging. */}\n {mode === \"sent\" && (\n <div data-xicms-feedback-ignore=\"\" style={{ ...cardStyle, textAlign: \"center\" }}>\n <div style={{ fontSize: 22 }}>✓</div>\n <p style={{ fontSize: 13, margin: \"4px 0 0\", color: \"#111827\" }}>Bedankt! Je feedback is opgeslagen.</p>\n </div>\n )}\n </div>\n );\n}\n\n/* ── Inline styles (los van de huisstijl van de site) ──────────────────────── */\nconst fabStyle: React.CSSProperties = {\n position: \"fixed\",\n left: 20,\n bottom: 20,\n zIndex: 2147483000,\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: 7,\n padding: \"9px 14px\",\n border: \"none\",\n borderRadius: 999,\n background: ACCENT,\n color: \"#fff\",\n fontSize: 13,\n fontWeight: 600,\n fontFamily: \"-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif\",\n cursor: \"pointer\",\n boxShadow: \"0 4px 14px rgba(79,70,229,0.4)\",\n};\nconst cardStyle: React.CSSProperties = {\n position: \"fixed\",\n left: 20,\n bottom: 20,\n zIndex: 2147483000,\n width: 320,\n maxWidth: \"calc(100vw - 40px)\",\n padding: 16,\n background: \"#fff\",\n borderRadius: 14,\n boxShadow: \"0 12px 40px rgba(15,15,25,0.22)\",\n border: \"1px solid #eceef2\",\n fontFamily: \"-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif\",\n color: \"#111827\",\n};\nconst bannerStyle: React.CSSProperties = {\n position: \"fixed\",\n top: 16,\n left: \"50%\",\n transform: \"translateX(-50%)\",\n zIndex: 2147483000,\n padding: \"8px 16px\",\n background: \"#111827\",\n color: \"#fff\",\n borderRadius: 999,\n fontSize: 13,\n fontFamily: \"-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif\",\n boxShadow: \"0 6px 20px rgba(0,0,0,0.3)\",\n};\nconst textareaStyle: React.CSSProperties = {\n width: \"100%\",\n boxSizing: \"border-box\",\n padding: \"8px 10px\",\n border: \"1px solid #d7dae0\",\n borderRadius: 8,\n fontSize: 13,\n fontFamily: \"inherit\",\n resize: \"vertical\",\n color: \"#111827\",\n};\nconst primaryBtn: React.CSSProperties = {\n padding: \"7px 14px\",\n border: \"none\",\n borderRadius: 8,\n background: ACCENT,\n color: \"#fff\",\n fontSize: 13,\n fontWeight: 600,\n cursor: \"pointer\",\n};\nconst ghostBtn: React.CSSProperties = {\n padding: \"7px 12px\",\n border: \"1px solid #d7dae0\",\n borderRadius: 8,\n background: \"#fff\",\n color: \"#4b5563\",\n fontSize: 13,\n cursor: \"pointer\",\n};\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feedback per onderdeel (review door een ingelogde editor/siteadmin). Opslag per site in
|
|
3
|
+
* `sites/{siteId}/feedback/{id}` (multi-tenant), screenshots in de privé-Storage
|
|
4
|
+
* `sites/{siteId}/feedback/{uuid}.png`. Editor-gated door de firestore/storage-rules. Wordt
|
|
5
|
+
* verzameld in het agency-dashboard via de listSiteFeedback-callable.
|
|
6
|
+
*/
|
|
7
|
+
export interface FeedbackPayload {
|
|
8
|
+
message: string;
|
|
9
|
+
blockKey: string;
|
|
10
|
+
blockType?: string;
|
|
11
|
+
pageRoute?: string;
|
|
12
|
+
authorUid?: string;
|
|
13
|
+
authorEmail?: string;
|
|
14
|
+
screenshotPath?: string;
|
|
15
|
+
}
|
|
16
|
+
/** Uploadt een screenshot-blob en geeft het Storage-pad terug (of undefined als Storage ontbreekt). */
|
|
17
|
+
export declare function uploadFeedbackShot(siteId: string, blob: Blob): Promise<string | undefined>;
|
|
18
|
+
/** Schrijft een feedback-item. `status` start op "open"; het dashboard zet later "verwerkt". */
|
|
19
|
+
export declare function addFeedback(siteId: string, payload: FeedbackPayload): Promise<void>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { addDoc, collection, serverTimestamp } from "firebase/firestore";
|
|
3
|
+
import { ref, uploadBytes } from "firebase/storage";
|
|
4
|
+
import { getDb, getBucket } from "./client";
|
|
5
|
+
async function uploadFeedbackShot(siteId, blob) {
|
|
6
|
+
const bucket = getBucket();
|
|
7
|
+
if (!bucket) return void 0;
|
|
8
|
+
const path = `sites/${siteId}/feedback/${crypto.randomUUID()}.png`;
|
|
9
|
+
await uploadBytes(ref(bucket, path), blob, { contentType: "image/png" });
|
|
10
|
+
return path;
|
|
11
|
+
}
|
|
12
|
+
async function addFeedback(siteId, payload) {
|
|
13
|
+
const db = getDb();
|
|
14
|
+
if (!db) throw new Error("Firebase niet beschikbaar.");
|
|
15
|
+
const clean = { status: "open", createdAt: serverTimestamp() };
|
|
16
|
+
for (const [k, v] of Object.entries(payload)) if (v !== void 0 && v !== "") clean[k] = v;
|
|
17
|
+
await addDoc(collection(db, "sites", siteId, "feedback"), clean);
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
addFeedback,
|
|
21
|
+
uploadFeedbackShot
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=feedback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/firebase/feedback.ts"],"sourcesContent":["\"use client\";\n\nimport { addDoc, collection, serverTimestamp } from \"firebase/firestore\";\nimport { ref, uploadBytes } from \"firebase/storage\";\nimport { getDb, getBucket } from \"./client\";\n\n/**\n * Feedback per onderdeel (review door een ingelogde editor/siteadmin). Opslag per site in\n * `sites/{siteId}/feedback/{id}` (multi-tenant), screenshots in de privé-Storage\n * `sites/{siteId}/feedback/{uuid}.png`. Editor-gated door de firestore/storage-rules. Wordt\n * verzameld in het agency-dashboard via de listSiteFeedback-callable.\n */\nexport interface FeedbackPayload {\n message: string;\n blockKey: string;\n blockType?: string;\n pageRoute?: string;\n authorUid?: string;\n authorEmail?: string;\n screenshotPath?: string;\n}\n\n/** Uploadt een screenshot-blob en geeft het Storage-pad terug (of undefined als Storage ontbreekt). */\nexport async function uploadFeedbackShot(siteId: string, blob: Blob): Promise<string | undefined> {\n const bucket = getBucket();\n if (!bucket) return undefined;\n const path = `sites/${siteId}/feedback/${crypto.randomUUID()}.png`;\n await uploadBytes(ref(bucket, path), blob, { contentType: \"image/png\" });\n return path;\n}\n\n/** Schrijft een feedback-item. `status` start op \"open\"; het dashboard zet later \"verwerkt\". */\nexport async function addFeedback(siteId: string, payload: FeedbackPayload): Promise<void> {\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet beschikbaar.\");\n // Undefined velden weglaten (Firestore accepteert geen undefined).\n const clean: Record<string, unknown> = { status: \"open\", createdAt: serverTimestamp() };\n for (const [k, v] of Object.entries(payload)) if (v !== undefined && v !== \"\") clean[k] = v;\n await addDoc(collection(db, \"sites\", siteId, \"feedback\"), clean);\n}\n"],"mappings":";AAEA,SAAS,QAAQ,YAAY,uBAAuB;AACpD,SAAS,KAAK,mBAAmB;AACjC,SAAS,OAAO,iBAAiB;AAmBjC,eAAsB,mBAAmB,QAAgB,MAAyC;AAChG,QAAM,SAAS,UAAU;AACzB,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,OAAO,SAAS,MAAM,aAAa,OAAO,WAAW,CAAC;AAC5D,QAAM,YAAY,IAAI,QAAQ,IAAI,GAAG,MAAM,EAAE,aAAa,YAAY,CAAC;AACvE,SAAO;AACT;AAGA,eAAsB,YAAY,QAAgB,SAAyC;AACzF,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,GAAI,OAAM,IAAI,MAAM,4BAA4B;AAErD,QAAM,QAAiC,EAAE,QAAQ,QAAQ,WAAW,gBAAgB,EAAE;AACtF,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,OAAO,EAAG,KAAI,MAAM,UAAa,MAAM,GAAI,OAAM,CAAC,IAAI;AAC1F,QAAM,OAAO,WAAW,IAAI,SAAS,QAAQ,UAAU,GAAG,KAAK;AACjE;","names":[]}
|
package/dist/schema/forms.d.ts
CHANGED
|
@@ -57,8 +57,13 @@ export declare const FormSchema: z.ZodObject<{
|
|
|
57
57
|
successMessage: z.ZodDefault<z.ZodString>;
|
|
58
58
|
/** Stuur na een geslaagde inzending door naar deze pagina (bv. een bedankt-pagina). */
|
|
59
59
|
successRedirect: z.ZodOptional<z.ZodString>;
|
|
60
|
-
/** Stuur de inzender een bevestigingsmail (naar het eerste e-mailveld van het formulier). */
|
|
60
|
+
/** Stuur de inzender een bevestigingsmail/kopie (naar het eerste e-mailveld van het formulier). */
|
|
61
61
|
confirmSubmitter: z.ZodOptional<z.ZodBoolean>;
|
|
62
|
+
/**
|
|
63
|
+
* Kopie van elke inzending naar dit e-mailadres. Leeg = de site-brede notificatie-instelling
|
|
64
|
+
* (Instellingen → E-mail → notificatie-adres, anders het contact-e-mailadres van de site).
|
|
65
|
+
*/
|
|
66
|
+
notifyTo: z.ZodOptional<z.ZodString>;
|
|
62
67
|
}, "strip", z.ZodTypeAny, {
|
|
63
68
|
name: string;
|
|
64
69
|
fields: {
|
|
@@ -71,6 +76,7 @@ export declare const FormSchema: z.ZodObject<{
|
|
|
71
76
|
}[];
|
|
72
77
|
submitLabel: string;
|
|
73
78
|
successMessage: string;
|
|
79
|
+
notifyTo?: string | undefined;
|
|
74
80
|
successRedirect?: string | undefined;
|
|
75
81
|
confirmSubmitter?: boolean | undefined;
|
|
76
82
|
}, {
|
|
@@ -83,6 +89,7 @@ export declare const FormSchema: z.ZodObject<{
|
|
|
83
89
|
placeholder?: string | undefined;
|
|
84
90
|
required?: boolean | undefined;
|
|
85
91
|
}[];
|
|
92
|
+
notifyTo?: string | undefined;
|
|
86
93
|
submitLabel?: string | undefined;
|
|
87
94
|
successMessage?: string | undefined;
|
|
88
95
|
successRedirect?: string | undefined;
|
package/dist/schema/forms.js
CHANGED
|
@@ -28,8 +28,13 @@ const FormSchema = z.object({
|
|
|
28
28
|
successMessage: z.string().max(500).default("Bedankt! We nemen snel contact op."),
|
|
29
29
|
/** Stuur na een geslaagde inzending door naar deze pagina (bv. een bedankt-pagina). */
|
|
30
30
|
successRedirect: z.string().max(400).optional(),
|
|
31
|
-
/** Stuur de inzender een bevestigingsmail (naar het eerste e-mailveld van het formulier). */
|
|
32
|
-
confirmSubmitter: z.boolean().optional()
|
|
31
|
+
/** Stuur de inzender een bevestigingsmail/kopie (naar het eerste e-mailveld van het formulier). */
|
|
32
|
+
confirmSubmitter: z.boolean().optional(),
|
|
33
|
+
/**
|
|
34
|
+
* Kopie van elke inzending naar dit e-mailadres. Leeg = de site-brede notificatie-instelling
|
|
35
|
+
* (Instellingen → E-mail → notificatie-adres, anders het contact-e-mailadres van de site).
|
|
36
|
+
*/
|
|
37
|
+
notifyTo: z.string().max(200).optional()
|
|
33
38
|
});
|
|
34
39
|
const SubmissionInputSchema = z.object({
|
|
35
40
|
siteId: z.string().min(1).max(80),
|
package/dist/schema/forms.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/schema/forms.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport const FieldTypeSchema = z.enum([\n \"text\",\n \"email\",\n \"tel\",\n \"textarea\",\n \"select\",\n \"checkbox\",\n // 4.16: update-veilig toegevoegd (oude definities blijven geldig).\n \"radio\",\n \"date\",\n \"number\",\n]);\nexport type FieldType = z.infer<typeof FieldTypeSchema>;\n\nexport const FormFieldSchema = z.object({\n /** Stabiele sleutel; wordt de key in de inzending. */\n name: z\n .string()\n .min(1)\n .max(60)\n .regex(/^[a-zA-Z][a-zA-Z0-9_]*$/, \"veldnaam moet met een letter beginnen en alleen letters/cijfers/_ bevatten\"),\n label: z.string().min(1).max(160),\n type: FieldTypeSchema,\n required: z.boolean().default(false),\n placeholder: z.string().max(200).optional(),\n /** Opties voor type \"select\" en \"radio\". */\n options: z.array(z.string().max(200)).max(50).optional(),\n});\nexport type FormField = z.infer<typeof FormFieldSchema>;\n\n/** Een formulierdefinitie: `sites/{siteId}/forms/{formId}`. */\nexport const FormSchema = z.object({\n name: z.string().min(1).max(160),\n fields: z.array(FormFieldSchema).min(1).max(40),\n submitLabel: z.string().max(80).default(\"Versturen\"),\n successMessage: z.string().max(500).default(\"Bedankt! We nemen snel contact op.\"),\n /** Stuur na een geslaagde inzending door naar deze pagina (bv. een bedankt-pagina). */\n successRedirect: z.string().max(400).optional(),\n /** Stuur de inzender een bevestigingsmail (naar het eerste e-mailveld van het formulier). */\n confirmSubmitter: z.boolean().optional(),\n});\nexport type FormDefinition = z.infer<typeof FormSchema>;\n\n/** Payload van een inzending (callable input). Per-veld-validatie gebeurt server-side\n * tegen de opgeslagen formulierdefinitie. */\nexport const SubmissionInputSchema = z.object({\n siteId: z.string().min(1).max(80),\n formId: z.string().min(1).max(80),\n values: z.record(z.string(), z.union([z.string().max(5000), z.boolean()])),\n /** Honeypot — moet leeg zijn (bot-detectie). */\n _hp: z.string().max(0).optional(),\n});\nexport type SubmissionInput = z.infer<typeof SubmissionInputSchema>;\n"],"mappings":"AAAA,SAAS,SAAS;AAEX,MAAM,kBAAkB,EAAE,KAAK;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,MAAM,kBAAkB,EAAE,OAAO;AAAA;AAAA,EAEtC,MAAM,EACH,OAAO,EACP,IAAI,CAAC,EACL,IAAI,EAAE,EACN,MAAM,2BAA2B,4EAA4E;AAAA,EAChH,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,EAChC,MAAM;AAAA,EACN,UAAU,EAAE,QAAQ,EAAE,QAAQ,KAAK;AAAA,EACnC,aAAa,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA;AAAA,EAE1C,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,CAAC,EAAE,IAAI,EAAE,EAAE,SAAS;AACzD,CAAC;AAIM,MAAM,aAAa,EAAE,OAAO;AAAA,EACjC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,EAC/B,QAAQ,EAAE,MAAM,eAAe,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE;AAAA,EAC9C,aAAa,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,QAAQ,WAAW;AAAA,EACnD,gBAAgB,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,QAAQ,oCAAoC;AAAA;AAAA,EAEhF,iBAAiB,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA;AAAA,EAE9C,kBAAkB,EAAE,QAAQ,EAAE,SAAS;AACzC,CAAC;AAKM,MAAM,wBAAwB,EAAE,OAAO;AAAA,EAC5C,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE;AAAA,EAChC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE;AAAA,EAChC,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,GAAI,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC;AAAA;AAAA,EAEzE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAClC,CAAC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/schema/forms.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport const FieldTypeSchema = z.enum([\n \"text\",\n \"email\",\n \"tel\",\n \"textarea\",\n \"select\",\n \"checkbox\",\n // 4.16: update-veilig toegevoegd (oude definities blijven geldig).\n \"radio\",\n \"date\",\n \"number\",\n]);\nexport type FieldType = z.infer<typeof FieldTypeSchema>;\n\nexport const FormFieldSchema = z.object({\n /** Stabiele sleutel; wordt de key in de inzending. */\n name: z\n .string()\n .min(1)\n .max(60)\n .regex(/^[a-zA-Z][a-zA-Z0-9_]*$/, \"veldnaam moet met een letter beginnen en alleen letters/cijfers/_ bevatten\"),\n label: z.string().min(1).max(160),\n type: FieldTypeSchema,\n required: z.boolean().default(false),\n placeholder: z.string().max(200).optional(),\n /** Opties voor type \"select\" en \"radio\". */\n options: z.array(z.string().max(200)).max(50).optional(),\n});\nexport type FormField = z.infer<typeof FormFieldSchema>;\n\n/** Een formulierdefinitie: `sites/{siteId}/forms/{formId}`. */\nexport const FormSchema = z.object({\n name: z.string().min(1).max(160),\n fields: z.array(FormFieldSchema).min(1).max(40),\n submitLabel: z.string().max(80).default(\"Versturen\"),\n successMessage: z.string().max(500).default(\"Bedankt! We nemen snel contact op.\"),\n /** Stuur na een geslaagde inzending door naar deze pagina (bv. een bedankt-pagina). */\n successRedirect: z.string().max(400).optional(),\n /** Stuur de inzender een bevestigingsmail/kopie (naar het eerste e-mailveld van het formulier). */\n confirmSubmitter: z.boolean().optional(),\n /**\n * Kopie van elke inzending naar dit e-mailadres. Leeg = de site-brede notificatie-instelling\n * (Instellingen → E-mail → notificatie-adres, anders het contact-e-mailadres van de site).\n */\n notifyTo: z.string().max(200).optional(),\n});\nexport type FormDefinition = z.infer<typeof FormSchema>;\n\n/** Payload van een inzending (callable input). Per-veld-validatie gebeurt server-side\n * tegen de opgeslagen formulierdefinitie. */\nexport const SubmissionInputSchema = z.object({\n siteId: z.string().min(1).max(80),\n formId: z.string().min(1).max(80),\n values: z.record(z.string(), z.union([z.string().max(5000), z.boolean()])),\n /** Honeypot — moet leeg zijn (bot-detectie). */\n _hp: z.string().max(0).optional(),\n});\nexport type SubmissionInput = z.infer<typeof SubmissionInputSchema>;\n"],"mappings":"AAAA,SAAS,SAAS;AAEX,MAAM,kBAAkB,EAAE,KAAK;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,MAAM,kBAAkB,EAAE,OAAO;AAAA;AAAA,EAEtC,MAAM,EACH,OAAO,EACP,IAAI,CAAC,EACL,IAAI,EAAE,EACN,MAAM,2BAA2B,4EAA4E;AAAA,EAChH,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,EAChC,MAAM;AAAA,EACN,UAAU,EAAE,QAAQ,EAAE,QAAQ,KAAK;AAAA,EACnC,aAAa,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA;AAAA,EAE1C,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,CAAC,EAAE,IAAI,EAAE,EAAE,SAAS;AACzD,CAAC;AAIM,MAAM,aAAa,EAAE,OAAO;AAAA,EACjC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,EAC/B,QAAQ,EAAE,MAAM,eAAe,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE;AAAA,EAC9C,aAAa,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,QAAQ,WAAW;AAAA,EACnD,gBAAgB,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,QAAQ,oCAAoC;AAAA;AAAA,EAEhF,iBAAiB,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA;AAAA,EAE9C,kBAAkB,EAAE,QAAQ,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,EAKvC,UAAU,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AACzC,CAAC;AAKM,MAAM,wBAAwB,EAAE,OAAO;AAAA,EAC5C,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE;AAAA,EAChC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE;AAAA,EAChC,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,GAAI,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC;AAAA;AAAA,EAEzE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAClC,CAAC;","names":[]}
|
package/dist/schema/mail.d.ts
CHANGED
|
@@ -66,6 +66,10 @@ export declare const MAIL_TEMPLATE_DEFS: readonly [{
|
|
|
66
66
|
}];
|
|
67
67
|
export declare const MAIL_TEMPLATE_KEYS: readonly MailTemplateKey[];
|
|
68
68
|
export type MailTemplateKey = (typeof MAIL_TEMPLATE_DEFS)[number]["key"];
|
|
69
|
+
/** Formulier-mails — bewerkbaar in het Formulieren-gedeelte. */
|
|
70
|
+
export declare const FORM_TEMPLATE_KEYS: readonly MailTemplateKey[];
|
|
71
|
+
/** Shop-mails — bewerkbaar in het Webwinkel-gedeelte. */
|
|
72
|
+
export declare const SHOP_TEMPLATE_KEYS: readonly MailTemplateKey[];
|
|
69
73
|
export declare const MailTemplateSchema: z.ZodObject<{
|
|
70
74
|
subject: z.ZodOptional<z.ZodString>;
|
|
71
75
|
bodyText: z.ZodOptional<z.ZodString>;
|
package/dist/schema/mail.js
CHANGED
|
@@ -58,6 +58,8 @@ const MAIL_TEMPLATE_DEFS = [
|
|
|
58
58
|
}
|
|
59
59
|
];
|
|
60
60
|
const MAIL_TEMPLATE_KEYS = MAIL_TEMPLATE_DEFS.map((d) => d.key);
|
|
61
|
+
const FORM_TEMPLATE_KEYS = MAIL_TEMPLATE_DEFS.filter((d) => d.module === "core").map((d) => d.key);
|
|
62
|
+
const SHOP_TEMPLATE_KEYS = MAIL_TEMPLATE_DEFS.filter((d) => d.module === "shop").map((d) => d.key);
|
|
61
63
|
const MailTemplateSchema = z.object({
|
|
62
64
|
subject: z.string().max(200).optional(),
|
|
63
65
|
bodyText: z.string().max(1e4).optional()
|
|
@@ -66,9 +68,11 @@ const MailTemplatesSchema = z.object(
|
|
|
66
68
|
Object.fromEntries(MAIL_TEMPLATE_DEFS.map((d) => [d.key, MailTemplateSchema.optional()]))
|
|
67
69
|
);
|
|
68
70
|
export {
|
|
71
|
+
FORM_TEMPLATE_KEYS,
|
|
69
72
|
MAIL_TEMPLATE_DEFS,
|
|
70
73
|
MAIL_TEMPLATE_KEYS,
|
|
71
74
|
MailTemplateSchema,
|
|
72
|
-
MailTemplatesSchema
|
|
75
|
+
MailTemplatesSchema,
|
|
76
|
+
SHOP_TEMPLATE_KEYS
|
|
73
77
|
};
|
|
74
78
|
//# sourceMappingURL=mail.js.map
|
package/dist/schema/mail.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/schema/mail.ts"],"sourcesContent":["import { z } from \"zod\";\n\n/**\n * E-mailtemplates (site-breed, 3.7) — `sites/{siteId}/settings/mailTemplates`. Per template een\n * onderwerp + platte-tekst-body met `{{placeholders}}`. Bewust géén HTML van beheerders; de\n * huisstijl-opmaak komt in de mail-pipeline (functions `mailLayout.ts`) om de tekst heen.\n *\n * De keys zijn getagd met de module waartoe ze horen (`core`/`shop`), zodat de editor in /beheer\n * shop-templates alleen toont op sites met de webwinkel. `core`-keys (formulieren) gelden altijd.\n */\n\nexport interface MailTemplateDef {\n key: string;\n label: string;\n hint: string;\n module: \"core\" | \"shop\";\n /** Beschikbare invulvelden, als tonende hint in de editor. */\n placeholders: string;\n}\n\nexport const MAIL_TEMPLATE_DEFS = [\n {\n key: \"formNotification\",\n label: \"Formulier-notificatie (naar jou)\",\n hint: \"Naar jou zodra iemand een formulier invult.\",\n module: \"core\",\n placeholders: \"{{sitenaam}} {{formulier}} {{regels}}\",\n },\n {\n key: \"formConfirmation\",\n label: \"Formulier-bevestiging (naar de inzender)\",\n hint: \"Ontvangstbevestiging naar wie het formulier invulde.\",\n module: \"core\",\n placeholders: \"{{sitenaam}} {{formulier}} {{regels}}\",\n },\n {\n key: \"orderConfirmation\",\n label: \"Bestelbevestiging\",\n hint: \"Naar de klant zodra de betaling binnen is — met factuur-PDF als bijlage.\",\n module: \"shop\",\n placeholders: \"{{naam}} {{ordernummer}} {{regels}} {{totaal}} {{verzendadres}} {{notitie}} {{sitenaam}}\",\n },\n {\n key: \"shopkeeperNotification\",\n label: \"Nieuwe-bestelling-notificatie (naar jou)\",\n hint: \"Naar jou bij elke nieuwe bestelling.\",\n module: \"shop\",\n placeholders: \"{{sitenaam}} {{ordernummer}} {{totaal}} {{klant}} {{regels}} {{verzendadres}} {{notitie}}\",\n },\n {\n key: \"orderShipped\",\n label: \"Bestelling verzonden\",\n hint: \"Naar de klant wanneer je de status op ‘verzonden’ zet.\",\n module: \"shop\",\n placeholders: \"{{naam}} {{ordernummer}} {{regels}} {{trackingUrl}} {{verzendadres}} {{sitenaam}}\",\n },\n {\n key: \"orderCancelled\",\n label: \"Bestelling geannuleerd\",\n hint: \"Naar de klant bij annulering.\",\n module: \"shop\",\n placeholders: \"{{naam}} {{ordernummer}} {{sitenaam}}\",\n },\n {\n key: \"orderRefunded\",\n label: \"Terugbetaald\",\n hint: \"Naar de klant na een terugbetaling.\",\n module: \"shop\",\n placeholders: \"{{naam}} {{ordernummer}} {{totaal}} {{sitenaam}}\",\n },\n {\n key: \"donationThanks\",\n label: \"Donatie-bedankje\",\n hint: \"Naar de gever na een donatie.\",\n module: \"shop\",\n placeholders: \"{{naam}} {{regels}} {{sitenaam}}\",\n },\n] as const satisfies readonly MailTemplateDef[];\n\nexport const MAIL_TEMPLATE_KEYS = MAIL_TEMPLATE_DEFS.map((d) => d.key) as unknown as readonly MailTemplateKey[];\nexport type MailTemplateKey = (typeof MAIL_TEMPLATE_DEFS)[number][\"key\"];\n\nexport const MailTemplateSchema = z.object({\n subject: z.string().max(200).optional(),\n bodyText: z.string().max(10_000).optional(),\n});\nexport const MailTemplatesSchema = z.object(\n Object.fromEntries(MAIL_TEMPLATE_DEFS.map((d) => [d.key, MailTemplateSchema.optional()])) as Record<\n MailTemplateKey,\n z.ZodOptional<typeof MailTemplateSchema>\n >,\n);\nexport type MailTemplates = z.infer<typeof MailTemplatesSchema>;\n"],"mappings":"AAAA,SAAS,SAAS;AAoBX,MAAM,qBAAqB;AAAA,EAChC;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AACF;AAEO,MAAM,qBAAqB,mBAAmB,IAAI,CAAC,MAAM,EAAE,GAAG;
|
|
1
|
+
{"version":3,"sources":["../../src/schema/mail.ts"],"sourcesContent":["import { z } from \"zod\";\n\n/**\n * E-mailtemplates (site-breed, 3.7) — `sites/{siteId}/settings/mailTemplates`. Per template een\n * onderwerp + platte-tekst-body met `{{placeholders}}`. Bewust géén HTML van beheerders; de\n * huisstijl-opmaak komt in de mail-pipeline (functions `mailLayout.ts`) om de tekst heen.\n *\n * De keys zijn getagd met de module waartoe ze horen (`core`/`shop`), zodat de editor in /beheer\n * shop-templates alleen toont op sites met de webwinkel. `core`-keys (formulieren) gelden altijd.\n */\n\nexport interface MailTemplateDef {\n key: string;\n label: string;\n hint: string;\n module: \"core\" | \"shop\";\n /** Beschikbare invulvelden, als tonende hint in de editor. */\n placeholders: string;\n}\n\nexport const MAIL_TEMPLATE_DEFS = [\n {\n key: \"formNotification\",\n label: \"Formulier-notificatie (naar jou)\",\n hint: \"Naar jou zodra iemand een formulier invult.\",\n module: \"core\",\n placeholders: \"{{sitenaam}} {{formulier}} {{regels}}\",\n },\n {\n key: \"formConfirmation\",\n label: \"Formulier-bevestiging (naar de inzender)\",\n hint: \"Ontvangstbevestiging naar wie het formulier invulde.\",\n module: \"core\",\n placeholders: \"{{sitenaam}} {{formulier}} {{regels}}\",\n },\n {\n key: \"orderConfirmation\",\n label: \"Bestelbevestiging\",\n hint: \"Naar de klant zodra de betaling binnen is — met factuur-PDF als bijlage.\",\n module: \"shop\",\n placeholders: \"{{naam}} {{ordernummer}} {{regels}} {{totaal}} {{verzendadres}} {{notitie}} {{sitenaam}}\",\n },\n {\n key: \"shopkeeperNotification\",\n label: \"Nieuwe-bestelling-notificatie (naar jou)\",\n hint: \"Naar jou bij elke nieuwe bestelling.\",\n module: \"shop\",\n placeholders: \"{{sitenaam}} {{ordernummer}} {{totaal}} {{klant}} {{regels}} {{verzendadres}} {{notitie}}\",\n },\n {\n key: \"orderShipped\",\n label: \"Bestelling verzonden\",\n hint: \"Naar de klant wanneer je de status op ‘verzonden’ zet.\",\n module: \"shop\",\n placeholders: \"{{naam}} {{ordernummer}} {{regels}} {{trackingUrl}} {{verzendadres}} {{sitenaam}}\",\n },\n {\n key: \"orderCancelled\",\n label: \"Bestelling geannuleerd\",\n hint: \"Naar de klant bij annulering.\",\n module: \"shop\",\n placeholders: \"{{naam}} {{ordernummer}} {{sitenaam}}\",\n },\n {\n key: \"orderRefunded\",\n label: \"Terugbetaald\",\n hint: \"Naar de klant na een terugbetaling.\",\n module: \"shop\",\n placeholders: \"{{naam}} {{ordernummer}} {{totaal}} {{sitenaam}}\",\n },\n {\n key: \"donationThanks\",\n label: \"Donatie-bedankje\",\n hint: \"Naar de gever na een donatie.\",\n module: \"shop\",\n placeholders: \"{{naam}} {{regels}} {{sitenaam}}\",\n },\n] as const satisfies readonly MailTemplateDef[];\n\nexport const MAIL_TEMPLATE_KEYS = MAIL_TEMPLATE_DEFS.map((d) => d.key) as unknown as readonly MailTemplateKey[];\nexport type MailTemplateKey = (typeof MAIL_TEMPLATE_DEFS)[number][\"key\"];\n\n/** Formulier-mails — bewerkbaar in het Formulieren-gedeelte. */\nexport const FORM_TEMPLATE_KEYS = MAIL_TEMPLATE_DEFS.filter((d) => d.module === \"core\").map((d) => d.key) as readonly MailTemplateKey[];\n/** Shop-mails — bewerkbaar in het Webwinkel-gedeelte. */\nexport const SHOP_TEMPLATE_KEYS = MAIL_TEMPLATE_DEFS.filter((d) => d.module === \"shop\").map((d) => d.key) as readonly MailTemplateKey[];\n\nexport const MailTemplateSchema = z.object({\n subject: z.string().max(200).optional(),\n bodyText: z.string().max(10_000).optional(),\n});\nexport const MailTemplatesSchema = z.object(\n Object.fromEntries(MAIL_TEMPLATE_DEFS.map((d) => [d.key, MailTemplateSchema.optional()])) as Record<\n MailTemplateKey,\n z.ZodOptional<typeof MailTemplateSchema>\n >,\n);\nexport type MailTemplates = z.infer<typeof MailTemplatesSchema>;\n"],"mappings":"AAAA,SAAS,SAAS;AAoBX,MAAM,qBAAqB;AAAA,EAChC;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AAAA,EACA;AAAA,IACE,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AACF;AAEO,MAAM,qBAAqB,mBAAmB,IAAI,CAAC,MAAM,EAAE,GAAG;AAI9D,MAAM,qBAAqB,mBAAmB,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG;AAEjG,MAAM,qBAAqB,mBAAmB,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG;AAEjG,MAAM,qBAAqB,EAAE,OAAO;AAAA,EACzC,SAAS,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EACtC,UAAU,EAAE,OAAO,EAAE,IAAI,GAAM,EAAE,SAAS;AAC5C,CAAC;AACM,MAAM,sBAAsB,EAAE;AAAA,EACnC,OAAO,YAAY,mBAAmB,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,mBAAmB,SAAS,CAAC,CAAC,CAAC;AAI1F;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiwebdesign/xicms-core",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.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": {
|
|
@@ -113,7 +113,8 @@
|
|
|
113
113
|
"firebase-admin": "^13.0.0",
|
|
114
114
|
"sanitize-html": "^2.14.0",
|
|
115
115
|
"tailwind-merge": "^2.6.0",
|
|
116
|
-
"zod": "^3.24.1"
|
|
116
|
+
"zod": "^3.24.1",
|
|
117
|
+
"html2canvas-pro": "^1.5.11"
|
|
117
118
|
},
|
|
118
119
|
"devDependencies": {
|
|
119
120
|
"@astryxdesign/cli": "^0.1.1",
|