@xiwebdesign/xicms-core 7.12.1 → 7.14.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/admin/AdminApp.js +2 -0
- package/dist/admin/AdminApp.js.map +1 -1
- package/dist/admin/lib/mediaUsage.d.ts +17 -0
- package/dist/admin/lib/mediaUsage.js +32 -1
- package/dist/admin/lib/mediaUsage.js.map +1 -1
- package/dist/admin/lib/zodFields.d.ts +2 -2
- package/dist/admin/lib/zodFields.js +2 -0
- package/dist/admin/lib/zodFields.js.map +1 -1
- package/dist/admin/media/MediaManager.js +74 -17
- package/dist/admin/media/MediaManager.js.map +1 -1
- package/dist/admin/ui/toast.d.ts +4 -0
- package/dist/admin/ui/toast.js +9 -3
- package/dist/admin/ui/toast.js.map +1 -1
- package/dist/admin/visual/VisualEditor.js +69 -5
- package/dist/admin/visual/VisualEditor.js.map +1 -1
- package/dist/blocks/BlockErrorBoundary.d.ts +1 -0
- package/dist/blocks/BlockErrorBoundary.js +4 -0
- package/dist/blocks/BlockErrorBoundary.js.map +1 -1
- package/dist/blocks/BlockRenderer.js +1 -1
- package/dist/blocks/BlockRenderer.js.map +1 -1
- package/dist/blocks/builtinSchemas.d.ts +23 -0
- package/dist/blocks/builtinSchemas.js +138 -0
- package/dist/blocks/builtinSchemas.js.map +1 -0
- package/dist/blocks/builtins/Accordion.js +0 -2
- package/dist/blocks/builtins/Accordion.js.map +1 -1
- package/dist/blocks/builtins/Agenda.js +0 -2
- package/dist/blocks/builtins/Agenda.js.map +1 -1
- package/dist/blocks/builtins/Banner.js +0 -2
- package/dist/blocks/builtins/Banner.js.map +1 -1
- package/dist/blocks/builtins/BeforeAfter.js +0 -2
- package/dist/blocks/builtins/BeforeAfter.js.map +1 -1
- package/dist/blocks/builtins/Booking.js +0 -2
- package/dist/blocks/builtins/Booking.js.map +1 -1
- package/dist/blocks/builtins/Breadcrumbs.js +0 -2
- package/dist/blocks/builtins/Breadcrumbs.js.map +1 -1
- package/dist/blocks/builtins/Cards.js +0 -2
- package/dist/blocks/builtins/Cards.js.map +1 -1
- package/dist/blocks/builtins/Columns.js +0 -2
- package/dist/blocks/builtins/Columns.js.map +1 -1
- package/dist/blocks/builtins/Contact.d.ts +1 -1
- package/dist/blocks/builtins/Contact.js +0 -3
- package/dist/blocks/builtins/Contact.js.map +1 -1
- package/dist/blocks/builtins/Countdown.js +0 -2
- package/dist/blocks/builtins/Countdown.js.map +1 -1
- package/dist/blocks/builtins/Cta.js +0 -2
- package/dist/blocks/builtins/Cta.js.map +1 -1
- package/dist/blocks/builtins/Downloads.js +0 -2
- package/dist/blocks/builtins/Downloads.js.map +1 -1
- package/dist/blocks/builtins/Elements.js +0 -4
- package/dist/blocks/builtins/Elements.js.map +1 -1
- package/dist/blocks/builtins/Embed.js +0 -2
- package/dist/blocks/builtins/Embed.js.map +1 -1
- package/dist/blocks/builtins/Faq.js +0 -2
- package/dist/blocks/builtins/Faq.js.map +1 -1
- package/dist/blocks/builtins/FormBlock.js +0 -2
- package/dist/blocks/builtins/FormBlock.js.map +1 -1
- package/dist/blocks/builtins/Gallery.d.ts +1 -1
- package/dist/blocks/builtins/Gallery.js +0 -2
- package/dist/blocks/builtins/Gallery.js.map +1 -1
- package/dist/blocks/builtins/Hero.js +0 -2
- package/dist/blocks/builtins/Hero.js.map +1 -1
- package/dist/blocks/builtins/ImageBlock.js +0 -2
- package/dist/blocks/builtins/ImageBlock.js.map +1 -1
- package/dist/blocks/builtins/Jobs.d.ts +1 -1
- package/dist/blocks/builtins/Jobs.js +0 -2
- package/dist/blocks/builtins/Jobs.js.map +1 -1
- package/dist/blocks/builtins/Layout.js +0 -3
- package/dist/blocks/builtins/Layout.js.map +1 -1
- package/dist/blocks/builtins/MapBlock.js +0 -2
- package/dist/blocks/builtins/MapBlock.js.map +1 -1
- package/dist/blocks/builtins/NewsListBlock.js +0 -2
- package/dist/blocks/builtins/NewsListBlock.js.map +1 -1
- package/dist/blocks/builtins/Newsletter.js +0 -2
- package/dist/blocks/builtins/Newsletter.js.map +1 -1
- package/dist/blocks/builtins/OpeningHours.js +0 -2
- package/dist/blocks/builtins/OpeningHours.js.map +1 -1
- package/dist/blocks/builtins/PdfBlock.js +0 -2
- package/dist/blocks/builtins/PdfBlock.js.map +1 -1
- package/dist/blocks/builtins/Portfolio.js +0 -2
- package/dist/blocks/builtins/Portfolio.js.map +1 -1
- package/dist/blocks/builtins/Pricing.d.ts +1 -1
- package/dist/blocks/builtins/Pricing.js +0 -2
- package/dist/blocks/builtins/Pricing.js.map +1 -1
- package/dist/blocks/builtins/RichTextBlock.js +0 -2
- package/dist/blocks/builtins/RichTextBlock.js.map +1 -1
- package/dist/blocks/builtins/Search.js +0 -2
- package/dist/blocks/builtins/Search.js.map +1 -1
- package/dist/blocks/builtins/Sections.js +0 -10
- package/dist/blocks/builtins/Sections.js.map +1 -1
- package/dist/blocks/builtins/Slider.js +0 -2
- package/dist/blocks/builtins/Slider.js.map +1 -1
- package/dist/blocks/builtins/Spacer.js +0 -2
- package/dist/blocks/builtins/Spacer.js.map +1 -1
- package/dist/blocks/builtins/SymbolRef.js +3 -5
- package/dist/blocks/builtins/SymbolRef.js.map +1 -1
- package/dist/blocks/builtins/Team.js +0 -2
- package/dist/blocks/builtins/Team.js.map +1 -1
- package/dist/blocks/builtins/Testimonials.d.ts +1 -1
- package/dist/blocks/builtins/Testimonials.js +0 -2
- package/dist/blocks/builtins/Testimonials.js.map +1 -1
- package/dist/blocks/builtins/Video.d.ts +1 -1
- package/dist/blocks/builtins/Video.js +0 -2
- package/dist/blocks/builtins/Video.js.map +1 -1
- package/dist/blocks/builtins/WhatsappChat.js +0 -2
- package/dist/blocks/builtins/WhatsappChat.js.map +1 -1
- package/dist/blocks/builtins/Widgets.js +0 -26
- package/dist/blocks/builtins/Widgets.js.map +1 -1
- package/dist/blocks/customTemplate/definition.js +1 -1
- package/dist/blocks/customTemplate/definition.js.map +1 -1
- package/dist/blocks/registry.d.ts +24 -13
- package/dist/blocks/registry.js +15 -4
- package/dist/blocks/registry.js.map +1 -1
- package/dist/blocks/reportRenderFout.d.ts +13 -0
- package/dist/blocks/reportRenderFout.js +31 -0
- package/dist/blocks/reportRenderFout.js.map +1 -0
- package/dist/media/ImageContext.js +24 -1
- package/dist/media/ImageContext.js.map +1 -1
- package/dist/modules/shop/blocks/Cart.js +1 -2
- package/dist/modules/shop/blocks/Cart.js.map +1 -1
- package/dist/modules/shop/blocks/Checkout.js +1 -2
- package/dist/modules/shop/blocks/Checkout.js.map +1 -1
- package/dist/modules/shop/blocks/Donation.js +1 -2
- package/dist/modules/shop/blocks/Donation.js.map +1 -1
- package/dist/modules/shop/blocks/OrderStatus.js +0 -2
- package/dist/modules/shop/blocks/OrderStatus.js.map +1 -1
- package/dist/modules/shop/blocks/ProductDetail.js +1 -7
- package/dist/modules/shop/blocks/ProductDetail.js.map +1 -1
- package/dist/modules/shop/blocks/ProductGrid.js +1 -2
- package/dist/modules/shop/blocks/ProductGrid.js.map +1 -1
- package/dist/modules/shop/blocks/ShopCategories.js +0 -2
- package/dist/modules/shop/blocks/ShopCategories.js.map +1 -1
- package/dist/modules/shop/module.js +1 -0
- package/dist/modules/shop/module.js.map +1 -1
- package/dist/modules/shop/schemas.d.ts +1 -0
- package/dist/modules/shop/schemas.js +32 -0
- package/dist/modules/shop/schemas.js.map +1 -0
- package/dist/modules/types.d.ts +6 -0
- package/dist/schema/blockBase.d.ts +99 -0
- package/dist/schema/blockBase.js +22 -0
- package/dist/schema/blockBase.js.map +1 -0
- package/dist/schema/blocks.d.ts +1 -91
- package/dist/schema/blocks.js +64 -77
- package/dist/schema/blocks.js.map +1 -1
- package/dist/schema/pages.js +1 -1
- package/dist/schema/pages.js.map +1 -1
- package/dist/schema/shop.d.ts +1 -11
- package/dist/schema/shop.js +2 -21
- package/dist/schema/shop.js.map +1 -1
- package/dist/schema/shopGeld.d.ts +18 -0
- package/dist/schema/shopGeld.js +27 -0
- package/dist/schema/shopGeld.js.map +1 -0
- package/dist/schema/symbols.js +1 -1
- package/dist/schema/symbols.js.map +1 -1
- package/dist/server/blockData.js +14 -1
- package/dist/server/blockData.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +2 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/pages.d.ts +16 -0
- package/dist/server/pages.js +9 -0
- package/dist/server/pages.js.map +1 -1
- package/dist/styles.css +62 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { z } from "zod";
|
|
2
|
-
import { ContactBlockSchema } from "../../schema/blocks.js";
|
|
2
|
+
import type { ContactBlockSchema } from "../../schema/blocks.js";
|
|
3
3
|
type Contact = z.infer<typeof ContactBlockSchema>;
|
|
4
4
|
export declare function contactJsonLd(b: Contact): Record<string, unknown>;
|
|
5
5
|
export declare const contactBlock: import("../index.js").BlockDefinition<z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { jsonLdHtml } from "./jsonld.js";
|
|
3
|
-
import { ContactBlockSchema, SocialBlockSchema } from "../../schema/blocks.js";
|
|
4
3
|
import { defineBlock } from "../registry.js";
|
|
5
4
|
import { Heading } from "../ui/index.js";
|
|
6
5
|
function contactJsonLd(b) {
|
|
@@ -50,7 +49,6 @@ function ContactRender({ block }) {
|
|
|
50
49
|
}
|
|
51
50
|
const contactBlock = defineBlock({
|
|
52
51
|
type: "contact",
|
|
53
|
-
schema: ContactBlockSchema,
|
|
54
52
|
Render: ContactRender,
|
|
55
53
|
label: "Contactgegevens",
|
|
56
54
|
icon: "\u{1F4C7}",
|
|
@@ -109,7 +107,6 @@ function SocialRender({ block }) {
|
|
|
109
107
|
}
|
|
110
108
|
const socialBlock = defineBlock({
|
|
111
109
|
type: "social",
|
|
112
|
-
schema: SocialBlockSchema,
|
|
113
110
|
Render: SocialRender,
|
|
114
111
|
label: "Social-links",
|
|
115
112
|
icon: "\u{1F517}",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/Contact.tsx"],"sourcesContent":["import type { z } from \"zod\";\nimport { jsonLdHtml } from \"./jsonld\";\nimport { ContactBlockSchema, SocialBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Heading } from \"../ui\";\n\n/* ── Contact ─────────────────────────────────────────────────────────────── */\ntype Contact = z.infer<typeof ContactBlockSchema>;\nexport function contactJsonLd(b: Contact): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"LocalBusiness\",\n // Gedeeld relatief @id: contact-, openingstijden- en layout-JSON-LD beschrijven hetzelfde\n // bedrijf; zonder @id ziet Google drie conflicterende LocalBusiness-entiteiten per pagina.\n \"@id\": \"#business\",\n // Bewust géén `name: heading` — de blokkop is meestal \"Contact\" en zou als bedrijfsnaam in de\n // samengevoegde #business-entiteit belanden; de naam komt uit de layout-brede businessJsonLd.\n ...(b.address ? { address: b.address } : {}),\n ...(b.phone ? { telephone: b.phone } : {}),\n ...(b.email ? { email: b.email } : {}),\n ...(b.geo ? { geo: { \"@type\": \"GeoCoordinates\", latitude: b.geo.lat, longitude: b.geo.lng } } : {}),\n };\n}\nfunction ContactRender({ block }: BlockRenderProps<Contact>) {\n return (\n <section className=\"xicms-contact\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n <dl className=\"xicms-contact__list\">\n {block.address && (\n <div className=\"xicms-contact__row\">\n <dt>Adres</dt>\n <dd data-xicms-edit=\"address\">{block.address}</dd>\n </div>\n )}\n {block.phone && (\n <div className=\"xicms-contact__row\">\n <dt>Telefoon</dt>\n <dd><a href={`tel:${block.phone.replace(/\\s+/g, \"\")}`} data-xicms-edit=\"phone\">{block.phone}</a></dd>\n </div>\n )}\n {block.email && (\n <div className=\"xicms-contact__row\">\n <dt>E-mail</dt>\n <dd><a href={`mailto:${block.email}`} data-xicms-edit=\"email\">{block.email}</a></dd>\n </div>\n )}\n {block.hours && (\n <div className=\"xicms-contact__row\">\n <dt>Openingstijden</dt>\n <dd data-xicms-edit=\"hours\">{block.hours}</dd>\n </div>\n )}\n </dl>\n {block.jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: jsonLdHtml(contactJsonLd(block)) }}\n />\n )}\n </section>\n );\n}\nexport const contactBlock = defineBlock({\n type: \"contact\",\n schema: ContactBlockSchema,\n Render: ContactRender,\n label: \"Contactgegevens\",\n icon: \"📇\",\n defaults: () => ({ heading: \"Contact\", jsonLd: true }),\n});\n\n/* ── Social ──────────────────────────────────────────────────────────────── */\ntype Social = z.infer<typeof SocialBlockSchema>;\n\n/**\n * Minimalistische, monochrome merk-iconen (currentColor) — geen icon-dependency nodig.\n * Onbekende platformen vallen terug op de tekstweergave, dus niets breekt.\n */\nfunction SocialIcon({ platform }: { platform: string }) {\n const p = platform.toLowerCase();\n const common = { width: 22, height: 22, viewBox: \"0 0 24 24\", \"aria-hidden\": true } as const;\n if (p.includes(\"instagram\")) {\n return (\n <svg {...common} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\">\n <rect x=\"4\" y=\"4\" width=\"16\" height=\"16\" rx=\"4.5\" />\n <circle cx=\"12\" cy=\"12\" r=\"3.6\" />\n <circle cx=\"16.7\" cy=\"7.3\" r=\"1.1\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n );\n }\n if (p.includes(\"facebook\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <path d=\"M13.5 20v-6.2h2.1l.4-2.6h-2.5V9.6c0-.8.3-1.3 1.3-1.3h1.3V6c-.3 0-1-.1-1.9-.1-2 0-3.3 1.2-3.3 3.4v1.9H8.7v2.6h2.2V20h2.6z\" />\n </svg>\n );\n }\n if (p.includes(\"linkedin\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <circle cx=\"6\" cy=\"6.4\" r=\"1.8\" />\n <rect x=\"4.6\" y=\"9.6\" width=\"2.8\" height=\"9.9\" />\n <path d=\"M10.4 9.6h2.7v1.4c.5-.9 1.6-1.7 3.1-1.7 2.4 0 3.7 1.5 3.7 4.3v5.9h-2.8v-5.4c0-1.5-.6-2.3-1.7-2.3-1.2 0-2.2.8-2.2 2.4v5.3h-2.8V9.6z\" />\n </svg>\n );\n }\n if (p.includes(\"twitter\") || p === \"x\" || p.startsWith(\"x \") || p.includes(\"(x)\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <path d=\"M4.5 4.5h3.6l4.1 5.6 4.7-5.6h2.6l-6.1 7.2 6.4 8.4h-3.6l-4.4-6-5 6H4.2l6.5-7.7-6.2-7.9z\" />\n </svg>\n );\n }\n if (p.includes(\"youtube\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <rect x=\"3.2\" y=\"6.2\" width=\"17.6\" height=\"11.6\" rx=\"3.2\" />\n <path d=\"M10.4 9.4v5.2l4.8-2.6z\" fill=\"var(--xicms-surface, #fff)\" />\n </svg>\n );\n }\n if (p.includes(\"whatsapp\")) {\n return (\n <svg {...common} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\">\n <path d=\"M12 4.2a7.8 7.8 0 0 0-6.7 11.8L4.4 19.6l3.7-.9A7.8 7.8 0 1 0 12 4.2z\" />\n <path d=\"M9.3 8.9c.7 2.4 2.6 4.4 5 5l1-1.2 2 1.2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n }\n if (p.includes(\"tiktok\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <path d=\"M14.5 4h2.6c.2 1.7 1.3 3 3 3.3v2.6a6.6 6.6 0 0 1-3.2-1v5.7a5.4 5.4 0 1 1-5.4-5.4c.2 0 .5 0 .7.1v2.8a2.6 2.6 0 1 0 2.3 2.6V4z\" />\n </svg>\n );\n }\n return null;\n}\n\nfunction SocialRender({ block }: BlockRenderProps<Social>) {\n const items = (block.items ?? []).filter((i) => i.href && i.platform);\n if (items.length === 0) return null;\n return (\n <section className=\"xicms-social\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n <ul className=\"xicms-social__list\">\n {items.map((it, i) => {\n // Als functie aangeroepen (geen hooks) zodat `null` echt null is — een JSX-element\n // zou altijd truthy zijn en de tekst-fallback nooit tonen.\n const icon = SocialIcon({ platform: it.platform });\n return (\n <li key={i}>\n <a href={it.href} rel=\"noopener noreferrer\" target=\"_blank\" aria-label={it.platform} title={it.platform} className={icon ? \"xicms-social__iconlink\" : undefined}>\n {icon ?? <span data-xicms-edit={`items.${i}.platform`}>{it.platform}</span>}\n </a>\n </li>\n );\n })}\n </ul>\n </section>\n );\n}\nexport const socialBlock = defineBlock({\n type: \"social\",\n schema: SocialBlockSchema,\n Render: SocialRender,\n label: \"Social-links\",\n icon: \"🔗\",\n defaults: () => ({ items: [{ platform: \"Instagram\", href: \"\" }] }),\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/Contact.tsx"],"sourcesContent":["import type { z } from \"zod\";\nimport { jsonLdHtml } from \"./jsonld\";\nimport type { ContactBlockSchema, SocialBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Heading } from \"../ui\";\n\n/* ── Contact ─────────────────────────────────────────────────────────────── */\ntype Contact = z.infer<typeof ContactBlockSchema>;\nexport function contactJsonLd(b: Contact): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"LocalBusiness\",\n // Gedeeld relatief @id: contact-, openingstijden- en layout-JSON-LD beschrijven hetzelfde\n // bedrijf; zonder @id ziet Google drie conflicterende LocalBusiness-entiteiten per pagina.\n \"@id\": \"#business\",\n // Bewust géén `name: heading` — de blokkop is meestal \"Contact\" en zou als bedrijfsnaam in de\n // samengevoegde #business-entiteit belanden; de naam komt uit de layout-brede businessJsonLd.\n ...(b.address ? { address: b.address } : {}),\n ...(b.phone ? { telephone: b.phone } : {}),\n ...(b.email ? { email: b.email } : {}),\n ...(b.geo ? { geo: { \"@type\": \"GeoCoordinates\", latitude: b.geo.lat, longitude: b.geo.lng } } : {}),\n };\n}\nfunction ContactRender({ block }: BlockRenderProps<Contact>) {\n return (\n <section className=\"xicms-contact\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n <dl className=\"xicms-contact__list\">\n {block.address && (\n <div className=\"xicms-contact__row\">\n <dt>Adres</dt>\n <dd data-xicms-edit=\"address\">{block.address}</dd>\n </div>\n )}\n {block.phone && (\n <div className=\"xicms-contact__row\">\n <dt>Telefoon</dt>\n <dd><a href={`tel:${block.phone.replace(/\\s+/g, \"\")}`} data-xicms-edit=\"phone\">{block.phone}</a></dd>\n </div>\n )}\n {block.email && (\n <div className=\"xicms-contact__row\">\n <dt>E-mail</dt>\n <dd><a href={`mailto:${block.email}`} data-xicms-edit=\"email\">{block.email}</a></dd>\n </div>\n )}\n {block.hours && (\n <div className=\"xicms-contact__row\">\n <dt>Openingstijden</dt>\n <dd data-xicms-edit=\"hours\">{block.hours}</dd>\n </div>\n )}\n </dl>\n {block.jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: jsonLdHtml(contactJsonLd(block)) }}\n />\n )}\n </section>\n );\n}\nexport const contactBlock = defineBlock({\n type: \"contact\",\n Render: ContactRender,\n label: \"Contactgegevens\",\n icon: \"📇\",\n defaults: () => ({ heading: \"Contact\", jsonLd: true }),\n});\n\n/* ── Social ──────────────────────────────────────────────────────────────── */\ntype Social = z.infer<typeof SocialBlockSchema>;\n\n/**\n * Minimalistische, monochrome merk-iconen (currentColor) — geen icon-dependency nodig.\n * Onbekende platformen vallen terug op de tekstweergave, dus niets breekt.\n */\nfunction SocialIcon({ platform }: { platform: string }) {\n const p = platform.toLowerCase();\n const common = { width: 22, height: 22, viewBox: \"0 0 24 24\", \"aria-hidden\": true } as const;\n if (p.includes(\"instagram\")) {\n return (\n <svg {...common} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\">\n <rect x=\"4\" y=\"4\" width=\"16\" height=\"16\" rx=\"4.5\" />\n <circle cx=\"12\" cy=\"12\" r=\"3.6\" />\n <circle cx=\"16.7\" cy=\"7.3\" r=\"1.1\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n );\n }\n if (p.includes(\"facebook\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <path d=\"M13.5 20v-6.2h2.1l.4-2.6h-2.5V9.6c0-.8.3-1.3 1.3-1.3h1.3V6c-.3 0-1-.1-1.9-.1-2 0-3.3 1.2-3.3 3.4v1.9H8.7v2.6h2.2V20h2.6z\" />\n </svg>\n );\n }\n if (p.includes(\"linkedin\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <circle cx=\"6\" cy=\"6.4\" r=\"1.8\" />\n <rect x=\"4.6\" y=\"9.6\" width=\"2.8\" height=\"9.9\" />\n <path d=\"M10.4 9.6h2.7v1.4c.5-.9 1.6-1.7 3.1-1.7 2.4 0 3.7 1.5 3.7 4.3v5.9h-2.8v-5.4c0-1.5-.6-2.3-1.7-2.3-1.2 0-2.2.8-2.2 2.4v5.3h-2.8V9.6z\" />\n </svg>\n );\n }\n if (p.includes(\"twitter\") || p === \"x\" || p.startsWith(\"x \") || p.includes(\"(x)\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <path d=\"M4.5 4.5h3.6l4.1 5.6 4.7-5.6h2.6l-6.1 7.2 6.4 8.4h-3.6l-4.4-6-5 6H4.2l6.5-7.7-6.2-7.9z\" />\n </svg>\n );\n }\n if (p.includes(\"youtube\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <rect x=\"3.2\" y=\"6.2\" width=\"17.6\" height=\"11.6\" rx=\"3.2\" />\n <path d=\"M10.4 9.4v5.2l4.8-2.6z\" fill=\"var(--xicms-surface, #fff)\" />\n </svg>\n );\n }\n if (p.includes(\"whatsapp\")) {\n return (\n <svg {...common} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\">\n <path d=\"M12 4.2a7.8 7.8 0 0 0-6.7 11.8L4.4 19.6l3.7-.9A7.8 7.8 0 1 0 12 4.2z\" />\n <path d=\"M9.3 8.9c.7 2.4 2.6 4.4 5 5l1-1.2 2 1.2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n }\n if (p.includes(\"tiktok\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <path d=\"M14.5 4h2.6c.2 1.7 1.3 3 3 3.3v2.6a6.6 6.6 0 0 1-3.2-1v5.7a5.4 5.4 0 1 1-5.4-5.4c.2 0 .5 0 .7.1v2.8a2.6 2.6 0 1 0 2.3 2.6V4z\" />\n </svg>\n );\n }\n return null;\n}\n\nfunction SocialRender({ block }: BlockRenderProps<Social>) {\n const items = (block.items ?? []).filter((i) => i.href && i.platform);\n if (items.length === 0) return null;\n return (\n <section className=\"xicms-social\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n <ul className=\"xicms-social__list\">\n {items.map((it, i) => {\n // Als functie aangeroepen (geen hooks) zodat `null` echt null is — een JSX-element\n // zou altijd truthy zijn en de tekst-fallback nooit tonen.\n const icon = SocialIcon({ platform: it.platform });\n return (\n <li key={i}>\n <a href={it.href} rel=\"noopener noreferrer\" target=\"_blank\" aria-label={it.platform} title={it.platform} className={icon ? \"xicms-social__iconlink\" : undefined}>\n {icon ?? <span data-xicms-edit={`items.${i}.platform`}>{it.platform}</span>}\n </a>\n </li>\n );\n })}\n </ul>\n </section>\n );\n}\nexport const socialBlock = defineBlock({\n type: \"social\",\n Render: SocialRender,\n label: \"Social-links\",\n icon: \"🔗\",\n defaults: () => ({ items: [{ platform: \"Instagram\", href: \"\" }] }),\n});\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
|
-
import { CountdownBlockSchema } from "../../schema/blocks.js";
|
|
5
4
|
import { defineBlock } from "../registry.js";
|
|
6
5
|
import { Heading } from "../ui/index.js";
|
|
7
6
|
function compute(target) {
|
|
@@ -51,7 +50,6 @@ function CountdownRender({ block, mode }) {
|
|
|
51
50
|
}
|
|
52
51
|
const countdownBlock = defineBlock({
|
|
53
52
|
type: "countdown",
|
|
54
|
-
schema: CountdownBlockSchema,
|
|
55
53
|
Render: CountdownRender,
|
|
56
54
|
label: "Aftelteller",
|
|
57
55
|
icon: "\u23F3",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/Countdown.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { z } from \"zod\";\nimport { CountdownBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Heading } from \"../ui\";\n\nexport interface Remaining {\n days: number;\n hours: number;\n minutes: number;\n seconds: number;\n done: boolean;\n}\n\nfunction compute(target: string): Remaining | null {\n const t = new Date(target).getTime();\n if (Number.isNaN(t)) return null;\n const diff = t - Date.now();\n if (diff <= 0) return { days: 0, hours: 0, minutes: 0, seconds: 0, done: true };\n const s = Math.floor(diff / 1000);\n return {\n days: Math.floor(s / 86400),\n hours: Math.floor((s % 86400) / 3600),\n minutes: Math.floor((s % 3600) / 60),\n seconds: s % 60,\n done: false,\n };\n}\n\n/**\n * Tikkende afteltimer naar `target`. Client-only (start `null` om hydration-mismatch te voorkomen).\n * Gedeeld door het volwaardige Countdown-blok en de compacte widget.\n */\nexport function useCountdown(target: string): Remaining | null {\n const [r, setR] = useState<Remaining | null>(null);\n useEffect(() => {\n if (!target) return;\n setR(compute(target));\n const id = setInterval(() => setR(compute(target)), 1000);\n return () => clearInterval(id);\n }, [target]);\n return r;\n}\n\nconst UNITS: { key: keyof Remaining; label: string }[] = [\n { key: \"days\", label: \"dagen\" },\n { key: \"hours\", label: \"uur\" },\n { key: \"minutes\", label: \"min\" },\n { key: \"seconds\", label: \"sec\" },\n];\n\nexport function CountdownDigits({ r, showLabels = true }: { r: Remaining; showLabels?: boolean }) {\n return (\n <div className=\"xicms-countdown__digits\" role=\"timer\" aria-live=\"off\">\n {UNITS.map((u) => (\n <div key={u.key} className=\"xicms-countdown__unit\">\n <span className=\"xicms-countdown__value\">{String(r[u.key] as number).padStart(2, \"0\")}</span>\n {showLabels && <span className=\"xicms-countdown__label\">{u.label}</span>}\n </div>\n ))}\n </div>\n );\n}\n\ntype Countdown = z.infer<typeof CountdownBlockSchema>;\n\nfunction CountdownRender({ block, mode }: BlockRenderProps<Countdown>) {\n const r = useCountdown(block.target);\n if (!block.target) return null;\n // Na afloop optioneel het hele blok verbergen (bv. een verlopen actie-aankondiging) —\n // behalve in de editor-preview, anders kan de editor het blok niet meer terugvinden.\n if (r?.done && block.onExpire === \"hide\" && mode !== \"preview\") return null;\n return (\n <section className=\"xicms-countdown\">\n {block.heading && <Heading level={2} className=\"xicms-countdown__heading\" data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {r === null ? (\n <p className=\"xicms-countdown__loading\" aria-hidden=\"true\">…</p>\n ) : r.done ? (\n <p className=\"xicms-countdown__expired\" data-xicms-edit=\"expiredText\">{block.expiredText}</p>\n ) : (\n <CountdownDigits r={r} showLabels={block.showLabels} />\n )}\n </section>\n );\n}\n\nexport const countdownBlock = defineBlock({\n type: \"countdown\",\n
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/Countdown.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { z } from \"zod\";\nimport type { CountdownBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Heading } from \"../ui\";\n\nexport interface Remaining {\n days: number;\n hours: number;\n minutes: number;\n seconds: number;\n done: boolean;\n}\n\nfunction compute(target: string): Remaining | null {\n const t = new Date(target).getTime();\n if (Number.isNaN(t)) return null;\n const diff = t - Date.now();\n if (diff <= 0) return { days: 0, hours: 0, minutes: 0, seconds: 0, done: true };\n const s = Math.floor(diff / 1000);\n return {\n days: Math.floor(s / 86400),\n hours: Math.floor((s % 86400) / 3600),\n minutes: Math.floor((s % 3600) / 60),\n seconds: s % 60,\n done: false,\n };\n}\n\n/**\n * Tikkende afteltimer naar `target`. Client-only (start `null` om hydration-mismatch te voorkomen).\n * Gedeeld door het volwaardige Countdown-blok en de compacte widget.\n */\nexport function useCountdown(target: string): Remaining | null {\n const [r, setR] = useState<Remaining | null>(null);\n useEffect(() => {\n if (!target) return;\n setR(compute(target));\n const id = setInterval(() => setR(compute(target)), 1000);\n return () => clearInterval(id);\n }, [target]);\n return r;\n}\n\nconst UNITS: { key: keyof Remaining; label: string }[] = [\n { key: \"days\", label: \"dagen\" },\n { key: \"hours\", label: \"uur\" },\n { key: \"minutes\", label: \"min\" },\n { key: \"seconds\", label: \"sec\" },\n];\n\nexport function CountdownDigits({ r, showLabels = true }: { r: Remaining; showLabels?: boolean }) {\n return (\n <div className=\"xicms-countdown__digits\" role=\"timer\" aria-live=\"off\">\n {UNITS.map((u) => (\n <div key={u.key} className=\"xicms-countdown__unit\">\n <span className=\"xicms-countdown__value\">{String(r[u.key] as number).padStart(2, \"0\")}</span>\n {showLabels && <span className=\"xicms-countdown__label\">{u.label}</span>}\n </div>\n ))}\n </div>\n );\n}\n\ntype Countdown = z.infer<typeof CountdownBlockSchema>;\n\nfunction CountdownRender({ block, mode }: BlockRenderProps<Countdown>) {\n const r = useCountdown(block.target);\n if (!block.target) return null;\n // Na afloop optioneel het hele blok verbergen (bv. een verlopen actie-aankondiging) —\n // behalve in de editor-preview, anders kan de editor het blok niet meer terugvinden.\n if (r?.done && block.onExpire === \"hide\" && mode !== \"preview\") return null;\n return (\n <section className=\"xicms-countdown\">\n {block.heading && <Heading level={2} className=\"xicms-countdown__heading\" data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {r === null ? (\n <p className=\"xicms-countdown__loading\" aria-hidden=\"true\">…</p>\n ) : r.done ? (\n <p className=\"xicms-countdown__expired\" data-xicms-edit=\"expiredText\">{block.expiredText}</p>\n ) : (\n <CountdownDigits r={r} showLabels={block.showLabels} />\n )}\n </section>\n );\n}\n\nexport const countdownBlock = defineBlock({\n type: \"countdown\",\n Render: CountdownRender,\n label: \"Aftelteller\",\n icon: \"⏳\",\n defaults: () => ({ heading: \"Nog even tot de start\", target: \"\", expiredText: \"Het is zover!\", showLabels: true }),\n});\n"],"mappings":";AAyDQ,SACE,KADF;AAvDR,SAAS,WAAW,gBAAgB;AAGpC,SAAS,mBAA0C;AACnD,SAAS,eAAe;AAUxB,SAAS,QAAQ,QAAkC;AACjD,QAAM,IAAI,IAAI,KAAK,MAAM,EAAE,QAAQ;AACnC,MAAI,OAAO,MAAM,CAAC,EAAG,QAAO;AAC5B,QAAM,OAAO,IAAI,KAAK,IAAI;AAC1B,MAAI,QAAQ,EAAG,QAAO,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,KAAK;AAC9E,QAAM,IAAI,KAAK,MAAM,OAAO,GAAI;AAChC,SAAO;AAAA,IACL,MAAM,KAAK,MAAM,IAAI,KAAK;AAAA,IAC1B,OAAO,KAAK,MAAO,IAAI,QAAS,IAAI;AAAA,IACpC,SAAS,KAAK,MAAO,IAAI,OAAQ,EAAE;AAAA,IACnC,SAAS,IAAI;AAAA,IACb,MAAM;AAAA,EACR;AACF;AAMO,SAAS,aAAa,QAAkC;AAC7D,QAAM,CAAC,GAAG,IAAI,IAAI,SAA2B,IAAI;AACjD,YAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AACb,SAAK,QAAQ,MAAM,CAAC;AACpB,UAAM,KAAK,YAAY,MAAM,KAAK,QAAQ,MAAM,CAAC,GAAG,GAAI;AACxD,WAAO,MAAM,cAAc,EAAE;AAAA,EAC/B,GAAG,CAAC,MAAM,CAAC;AACX,SAAO;AACT;AAEA,MAAM,QAAmD;AAAA,EACvD,EAAE,KAAK,QAAQ,OAAO,QAAQ;AAAA,EAC9B,EAAE,KAAK,SAAS,OAAO,MAAM;AAAA,EAC7B,EAAE,KAAK,WAAW,OAAO,MAAM;AAAA,EAC/B,EAAE,KAAK,WAAW,OAAO,MAAM;AACjC;AAEO,SAAS,gBAAgB,EAAE,GAAG,aAAa,KAAK,GAA2C;AAChG,SACE,oBAAC,SAAI,WAAU,2BAA0B,MAAK,SAAQ,aAAU,OAC7D,gBAAM,IAAI,CAAC,MACV,qBAAC,SAAgB,WAAU,yBACzB;AAAA,wBAAC,UAAK,WAAU,0BAA0B,iBAAO,EAAE,EAAE,GAAG,CAAW,EAAE,SAAS,GAAG,GAAG,GAAE;AAAA,IACrF,cAAc,oBAAC,UAAK,WAAU,0BAA0B,YAAE,OAAM;AAAA,OAFzD,EAAE,GAGZ,CACD,GACH;AAEJ;AAIA,SAAS,gBAAgB,EAAE,OAAO,KAAK,GAAgC;AACrE,QAAM,IAAI,aAAa,MAAM,MAAM;AACnC,MAAI,CAAC,MAAM,OAAQ,QAAO;AAG1B,MAAI,GAAG,QAAQ,MAAM,aAAa,UAAU,SAAS,UAAW,QAAO;AACvE,SACE,qBAAC,aAAQ,WAAU,mBAChB;AAAA,UAAM,WAAW,oBAAC,WAAQ,OAAO,GAAG,WAAU,4BAA2B,mBAAgB,WAAW,gBAAM,SAAQ;AAAA,IAClH,MAAM,OACL,oBAAC,OAAE,WAAU,4BAA2B,eAAY,QAAO,oBAAC,IAC1D,EAAE,OACJ,oBAAC,OAAE,WAAU,4BAA2B,mBAAgB,eAAe,gBAAM,aAAY,IAEzF,oBAAC,mBAAgB,GAAM,YAAY,MAAM,YAAY;AAAA,KAEzD;AAEJ;AAEO,MAAM,iBAAiB,YAAY;AAAA,EACxC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,SAAS,yBAAyB,QAAQ,IAAI,aAAa,iBAAiB,YAAY,KAAK;AAClH,CAAC;","names":[]}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { CtaBlockSchema } from "../../schema/blocks.js";
|
|
4
3
|
import { defineBlock } from "../registry.js";
|
|
5
4
|
import { VStack, HStack, Card, Heading, Text, Btn } from "../ui/index.js";
|
|
6
5
|
function CtaRender({ block }) {
|
|
@@ -41,7 +40,6 @@ function CtaRender({ block }) {
|
|
|
41
40
|
const ctaBlock = defineBlock({
|
|
42
41
|
type: "cta",
|
|
43
42
|
fields: { buttonVariant: { type: "select", options: [{ value: "primary", label: "Primair" }, { value: "secondary", label: "Secundair" }, { value: "outline", label: "Outline" }] } },
|
|
44
|
-
schema: CtaBlockSchema,
|
|
45
43
|
Render: CtaRender,
|
|
46
44
|
label: "Call-to-action",
|
|
47
45
|
icon: "\u{1F4E3}",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/Cta.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport { CtaBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { VStack, HStack, Card, Heading, Text, Btn } from \"../ui\";\n\ntype Cta = z.infer<typeof CtaBlockSchema>;\n\nfunction CtaRender({ block }: BlockRenderProps<Cta>) {\n if (block.sticky) {\n return (\n <HStack\n gap={3}\n vAlign=\"center\"\n hAlign=\"between\"\n role=\"region\"\n aria-label={block.title || \"Call-to-action\"}\n style={{ position: \"sticky\", bottom: 0, zIndex: 30, padding: \"0.75rem 1rem\", background: \"var(--color-background-card)\", borderTop: \"1px solid var(--color-border)\", flexWrap: \"wrap\" }}\n >\n <Text weight=\"bold\">{block.title || block.text}</Text>\n <Btn label={block.buttonLabel} href={block.buttonHref} variant={block.buttonVariant} size=\"md\" editField=\"buttonLabel\" />\n </HStack>\n );\n }\n return (\n <Card\n padding={6}\n variant={block.backgroundColor ? \"default\" : \"muted\"}\n style={block.backgroundColor ? { background: block.backgroundColor } : undefined}\n >\n <VStack gap={3} hAlign=\"center\" style={{ textAlign: \"center\" }}>\n {block.title && <Heading level={2} data-xicms-edit=\"title\">{block.title}</Heading>}\n {block.text && <Text type=\"large\" color=\"secondary\" data-xicms-edit=\"text\">{block.text}</Text>}\n <HStack gap={2} hAlign=\"center\" style={{ flexWrap: \"wrap\" }}>\n <Btn label={block.buttonLabel} href={block.buttonHref} variant={block.buttonVariant} editField=\"buttonLabel\" />\n {block.button2Label && (\n <Btn label={block.button2Label} href={block.button2Href ?? \"#\"} variant=\"outline\" editField=\"button2Label\" />\n )}\n </HStack>\n </VStack>\n </Card>\n );\n}\n\nexport const ctaBlock = defineBlock({\n type: \"cta\",\n fields: { buttonVariant: { type: \"select\", options: [{ value: \"primary\", label: \"Primair\" }, { value: \"secondary\", label: \"Secundair\" }, { value: \"outline\", label: \"Outline\" }] } },\n
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/Cta.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport type { CtaBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { VStack, HStack, Card, Heading, Text, Btn } from \"../ui\";\n\ntype Cta = z.infer<typeof CtaBlockSchema>;\n\nfunction CtaRender({ block }: BlockRenderProps<Cta>) {\n if (block.sticky) {\n return (\n <HStack\n gap={3}\n vAlign=\"center\"\n hAlign=\"between\"\n role=\"region\"\n aria-label={block.title || \"Call-to-action\"}\n style={{ position: \"sticky\", bottom: 0, zIndex: 30, padding: \"0.75rem 1rem\", background: \"var(--color-background-card)\", borderTop: \"1px solid var(--color-border)\", flexWrap: \"wrap\" }}\n >\n <Text weight=\"bold\">{block.title || block.text}</Text>\n <Btn label={block.buttonLabel} href={block.buttonHref} variant={block.buttonVariant} size=\"md\" editField=\"buttonLabel\" />\n </HStack>\n );\n }\n return (\n <Card\n padding={6}\n variant={block.backgroundColor ? \"default\" : \"muted\"}\n style={block.backgroundColor ? { background: block.backgroundColor } : undefined}\n >\n <VStack gap={3} hAlign=\"center\" style={{ textAlign: \"center\" }}>\n {block.title && <Heading level={2} data-xicms-edit=\"title\">{block.title}</Heading>}\n {block.text && <Text type=\"large\" color=\"secondary\" data-xicms-edit=\"text\">{block.text}</Text>}\n <HStack gap={2} hAlign=\"center\" style={{ flexWrap: \"wrap\" }}>\n <Btn label={block.buttonLabel} href={block.buttonHref} variant={block.buttonVariant} editField=\"buttonLabel\" />\n {block.button2Label && (\n <Btn label={block.button2Label} href={block.button2Href ?? \"#\"} variant=\"outline\" editField=\"button2Label\" />\n )}\n </HStack>\n </VStack>\n </Card>\n );\n}\n\nexport const ctaBlock = defineBlock({\n type: \"cta\",\n fields: { buttonVariant: { type: \"select\", options: [{ value: \"primary\", label: \"Primair\" }, { value: \"secondary\", label: \"Secundair\" }, { value: \"outline\", label: \"Outline\" }] } },\n Render: CtaRender,\n label: \"Call-to-action\",\n icon: \"📣\",\n defaults: () => ({ title: \"Klaar om te starten?\", buttonLabel: \"Neem contact op\", buttonHref: \"#contact\", buttonVariant: \"primary\" }),\n});\n"],"mappings":";AAYM,SAQE,KARF;AARN,SAAS,mBAA0C;AACnD,SAAS,QAAQ,QAAQ,MAAM,SAAS,MAAM,WAAW;AAIzD,SAAS,UAAU,EAAE,MAAM,GAA0B;AACnD,MAAI,MAAM,QAAQ;AAChB,WACE;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,QAAO;AAAA,QACP,QAAO;AAAA,QACP,MAAK;AAAA,QACL,cAAY,MAAM,SAAS;AAAA,QAC3B,OAAO,EAAE,UAAU,UAAU,QAAQ,GAAG,QAAQ,IAAI,SAAS,gBAAgB,YAAY,gCAAgC,WAAW,iCAAiC,UAAU,OAAO;AAAA,QAEtL;AAAA,8BAAC,QAAK,QAAO,QAAQ,gBAAM,SAAS,MAAM,MAAK;AAAA,UAC/C,oBAAC,OAAI,OAAO,MAAM,aAAa,MAAM,MAAM,YAAY,SAAS,MAAM,eAAe,MAAK,MAAK,WAAU,eAAc;AAAA;AAAA;AAAA,IACzH;AAAA,EAEJ;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,SAAS;AAAA,MACT,SAAS,MAAM,kBAAkB,YAAY;AAAA,MAC7C,OAAO,MAAM,kBAAkB,EAAE,YAAY,MAAM,gBAAgB,IAAI;AAAA,MAEvE,+BAAC,UAAO,KAAK,GAAG,QAAO,UAAS,OAAO,EAAE,WAAW,SAAS,GAC1D;AAAA,cAAM,SAAS,oBAAC,WAAQ,OAAO,GAAG,mBAAgB,SAAS,gBAAM,OAAM;AAAA,QACvE,MAAM,QAAQ,oBAAC,QAAK,MAAK,SAAQ,OAAM,aAAY,mBAAgB,QAAQ,gBAAM,MAAK;AAAA,QACvF,qBAAC,UAAO,KAAK,GAAG,QAAO,UAAS,OAAO,EAAE,UAAU,OAAO,GACxD;AAAA,8BAAC,OAAI,OAAO,MAAM,aAAa,MAAM,MAAM,YAAY,SAAS,MAAM,eAAe,WAAU,eAAc;AAAA,UAC5G,MAAM,gBACL,oBAAC,OAAI,OAAO,MAAM,cAAc,MAAM,MAAM,eAAe,KAAK,SAAQ,WAAU,WAAU,gBAAe;AAAA,WAE/G;AAAA,SACF;AAAA;AAAA,EACF;AAEJ;AAEO,MAAM,WAAW,YAAY;AAAA,EAClC,MAAM;AAAA,EACN,QAAQ,EAAE,eAAe,EAAE,MAAM,UAAU,SAAS,CAAC,EAAE,OAAO,WAAW,OAAO,UAAU,GAAG,EAAE,OAAO,aAAa,OAAO,YAAY,GAAG,EAAE,OAAO,WAAW,OAAO,UAAU,CAAC,EAAE,EAAE;AAAA,EACnL,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,OAAO,wBAAwB,aAAa,mBAAmB,YAAY,YAAY,eAAe,UAAU;AACrI,CAAC;","names":[]}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { DownloadsBlockSchema } from "../../schema/blocks.js";
|
|
3
2
|
import { defineBlock } from "../registry.js";
|
|
4
3
|
import { Heading } from "../ui/index.js";
|
|
5
4
|
import { fileIcon } from "./lib/fileIcon.js";
|
|
@@ -17,7 +16,6 @@ function DownloadsRender({ block }) {
|
|
|
17
16
|
}
|
|
18
17
|
const downloadsBlock = defineBlock({
|
|
19
18
|
type: "downloads",
|
|
20
|
-
schema: DownloadsBlockSchema,
|
|
21
19
|
Render: DownloadsRender,
|
|
22
20
|
label: "Downloads",
|
|
23
21
|
icon: "\u{1F4E5}",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/Downloads.tsx"],"sourcesContent":["import type { z } from \"zod\";\nimport { DownloadsBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Heading } from \"../ui\";\nimport { fileIcon } from \"./lib/fileIcon\";\n\ntype Downloads = z.infer<typeof DownloadsBlockSchema>;\n\n/** Downloadbare documenten (brochures, prijslijsten, voorwaarden). Server-safe. */\nfunction DownloadsRender({ block }: BlockRenderProps<Downloads>) {\n // _i = index in de OORSPRONKELIJKE array — de lijst wordt gefilterd op label && href.\n const items = (block.items ?? []).map((d, i) => ({ ...d, _i: i })).filter((d) => d.label && d.href);\n if (items.length === 0) return null;\n return (\n <section className=\"xicms-downloads\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n <ul className=\"xicms-downloads__list\">\n {items.map((d) => (\n <li key={d._i} className=\"xicms-downloads__item\">\n <a className=\"xicms-downloads__link\" href={d.href} download>\n <span className=\"xicms-downloads__icon\" aria-hidden=\"true\">{fileIcon(d.href)}</span>\n <span className=\"xicms-downloads__label\" data-xicms-edit={`items.${d._i}.label`}>{d.label}</span>\n {d.meta && <span className=\"xicms-downloads__meta\" data-xicms-edit={`items.${d._i}.meta`}>{d.meta}</span>}\n </a>\n </li>\n ))}\n </ul>\n </section>\n );\n}\n\nexport const downloadsBlock = defineBlock({\n type: \"downloads\",\n
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/Downloads.tsx"],"sourcesContent":["import type { z } from \"zod\";\nimport type { DownloadsBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Heading } from \"../ui\";\nimport { fileIcon } from \"./lib/fileIcon\";\n\ntype Downloads = z.infer<typeof DownloadsBlockSchema>;\n\n/** Downloadbare documenten (brochures, prijslijsten, voorwaarden). Server-safe. */\nfunction DownloadsRender({ block }: BlockRenderProps<Downloads>) {\n // _i = index in de OORSPRONKELIJKE array — de lijst wordt gefilterd op label && href.\n const items = (block.items ?? []).map((d, i) => ({ ...d, _i: i })).filter((d) => d.label && d.href);\n if (items.length === 0) return null;\n return (\n <section className=\"xicms-downloads\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n <ul className=\"xicms-downloads__list\">\n {items.map((d) => (\n <li key={d._i} className=\"xicms-downloads__item\">\n <a className=\"xicms-downloads__link\" href={d.href} download>\n <span className=\"xicms-downloads__icon\" aria-hidden=\"true\">{fileIcon(d.href)}</span>\n <span className=\"xicms-downloads__label\" data-xicms-edit={`items.${d._i}.label`}>{d.label}</span>\n {d.meta && <span className=\"xicms-downloads__meta\" data-xicms-edit={`items.${d._i}.meta`}>{d.meta}</span>}\n </a>\n </li>\n ))}\n </ul>\n </section>\n );\n}\n\nexport const downloadsBlock = defineBlock({\n type: \"downloads\",\n Render: DownloadsRender,\n label: \"Downloads\",\n icon: \"📥\",\n defaults: () => ({\n heading: \"Downloads\",\n items: [{ label: \"Brochure (PDF)\", href: \"\", meta: \"PDF\" }],\n }),\n});\n"],"mappings":"AAewB,cAIZ,YAJY;AAbxB,SAAS,mBAA0C;AACnD,SAAS,eAAe;AACxB,SAAS,gBAAgB;AAKzB,SAAS,gBAAgB,EAAE,MAAM,GAAgC;AAE/D,QAAM,SAAS,MAAM,SAAS,CAAC,GAAG,IAAI,CAAC,GAAG,OAAO,EAAE,GAAG,GAAG,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI;AAClG,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,SACE,qBAAC,aAAQ,WAAU,mBAChB;AAAA,UAAM,WAAW,oBAAC,WAAQ,OAAO,GAAG,mBAAgB,WAAW,gBAAM,SAAQ;AAAA,IAC9E,oBAAC,QAAG,WAAU,yBACX,gBAAM,IAAI,CAAC,MACV,oBAAC,QAAc,WAAU,yBACvB,+BAAC,OAAE,WAAU,yBAAwB,MAAM,EAAE,MAAM,UAAQ,MACzD;AAAA,0BAAC,UAAK,WAAU,yBAAwB,eAAY,QAAQ,mBAAS,EAAE,IAAI,GAAE;AAAA,MAC7E,oBAAC,UAAK,WAAU,0BAAyB,mBAAiB,SAAS,EAAE,EAAE,UAAW,YAAE,OAAM;AAAA,MACzF,EAAE,QAAQ,oBAAC,UAAK,WAAU,yBAAwB,mBAAiB,SAAS,EAAE,EAAE,SAAU,YAAE,MAAK;AAAA,OACpG,KALO,EAAE,EAMX,CACD,GACH;AAAA,KACF;AAEJ;AAEO,MAAM,iBAAiB,YAAY;AAAA,EACxC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO;AAAA,IACf,SAAS;AAAA,IACT,OAAO,CAAC,EAAE,OAAO,kBAAkB,MAAM,IAAI,MAAM,MAAM,CAAC;AAAA,EAC5D;AACF,CAAC;","names":[]}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { ButtonsBlockSchema, QuoteBlockSchema, DividerBlockSchema } from "../../schema/blocks.js";
|
|
4
3
|
import { defineBlock } from "../registry.js";
|
|
5
4
|
import { HStack, VStack, Card, Text, Btn, Divider } from "../ui/index.js";
|
|
6
5
|
function ButtonsRender({ block }) {
|
|
@@ -12,7 +11,6 @@ function ButtonsRender({ block }) {
|
|
|
12
11
|
const buttonsBlock = defineBlock({
|
|
13
12
|
type: "buttons",
|
|
14
13
|
fields: { "items.variant": { type: "select", options: [{ value: "primary", label: "Primair" }, { value: "secondary", label: "Secundair" }, { value: "outline", label: "Outline" }] } },
|
|
15
|
-
schema: ButtonsBlockSchema,
|
|
16
14
|
Render: ButtonsRender,
|
|
17
15
|
label: "Knoppen",
|
|
18
16
|
icon: "\u{1F518}",
|
|
@@ -38,7 +36,6 @@ function QuoteRender({ block }) {
|
|
|
38
36
|
}
|
|
39
37
|
const quoteBlock = defineBlock({
|
|
40
38
|
type: "quote",
|
|
41
|
-
schema: QuoteBlockSchema,
|
|
42
39
|
Render: QuoteRender,
|
|
43
40
|
label: "Uitspraak",
|
|
44
41
|
icon: "\u275D",
|
|
@@ -54,7 +51,6 @@ function DividerRender({ block }) {
|
|
|
54
51
|
}
|
|
55
52
|
const dividerBlock = defineBlock({
|
|
56
53
|
type: "divider",
|
|
57
|
-
schema: DividerBlockSchema,
|
|
58
54
|
Render: DividerRender,
|
|
59
55
|
label: "Scheiding",
|
|
60
56
|
icon: "\u2796",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/Elements.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport { ButtonsBlockSchema, QuoteBlockSchema, DividerBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { HStack, VStack, Card, Text, Btn, Divider } from \"../ui\";\n\n/* ── Buttons (knopgroep) ─────────────────────────────────────────────────── */\ntype Buttons = z.infer<typeof ButtonsBlockSchema>;\nfunction ButtonsRender({ block }: BlockRenderProps<Buttons>) {\n // _i = index in de OORSPRONKELIJKE array — inline-edit-paden moeten naar de ongefilterde\n // items wijzen, anders schrijft bewerken naar het verkeerde (weggefilterde) item.\n const items = (block.items ?? []).map((b, i) => ({ ...b, _i: i })).filter((b) => b.label);\n if (items.length === 0) return null;\n const hAlign = block.align === \"center\" ? \"center\" : block.align === \"right\" ? \"end\" : \"start\";\n return (\n <HStack gap={2} hAlign={hAlign} style={{ flexWrap: \"wrap\" }}>\n {items.map((b) => (\n <Btn key={b._i} label={b.label} href={b.href} variant={b.variant} editField={`items.${b._i}.label`} />\n ))}\n </HStack>\n );\n}\nexport const buttonsBlock = defineBlock({\n type: \"buttons\",\n fields: { \"items.variant\": { type: \"select\", options: [{ value: \"primary\", label: \"Primair\" }, { value: \"secondary\", label: \"Secundair\" }, { value: \"outline\", label: \"Outline\" }] } },\n
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/Elements.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport type { ButtonsBlockSchema, QuoteBlockSchema, DividerBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { HStack, VStack, Card, Text, Btn, Divider } from \"../ui\";\n\n/* ── Buttons (knopgroep) ─────────────────────────────────────────────────── */\ntype Buttons = z.infer<typeof ButtonsBlockSchema>;\nfunction ButtonsRender({ block }: BlockRenderProps<Buttons>) {\n // _i = index in de OORSPRONKELIJKE array — inline-edit-paden moeten naar de ongefilterde\n // items wijzen, anders schrijft bewerken naar het verkeerde (weggefilterde) item.\n const items = (block.items ?? []).map((b, i) => ({ ...b, _i: i })).filter((b) => b.label);\n if (items.length === 0) return null;\n const hAlign = block.align === \"center\" ? \"center\" : block.align === \"right\" ? \"end\" : \"start\";\n return (\n <HStack gap={2} hAlign={hAlign} style={{ flexWrap: \"wrap\" }}>\n {items.map((b) => (\n <Btn key={b._i} label={b.label} href={b.href} variant={b.variant} editField={`items.${b._i}.label`} />\n ))}\n </HStack>\n );\n}\nexport const buttonsBlock = defineBlock({\n type: \"buttons\",\n fields: { \"items.variant\": { type: \"select\", options: [{ value: \"primary\", label: \"Primair\" }, { value: \"secondary\", label: \"Secundair\" }, { value: \"outline\", label: \"Outline\" }] } },\n Render: ButtonsRender,\n label: \"Knoppen\",\n icon: \"🔘\",\n defaults: () => ({ align: \"left\", items: [{ label: \"Knop\", href: \"#\", variant: \"primary\" }] }),\n});\n\n/* ── Quote (uitspraak) ───────────────────────────────────────────────────── */\ntype Quote = z.infer<typeof QuoteBlockSchema>;\nfunction QuoteRender({ block }: BlockRenderProps<Quote>) {\n if (!block.text) return null;\n return (\n <Card padding={5} variant=\"muted\">\n <VStack gap={2} as=\"figure\" style={{ margin: 0 }}>\n <Text type=\"large\" as=\"p\" style={{ fontStyle: \"italic\" }}>“<span data-xicms-edit=\"text\">{block.text}</span>”</Text>\n {(block.author || block.role) && (\n <Text as=\"label\" color=\"secondary\">\n — <span data-xicms-edit=\"author\">{block.author}</span>\n {block.role ? <> · <span data-xicms-edit=\"role\">{block.role}</span></> : \"\"}\n </Text>\n )}\n </VStack>\n </Card>\n );\n}\nexport const quoteBlock = defineBlock({\n type: \"quote\",\n Render: QuoteRender,\n label: \"Uitspraak\",\n icon: \"❝\",\n defaults: () => ({ text: \"Een krachtige uitspraak.\", author: \"\" }),\n});\n\n/* ── Divider (scheiding) ─────────────────────────────────────────────────── */\ntype DividerBlk = z.infer<typeof DividerBlockSchema>;\nfunction DividerRender({ block }: BlockRenderProps<DividerBlk>) {\n const pad = block.spacing === \"sm\" ? \"0.75rem\" : block.spacing === \"lg\" ? \"3rem\" : \"1.5rem\";\n if (block.variant === \"space\") return <div style={{ height: pad }} aria-hidden=\"true\" />;\n if (block.variant === \"dots\") {\n return (\n <div style={{ textAlign: \"center\", padding: `${pad} 0`, color: \"var(--color-text-secondary)\", letterSpacing: \"0.5em\" }} aria-hidden=\"true\">\n • • •\n </div>\n );\n }\n return <div style={{ padding: `${pad} 0` }}><Divider variant=\"subtle\" /></div>;\n}\nexport const dividerBlock = defineBlock({\n type: \"divider\",\n Render: DividerRender,\n label: \"Scheiding\",\n icon: \"➖\",\n defaults: () => ({ variant: \"line\", spacing: \"md\" }),\n});\n"],"mappings":";AAkBQ,SAyBkB,UAzBlB,KAqBA,YArBA;AAdR,SAAS,mBAA0C;AACnD,SAAS,QAAQ,QAAQ,MAAM,MAAM,KAAK,eAAe;AAIzD,SAAS,cAAc,EAAE,MAAM,GAA8B;AAG3D,QAAM,SAAS,MAAM,SAAS,CAAC,GAAG,IAAI,CAAC,GAAG,OAAO,EAAE,GAAG,GAAG,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK;AACxF,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,SAAS,MAAM,UAAU,WAAW,WAAW,MAAM,UAAU,UAAU,QAAQ;AACvF,SACE,oBAAC,UAAO,KAAK,GAAG,QAAgB,OAAO,EAAE,UAAU,OAAO,GACvD,gBAAM,IAAI,CAAC,MACV,oBAAC,OAAe,OAAO,EAAE,OAAO,MAAM,EAAE,MAAM,SAAS,EAAE,SAAS,WAAW,SAAS,EAAE,EAAE,YAAhF,EAAE,EAAwF,CACrG,GACH;AAEJ;AACO,MAAM,eAAe,YAAY;AAAA,EACtC,MAAM;AAAA,EACN,QAAQ,EAAE,iBAAiB,EAAE,MAAM,UAAU,SAAS,CAAC,EAAE,OAAO,WAAW,OAAO,UAAU,GAAG,EAAE,OAAO,aAAa,OAAO,YAAY,GAAG,EAAE,OAAO,WAAW,OAAO,UAAU,CAAC,EAAE,EAAE;AAAA,EACrL,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,OAAO,QAAQ,OAAO,CAAC,EAAE,OAAO,QAAQ,MAAM,KAAK,SAAS,UAAU,CAAC,EAAE;AAC9F,CAAC;AAID,SAAS,YAAY,EAAE,MAAM,GAA4B;AACvD,MAAI,CAAC,MAAM,KAAM,QAAO;AACxB,SACE,oBAAC,QAAK,SAAS,GAAG,SAAQ,SACxB,+BAAC,UAAO,KAAK,GAAG,IAAG,UAAS,OAAO,EAAE,QAAQ,EAAE,GAC7C;AAAA,yBAAC,QAAK,MAAK,SAAQ,IAAG,KAAI,OAAO,EAAE,WAAW,SAAS,GAAG;AAAA;AAAA,MAAC,oBAAC,UAAK,mBAAgB,QAAQ,gBAAM,MAAK;AAAA,MAAO;AAAA,OAAC;AAAA,KAC1G,MAAM,UAAU,MAAM,SACtB,qBAAC,QAAK,IAAG,SAAQ,OAAM,aAAY;AAAA;AAAA,MAC/B,oBAAC,UAAK,mBAAgB,UAAU,gBAAM,QAAO;AAAA,MAC9C,MAAM,OAAO,iCAAE;AAAA;AAAA,QAAG,oBAAC,UAAK,mBAAgB,QAAQ,gBAAM,MAAK;AAAA,SAAO,IAAM;AAAA,OAC3E;AAAA,KAEJ,GACF;AAEJ;AACO,MAAM,aAAa,YAAY;AAAA,EACpC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,MAAM,4BAA4B,QAAQ,GAAG;AAClE,CAAC;AAID,SAAS,cAAc,EAAE,MAAM,GAAiC;AAC9D,QAAM,MAAM,MAAM,YAAY,OAAO,YAAY,MAAM,YAAY,OAAO,SAAS;AACnF,MAAI,MAAM,YAAY,QAAS,QAAO,oBAAC,SAAI,OAAO,EAAE,QAAQ,IAAI,GAAG,eAAY,QAAO;AACtF,MAAI,MAAM,YAAY,QAAQ;AAC5B,WACE,oBAAC,SAAI,OAAO,EAAE,WAAW,UAAU,SAAS,GAAG,GAAG,MAAM,OAAO,+BAA+B,eAAe,QAAQ,GAAG,eAAY,QAAO,kCAE3I;AAAA,EAEJ;AACA,SAAO,oBAAC,SAAI,OAAO,EAAE,SAAS,GAAG,GAAG,KAAK,GAAG,8BAAC,WAAQ,SAAQ,UAAS,GAAE;AAC1E;AACO,MAAM,eAAe,YAAY;AAAA,EACtC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,SAAS,QAAQ,SAAS,KAAK;AACpD,CAAC;","names":[]}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
|
-
import { EmbedBlockSchema } from "../../schema/blocks.js";
|
|
4
3
|
import { defineBlock } from "../registry.js";
|
|
5
4
|
import { ConsentEmbed } from "./consentEmbed.js";
|
|
6
5
|
function EmbedRender({ block, mode }) {
|
|
@@ -20,7 +19,6 @@ function EmbedRender({ block, mode }) {
|
|
|
20
19
|
}
|
|
21
20
|
const embedBlock = defineBlock({
|
|
22
21
|
type: "embed",
|
|
23
|
-
schema: EmbedBlockSchema,
|
|
24
22
|
Render: EmbedRender,
|
|
25
23
|
label: "Embed (iframe)",
|
|
26
24
|
icon: "\u{1F9E9}",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/Embed.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport { EmbedBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { ConsentEmbed } from \"./consentEmbed\";\n\ntype Embed = z.infer<typeof EmbedBlockSchema>;\n\n/** Generieke responsive iframe-embed (consent-gated by default) — voor wat dan ook met een URL. */\nfunction EmbedRender({ block, mode }: BlockRenderProps<Embed>) {\n if (!block.url) return null;\n // \"fixed\" = vaste hoogte (bestaand gedrag); anders schaalt de embed mee met de beeldverhouding.\n const sizing =\n block.aspectRatio && block.aspectRatio !== \"fixed\"\n ? { aspectRatio: block.aspectRatio.replace(\"/\", \" / \") }\n : { height: `${block.height}px` };\n return (\n <div className=\"xicms-embed\" style={sizing}>\n <ConsentEmbed gated={block.consentGated} preview={mode === \"preview\"} label={block.title ?? \"Embed\"}>\n <iframe\n src={block.url}\n title={block.title ?? \"Embed\"}\n loading=\"lazy\"\n referrerPolicy=\"no-referrer-when-downgrade\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups\"\n style={{ border: 0, width: \"100%\", height: \"100%\" }}\n />\n </ConsentEmbed>\n </div>\n );\n}\n\nexport const embedBlock = defineBlock({\n type: \"embed\",\n
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/Embed.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport type { EmbedBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { ConsentEmbed } from \"./consentEmbed\";\n\ntype Embed = z.infer<typeof EmbedBlockSchema>;\n\n/** Generieke responsive iframe-embed (consent-gated by default) — voor wat dan ook met een URL. */\nfunction EmbedRender({ block, mode }: BlockRenderProps<Embed>) {\n if (!block.url) return null;\n // \"fixed\" = vaste hoogte (bestaand gedrag); anders schaalt de embed mee met de beeldverhouding.\n const sizing =\n block.aspectRatio && block.aspectRatio !== \"fixed\"\n ? { aspectRatio: block.aspectRatio.replace(\"/\", \" / \") }\n : { height: `${block.height}px` };\n return (\n <div className=\"xicms-embed\" style={sizing}>\n <ConsentEmbed gated={block.consentGated} preview={mode === \"preview\"} label={block.title ?? \"Embed\"}>\n <iframe\n src={block.url}\n title={block.title ?? \"Embed\"}\n loading=\"lazy\"\n referrerPolicy=\"no-referrer-when-downgrade\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups\"\n style={{ border: 0, width: \"100%\", height: \"100%\" }}\n />\n </ConsentEmbed>\n </div>\n );\n}\n\nexport const embedBlock = defineBlock({\n type: \"embed\",\n Render: EmbedRender,\n label: \"Embed (iframe)\",\n icon: \"🧩\",\n defaults: () => ({ url: \"\", height: 420, consentGated: true }),\n});\n"],"mappings":";AAoBQ;AAhBR,SAAS,mBAA0C;AACnD,SAAS,oBAAoB;AAK7B,SAAS,YAAY,EAAE,OAAO,KAAK,GAA4B;AAC7D,MAAI,CAAC,MAAM,IAAK,QAAO;AAEvB,QAAM,SACJ,MAAM,eAAe,MAAM,gBAAgB,UACvC,EAAE,aAAa,MAAM,YAAY,QAAQ,KAAK,KAAK,EAAE,IACrD,EAAE,QAAQ,GAAG,MAAM,MAAM,KAAK;AACpC,SACE,oBAAC,SAAI,WAAU,eAAc,OAAO,QAClC,8BAAC,gBAAa,OAAO,MAAM,cAAc,SAAS,SAAS,WAAW,OAAO,MAAM,SAAS,SAC1F;AAAA,IAAC;AAAA;AAAA,MACC,KAAK,MAAM;AAAA,MACX,OAAO,MAAM,SAAS;AAAA,MACtB,SAAQ;AAAA,MACR,gBAAe;AAAA,MACf,SAAQ;AAAA,MACR,OAAO,EAAE,QAAQ,GAAG,OAAO,QAAQ,QAAQ,OAAO;AAAA;AAAA,EACpD,GACF,GACF;AAEJ;AAEO,MAAM,aAAa,YAAY;AAAA,EACpC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,KAAK,IAAI,QAAQ,KAAK,cAAc,KAAK;AAC9D,CAAC;","names":[]}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useMemo, useState } from "react";
|
|
4
|
-
import { FaqBlockSchema } from "../../schema/blocks.js";
|
|
5
4
|
import { defineBlock } from "../registry.js";
|
|
6
5
|
import { Collapsible } from "@astryxdesign/core/Collapsible";
|
|
7
6
|
import { TextInput } from "@astryxdesign/core/TextInput";
|
|
@@ -101,7 +100,6 @@ function FaqRender({ block }) {
|
|
|
101
100
|
}
|
|
102
101
|
const faqBlock = defineBlock({
|
|
103
102
|
type: "faq",
|
|
104
|
-
schema: FaqBlockSchema,
|
|
105
103
|
Render: FaqRender,
|
|
106
104
|
label: "Veelgestelde vragen",
|
|
107
105
|
icon: "\u2753",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/Faq.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport type { z } from \"zod\";\nimport { FaqBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Collapsible } from \"@astryxdesign/core/Collapsible\";\nimport { TextInput } from \"@astryxdesign/core/TextInput\";\nimport { VStack, Heading, Text } from \"../ui\";\nimport { jsonLdHtml } from \"./jsonld\";\n\ntype Faq = z.infer<typeof FaqBlockSchema>;\n\n/** Anker-id voor een vraag (deep-link: /faq#hoe-werkt-het). */\nexport function questionSlug(q: string): string {\n return q\n .toLowerCase()\n .normalize(\"NFD\")\n .replace(/[̀-ͯ]/g, \"\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 60);\n}\n\n/** Unieke anker-ids voor een vragenlijst: identieke vragen krijgen -2, -3, … (ids moeten uniek zijn). */\nexport function questionSlugs(questions: string[]): string[] {\n const seen = new Map<string, number>();\n return questions.map((q) => {\n const base = questionSlug(q);\n if (!base) return \"\";\n const n = (seen.get(base) ?? 0) + 1;\n seen.set(base, n);\n return n === 1 ? base : `${base}-${n}`;\n });\n}\n\nfunction faqJsonLd(items: { q: string; a: string; aHtml?: string }[]): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"FAQPage\",\n mainEntity: items\n .filter((it) => it.q && (it.a || it.aHtml))\n .map((it) => ({\n \"@type\": \"Question\",\n name: it.q,\n acceptedAnswer: { \"@type\": \"Answer\", text: it.aHtml ? it.aHtml.replace(/<[^>]*>/g, \" \").trim() : it.a },\n })),\n };\n}\n\nfunction FaqRender({ block }: BlockRenderProps<Faq>) {\n const items = block.items ?? [];\n const [q, setQ] = useState(\"\");\n // Slug per item vooraf berekenen (met dedupe), zodat dubbele vragen geen dubbele ids opleveren\n // en de hash-match precies één vraag opent.\n const withSlugs = useMemo(() => {\n const slugs = questionSlugs(items.map((it) => it.q));\n // idx = originele index in block.items — nodig voor de inline-edit-paden, want `filtered`\n // kan bij een actieve zoekterm een deelverzameling met verschoven indexen zijn.\n return items.map((it, i) => ({ it, slug: slugs[i], idx: i }));\n }, [items]);\n // Deep-link: bij laden met #<vraag-slug> die vraag openen (en ernaartoe scrollen).\n const [openSlug, setOpenSlug] = useState<string | null>(null);\n useEffect(() => {\n const hash = window.location.hash.slice(1);\n if (hash && withSlugs.some(({ slug }) => slug === hash)) {\n setOpenSlug(hash);\n document.getElementById(hash)?.scrollIntoView({ block: \"start\" });\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n const filtered = useMemo(() => {\n if (!block.searchable || !q.trim()) return withSlugs;\n const needle = q.toLowerCase();\n return withSlugs.filter(({ it }) => (it.q + \" \" + it.a + \" \" + (it.aHtml ?? \"\")).toLowerCase().includes(needle));\n }, [withSlugs, q, block.searchable]);\n\n return (\n <VStack gap={3} as=\"section\" className=\"xicms-faq\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {block.searchable && (\n <div style={{ maxWidth: 360 }}>\n <TextInput\n label=\"Zoek in veelgestelde vragen\"\n isLabelHidden\n placeholder=\"Zoek in vragen…\"\n value={q}\n onChange={setQ}\n />\n </div>\n )}\n <VStack gap={1.5}>\n {filtered.map(({ it, slug, idx }, i) => {\n return (\n <div key={i} id={slug || undefined}>\n <Collapsible\n // key wisselt zodra de hash-match binnenkomt (effect ná mount) → remount met open staat.\n key={`${i}-${slug === openSlug ? \"open\" : \"dicht\"}`}\n trigger={<Text weight=\"bold\" className=\"xicms-faq__question\" data-xicms-edit={`items.${idx}.q`}>{it.q}</Text>}\n defaultIsOpen={block.defaultOpen || (slug !== \"\" && slug === openSlug)}\n >\n {it.aHtml ? (\n // Server-side gesanitized bij publiceren (velden op *Html) — zie functions/lib/sanitize.\n // Inline bewerkbaar MET opmaak: `data-xicms-edit` wijst naar het eigen veld, zodat\n // de editor hier niet naar een niet-bestaand \"html\" schrijft.\n // eslint-disable-next-line react/no-danger\n <div\n className=\"xicms-richtext xicms-faq__answer\"\n data-xicms-edit={`items.${idx}.aHtml`}\n dangerouslySetInnerHTML={{ __html: it.aHtml }}\n />\n ) : (\n <Text color=\"secondary\" className=\"xicms-faq__answer\" data-xicms-edit={`items.${idx}.a`}>{it.a}</Text>\n )}\n </Collapsible>\n </div>\n );\n })}\n {filtered.length === 0 && <Text color=\"secondary\">Geen vragen gevonden.</Text>}\n </VStack>\n {block.jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: jsonLdHtml(faqJsonLd(items)) }}\n />\n )}\n </VStack>\n );\n}\n\nexport const faqBlock = defineBlock({\n type: \"faq\",\n
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/Faq.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport type { z } from \"zod\";\nimport type { FaqBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Collapsible } from \"@astryxdesign/core/Collapsible\";\nimport { TextInput } from \"@astryxdesign/core/TextInput\";\nimport { VStack, Heading, Text } from \"../ui\";\nimport { jsonLdHtml } from \"./jsonld\";\n\ntype Faq = z.infer<typeof FaqBlockSchema>;\n\n/** Anker-id voor een vraag (deep-link: /faq#hoe-werkt-het). */\nexport function questionSlug(q: string): string {\n return q\n .toLowerCase()\n .normalize(\"NFD\")\n .replace(/[̀-ͯ]/g, \"\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 60);\n}\n\n/** Unieke anker-ids voor een vragenlijst: identieke vragen krijgen -2, -3, … (ids moeten uniek zijn). */\nexport function questionSlugs(questions: string[]): string[] {\n const seen = new Map<string, number>();\n return questions.map((q) => {\n const base = questionSlug(q);\n if (!base) return \"\";\n const n = (seen.get(base) ?? 0) + 1;\n seen.set(base, n);\n return n === 1 ? base : `${base}-${n}`;\n });\n}\n\nfunction faqJsonLd(items: { q: string; a: string; aHtml?: string }[]): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"FAQPage\",\n mainEntity: items\n .filter((it) => it.q && (it.a || it.aHtml))\n .map((it) => ({\n \"@type\": \"Question\",\n name: it.q,\n acceptedAnswer: { \"@type\": \"Answer\", text: it.aHtml ? it.aHtml.replace(/<[^>]*>/g, \" \").trim() : it.a },\n })),\n };\n}\n\nfunction FaqRender({ block }: BlockRenderProps<Faq>) {\n const items = block.items ?? [];\n const [q, setQ] = useState(\"\");\n // Slug per item vooraf berekenen (met dedupe), zodat dubbele vragen geen dubbele ids opleveren\n // en de hash-match precies één vraag opent.\n const withSlugs = useMemo(() => {\n const slugs = questionSlugs(items.map((it) => it.q));\n // idx = originele index in block.items — nodig voor de inline-edit-paden, want `filtered`\n // kan bij een actieve zoekterm een deelverzameling met verschoven indexen zijn.\n return items.map((it, i) => ({ it, slug: slugs[i], idx: i }));\n }, [items]);\n // Deep-link: bij laden met #<vraag-slug> die vraag openen (en ernaartoe scrollen).\n const [openSlug, setOpenSlug] = useState<string | null>(null);\n useEffect(() => {\n const hash = window.location.hash.slice(1);\n if (hash && withSlugs.some(({ slug }) => slug === hash)) {\n setOpenSlug(hash);\n document.getElementById(hash)?.scrollIntoView({ block: \"start\" });\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n const filtered = useMemo(() => {\n if (!block.searchable || !q.trim()) return withSlugs;\n const needle = q.toLowerCase();\n return withSlugs.filter(({ it }) => (it.q + \" \" + it.a + \" \" + (it.aHtml ?? \"\")).toLowerCase().includes(needle));\n }, [withSlugs, q, block.searchable]);\n\n return (\n <VStack gap={3} as=\"section\" className=\"xicms-faq\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {block.searchable && (\n <div style={{ maxWidth: 360 }}>\n <TextInput\n label=\"Zoek in veelgestelde vragen\"\n isLabelHidden\n placeholder=\"Zoek in vragen…\"\n value={q}\n onChange={setQ}\n />\n </div>\n )}\n <VStack gap={1.5}>\n {filtered.map(({ it, slug, idx }, i) => {\n return (\n <div key={i} id={slug || undefined}>\n <Collapsible\n // key wisselt zodra de hash-match binnenkomt (effect ná mount) → remount met open staat.\n key={`${i}-${slug === openSlug ? \"open\" : \"dicht\"}`}\n trigger={<Text weight=\"bold\" className=\"xicms-faq__question\" data-xicms-edit={`items.${idx}.q`}>{it.q}</Text>}\n defaultIsOpen={block.defaultOpen || (slug !== \"\" && slug === openSlug)}\n >\n {it.aHtml ? (\n // Server-side gesanitized bij publiceren (velden op *Html) — zie functions/lib/sanitize.\n // Inline bewerkbaar MET opmaak: `data-xicms-edit` wijst naar het eigen veld, zodat\n // de editor hier niet naar een niet-bestaand \"html\" schrijft.\n // eslint-disable-next-line react/no-danger\n <div\n className=\"xicms-richtext xicms-faq__answer\"\n data-xicms-edit={`items.${idx}.aHtml`}\n dangerouslySetInnerHTML={{ __html: it.aHtml }}\n />\n ) : (\n <Text color=\"secondary\" className=\"xicms-faq__answer\" data-xicms-edit={`items.${idx}.a`}>{it.a}</Text>\n )}\n </Collapsible>\n </div>\n );\n })}\n {filtered.length === 0 && <Text color=\"secondary\">Geen vragen gevonden.</Text>}\n </VStack>\n {block.jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: jsonLdHtml(faqJsonLd(items)) }}\n />\n )}\n </VStack>\n );\n}\n\nexport const faqBlock = defineBlock({\n type: \"faq\",\n Render: FaqRender,\n label: \"Veelgestelde vragen\",\n icon: \"❓\",\n defaults: () => ({\n heading: \"Veelgestelde vragen\",\n items: [{ q: \"Een vraag?\", a: \"Het antwoord.\" }],\n searchable: false,\n jsonLd: true,\n }),\n});\n"],"mappings":";AA+EwB,cAYlB,YAZkB;AA7ExB,SAAS,WAAW,SAAS,gBAAgB;AAG7C,SAAS,mBAA0C;AACnD,SAAS,mBAAmB;AAC5B,SAAS,iBAAiB;AAC1B,SAAS,QAAQ,SAAS,YAAY;AACtC,SAAS,kBAAkB;AAKpB,SAAS,aAAa,GAAmB;AAC9C,SAAO,EACJ,YAAY,EACZ,UAAU,KAAK,EACf,QAAQ,UAAU,EAAE,EACpB,QAAQ,eAAe,GAAG,EAC1B,QAAQ,YAAY,EAAE,EACtB,MAAM,GAAG,EAAE;AAChB;AAGO,SAAS,cAAc,WAA+B;AAC3D,QAAM,OAAO,oBAAI,IAAoB;AACrC,SAAO,UAAU,IAAI,CAAC,MAAM;AAC1B,UAAM,OAAO,aAAa,CAAC;AAC3B,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK;AAClC,SAAK,IAAI,MAAM,CAAC;AAChB,WAAO,MAAM,IAAI,OAAO,GAAG,IAAI,IAAI,CAAC;AAAA,EACtC,CAAC;AACH;AAEA,SAAS,UAAU,OAA4E;AAC7F,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,YAAY,MACT,OAAO,CAAC,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,EACzC,IAAI,CAAC,QAAQ;AAAA,MACZ,SAAS;AAAA,MACT,MAAM,GAAG;AAAA,MACT,gBAAgB,EAAE,SAAS,UAAU,MAAM,GAAG,QAAQ,GAAG,MAAM,QAAQ,YAAY,GAAG,EAAE,KAAK,IAAI,GAAG,EAAE;AAAA,IACxG,EAAE;AAAA,EACN;AACF;AAEA,SAAS,UAAU,EAAE,MAAM,GAA0B;AACnD,QAAM,QAAQ,MAAM,SAAS,CAAC;AAC9B,QAAM,CAAC,GAAG,IAAI,IAAI,SAAS,EAAE;AAG7B,QAAM,YAAY,QAAQ,MAAM;AAC9B,UAAM,QAAQ,cAAc,MAAM,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC;AAGnD,WAAO,MAAM,IAAI,CAAC,IAAI,OAAO,EAAE,IAAI,MAAM,MAAM,CAAC,GAAG,KAAK,EAAE,EAAE;AAAA,EAC9D,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,CAAC,UAAU,WAAW,IAAI,SAAwB,IAAI;AAC5D,YAAU,MAAM;AACd,UAAM,OAAO,OAAO,SAAS,KAAK,MAAM,CAAC;AACzC,QAAI,QAAQ,UAAU,KAAK,CAAC,EAAE,KAAK,MAAM,SAAS,IAAI,GAAG;AACvD,kBAAY,IAAI;AAChB,eAAS,eAAe,IAAI,GAAG,eAAe,EAAE,OAAO,QAAQ,CAAC;AAAA,IAClE;AAAA,EAEF,GAAG,CAAC,CAAC;AACL,QAAM,WAAW,QAAQ,MAAM;AAC7B,QAAI,CAAC,MAAM,cAAc,CAAC,EAAE,KAAK,EAAG,QAAO;AAC3C,UAAM,SAAS,EAAE,YAAY;AAC7B,WAAO,UAAU,OAAO,CAAC,EAAE,GAAG,OAAO,GAAG,IAAI,MAAM,GAAG,IAAI,OAAO,GAAG,SAAS,KAAK,YAAY,EAAE,SAAS,MAAM,CAAC;AAAA,EACjH,GAAG,CAAC,WAAW,GAAG,MAAM,UAAU,CAAC;AAEnC,SACE,qBAAC,UAAO,KAAK,GAAG,IAAG,WAAU,WAAU,aACpC;AAAA,UAAM,WAAW,oBAAC,WAAQ,OAAO,GAAG,mBAAgB,WAAW,gBAAM,SAAQ;AAAA,IAC7E,MAAM,cACL,oBAAC,SAAI,OAAO,EAAE,UAAU,IAAI,GAC1B;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,eAAa;AAAA,QACb,aAAY;AAAA,QACZ,OAAO;AAAA,QACP,UAAU;AAAA;AAAA,IACZ,GACF;AAAA,IAEF,qBAAC,UAAO,KAAK,KACV;AAAA,eAAS,IAAI,CAAC,EAAE,IAAI,MAAM,IAAI,GAAG,MAAM;AACtC,eACE,oBAAC,SAAY,IAAI,QAAQ,QACvB;AAAA,UAAC;AAAA;AAAA,YAGC,SAAS,oBAAC,QAAK,QAAO,QAAO,WAAU,uBAAsB,mBAAiB,SAAS,GAAG,MAAO,aAAG,GAAE;AAAA,YACtG,eAAe,MAAM,eAAgB,SAAS,MAAM,SAAS;AAAA,YAE5D,aAAG;AAAA;AAAA;AAAA;AAAA;AAAA,cAKF;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAU;AAAA,kBACV,mBAAiB,SAAS,GAAG;AAAA,kBAC7B,yBAAyB,EAAE,QAAQ,GAAG,MAAM;AAAA;AAAA,cAC9C;AAAA,gBAEA,oBAAC,QAAK,OAAM,aAAY,WAAU,qBAAoB,mBAAiB,SAAS,GAAG,MAAO,aAAG,GAAE;AAAA;AAAA,UAf5F,GAAG,CAAC,IAAI,SAAS,WAAW,SAAS,OAAO;AAAA,QAiBnD,KApBQ,CAqBV;AAAA,MAEJ,CAAC;AAAA,MACA,SAAS,WAAW,KAAK,oBAAC,QAAK,OAAM,aAAY,mCAAqB;AAAA,OACzE;AAAA,IACC,MAAM,UACL;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QAEL,yBAAyB,EAAE,QAAQ,WAAW,UAAU,KAAK,CAAC,EAAE;AAAA;AAAA,IAClE;AAAA,KAEJ;AAEJ;AAEO,MAAM,WAAW,YAAY;AAAA,EAClC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO;AAAA,IACf,SAAS;AAAA,IACT,OAAO,CAAC,EAAE,GAAG,cAAc,GAAG,gBAAgB,CAAC;AAAA,IAC/C,YAAY;AAAA,IACZ,QAAQ;AAAA,EACV;AACF,CAAC;","names":[]}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
|
-
import { FormBlockSchema } from "../../schema/blocks.js";
|
|
5
4
|
import { defineBlock } from "../registry.js";
|
|
6
5
|
import { useBlockData } from "../context.js";
|
|
7
6
|
import { submitFormValues } from "./lib/submitForm.js";
|
|
@@ -223,7 +222,6 @@ function FormRender({ block, siteId, mode }) {
|
|
|
223
222
|
}
|
|
224
223
|
const formBlock = defineBlock({
|
|
225
224
|
type: "form",
|
|
226
|
-
schema: FormBlockSchema,
|
|
227
225
|
Render: FormRender,
|
|
228
226
|
label: "Formulier",
|
|
229
227
|
icon: "\u2709\uFE0F",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/FormBlock.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState, type FormEvent } from \"react\";\nimport type { z } from \"zod\";\nimport { FormBlockSchema } from \"../../schema/blocks\";\nimport type { FormDefinition } from \"../../schema/forms\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { useBlockData } from \"../context\";\nimport { submitFormValues } from \"./lib/submitForm\";\nimport { isDemoActive, getDemoForm } from \"../../demo/demoStore\";\nimport { useSiteConfig } from \"../../config\";\nimport { TextInput } from \"@astryxdesign/core/TextInput\";\nimport { TextArea } from \"@astryxdesign/core/TextArea\";\nimport { Selector } from \"@astryxdesign/core/Selector\";\nimport { CheckboxInput } from \"@astryxdesign/core/CheckboxInput\";\nimport { Spinner } from \"@astryxdesign/core/Spinner\";\nimport { Button } from \"@astryxdesign/core/Button\";\nimport { VStack, Card, Heading, Text } from \"../ui\";\n\ntype FormBlk = z.infer<typeof FormBlockSchema>;\n\n/** Voorbeeldformulier voor de demo wanneer er geen specifieke definitie is geregistreerd. */\nconst DEMO_FALLBACK_FORM: FormDefinition = {\n name: \"Contact\",\n submitLabel: \"Versturen\",\n successMessage: \"Bedankt! Dit is een demo — je bericht is niet echt verstuurd.\",\n fields: [\n { name: \"naam\", label: \"Naam\", type: \"text\", required: true },\n { name: \"email\", label: \"E-mail\", type: \"email\", required: true },\n { name: \"bericht\", label: \"Bericht\", type: \"textarea\", required: false, placeholder: \"Waarmee kunnen we je helpen?\" },\n ],\n};\n\nfunction FormRender({ block, siteId, mode }: BlockRenderProps<FormBlk>) {\n const siteMode = useSiteConfig().mode;\n // Server-seed (7.12): de route leest formulierdefinities server-side (getPageBlockData) en\n // geeft ze mee als blockData. Het formulier staat dan al in de SSR-HTML en de per-bezoeker\n // `getDoc` hieronder vervalt. Zonder seed (oudere site, admin-preview) werkt het oude pad.\n const serverForms = useBlockData<Record<string, FormDefinition>>(\"forms\");\n const serverDef = serverForms?.[block.formId] ?? null;\n const [def, setDef] = useState<FormDefinition | null>(serverDef);\n const [unavailable, setUnavailable] = useState(false);\n const [values, setValues] = useState<Record<string, string | boolean>>({});\n const [hp, setHp] = useState(\"\");\n const [status, setStatus] = useState<\"idle\" | \"busy\" | \"ok\" | \"error\">(\"idle\");\n const [error, setError] = useState<string | null>(null);\n\n const demo = isDemoActive();\n // \"Lokaal\" inzenden (geen Firebase-call) bij demo OF wanneer er geen Firebase is maar wél een\n // geregistreerd voorbeeldformulier — zo werkt een statische referentiesite z'n formulier ook.\n const [localOnly, setLocalOnly] = useState(demo);\n\n useEffect(() => {\n if (demo) {\n setDef(getDemoForm(siteId, block.formId) ?? DEMO_FALLBACK_FORM);\n setLocalOnly(true);\n return;\n }\n if (serverDef) return; // definitie kwam al server-side mee\n // Firestore dynamisch: alleen een pagina die écht een formulier toont haalt de SDK binnen.\n void (async () => {\n const [{ getDb }, { doc, getDoc }] = await Promise.all([\n import(\"../../firebase/client\"),\n import(\"firebase/firestore\"),\n ]);\n const db = getDb();\n if (!db) {\n // Geen Firebase: val terug op een geregistreerd formulier, of (op een mode:\"demo\"-site) op\n // het ingebouwde voorbeeld; beide zenden lokaal in. Anders een nette \"niet beschikbaar\".\n const reg = getDemoForm(siteId, block.formId) ?? (siteMode === \"demo\" ? DEMO_FALLBACK_FORM : null);\n if (reg) { setDef(reg); setLocalOnly(true); } else { setUnavailable(true); }\n return;\n }\n try {\n const snap = await getDoc(doc(db, \"sites\", siteId, \"forms\", block.formId));\n if (snap.exists()) setDef(snap.data() as FormDefinition);\n else setUnavailable(true);\n } catch {\n setUnavailable(true);\n }\n })();\n }, [demo, siteId, block.formId, siteMode, serverDef]);\n\n if (unavailable) {\n return (\n <Card padding={4} variant=\"muted\">\n <Text>{block.heading ? `${block.heading}: ` : \"\"}neem contact met ons op — we reageren snel.</Text>\n </Card>\n );\n }\n if (!def) {\n return (\n <div style={{ display: \"grid\", placeItems: \"center\", minHeight: 120 }}>\n <Spinner label=\"Formulier laden…\" />\n </div>\n );\n }\n\n async function submit(e: FormEvent) {\n e.preventDefault();\n if (mode === \"preview\") return;\n if (localOnly) {\n // Lokaal (demo / geen backend): niets versturen, alleen de successtaat tonen.\n setStatus(\"ok\");\n setValues({});\n return;\n }\n setStatus(\"busy\");\n setError(null);\n try {\n await submitFormValues(siteId, block.formId, values, hp);\n // Optioneel doorsturen naar een bedankt-pagina (conversiemeting/advertenties).\n if (def?.successRedirect) {\n window.location.assign(def.successRedirect);\n return;\n }\n setStatus(\"ok\");\n setValues({});\n } catch {\n setStatus(\"error\");\n setError(\"Versturen mislukt. Probeer het later opnieuw.\");\n }\n }\n\n if (status === \"ok\") {\n return (\n // role=\"status\": screenreaders melden de succesboodschap wanneer het formulier verdwijnt.\n <Card padding={4} variant=\"muted\" role=\"status\">\n <Text>{def.successMessage}</Text>\n </Card>\n );\n }\n\n const setVal = (name: string, v: string | boolean) => setValues((prev) => ({ ...prev, [name]: v }));\n\n return (\n <form className=\"xicms-form\" onSubmit={submit}>\n <VStack gap={3}>\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {def.fields.map((f) => {\n const sv = (values[f.name] as string) ?? \"\";\n if (f.type === \"textarea\") {\n return (\n <TextArea key={f.name} label={f.label} isRequired={f.required} placeholder={f.placeholder} rows={4}\n value={sv} onChange={(v) => setVal(f.name, v)} />\n );\n }\n if (f.type === \"select\") {\n return (\n <Selector key={f.name} label={f.label} isRequired={f.required} placeholder=\"Kies…\"\n options={f.options ?? []} value={sv} onChange={(v) => setVal(f.name, (v as string) ?? \"\")} />\n );\n }\n if (f.type === \"checkbox\") {\n return (\n <CheckboxInput key={f.name} label={f.label} isRequired={f.required}\n value={Boolean(values[f.name])} onChange={(checked) => setVal(f.name, checked)} />\n );\n }\n if (f.type === \"radio\") {\n return (\n <fieldset key={f.name} className=\"xicms-form__radios\">\n <legend>{f.label}{f.required && \" *\"}</legend>\n {(f.options ?? []).map((opt) => (\n <label key={opt} className=\"xicms-form__radio\">\n <input\n type=\"radio\"\n name={f.name}\n value={opt}\n required={f.required}\n checked={sv === opt}\n onChange={() => setVal(f.name, opt)}\n />\n {opt}\n </label>\n ))}\n </fieldset>\n );\n }\n if (f.type === \"date\" || f.type === \"number\") {\n // Astryx TextInput kent geen date/number → native input in dezelfde stijl.\n return (\n <label key={f.name} className=\"xicms-form__native\">\n <span className=\"xicms-form__native-label\">{f.label}{f.required && \" *\"}</span>\n <input\n className=\"xicms-input\"\n type={f.type}\n required={f.required}\n placeholder={f.placeholder}\n value={sv}\n onChange={(e) => setVal(f.name, e.target.value)}\n />\n </label>\n );\n }\n return (\n <TextInput key={f.name} label={f.label} type={f.type === \"email\" ? \"email\" : \"text\"}\n isRequired={f.required} placeholder={f.placeholder}\n value={sv} onChange={(v) => setVal(f.name, v)} />\n );\n })}\n {/* Honeypot — verborgen voor mensen, bots vullen 'm vaak wel in. */}\n <input\n type=\"text\"\n tabIndex={-1}\n autoComplete=\"off\"\n value={hp}\n onChange={(e) => setHp(e.target.value)}\n style={{ position: \"absolute\", left: \"-9999px\", width: 1, height: 1 }}\n aria-hidden\n />\n {error && <Text role=\"alert\" style={{ color: \"var(--color-error, #c0392b)\" }}>{error}</Text>}\n <div>\n <Button label={status === \"busy\" ? \"Versturen…\" : def.submitLabel} variant=\"primary\" size=\"lg\" type=\"submit\" isLoading={status === \"busy\"} />\n </div>\n </VStack>\n </form>\n );\n}\n\nexport const formBlock = defineBlock({\n type: \"form\",\n schema: FormBlockSchema,\n Render: FormRender,\n label: \"Formulier\",\n icon: \"✉️\",\n defaults: () => ({ formId: \"\" }),\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/FormBlock.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState, type FormEvent } from \"react\";\nimport type { z } from \"zod\";\nimport type { FormBlockSchema } from \"../../schema/blocks\";\nimport type { FormDefinition } from \"../../schema/forms\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { useBlockData } from \"../context\";\nimport { submitFormValues } from \"./lib/submitForm\";\nimport { isDemoActive, getDemoForm } from \"../../demo/demoStore\";\nimport { useSiteConfig } from \"../../config\";\nimport { TextInput } from \"@astryxdesign/core/TextInput\";\nimport { TextArea } from \"@astryxdesign/core/TextArea\";\nimport { Selector } from \"@astryxdesign/core/Selector\";\nimport { CheckboxInput } from \"@astryxdesign/core/CheckboxInput\";\nimport { Spinner } from \"@astryxdesign/core/Spinner\";\nimport { Button } from \"@astryxdesign/core/Button\";\nimport { VStack, Card, Heading, Text } from \"../ui\";\n\ntype FormBlk = z.infer<typeof FormBlockSchema>;\n\n/** Voorbeeldformulier voor de demo wanneer er geen specifieke definitie is geregistreerd. */\nconst DEMO_FALLBACK_FORM: FormDefinition = {\n name: \"Contact\",\n submitLabel: \"Versturen\",\n successMessage: \"Bedankt! Dit is een demo — je bericht is niet echt verstuurd.\",\n fields: [\n { name: \"naam\", label: \"Naam\", type: \"text\", required: true },\n { name: \"email\", label: \"E-mail\", type: \"email\", required: true },\n { name: \"bericht\", label: \"Bericht\", type: \"textarea\", required: false, placeholder: \"Waarmee kunnen we je helpen?\" },\n ],\n};\n\nfunction FormRender({ block, siteId, mode }: BlockRenderProps<FormBlk>) {\n const siteMode = useSiteConfig().mode;\n // Server-seed (7.12): de route leest formulierdefinities server-side (getPageBlockData) en\n // geeft ze mee als blockData. Het formulier staat dan al in de SSR-HTML en de per-bezoeker\n // `getDoc` hieronder vervalt. Zonder seed (oudere site, admin-preview) werkt het oude pad.\n const serverForms = useBlockData<Record<string, FormDefinition>>(\"forms\");\n const serverDef = serverForms?.[block.formId] ?? null;\n const [def, setDef] = useState<FormDefinition | null>(serverDef);\n const [unavailable, setUnavailable] = useState(false);\n const [values, setValues] = useState<Record<string, string | boolean>>({});\n const [hp, setHp] = useState(\"\");\n const [status, setStatus] = useState<\"idle\" | \"busy\" | \"ok\" | \"error\">(\"idle\");\n const [error, setError] = useState<string | null>(null);\n\n const demo = isDemoActive();\n // \"Lokaal\" inzenden (geen Firebase-call) bij demo OF wanneer er geen Firebase is maar wél een\n // geregistreerd voorbeeldformulier — zo werkt een statische referentiesite z'n formulier ook.\n const [localOnly, setLocalOnly] = useState(demo);\n\n useEffect(() => {\n if (demo) {\n setDef(getDemoForm(siteId, block.formId) ?? DEMO_FALLBACK_FORM);\n setLocalOnly(true);\n return;\n }\n if (serverDef) return; // definitie kwam al server-side mee\n // Firestore dynamisch: alleen een pagina die écht een formulier toont haalt de SDK binnen.\n void (async () => {\n const [{ getDb }, { doc, getDoc }] = await Promise.all([\n import(\"../../firebase/client\"),\n import(\"firebase/firestore\"),\n ]);\n const db = getDb();\n if (!db) {\n // Geen Firebase: val terug op een geregistreerd formulier, of (op een mode:\"demo\"-site) op\n // het ingebouwde voorbeeld; beide zenden lokaal in. Anders een nette \"niet beschikbaar\".\n const reg = getDemoForm(siteId, block.formId) ?? (siteMode === \"demo\" ? DEMO_FALLBACK_FORM : null);\n if (reg) { setDef(reg); setLocalOnly(true); } else { setUnavailable(true); }\n return;\n }\n try {\n const snap = await getDoc(doc(db, \"sites\", siteId, \"forms\", block.formId));\n if (snap.exists()) setDef(snap.data() as FormDefinition);\n else setUnavailable(true);\n } catch {\n setUnavailable(true);\n }\n })();\n }, [demo, siteId, block.formId, siteMode, serverDef]);\n\n if (unavailable) {\n return (\n <Card padding={4} variant=\"muted\">\n <Text>{block.heading ? `${block.heading}: ` : \"\"}neem contact met ons op — we reageren snel.</Text>\n </Card>\n );\n }\n if (!def) {\n return (\n <div style={{ display: \"grid\", placeItems: \"center\", minHeight: 120 }}>\n <Spinner label=\"Formulier laden…\" />\n </div>\n );\n }\n\n async function submit(e: FormEvent) {\n e.preventDefault();\n if (mode === \"preview\") return;\n if (localOnly) {\n // Lokaal (demo / geen backend): niets versturen, alleen de successtaat tonen.\n setStatus(\"ok\");\n setValues({});\n return;\n }\n setStatus(\"busy\");\n setError(null);\n try {\n await submitFormValues(siteId, block.formId, values, hp);\n // Optioneel doorsturen naar een bedankt-pagina (conversiemeting/advertenties).\n if (def?.successRedirect) {\n window.location.assign(def.successRedirect);\n return;\n }\n setStatus(\"ok\");\n setValues({});\n } catch {\n setStatus(\"error\");\n setError(\"Versturen mislukt. Probeer het later opnieuw.\");\n }\n }\n\n if (status === \"ok\") {\n return (\n // role=\"status\": screenreaders melden de succesboodschap wanneer het formulier verdwijnt.\n <Card padding={4} variant=\"muted\" role=\"status\">\n <Text>{def.successMessage}</Text>\n </Card>\n );\n }\n\n const setVal = (name: string, v: string | boolean) => setValues((prev) => ({ ...prev, [name]: v }));\n\n return (\n <form className=\"xicms-form\" onSubmit={submit}>\n <VStack gap={3}>\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {def.fields.map((f) => {\n const sv = (values[f.name] as string) ?? \"\";\n if (f.type === \"textarea\") {\n return (\n <TextArea key={f.name} label={f.label} isRequired={f.required} placeholder={f.placeholder} rows={4}\n value={sv} onChange={(v) => setVal(f.name, v)} />\n );\n }\n if (f.type === \"select\") {\n return (\n <Selector key={f.name} label={f.label} isRequired={f.required} placeholder=\"Kies…\"\n options={f.options ?? []} value={sv} onChange={(v) => setVal(f.name, (v as string) ?? \"\")} />\n );\n }\n if (f.type === \"checkbox\") {\n return (\n <CheckboxInput key={f.name} label={f.label} isRequired={f.required}\n value={Boolean(values[f.name])} onChange={(checked) => setVal(f.name, checked)} />\n );\n }\n if (f.type === \"radio\") {\n return (\n <fieldset key={f.name} className=\"xicms-form__radios\">\n <legend>{f.label}{f.required && \" *\"}</legend>\n {(f.options ?? []).map((opt) => (\n <label key={opt} className=\"xicms-form__radio\">\n <input\n type=\"radio\"\n name={f.name}\n value={opt}\n required={f.required}\n checked={sv === opt}\n onChange={() => setVal(f.name, opt)}\n />\n {opt}\n </label>\n ))}\n </fieldset>\n );\n }\n if (f.type === \"date\" || f.type === \"number\") {\n // Astryx TextInput kent geen date/number → native input in dezelfde stijl.\n return (\n <label key={f.name} className=\"xicms-form__native\">\n <span className=\"xicms-form__native-label\">{f.label}{f.required && \" *\"}</span>\n <input\n className=\"xicms-input\"\n type={f.type}\n required={f.required}\n placeholder={f.placeholder}\n value={sv}\n onChange={(e) => setVal(f.name, e.target.value)}\n />\n </label>\n );\n }\n return (\n <TextInput key={f.name} label={f.label} type={f.type === \"email\" ? \"email\" : \"text\"}\n isRequired={f.required} placeholder={f.placeholder}\n value={sv} onChange={(v) => setVal(f.name, v)} />\n );\n })}\n {/* Honeypot — verborgen voor mensen, bots vullen 'm vaak wel in. */}\n <input\n type=\"text\"\n tabIndex={-1}\n autoComplete=\"off\"\n value={hp}\n onChange={(e) => setHp(e.target.value)}\n style={{ position: \"absolute\", left: \"-9999px\", width: 1, height: 1 }}\n aria-hidden\n />\n {error && <Text role=\"alert\" style={{ color: \"var(--color-error, #c0392b)\" }}>{error}</Text>}\n <div>\n <Button label={status === \"busy\" ? \"Versturen…\" : def.submitLabel} variant=\"primary\" size=\"lg\" type=\"submit\" isLoading={status === \"busy\"} />\n </div>\n </VStack>\n </form>\n );\n}\n\nexport const formBlock = defineBlock({\n type: \"form\",\n Render: FormRender,\n label: \"Formulier\",\n icon: \"✉️\",\n defaults: () => ({ formId: \"\" }),\n});\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { z } from "zod";
|
|
2
|
-
import { GalleryBlockSchema } from "../../schema/blocks.js";
|
|
2
|
+
import type { GalleryBlockSchema } from "../../schema/blocks.js";
|
|
3
3
|
type Gallery = z.infer<typeof GalleryBlockSchema>;
|
|
4
4
|
type Item = Gallery["items"][number];
|
|
5
5
|
/** Per-site instelbaar via `modules.gallery.config` (door de agent of in /beheer). */
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
4
|
import { createPortal } from "react-dom";
|
|
5
|
-
import { GalleryBlockSchema } from "../../schema/blocks.js";
|
|
6
5
|
import { defineBlock } from "../registry.js";
|
|
7
6
|
import { useModuleSettings } from "../../modules/context.js";
|
|
8
7
|
import { Grid } from "../ui/index.js";
|
|
@@ -164,7 +163,6 @@ function GalleryRender({ block }) {
|
|
|
164
163
|
}
|
|
165
164
|
const galleryBlock = defineBlock({
|
|
166
165
|
type: "gallery",
|
|
167
|
-
schema: GalleryBlockSchema,
|
|
168
166
|
Render: GalleryRender,
|
|
169
167
|
label: "Galerij",
|
|
170
168
|
icon: "\u{1F5BC}",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/Gallery.tsx"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport type { z } from \"zod\";\nimport { GalleryBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { useModuleSettings } from \"../../modules/context\";\nimport { Grid } from \"../ui\";\nimport { Img } from \"../../media/ImageContext\";\n\ntype Gallery = z.infer<typeof GalleryBlockSchema>;\ntype Item = Gallery[\"items\"][number];\n\n/** Per-site instelbaar via `modules.gallery.config` (door de agent of in /beheer). */\nexport interface GallerySettings {\n columns?: number;\n lightbox?: boolean;\n}\n\n/**\n * Eigen lightbox-overlay: rendert via een portal naar `document.body` (zo raakt geen enkele\n * ancestor-transform — bv. de scroll-reveal — de full-screen positionering). Sluit op klik náást\n * de foto én op Escape; vorige/volgende met teller; herstelt focus zónder de pagina te laten\n * verspringen (`preventScroll`) en zet de body-scroll vast zolang hij open is.\n */\nexport function LightboxOverlay({\n items,\n index,\n onIndex,\n onClose,\n}: {\n items: Item[];\n index: number;\n onIndex: (i: number) => void;\n onClose: () => void;\n}) {\n const it = items[index];\n const total = items.length;\n const overlayRef = useRef<HTMLDivElement | null>(null);\n const prev = useCallback(() => onIndex((index - 1 + total) % total), [index, total, onIndex]);\n const next = useCallback(() => onIndex((index + 1) % total), [index, total, onIndex]);\n // Swipe (pointer events, werkt voor touch én muis-sleep): horizontaal > 40px = vorige/volgende.\n const swipeStart = useRef<number | null>(null);\n const onPointerDown = (e: React.PointerEvent) => {\n swipeStart.current = e.clientX;\n };\n const onPointerUp = (e: React.PointerEvent) => {\n if (swipeStart.current === null || total < 2) return;\n const dx = e.clientX - swipeStart.current;\n swipeStart.current = null;\n if (dx > 40) prev();\n else if (dx < -40) next();\n };\n\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n else if (e.key === \"ArrowLeft\") prev();\n else if (e.key === \"ArrowRight\") next();\n else if (e.key === \"Tab\") {\n // Focus-trap (aria-modal): Tab cirkelt binnen de dialog i.p.v. naar de pagina eronder.\n const el = overlayRef.current;\n const focusables = el ? [...el.querySelectorAll<HTMLElement>(\"button, a[href]\")] : [];\n if (focusables.length === 0) return;\n const first = focusables[0];\n const last = focusables[focusables.length - 1];\n const active = document.activeElement;\n if (e.shiftKey && (active === first || !el?.contains(active))) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && (active === last || !el?.contains(active))) {\n e.preventDefault();\n first.focus();\n }\n }\n };\n document.addEventListener(\"keydown\", onKey);\n const prevOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\"; // scroll-lock terwijl open\n // Initiële focus in de dialog (dialog-conventie; toetsenbord/screenreader landt niet \"achter\" de overlay).\n overlayRef.current?.querySelector<HTMLElement>(\".xicms-lightbox__close\")?.focus({ preventScroll: true });\n return () => {\n document.removeEventListener(\"keydown\", onKey);\n document.body.style.overflow = prevOverflow;\n };\n }, [onClose, prev, next]);\n\n if (typeof document === \"undefined\") return null;\n return createPortal(\n <div\n ref={overlayRef}\n className=\"xicms-lightbox\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={it.alt || it.caption || \"Foto\"}\n onClick={onClose}\n >\n <button type=\"button\" className=\"xicms-lightbox__close\" aria-label=\"Sluiten\" onClick={onClose}>✕</button>\n {total > 1 && (\n <>\n <button type=\"button\" className=\"xicms-lightbox__nav xicms-lightbox__nav--prev\" aria-label=\"Vorige\" onClick={(e) => { e.stopPropagation(); prev(); }}>‹</button>\n <button type=\"button\" className=\"xicms-lightbox__nav xicms-lightbox__nav--next\" aria-label=\"Volgende\" onClick={(e) => { e.stopPropagation(); next(); }}>›</button>\n <span className=\"xicms-lightbox__counter\">{index + 1} / {total}</span>\n </>\n )}\n {/* Klik op de foto/onderschrift sluit NIET (stopPropagation); klik op de rand wel.\n touchAction pan-y: verticaal scrollen blijft van de browser, horizontaal is swipen. */}\n <figure\n className=\"xicms-lightbox__figure\"\n onClick={(e) => e.stopPropagation()}\n onPointerDown={onPointerDown}\n onPointerUp={onPointerUp}\n style={{ touchAction: \"pan-y\" }}\n >\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img src={it.url} alt={it.alt ?? \"\"} />\n {it.caption && <figcaption>{it.caption}</figcaption>}\n </figure>\n </div>,\n document.body,\n );\n}\n\n/** Galerij — responsive grid met instelbare beeldverhouding en optionele lightbox. */\nfunction GalleryRender({ block }: BlockRenderProps<Gallery>) {\n const settings = useModuleSettings<GallerySettings>(\"gallery\");\n // `_i` = de index in block.items (vóór url-/tagfilters): het inline-edit-pad\n // (`items.<_i>.caption`) moet naar het ORIGINELE array-item wijzen, niet naar de filterpositie.\n const allItems = (block.items ?? []).map((it, _i) => ({ ...it, _i })).filter((i) => i.url);\n const [open, setOpen] = useState(false);\n const [index, setIndex] = useState(0);\n const [activeTag, setActiveTag] = useState<string | null>(null);\n const triggerRef = useRef<HTMLButtonElement | null>(null);\n\n // Filterbalk op item-tags (opt-in): lightbox-navigatie volgt de gefilterde lijst.\n const tags = block.showFilter ? [...new Set(allItems.map((i) => i.tag).filter((t): t is string => Boolean(t)))] : [];\n const items = activeTag ? allItems.filter((i) => i.tag === activeTag) : allItems;\n\n if (allItems.length === 0) return null;\n\n const cols = block.columns && block.columns > 0 ? block.columns : settings.columns ?? 3;\n const lightbox =\n block.lightbox === \"on\" ? true : block.lightbox === \"off\" ? false : settings.lightbox ?? true;\n const ar = block.aspectRatio ?? \"1/1\";\n\n const imgStyle = {\n width: \"100%\",\n aspectRatio: ar === \"auto\" ? undefined : ar.replace(\"/\", \" / \"),\n objectFit: \"cover\" as const,\n borderRadius: \"var(--radius-element)\",\n display: \"block\",\n };\n\n const close = () => {\n setOpen(false);\n triggerRef.current?.focus({ preventScroll: true }); // geen page-jump bij sluiten\n };\n\n return (\n <div className=\"xicms-gallery\">\n {tags.length > 0 && (\n <div className=\"xicms-gallery__filter\" role=\"group\" aria-label=\"Filter op categorie\">\n <button type=\"button\" className=\"xicms-gallery__filterbtn\" aria-pressed={activeTag === null} data-active={activeTag === null ? \"1\" : \"0\"} onClick={() => { setActiveTag(null); setIndex(0); }}>\n Alles\n </button>\n {tags.map((t) => (\n <button key={t} type=\"button\" className=\"xicms-gallery__filterbtn\" aria-pressed={activeTag === t} data-active={activeTag === t ? \"1\" : \"0\"} onClick={() => { setActiveTag(t); setIndex(0); }}>\n {t}\n </button>\n ))}\n </div>\n )}\n <Grid columns={{ minWidth: 160, max: Math.min(cols, 6) }} gap={2}>\n {items.map((i, idx) => (\n <figure key={idx} style={{ margin: 0 }} className=\"xicms-gallery__item\">\n {lightbox ? (\n <button\n type=\"button\"\n onClick={(e) => { triggerRef.current = e.currentTarget; setIndex(idx); setOpen(true); }}\n aria-label={i.alt || i.caption || `Foto ${idx + 1} vergroten`}\n style={{ display: \"block\", width: \"100%\", padding: 0, border: 0, background: \"none\", cursor: \"zoom-in\" }}\n >\n <Img src={i.url as string} alt={i.alt ?? \"\"} width={600} height={600} sizes=\"(max-width: 640px) 50vw, 280px\" style={imgStyle} />\n </button>\n ) : (\n <Img src={i.url as string} alt={i.alt ?? \"\"} width={600} height={600} sizes=\"(max-width: 640px) 50vw, 280px\" style={imgStyle} />\n )}\n {i.caption && (\n <figcaption className=\"xicms-gallery__caption\" data-xicms-edit={`items.${i._i}.caption`} style={{ marginTop: \"0.4rem\", fontSize: \"0.85rem\", color: \"var(--color-text-secondary)\" }}>\n {i.caption}\n </figcaption>\n )}\n </figure>\n ))}\n </Grid>\n\n {lightbox && open && (\n <LightboxOverlay items={items} index={index} onIndex={setIndex} onClose={close} />\n )}\n </div>\n );\n}\n\nexport const galleryBlock = defineBlock({\n type: \"gallery\",\n schema: GalleryBlockSchema,\n Render: GalleryRender,\n label: \"Galerij\",\n icon: \"🖼\",\n defaults: () => ({ items: [], columns: 0, lightbox: \"inherit\", aspectRatio: \"1/1\" }),\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/Gallery.tsx"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport type { z } from \"zod\";\nimport type { GalleryBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { useModuleSettings } from \"../../modules/context\";\nimport { Grid } from \"../ui\";\nimport { Img } from \"../../media/ImageContext\";\n\ntype Gallery = z.infer<typeof GalleryBlockSchema>;\ntype Item = Gallery[\"items\"][number];\n\n/** Per-site instelbaar via `modules.gallery.config` (door de agent of in /beheer). */\nexport interface GallerySettings {\n columns?: number;\n lightbox?: boolean;\n}\n\n/**\n * Eigen lightbox-overlay: rendert via een portal naar `document.body` (zo raakt geen enkele\n * ancestor-transform — bv. de scroll-reveal — de full-screen positionering). Sluit op klik náást\n * de foto én op Escape; vorige/volgende met teller; herstelt focus zónder de pagina te laten\n * verspringen (`preventScroll`) en zet de body-scroll vast zolang hij open is.\n */\nexport function LightboxOverlay({\n items,\n index,\n onIndex,\n onClose,\n}: {\n items: Item[];\n index: number;\n onIndex: (i: number) => void;\n onClose: () => void;\n}) {\n const it = items[index];\n const total = items.length;\n const overlayRef = useRef<HTMLDivElement | null>(null);\n const prev = useCallback(() => onIndex((index - 1 + total) % total), [index, total, onIndex]);\n const next = useCallback(() => onIndex((index + 1) % total), [index, total, onIndex]);\n // Swipe (pointer events, werkt voor touch én muis-sleep): horizontaal > 40px = vorige/volgende.\n const swipeStart = useRef<number | null>(null);\n const onPointerDown = (e: React.PointerEvent) => {\n swipeStart.current = e.clientX;\n };\n const onPointerUp = (e: React.PointerEvent) => {\n if (swipeStart.current === null || total < 2) return;\n const dx = e.clientX - swipeStart.current;\n swipeStart.current = null;\n if (dx > 40) prev();\n else if (dx < -40) next();\n };\n\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n else if (e.key === \"ArrowLeft\") prev();\n else if (e.key === \"ArrowRight\") next();\n else if (e.key === \"Tab\") {\n // Focus-trap (aria-modal): Tab cirkelt binnen de dialog i.p.v. naar de pagina eronder.\n const el = overlayRef.current;\n const focusables = el ? [...el.querySelectorAll<HTMLElement>(\"button, a[href]\")] : [];\n if (focusables.length === 0) return;\n const first = focusables[0];\n const last = focusables[focusables.length - 1];\n const active = document.activeElement;\n if (e.shiftKey && (active === first || !el?.contains(active))) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && (active === last || !el?.contains(active))) {\n e.preventDefault();\n first.focus();\n }\n }\n };\n document.addEventListener(\"keydown\", onKey);\n const prevOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\"; // scroll-lock terwijl open\n // Initiële focus in de dialog (dialog-conventie; toetsenbord/screenreader landt niet \"achter\" de overlay).\n overlayRef.current?.querySelector<HTMLElement>(\".xicms-lightbox__close\")?.focus({ preventScroll: true });\n return () => {\n document.removeEventListener(\"keydown\", onKey);\n document.body.style.overflow = prevOverflow;\n };\n }, [onClose, prev, next]);\n\n if (typeof document === \"undefined\") return null;\n return createPortal(\n <div\n ref={overlayRef}\n className=\"xicms-lightbox\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={it.alt || it.caption || \"Foto\"}\n onClick={onClose}\n >\n <button type=\"button\" className=\"xicms-lightbox__close\" aria-label=\"Sluiten\" onClick={onClose}>✕</button>\n {total > 1 && (\n <>\n <button type=\"button\" className=\"xicms-lightbox__nav xicms-lightbox__nav--prev\" aria-label=\"Vorige\" onClick={(e) => { e.stopPropagation(); prev(); }}>‹</button>\n <button type=\"button\" className=\"xicms-lightbox__nav xicms-lightbox__nav--next\" aria-label=\"Volgende\" onClick={(e) => { e.stopPropagation(); next(); }}>›</button>\n <span className=\"xicms-lightbox__counter\">{index + 1} / {total}</span>\n </>\n )}\n {/* Klik op de foto/onderschrift sluit NIET (stopPropagation); klik op de rand wel.\n touchAction pan-y: verticaal scrollen blijft van de browser, horizontaal is swipen. */}\n <figure\n className=\"xicms-lightbox__figure\"\n onClick={(e) => e.stopPropagation()}\n onPointerDown={onPointerDown}\n onPointerUp={onPointerUp}\n style={{ touchAction: \"pan-y\" }}\n >\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img src={it.url} alt={it.alt ?? \"\"} />\n {it.caption && <figcaption>{it.caption}</figcaption>}\n </figure>\n </div>,\n document.body,\n );\n}\n\n/** Galerij — responsive grid met instelbare beeldverhouding en optionele lightbox. */\nfunction GalleryRender({ block }: BlockRenderProps<Gallery>) {\n const settings = useModuleSettings<GallerySettings>(\"gallery\");\n // `_i` = de index in block.items (vóór url-/tagfilters): het inline-edit-pad\n // (`items.<_i>.caption`) moet naar het ORIGINELE array-item wijzen, niet naar de filterpositie.\n const allItems = (block.items ?? []).map((it, _i) => ({ ...it, _i })).filter((i) => i.url);\n const [open, setOpen] = useState(false);\n const [index, setIndex] = useState(0);\n const [activeTag, setActiveTag] = useState<string | null>(null);\n const triggerRef = useRef<HTMLButtonElement | null>(null);\n\n // Filterbalk op item-tags (opt-in): lightbox-navigatie volgt de gefilterde lijst.\n const tags = block.showFilter ? [...new Set(allItems.map((i) => i.tag).filter((t): t is string => Boolean(t)))] : [];\n const items = activeTag ? allItems.filter((i) => i.tag === activeTag) : allItems;\n\n if (allItems.length === 0) return null;\n\n const cols = block.columns && block.columns > 0 ? block.columns : settings.columns ?? 3;\n const lightbox =\n block.lightbox === \"on\" ? true : block.lightbox === \"off\" ? false : settings.lightbox ?? true;\n const ar = block.aspectRatio ?? \"1/1\";\n\n const imgStyle = {\n width: \"100%\",\n aspectRatio: ar === \"auto\" ? undefined : ar.replace(\"/\", \" / \"),\n objectFit: \"cover\" as const,\n borderRadius: \"var(--radius-element)\",\n display: \"block\",\n };\n\n const close = () => {\n setOpen(false);\n triggerRef.current?.focus({ preventScroll: true }); // geen page-jump bij sluiten\n };\n\n return (\n <div className=\"xicms-gallery\">\n {tags.length > 0 && (\n <div className=\"xicms-gallery__filter\" role=\"group\" aria-label=\"Filter op categorie\">\n <button type=\"button\" className=\"xicms-gallery__filterbtn\" aria-pressed={activeTag === null} data-active={activeTag === null ? \"1\" : \"0\"} onClick={() => { setActiveTag(null); setIndex(0); }}>\n Alles\n </button>\n {tags.map((t) => (\n <button key={t} type=\"button\" className=\"xicms-gallery__filterbtn\" aria-pressed={activeTag === t} data-active={activeTag === t ? \"1\" : \"0\"} onClick={() => { setActiveTag(t); setIndex(0); }}>\n {t}\n </button>\n ))}\n </div>\n )}\n <Grid columns={{ minWidth: 160, max: Math.min(cols, 6) }} gap={2}>\n {items.map((i, idx) => (\n <figure key={idx} style={{ margin: 0 }} className=\"xicms-gallery__item\">\n {lightbox ? (\n <button\n type=\"button\"\n onClick={(e) => { triggerRef.current = e.currentTarget; setIndex(idx); setOpen(true); }}\n aria-label={i.alt || i.caption || `Foto ${idx + 1} vergroten`}\n style={{ display: \"block\", width: \"100%\", padding: 0, border: 0, background: \"none\", cursor: \"zoom-in\" }}\n >\n <Img src={i.url as string} alt={i.alt ?? \"\"} width={600} height={600} sizes=\"(max-width: 640px) 50vw, 280px\" style={imgStyle} />\n </button>\n ) : (\n <Img src={i.url as string} alt={i.alt ?? \"\"} width={600} height={600} sizes=\"(max-width: 640px) 50vw, 280px\" style={imgStyle} />\n )}\n {i.caption && (\n <figcaption className=\"xicms-gallery__caption\" data-xicms-edit={`items.${i._i}.caption`} style={{ marginTop: \"0.4rem\", fontSize: \"0.85rem\", color: \"var(--color-text-secondary)\" }}>\n {i.caption}\n </figcaption>\n )}\n </figure>\n ))}\n </Grid>\n\n {lightbox && open && (\n <LightboxOverlay items={items} index={index} onIndex={setIndex} onClose={close} />\n )}\n </div>\n );\n}\n\nexport const galleryBlock = defineBlock({\n type: \"gallery\",\n Render: GalleryRender,\n label: \"Galerij\",\n icon: \"🖼\",\n defaults: () => ({ items: [], columns: 0, lightbox: \"inherit\", aspectRatio: \"1/1\" }),\n});\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useRef, useState } from "react";
|
|
4
|
-
import { HeroBlockSchema } from "../../schema/blocks.js";
|
|
5
4
|
import { defineBlock } from "../registry.js";
|
|
6
5
|
import { Btn, Heading, Text } from "../ui/index.js";
|
|
7
6
|
import { Img } from "../../media/ImageContext.js";
|
|
@@ -74,7 +73,6 @@ function HeroRender({ block }) {
|
|
|
74
73
|
}
|
|
75
74
|
const heroBlock = defineBlock({
|
|
76
75
|
type: "hero",
|
|
77
|
-
schema: HeroBlockSchema,
|
|
78
76
|
Render: HeroRender,
|
|
79
77
|
label: "Hero",
|
|
80
78
|
icon: "\u2728",
|