@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 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/Hero.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState, type CSSProperties } from \"react\";\nimport type { z } from \"zod\";\nimport { HeroBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Btn, Heading, Text } from \"../ui\";\nimport { Img } from \"../../media/ImageContext\";\n\ntype Hero = z.infer<typeof HeroBlockSchema>;\n\nfunction HeroRender({ block }: BlockRenderProps<Hero>) {\n // Achtergrondvideo WCAG-proof (2.2.2): geen autoPlay-attribuut (hydration-veilig) — playback\n // start in een effect, en alléén zonder reduced-motion-voorkeur. Altijd een pauzeknop, en\n // imageUrl dient als poster (direct beeld i.p.v. zwart vlak → betere LCP).\n const videoRef = useRef<HTMLVideoElement>(null);\n const [playing, setPlaying] = useState(false);\n useEffect(() => {\n const v = videoRef.current;\n if (!v) return;\n if (window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches) return;\n v.play().then(() => setPlaying(true)).catch(() => undefined);\n }, [block.videoUrl]);\n const toggleVideo = () => {\n const v = videoRef.current;\n if (!v) return;\n if (v.paused) v.play().then(() => setPlaying(true)).catch(() => undefined);\n else {\n v.pause();\n setPlaying(false);\n }\n };\n const overlay =\n block.overlayOpacity > 0\n ? { background: block.overlayColor ?? \"#000000\", opacity: block.overlayOpacity / 100 }\n : null;\n // Beeld/video als dekkende achtergrond (niet bij height \"auto\"): dan moet de tekst licht zijn.\n const coverMedia = Boolean(block.videoUrl || block.imageUrl) && block.minHeight !== \"auto\";\n const light: CSSProperties | undefined = coverMedia\n ? { color: \"#ffffff\", textShadow: \"0 2px 14px rgba(0,0,0,.45)\" }\n : undefined;\n const buttons = block.buttons ?? [];\n\n return (\n <section\n className=\"xicms-hero\"\n data-align={block.align}\n data-valign={block.vAlign}\n data-height={block.minHeight}\n data-media={coverMedia ? \"cover\" : undefined}\n >\n {block.videoUrl ? (\n <video\n ref={videoRef}\n className=\"xicms-hero__media\"\n src={block.videoUrl}\n poster={block.imageUrl || undefined}\n muted\n loop\n playsInline\n preload=\"metadata\"\n aria-hidden=\"true\"\n />\n ) : block.imageUrl ? (\n <Img src={block.imageUrl} alt=\"\" className=\"xicms-hero__media\" width={1600} height={900} sizes=\"100vw\" priority editField=\"imageUrl\" editFill />\n ) : null}\n {overlay && <div className=\"xicms-hero__overlay\" style={overlay} aria-hidden=\"true\" />}\n {/* Gradient (donker onderaan → transparant): leesbare tekst zonder de hele foto te dimmen. */}\n {block.overlayGradient && coverMedia && <div className=\"xicms-hero__gradient\" aria-hidden=\"true\" />}\n <div className=\"xicms-hero__inner\">\n {block.eyebrow && (\n <Text type=\"label\" className=\"xicms-hero__eyebrow\" style={light} data-xicms-edit=\"eyebrow\">\n {block.eyebrow}\n </Text>\n )}\n {block.title && (\n <Heading level={block.headingLevel === \"2\" ? 2 : 1} type=\"display-1\" className=\"xicms-hero__title\" style={light} data-xicms-edit=\"title\">\n {block.title}\n </Heading>\n )}\n {block.subtitle && (\n <Text type=\"large\" className=\"xicms-hero__sub\" style={light} data-xicms-edit=\"subtitle\">\n {block.subtitle}\n </Text>\n )}\n {buttons.length > 0 && (\n <div className=\"xicms-hero__buttons\">\n {/* Via Astryx Btn (zelfde systeem als Cta/Pricing/…): volgt de site-theming en de\n outline→ghost-variantmapping — geen tweede, handmatig te onderhouden knop-CSS. */}\n {buttons.map((b, i) => ({ ...b, _i: i })).filter((b) => b.label).map((b) => (\n <Btn key={b._i} label={b.label} href={b.href} variant={b.variant} size=\"lg\" editField={`buttons.${b._i}.label`} />\n ))}\n </div>\n )}\n </div>\n {block.videoUrl && (\n <button\n type=\"button\"\n className=\"xicms-hero__videotoggle\"\n onClick={toggleVideo}\n aria-label={playing ? \"Achtergrondvideo pauzeren\" : \"Achtergrondvideo afspelen\"}\n >\n {playing ? \"⏸\" : \"▶\"}\n </button>\n )}\n </section>\n );\n}\n\nexport const heroBlock = defineBlock({\n type: \"hero\",\n
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/Hero.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState, type CSSProperties } from \"react\";\nimport type { z } from \"zod\";\nimport type { HeroBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Btn, Heading, Text } from \"../ui\";\nimport { Img } from \"../../media/ImageContext\";\n\ntype Hero = z.infer<typeof HeroBlockSchema>;\n\nfunction HeroRender({ block }: BlockRenderProps<Hero>) {\n // Achtergrondvideo WCAG-proof (2.2.2): geen autoPlay-attribuut (hydration-veilig) — playback\n // start in een effect, en alléén zonder reduced-motion-voorkeur. Altijd een pauzeknop, en\n // imageUrl dient als poster (direct beeld i.p.v. zwart vlak → betere LCP).\n const videoRef = useRef<HTMLVideoElement>(null);\n const [playing, setPlaying] = useState(false);\n useEffect(() => {\n const v = videoRef.current;\n if (!v) return;\n if (window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches) return;\n v.play().then(() => setPlaying(true)).catch(() => undefined);\n }, [block.videoUrl]);\n const toggleVideo = () => {\n const v = videoRef.current;\n if (!v) return;\n if (v.paused) v.play().then(() => setPlaying(true)).catch(() => undefined);\n else {\n v.pause();\n setPlaying(false);\n }\n };\n const overlay =\n block.overlayOpacity > 0\n ? { background: block.overlayColor ?? \"#000000\", opacity: block.overlayOpacity / 100 }\n : null;\n // Beeld/video als dekkende achtergrond (niet bij height \"auto\"): dan moet de tekst licht zijn.\n const coverMedia = Boolean(block.videoUrl || block.imageUrl) && block.minHeight !== \"auto\";\n const light: CSSProperties | undefined = coverMedia\n ? { color: \"#ffffff\", textShadow: \"0 2px 14px rgba(0,0,0,.45)\" }\n : undefined;\n const buttons = block.buttons ?? [];\n\n return (\n <section\n className=\"xicms-hero\"\n data-align={block.align}\n data-valign={block.vAlign}\n data-height={block.minHeight}\n data-media={coverMedia ? \"cover\" : undefined}\n >\n {block.videoUrl ? (\n <video\n ref={videoRef}\n className=\"xicms-hero__media\"\n src={block.videoUrl}\n poster={block.imageUrl || undefined}\n muted\n loop\n playsInline\n preload=\"metadata\"\n aria-hidden=\"true\"\n />\n ) : block.imageUrl ? (\n <Img src={block.imageUrl} alt=\"\" className=\"xicms-hero__media\" width={1600} height={900} sizes=\"100vw\" priority editField=\"imageUrl\" editFill />\n ) : null}\n {overlay && <div className=\"xicms-hero__overlay\" style={overlay} aria-hidden=\"true\" />}\n {/* Gradient (donker onderaan → transparant): leesbare tekst zonder de hele foto te dimmen. */}\n {block.overlayGradient && coverMedia && <div className=\"xicms-hero__gradient\" aria-hidden=\"true\" />}\n <div className=\"xicms-hero__inner\">\n {block.eyebrow && (\n <Text type=\"label\" className=\"xicms-hero__eyebrow\" style={light} data-xicms-edit=\"eyebrow\">\n {block.eyebrow}\n </Text>\n )}\n {block.title && (\n <Heading level={block.headingLevel === \"2\" ? 2 : 1} type=\"display-1\" className=\"xicms-hero__title\" style={light} data-xicms-edit=\"title\">\n {block.title}\n </Heading>\n )}\n {block.subtitle && (\n <Text type=\"large\" className=\"xicms-hero__sub\" style={light} data-xicms-edit=\"subtitle\">\n {block.subtitle}\n </Text>\n )}\n {buttons.length > 0 && (\n <div className=\"xicms-hero__buttons\">\n {/* Via Astryx Btn (zelfde systeem als Cta/Pricing/…): volgt de site-theming en de\n outline→ghost-variantmapping — geen tweede, handmatig te onderhouden knop-CSS. */}\n {buttons.map((b, i) => ({ ...b, _i: i })).filter((b) => b.label).map((b) => (\n <Btn key={b._i} label={b.label} href={b.href} variant={b.variant} size=\"lg\" editField={`buttons.${b._i}.label`} />\n ))}\n </div>\n )}\n </div>\n {block.videoUrl && (\n <button\n type=\"button\"\n className=\"xicms-hero__videotoggle\"\n onClick={toggleVideo}\n aria-label={playing ? \"Achtergrondvideo pauzeren\" : \"Achtergrondvideo afspelen\"}\n >\n {playing ? \"⏸\" : \"▶\"}\n </button>\n )}\n </section>\n );\n}\n\nexport const heroBlock = defineBlock({\n type: \"hero\",\n Render: HeroRender,\n label: \"Hero\",\n icon: \"✨\",\n defaults: () => ({ title: \"Welkom\", subtitle: \"\", align: \"left\", vAlign: \"center\", minHeight: \"auto\", overlayOpacity: 0, buttons: [] }),\n});\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { ImageBlockSchema } from "../../schema/blocks.js";
|
|
4
3
|
import { defineBlock } from "../registry.js";
|
|
5
4
|
import { Img } from "../../media/ImageContext.js";
|
|
6
5
|
function ImageRender({ block }) {
|
|
@@ -23,7 +22,6 @@ function ImageRender({ block }) {
|
|
|
23
22
|
}
|
|
24
23
|
const imageBlock = defineBlock({
|
|
25
24
|
type: "image",
|
|
26
|
-
schema: ImageBlockSchema,
|
|
27
25
|
Render: ImageRender,
|
|
28
26
|
label: "Afbeelding",
|
|
29
27
|
icon: "\u{1F5BC}\uFE0F",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/ImageBlock.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport { ImageBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Img } from \"../../media/ImageContext\";\n\ntype ImageBlk = z.infer<typeof ImageBlockSchema>;\n\nfunction ImageRender({ block }: BlockRenderProps<ImageBlk>) {\n if (!block.url) return null;\n // Via het geïnjecteerde Img-component (next/image op consumer-sites → optimalisatie/lazy-load)\n // mét editField: klik-om-te-vervangen in de visuele editor. Dit blok — welks hele doel beeld is —\n // was gek genoeg de enige beeld-route zónder beide (rendde om Img heen). De aspect-ratio-CSS\n // (.xicms-image[data-ratio] img) blijft gewoon werken.\n const img = (\n <Img\n src={block.url}\n alt={block.alt ?? \"\"}\n width={1600}\n height={1067}\n sizes=\"(max-width: 900px) 100vw, 900px\"\n editField=\"url\"\n />\n );\n return (\n <figure className=\"xicms-image\" data-align={block.align} data-ratio={block.aspectRatio}>\n {block.href ? <a href={block.href}>{img}</a> : img}\n {block.caption && <figcaption data-xicms-edit=\"caption\">{block.caption}</figcaption>}\n </figure>\n );\n}\n\nexport const imageBlock = defineBlock({\n type: \"image\",\n
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/ImageBlock.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport type { ImageBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Img } from \"../../media/ImageContext\";\n\ntype ImageBlk = z.infer<typeof ImageBlockSchema>;\n\nfunction ImageRender({ block }: BlockRenderProps<ImageBlk>) {\n if (!block.url) return null;\n // Via het geïnjecteerde Img-component (next/image op consumer-sites → optimalisatie/lazy-load)\n // mét editField: klik-om-te-vervangen in de visuele editor. Dit blok — welks hele doel beeld is —\n // was gek genoeg de enige beeld-route zónder beide (rendde om Img heen). De aspect-ratio-CSS\n // (.xicms-image[data-ratio] img) blijft gewoon werken.\n const img = (\n <Img\n src={block.url}\n alt={block.alt ?? \"\"}\n width={1600}\n height={1067}\n sizes=\"(max-width: 900px) 100vw, 900px\"\n editField=\"url\"\n />\n );\n return (\n <figure className=\"xicms-image\" data-align={block.align} data-ratio={block.aspectRatio}>\n {block.href ? <a href={block.href}>{img}</a> : img}\n {block.caption && <figcaption data-xicms-edit=\"caption\">{block.caption}</figcaption>}\n </figure>\n );\n}\n\nexport const imageBlock = defineBlock({\n type: \"image\",\n Render: ImageRender,\n label: \"Afbeelding\",\n icon: \"🖼️\",\n defaults: () => ({ alt: \"\", align: \"center\", aspectRatio: \"auto\" }),\n});\n"],"mappings":";AAgBI,cAUA,YAVA;AAZJ,SAAS,mBAA0C;AACnD,SAAS,WAAW;AAIpB,SAAS,YAAY,EAAE,MAAM,GAA+B;AAC1D,MAAI,CAAC,MAAM,IAAK,QAAO;AAKvB,QAAM,MACJ;AAAA,IAAC;AAAA;AAAA,MACC,KAAK,MAAM;AAAA,MACX,KAAK,MAAM,OAAO;AAAA,MAClB,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,OAAM;AAAA,MACN,WAAU;AAAA;AAAA,EACZ;AAEF,SACE,qBAAC,YAAO,WAAU,eAAc,cAAY,MAAM,OAAO,cAAY,MAAM,aACxE;AAAA,UAAM,OAAO,oBAAC,OAAE,MAAM,MAAM,MAAO,eAAI,IAAO;AAAA,IAC9C,MAAM,WAAW,oBAAC,gBAAW,mBAAgB,WAAW,gBAAM,SAAQ;AAAA,KACzE;AAEJ;AAEO,MAAM,aAAa,YAAY;AAAA,EACpC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,KAAK,IAAI,OAAO,UAAU,aAAa,OAAO;AACnE,CAAC;","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { z } from "zod";
|
|
2
|
-
import { JobsBlockSchema } from "../../schema/blocks.js";
|
|
2
|
+
import type { JobsBlockSchema } from "../../schema/blocks.js";
|
|
3
3
|
type Jobs = z.infer<typeof JobsBlockSchema>;
|
|
4
4
|
type JobItem = Jobs["items"][number];
|
|
5
5
|
/** Vrije-tekst dienstverband → schema.org-enum (Google keurt vrije tekst af). */
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { JobsBlockSchema } from "../../schema/blocks.js";
|
|
4
3
|
import { defineBlock } from "../registry.js";
|
|
5
4
|
import { useSiteConfig } from "../../config.js";
|
|
6
5
|
import { VStack, Card, Heading, Text, Btn } from "../ui/index.js";
|
|
@@ -68,7 +67,6 @@ function JobsRender({ block }) {
|
|
|
68
67
|
const jobsBlock = defineBlock({
|
|
69
68
|
type: "jobs",
|
|
70
69
|
fields: { "items.datePosted": { type: "date" } },
|
|
71
|
-
schema: JobsBlockSchema,
|
|
72
70
|
Render: JobsRender,
|
|
73
71
|
label: "Vacatures",
|
|
74
72
|
icon: "\u{1F9D1}\u200D\u{1F527}",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/Jobs.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport { JobsBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { useSiteConfig } from \"../../config\";\nimport { VStack, Card, Heading, Text, Btn } from \"../ui\";\nimport { jsonLdHtml } from \"./jsonld\";\n\ntype Jobs = z.infer<typeof JobsBlockSchema>;\ntype JobItem = Jobs[\"items\"][number];\n\n/** Vrije-tekst dienstverband → schema.org-enum (Google keurt vrije tekst af). */\nexport function employmentTypeEnum(value?: string): string | null {\n const v = (value ?? \"\").toLowerCase();\n if (!v) return null;\n if (v.includes(\"full\") || v.includes(\"voltijd\")) return \"FULL_TIME\";\n if (v.includes(\"part\") || v.includes(\"deeltijd\") || /\\d+\\s*[–-]?\\s*\\d*\\s*uur/.test(v)) return \"PART_TIME\";\n if (v.includes(\"bbl\") || v.includes(\"leerling\") || v.includes(\"stage\") || v.includes(\"intern\")) return \"INTERN\";\n if (v.includes(\"tijdelijk\") || v.includes(\"temp\")) return \"TEMPORARY\";\n if (v.includes(\"zzp\") || v.includes(\"freelance\") || v.includes(\"contract\")) return \"CONTRACTOR\";\n return null;\n}\n\n/**\n * JobPosting-JSON-LD per vacature (Google Jobs). Puur en getest. Alleen vacatures met de door\n * Google vereiste kernvelden (titel + plaatsingsdatum + locatie + omschrijving) komen in de JSON-LD\n * — incomplete items renderen wél op de pagina maar leveren geen Search-Console-errors op. Salaris\n * (\"€ 2.800 – € 3.400 p/m\") is geen geldige MonetaryAmount en wordt bewust weggelaten.\n */\nexport function jobsJsonLd(items: JobItem[], orgName: string): Array<Record<string, unknown>> {\n return items\n .filter((j) => j.title && j.datePosted && j.location && j.description)\n .map((j) => {\n const employment = employmentTypeEnum(j.employmentType);\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"JobPosting\",\n title: j.title,\n hiringOrganization: { \"@type\": \"Organization\", name: orgName },\n ...(j.description ? { description: j.description } : {}),\n datePosted: j.datePosted,\n ...(employment ? { employmentType: employment } : {}),\n jobLocation: { \"@type\": \"Place\", address: { \"@type\": \"PostalAddress\", addressLocality: j.location } },\n ...(j.applyUrl ? { url: j.applyUrl } : {}),\n };\n });\n}\n\n/** Vacature-overzicht met JobPosting-JSON-LD — vindbaar via Google Jobs. */\nfunction JobsRender({ block }: BlockRenderProps<Jobs>) {\n const { name } = useSiteConfig();\n // _i = index in de OORSPRONKELIJKE array — inline-edit-paden (items.N.title) moeten daarheen wijzen.\n const items = (block.items ?? []).map((it, i) => ({ ...it, _i: i })).filter((j) => j.title);\n if (items.length === 0 && !block.heading) return null;\n return (\n <VStack gap={4} as=\"section\" className=\"xicms-jobs\">\n {(block.heading || block.intro) && (\n <VStack gap={1}>\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {block.intro && <Text color=\"secondary\" data-xicms-edit=\"intro\">{block.intro}</Text>}\n </VStack>\n )}\n {items.length === 0 && <Text color=\"secondary\">Er zijn op dit moment geen openstaande vacatures.</Text>}\n <VStack gap={3}>\n {items.map((j) => {\n const i = j._i;\n return (\n <Card key={i} padding={4} className=\"xicms-jobs__item\">\n <VStack gap={1.5}>\n <div className=\"xicms-jobs__head\">\n <Heading level={3} data-xicms-edit={`items.${i}.title`}>{j.title}</Heading>\n <span className=\"xicms-jobs__meta\">\n {([\"employmentType\", \"location\", \"salary\"] as const)\n .map((veld) => ({ veld, waarde: j[veld] }))\n .filter((f) => f.waarde)\n .map((f, n) => (\n <span key={f.veld}>\n {n > 0 && \" · \"}\n <span data-xicms-edit={`items.${i}.${f.veld}`}>{f.waarde}</span>\n </span>\n ))}\n </span>\n </div>\n {j.description && <Text color=\"secondary\" data-xicms-edit={`items.${i}.description`}>{j.description}</Text>}\n {j.applyUrl && (\n <div>\n <Btn label=\"Solliciteer\" href={j.applyUrl} variant=\"primary\" />\n </div>\n )}\n </VStack>\n </Card>\n );\n })}\n </VStack>\n {block.jsonLd &&\n jobsJsonLd(items, name).map((ld, i) => (\n <script\n key={i}\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: jsonLdHtml(ld) }}\n />\n ))}\n </VStack>\n );\n}\n\nexport const jobsBlock = defineBlock({\n type: \"jobs\",\n fields: { \"items.datePosted\": { type: \"date\" } },\n
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/Jobs.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport type { JobsBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { useSiteConfig } from \"../../config\";\nimport { VStack, Card, Heading, Text, Btn } from \"../ui\";\nimport { jsonLdHtml } from \"./jsonld\";\n\ntype Jobs = z.infer<typeof JobsBlockSchema>;\ntype JobItem = Jobs[\"items\"][number];\n\n/** Vrije-tekst dienstverband → schema.org-enum (Google keurt vrije tekst af). */\nexport function employmentTypeEnum(value?: string): string | null {\n const v = (value ?? \"\").toLowerCase();\n if (!v) return null;\n if (v.includes(\"full\") || v.includes(\"voltijd\")) return \"FULL_TIME\";\n if (v.includes(\"part\") || v.includes(\"deeltijd\") || /\\d+\\s*[–-]?\\s*\\d*\\s*uur/.test(v)) return \"PART_TIME\";\n if (v.includes(\"bbl\") || v.includes(\"leerling\") || v.includes(\"stage\") || v.includes(\"intern\")) return \"INTERN\";\n if (v.includes(\"tijdelijk\") || v.includes(\"temp\")) return \"TEMPORARY\";\n if (v.includes(\"zzp\") || v.includes(\"freelance\") || v.includes(\"contract\")) return \"CONTRACTOR\";\n return null;\n}\n\n/**\n * JobPosting-JSON-LD per vacature (Google Jobs). Puur en getest. Alleen vacatures met de door\n * Google vereiste kernvelden (titel + plaatsingsdatum + locatie + omschrijving) komen in de JSON-LD\n * — incomplete items renderen wél op de pagina maar leveren geen Search-Console-errors op. Salaris\n * (\"€ 2.800 – € 3.400 p/m\") is geen geldige MonetaryAmount en wordt bewust weggelaten.\n */\nexport function jobsJsonLd(items: JobItem[], orgName: string): Array<Record<string, unknown>> {\n return items\n .filter((j) => j.title && j.datePosted && j.location && j.description)\n .map((j) => {\n const employment = employmentTypeEnum(j.employmentType);\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"JobPosting\",\n title: j.title,\n hiringOrganization: { \"@type\": \"Organization\", name: orgName },\n ...(j.description ? { description: j.description } : {}),\n datePosted: j.datePosted,\n ...(employment ? { employmentType: employment } : {}),\n jobLocation: { \"@type\": \"Place\", address: { \"@type\": \"PostalAddress\", addressLocality: j.location } },\n ...(j.applyUrl ? { url: j.applyUrl } : {}),\n };\n });\n}\n\n/** Vacature-overzicht met JobPosting-JSON-LD — vindbaar via Google Jobs. */\nfunction JobsRender({ block }: BlockRenderProps<Jobs>) {\n const { name } = useSiteConfig();\n // _i = index in de OORSPRONKELIJKE array — inline-edit-paden (items.N.title) moeten daarheen wijzen.\n const items = (block.items ?? []).map((it, i) => ({ ...it, _i: i })).filter((j) => j.title);\n if (items.length === 0 && !block.heading) return null;\n return (\n <VStack gap={4} as=\"section\" className=\"xicms-jobs\">\n {(block.heading || block.intro) && (\n <VStack gap={1}>\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {block.intro && <Text color=\"secondary\" data-xicms-edit=\"intro\">{block.intro}</Text>}\n </VStack>\n )}\n {items.length === 0 && <Text color=\"secondary\">Er zijn op dit moment geen openstaande vacatures.</Text>}\n <VStack gap={3}>\n {items.map((j) => {\n const i = j._i;\n return (\n <Card key={i} padding={4} className=\"xicms-jobs__item\">\n <VStack gap={1.5}>\n <div className=\"xicms-jobs__head\">\n <Heading level={3} data-xicms-edit={`items.${i}.title`}>{j.title}</Heading>\n <span className=\"xicms-jobs__meta\">\n {([\"employmentType\", \"location\", \"salary\"] as const)\n .map((veld) => ({ veld, waarde: j[veld] }))\n .filter((f) => f.waarde)\n .map((f, n) => (\n <span key={f.veld}>\n {n > 0 && \" · \"}\n <span data-xicms-edit={`items.${i}.${f.veld}`}>{f.waarde}</span>\n </span>\n ))}\n </span>\n </div>\n {j.description && <Text color=\"secondary\" data-xicms-edit={`items.${i}.description`}>{j.description}</Text>}\n {j.applyUrl && (\n <div>\n <Btn label=\"Solliciteer\" href={j.applyUrl} variant=\"primary\" />\n </div>\n )}\n </VStack>\n </Card>\n );\n })}\n </VStack>\n {block.jsonLd &&\n jobsJsonLd(items, name).map((ld, i) => (\n <script\n key={i}\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: jsonLdHtml(ld) }}\n />\n ))}\n </VStack>\n );\n}\n\nexport const jobsBlock = defineBlock({\n type: \"jobs\",\n fields: { \"items.datePosted\": { type: \"date\" } },\n Render: JobsRender,\n label: \"Vacatures\",\n icon: \"🧑🔧\",\n defaults: () => ({\n heading: \"Vacatures\",\n items: [{ title: \"Vacaturetitel\", employmentType: \"Fulltime\", location: \"\" }],\n jsonLd: true,\n }),\n});\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
|
-
import { WithSidebarBlockSchema, TabsBlockSchema } from "../../schema/blocks.js";
|
|
5
4
|
import { defineBlock } from "../registry.js";
|
|
6
5
|
import { BlockRenderer } from "../BlockRenderer.js";
|
|
7
6
|
import { useRegistry } from "../context.js";
|
|
@@ -28,7 +27,6 @@ function WithSidebarRender({ block, siteId, mode }) {
|
|
|
28
27
|
}
|
|
29
28
|
const withSidebarBlock = defineBlock({
|
|
30
29
|
type: "withSidebar",
|
|
31
|
-
schema: WithSidebarBlockSchema,
|
|
32
30
|
Render: WithSidebarRender,
|
|
33
31
|
label: "Met zijbalk",
|
|
34
32
|
icon: "\u25A4",
|
|
@@ -48,7 +46,6 @@ function TabsRender({ block, siteId, mode }) {
|
|
|
48
46
|
}
|
|
49
47
|
const tabsBlock = defineBlock({
|
|
50
48
|
type: "tabs",
|
|
51
|
-
schema: TabsBlockSchema,
|
|
52
49
|
Render: TabsRender,
|
|
53
50
|
label: "Tabbladen",
|
|
54
51
|
icon: "\u{1F5C2}\uFE0F",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/Layout.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport type { z } from \"zod\";\nimport { WithSidebarBlockSchema, TabsBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { BlockRenderer } from \"../BlockRenderer\";\nimport { useRegistry } from \"../context\";\nimport { TabList, Tab } from \"@astryxdesign/core/TabList\";\n\n/* ── Met zijbalk ───────────────────────────────────────────────────────────\n * Brede hoofdkolom + smalle (sticky) zijbalk. De zijbalk is dé plek voor de compacte\n * widget-varianten (\"laatste nieuws\", \"komende agenda\", contact, …). Beide kanten zijn\n * gewone block-arrays, dus alles wat op een pagina kan, kan ook hier. */\ntype WithSidebar = z.infer<typeof WithSidebarBlockSchema>;\n\nfunction WithSidebarRender({ block, siteId, mode }: BlockRenderProps<WithSidebar>) {\n const registry = useRegistry();\n const main = (\n <div className=\"xicms-sidebar__main\">\n <BlockRenderer blocks={block.main} registry={registry} siteId={siteId} mode={mode} />\n </div>\n );\n const aside = (\n <aside aria-label=\"Zijbalk\" className={`xicms-sidebar__aside${block.sticky ? \" xicms-sidebar__aside--sticky\" : \"\"}`}>\n <BlockRenderer blocks={block.aside} registry={registry} siteId={siteId} mode={mode} />\n </aside>\n );\n return (\n <div\n className=\"xicms-sidebar\"\n data-side={block.side}\n data-width={block.sidebarWidth}\n >\n {block.side === \"left\" ? (\n <>\n {aside}\n {main}\n </>\n ) : (\n <>\n {main}\n {aside}\n </>\n )}\n </div>\n );\n}\n\nexport const withSidebarBlock = defineBlock({\n type: \"withSidebar\",\n
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/Layout.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport type { z } from \"zod\";\nimport type { WithSidebarBlockSchema, TabsBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { BlockRenderer } from \"../BlockRenderer\";\nimport { useRegistry } from \"../context\";\nimport { TabList, Tab } from \"@astryxdesign/core/TabList\";\n\n/* ── Met zijbalk ───────────────────────────────────────────────────────────\n * Brede hoofdkolom + smalle (sticky) zijbalk. De zijbalk is dé plek voor de compacte\n * widget-varianten (\"laatste nieuws\", \"komende agenda\", contact, …). Beide kanten zijn\n * gewone block-arrays, dus alles wat op een pagina kan, kan ook hier. */\ntype WithSidebar = z.infer<typeof WithSidebarBlockSchema>;\n\nfunction WithSidebarRender({ block, siteId, mode }: BlockRenderProps<WithSidebar>) {\n const registry = useRegistry();\n const main = (\n <div className=\"xicms-sidebar__main\">\n <BlockRenderer blocks={block.main} registry={registry} siteId={siteId} mode={mode} />\n </div>\n );\n const aside = (\n <aside aria-label=\"Zijbalk\" className={`xicms-sidebar__aside${block.sticky ? \" xicms-sidebar__aside--sticky\" : \"\"}`}>\n <BlockRenderer blocks={block.aside} registry={registry} siteId={siteId} mode={mode} />\n </aside>\n );\n return (\n <div\n className=\"xicms-sidebar\"\n data-side={block.side}\n data-width={block.sidebarWidth}\n >\n {block.side === \"left\" ? (\n <>\n {aside}\n {main}\n </>\n ) : (\n <>\n {main}\n {aside}\n </>\n )}\n </div>\n );\n}\n\nexport const withSidebarBlock = defineBlock({\n type: \"withSidebar\",\n Render: WithSidebarRender,\n label: \"Met zijbalk\",\n icon: \"▤\",\n category: \"layout\",\n defaults: () => ({ main: [], aside: [], side: \"right\", sidebarWidth: \"md\", sticky: true }),\n});\n\n/* ── Tabbladen ─────────────────────────────────────────────────────────────\n * Per tab een eigen set blokken. Toegankelijk (role=tab/tabpanel, pijltjes-navigatie via\n * de browser-default focus). */\ntype Tabs = z.infer<typeof TabsBlockSchema>;\n\nfunction TabsRender({ block, siteId, mode }: BlockRenderProps<Tabs>) {\n const registry = useRegistry();\n const tabs = block.tabs ?? [];\n const [active, setActive] = useState(\"0\");\n if (tabs.length === 0) return null;\n const current = Math.min(Math.max(Number(active) || 0, 0), tabs.length - 1);\n return (\n <section className=\"xicms-tabs\">\n <TabList value={String(current)} onChange={setActive} hasDivider>\n {tabs.map((t, i) => (\n <Tab key={i} value={String(i)} label={t.label || `Tab ${i + 1}`} />\n ))}\n </TabList>\n <div role=\"tabpanel\" aria-label={tabs[current].label || `Tab ${current + 1}`} style={{ marginTop: \"1.25rem\" }}>\n <BlockRenderer blocks={tabs[current].blocks} registry={registry} siteId={siteId} mode={mode} />\n </div>\n </section>\n );\n}\n\nexport const tabsBlock = defineBlock({\n type: \"tabs\",\n Render: TabsRender,\n label: \"Tabbladen\",\n icon: \"🗂️\",\n category: \"layout\",\n defaults: () => ({\n tabs: [\n { label: \"Tab 1\", blocks: [] },\n { label: \"Tab 2\", blocks: [] },\n ],\n }),\n});\n"],"mappings":";AAoBM,SAeE,UAfF,KAeE,YAfF;AAlBN,SAAS,gBAAgB;AAGzB,SAAS,mBAA0C;AACnD,SAAS,qBAAqB;AAC9B,SAAS,mBAAmB;AAC5B,SAAS,SAAS,WAAW;AAQ7B,SAAS,kBAAkB,EAAE,OAAO,QAAQ,KAAK,GAAkC;AACjF,QAAM,WAAW,YAAY;AAC7B,QAAM,OACJ,oBAAC,SAAI,WAAU,uBACb,8BAAC,iBAAc,QAAQ,MAAM,MAAM,UAAoB,QAAgB,MAAY,GACrF;AAEF,QAAM,QACJ,oBAAC,WAAM,cAAW,WAAU,WAAW,uBAAuB,MAAM,SAAS,kCAAkC,EAAE,IAC/G,8BAAC,iBAAc,QAAQ,MAAM,OAAO,UAAoB,QAAgB,MAAY,GACtF;AAEF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,aAAW,MAAM;AAAA,MACjB,cAAY,MAAM;AAAA,MAEjB,gBAAM,SAAS,SACd,iCACG;AAAA;AAAA,QACA;AAAA,SACH,IAEA,iCACG;AAAA;AAAA,QACA;AAAA,SACH;AAAA;AAAA,EAEJ;AAEJ;AAEO,MAAM,mBAAmB,YAAY;AAAA,EAC1C,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU;AAAA,EACV,UAAU,OAAO,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,MAAM,SAAS,cAAc,MAAM,QAAQ,KAAK;AAC1F,CAAC;AAOD,SAAS,WAAW,EAAE,OAAO,QAAQ,KAAK,GAA2B;AACnE,QAAM,WAAW,YAAY;AAC7B,QAAM,OAAO,MAAM,QAAQ,CAAC;AAC5B,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,GAAG;AACxC,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,QAAM,UAAU,KAAK,IAAI,KAAK,IAAI,OAAO,MAAM,KAAK,GAAG,CAAC,GAAG,KAAK,SAAS,CAAC;AAC1E,SACE,qBAAC,aAAQ,WAAU,cACjB;AAAA,wBAAC,WAAQ,OAAO,OAAO,OAAO,GAAG,UAAU,WAAW,YAAU,MAC7D,eAAK,IAAI,CAAC,GAAG,MACZ,oBAAC,OAAY,OAAO,OAAO,CAAC,GAAG,OAAO,EAAE,SAAS,OAAO,IAAI,CAAC,MAAnD,CAAuD,CAClE,GACH;AAAA,IACA,oBAAC,SAAI,MAAK,YAAW,cAAY,KAAK,OAAO,EAAE,SAAS,OAAO,UAAU,CAAC,IAAI,OAAO,EAAE,WAAW,UAAU,GAC1G,8BAAC,iBAAc,QAAQ,KAAK,OAAO,EAAE,QAAQ,UAAoB,QAAgB,MAAY,GAC/F;AAAA,KACF;AAEJ;AAEO,MAAM,YAAY,YAAY;AAAA,EACnC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU;AAAA,EACV,UAAU,OAAO;AAAA,IACf,MAAM;AAAA,MACJ,EAAE,OAAO,SAAS,QAAQ,CAAC,EAAE;AAAA,MAC7B,EAAE,OAAO,SAAS,QAAQ,CAAC,EAAE;AAAA,IAC/B;AAAA,EACF;AACF,CAAC;","names":[]}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { MapBlockSchema } from "../../schema/blocks.js";
|
|
4
3
|
import { defineBlock } from "../registry.js";
|
|
5
4
|
import { ConsentEmbed } from "./consentEmbed.js";
|
|
6
5
|
function osmSrc(block) {
|
|
@@ -46,7 +45,6 @@ function MapRender({ block, mode }) {
|
|
|
46
45
|
}
|
|
47
46
|
const mapBlock = defineBlock({
|
|
48
47
|
type: "map",
|
|
49
|
-
schema: MapBlockSchema,
|
|
50
48
|
Render: MapRender,
|
|
51
49
|
label: "Kaart",
|
|
52
50
|
icon: "\u{1F5FA}\uFE0F",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/MapBlock.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport { MapBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { ConsentEmbed } from \"./consentEmbed\";\n\ntype MapBlk = z.infer<typeof MapBlockSchema>;\n\n/**\n * Bouwt de OSM-embed-URL. OSM's `export/embed.html` negeert een tekst-`q` en centreert puur op\n * `bbox`; zonder bbox toont het de HELE WERELD. Daarom: als er `lat`/`lng` zijn, berekenen we\n * deterministisch een echte bbox rond het punt (zoom → graden-span, cos(lat)-correctie zodat de\n * kaart niet vervormt) + een marker. Zonder coördinaten is er geen zinvolle embed — de render\n * toont dan een kaartlink op basis van `query` (beter dan de wereldkaart die het q=-pad gaf).\n */\nfunction osmSrc(block: MapBlk): string | null {\n if (block.lat != null && block.lng != null) {\n const degPerTile = 360 / Math.pow(2, block.zoom);\n const lngHalf = degPerTile * 1.25;\n const latHalf = lngHalf * Math.cos((block.lat * Math.PI) / 180) * 0.6;\n const bbox = [block.lng - lngHalf, block.lat - latHalf, block.lng + lngHalf, block.lat + latHalf].join(\",\");\n const marker = `${block.lat},${block.lng}`;\n return `https://www.openstreetmap.org/export/embed.html?bbox=${encodeURIComponent(bbox)}&layer=mapnik&marker=${encodeURIComponent(marker)}`;\n }\n return null;\n}\n\nfunction MapRender({ block, mode }: BlockRenderProps<MapBlk>) {\n const src = block.provider === \"google\" ? block.query || null : osmSrc(block);\n if (!src) {\n // OSM zonder coördinaten kán niet zinvol embedden (het legacy q=-pad toonde de wereldkaart).\n // Toon een duidelijke kaartlink i.p.v. een nutteloze kaart, en leg het in de editor uit.\n if (block.provider === \"osm\" && block.query) {\n // In de editor stond hier alleen de uitleg. Daardoor was de zoekterm — het enige wat de\n // bezoeker straks ziet — nergens aanklikbaar en dus niet inline te bewerken. Nu tonen we\n // de uitleg én de kaartlink zoals hij live wordt.\n return (\n <>\n {mode === \"preview\" && (\n <p className=\"xicms-ve__hint\">🗺️ Kaart: vul coördinaten (lat/lng) in om de kaart te tonen — nu staat er een kaartlink.</p>\n )}\n <p className=\"xicms-map__fallback\">\n 📍{\" \"}\n <a href={`https://www.openstreetmap.org/search?query=${encodeURIComponent(block.query)}`} target=\"_blank\" rel=\"noopener noreferrer\">\n Bekijk <span data-xicms-edit=\"query\">{block.query}</span> op de kaart\n </a>\n </p>\n </>\n );\n }\n return null;\n }\n return (\n <div className=\"xicms-map\" style={{ height: `${block.height}px` }}>\n <ConsentEmbed gated={block.consentGated} preview={mode === \"preview\"} label=\"Kaart\">\n <iframe\n src={src}\n title={block.query ? `Kaart: ${block.query}` : \"Kaart\"}\n loading=\"lazy\"\n style={{ border: 0, width: \"100%\", height: \"100%\" }}\n referrerPolicy=\"no-referrer-when-downgrade\"\n />\n </ConsentEmbed>\n </div>\n );\n}\n\nexport const mapBlock = defineBlock({\n type: \"map\",\n
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/MapBlock.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport type { MapBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { ConsentEmbed } from \"./consentEmbed\";\n\ntype MapBlk = z.infer<typeof MapBlockSchema>;\n\n/**\n * Bouwt de OSM-embed-URL. OSM's `export/embed.html` negeert een tekst-`q` en centreert puur op\n * `bbox`; zonder bbox toont het de HELE WERELD. Daarom: als er `lat`/`lng` zijn, berekenen we\n * deterministisch een echte bbox rond het punt (zoom → graden-span, cos(lat)-correctie zodat de\n * kaart niet vervormt) + een marker. Zonder coördinaten is er geen zinvolle embed — de render\n * toont dan een kaartlink op basis van `query` (beter dan de wereldkaart die het q=-pad gaf).\n */\nfunction osmSrc(block: MapBlk): string | null {\n if (block.lat != null && block.lng != null) {\n const degPerTile = 360 / Math.pow(2, block.zoom);\n const lngHalf = degPerTile * 1.25;\n const latHalf = lngHalf * Math.cos((block.lat * Math.PI) / 180) * 0.6;\n const bbox = [block.lng - lngHalf, block.lat - latHalf, block.lng + lngHalf, block.lat + latHalf].join(\",\");\n const marker = `${block.lat},${block.lng}`;\n return `https://www.openstreetmap.org/export/embed.html?bbox=${encodeURIComponent(bbox)}&layer=mapnik&marker=${encodeURIComponent(marker)}`;\n }\n return null;\n}\n\nfunction MapRender({ block, mode }: BlockRenderProps<MapBlk>) {\n const src = block.provider === \"google\" ? block.query || null : osmSrc(block);\n if (!src) {\n // OSM zonder coördinaten kán niet zinvol embedden (het legacy q=-pad toonde de wereldkaart).\n // Toon een duidelijke kaartlink i.p.v. een nutteloze kaart, en leg het in de editor uit.\n if (block.provider === \"osm\" && block.query) {\n // In de editor stond hier alleen de uitleg. Daardoor was de zoekterm — het enige wat de\n // bezoeker straks ziet — nergens aanklikbaar en dus niet inline te bewerken. Nu tonen we\n // de uitleg én de kaartlink zoals hij live wordt.\n return (\n <>\n {mode === \"preview\" && (\n <p className=\"xicms-ve__hint\">🗺️ Kaart: vul coördinaten (lat/lng) in om de kaart te tonen — nu staat er een kaartlink.</p>\n )}\n <p className=\"xicms-map__fallback\">\n 📍{\" \"}\n <a href={`https://www.openstreetmap.org/search?query=${encodeURIComponent(block.query)}`} target=\"_blank\" rel=\"noopener noreferrer\">\n Bekijk <span data-xicms-edit=\"query\">{block.query}</span> op de kaart\n </a>\n </p>\n </>\n );\n }\n return null;\n }\n return (\n <div className=\"xicms-map\" style={{ height: `${block.height}px` }}>\n <ConsentEmbed gated={block.consentGated} preview={mode === \"preview\"} label=\"Kaart\">\n <iframe\n src={src}\n title={block.query ? `Kaart: ${block.query}` : \"Kaart\"}\n loading=\"lazy\"\n style={{ border: 0, width: \"100%\", height: \"100%\" }}\n referrerPolicy=\"no-referrer-when-downgrade\"\n />\n </ConsentEmbed>\n </div>\n );\n}\n\nexport const mapBlock = defineBlock({\n type: \"map\",\n Render: MapRender,\n label: \"Kaart\",\n icon: \"🗺️\",\n defaults: () => ({ provider: \"osm\", query: \"\", zoom: 14, height: 360, consentGated: true }),\n});\n"],"mappings":";AAsCQ,mBAEI,KAIA,YANJ;AAlCR,SAAS,mBAA0C;AACnD,SAAS,oBAAoB;AAW7B,SAAS,OAAO,OAA8B;AAC5C,MAAI,MAAM,OAAO,QAAQ,MAAM,OAAO,MAAM;AAC1C,UAAM,aAAa,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI;AAC/C,UAAM,UAAU,aAAa;AAC7B,UAAM,UAAU,UAAU,KAAK,IAAK,MAAM,MAAM,KAAK,KAAM,GAAG,IAAI;AAClE,UAAM,OAAO,CAAC,MAAM,MAAM,SAAS,MAAM,MAAM,SAAS,MAAM,MAAM,SAAS,MAAM,MAAM,OAAO,EAAE,KAAK,GAAG;AAC1G,UAAM,SAAS,GAAG,MAAM,GAAG,IAAI,MAAM,GAAG;AACxC,WAAO,wDAAwD,mBAAmB,IAAI,CAAC,wBAAwB,mBAAmB,MAAM,CAAC;AAAA,EAC3I;AACA,SAAO;AACT;AAEA,SAAS,UAAU,EAAE,OAAO,KAAK,GAA6B;AAC5D,QAAM,MAAM,MAAM,aAAa,WAAW,MAAM,SAAS,OAAO,OAAO,KAAK;AAC5E,MAAI,CAAC,KAAK;AAGR,QAAI,MAAM,aAAa,SAAS,MAAM,OAAO;AAI3C,aACE,iCACG;AAAA,iBAAS,aACR,oBAAC,OAAE,WAAU,kBAAiB,2HAAyF;AAAA,QAEzH,qBAAC,OAAE,WAAU,uBAAsB;AAAA;AAAA,UAC9B;AAAA,UACH,qBAAC,OAAE,MAAM,8CAA8C,mBAAmB,MAAM,KAAK,CAAC,IAAI,QAAO,UAAS,KAAI,uBAAsB;AAAA;AAAA,YAC3H,oBAAC,UAAK,mBAAgB,SAAS,gBAAM,OAAM;AAAA,YAAO;AAAA,aAC3D;AAAA,WACF;AAAA,SACF;AAAA,IAEJ;AACA,WAAO;AAAA,EACT;AACA,SACE,oBAAC,SAAI,WAAU,aAAY,OAAO,EAAE,QAAQ,GAAG,MAAM,MAAM,KAAK,GAC9D,8BAAC,gBAAa,OAAO,MAAM,cAAc,SAAS,SAAS,WAAW,OAAM,SAC1E;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,OAAO,MAAM,QAAQ,UAAU,MAAM,KAAK,KAAK;AAAA,MAC/C,SAAQ;AAAA,MACR,OAAO,EAAE,QAAQ,GAAG,OAAO,QAAQ,QAAQ,OAAO;AAAA,MAClD,gBAAe;AAAA;AAAA,EACjB,GACF,GACF;AAEJ;AAEO,MAAM,WAAW,YAAY;AAAA,EAClC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,UAAU,OAAO,OAAO,IAAI,MAAM,IAAI,QAAQ,KAAK,cAAc,KAAK;AAC3F,CAAC;","names":[]}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { NewsListBlockSchema } from "../../schema/blocks.js";
|
|
4
3
|
import { defineBlock } from "../registry.js";
|
|
5
4
|
import { Heading } from "../ui/index.js";
|
|
6
5
|
import { NewsList } from "../../components/News.js";
|
|
@@ -12,7 +11,6 @@ function NewsListRender({ block }) {
|
|
|
12
11
|
}
|
|
13
12
|
const newsListBlock = defineBlock({
|
|
14
13
|
type: "newsList",
|
|
15
|
-
schema: NewsListBlockSchema,
|
|
16
14
|
Render: NewsListRender,
|
|
17
15
|
label: "Nieuwsoverzicht",
|
|
18
16
|
icon: "\u{1F5DE}\uFE0F",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/NewsListBlock.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport { NewsListBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Heading } from \"../ui\";\nimport { NewsList } from \"../../components/News\";\n\ntype NewsListBlk = z.infer<typeof NewsListBlockSchema>;\n\nfunction NewsListRender({ block }: BlockRenderProps<NewsListBlk>) {\n return (\n <section className=\"xicms-newslist\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n <NewsList basePath={block.basePath} limit={block.limit} pageSize={block.pageSize} />\n </section>\n );\n}\n\nexport const newsListBlock = defineBlock({\n type: \"newsList\",\n
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/NewsListBlock.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport type { NewsListBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Heading } from \"../ui\";\nimport { NewsList } from \"../../components/News\";\n\ntype NewsListBlk = z.infer<typeof NewsListBlockSchema>;\n\nfunction NewsListRender({ block }: BlockRenderProps<NewsListBlk>) {\n return (\n <section className=\"xicms-newslist\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n <NewsList basePath={block.basePath} limit={block.limit} pageSize={block.pageSize} />\n </section>\n );\n}\n\nexport const newsListBlock = defineBlock({\n type: \"newsList\",\n Render: NewsListRender,\n label: \"Nieuwsoverzicht\",\n icon: \"🗞️\",\n defaults: () => ({ heading: \"Laatste nieuws\", limit: 6, basePath: \"/nieuws\" }),\n});\n"],"mappings":";AAYI,SACoB,KADpB;AARJ,SAAS,mBAA0C;AACnD,SAAS,eAAe;AACxB,SAAS,gBAAgB;AAIzB,SAAS,eAAe,EAAE,MAAM,GAAkC;AAChE,SACE,qBAAC,aAAQ,WAAU,kBAChB;AAAA,UAAM,WAAW,oBAAC,WAAQ,OAAO,GAAG,mBAAgB,WAAW,gBAAM,SAAQ;AAAA,IAC9E,oBAAC,YAAS,UAAU,MAAM,UAAU,OAAO,MAAM,OAAO,UAAU,MAAM,UAAU;AAAA,KACpF;AAEJ;AAEO,MAAM,gBAAgB,YAAY;AAAA,EACvC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,SAAS,kBAAkB,OAAO,GAAG,UAAU,UAAU;AAC9E,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 { NewsletterBlockSchema } from "../../schema/blocks.js";
|
|
5
4
|
import { defineBlock } from "../registry.js";
|
|
6
5
|
import { Button } from "@astryxdesign/core/Button";
|
|
7
6
|
import { TextInput } from "@astryxdesign/core/TextInput";
|
|
@@ -90,7 +89,6 @@ function NewsletterRender({ block, siteId, mode }) {
|
|
|
90
89
|
}
|
|
91
90
|
const newsletterBlock = defineBlock({
|
|
92
91
|
type: "newsletter",
|
|
93
|
-
schema: NewsletterBlockSchema,
|
|
94
92
|
Render: NewsletterRender,
|
|
95
93
|
label: "Nieuwsbrief",
|
|
96
94
|
icon: "\u2709\uFE0F",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/Newsletter.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState, type FormEvent } from \"react\";\nimport type { z } from \"zod\";\nimport { NewsletterBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Button } from \"@astryxdesign/core/Button\";\nimport { TextInput } from \"@astryxdesign/core/TextInput\";\nimport { CheckboxInput } from \"@astryxdesign/core/CheckboxInput\";\nimport { VStack, Card, Heading, Text } from \"../ui\";\nimport { isDemoActive } from \"../../demo/demoStore\";\nimport { Onvoltooid } from \"./lib/Onvoltooid\";\nimport { isFirebaseConfigured } from \"../../firebase/config\";\nimport { submitFormValues } from \"./lib/submitForm\";\n\ntype Newsletter = z.infer<typeof NewsletterBlockSchema>;\n\n/**\n * Nieuwsbrief-aanmelding. Twee modi:\n * - `actionUrl` (bv. de embed-action van Mailchimp/Brevo): native form-POST naar de provider.\n * - `formId` (zonder actionUrl): opslag via het XiCMS-formulier (submitForm-callable) — inzendingen\n * verschijnen in /beheer → Formulieren, met dezelfde rate-limiting/validatie als het formulier-blok.\n * Optionele AVG-checkbox via `consentText`.\n */\nfunction NewsletterRender({ block, siteId, mode }: BlockRenderProps<Newsletter>) {\n const [email, setEmail] = useState(\"\");\n const [agreed, setAgreed] = useState(false);\n const [status, setStatus] = useState<\"idle\" | \"busy\" | \"ok\" | \"error\">(\"idle\");\n /**\n * In een demo is er altijd een werkend formulier: het slaat lokaal \"op\" (zie submitViaXicms),\n * precies zoals het formulier-blok. Een demo die om configuratie vraagt verkoopt niets.\n *\n * Via state + effect en niet rechtstreeks in de render: `setDemoActive` draait in een effect van\n * de provider, dus tijdens de eerste render is `isDemoActive()` nog false en zou dit blok zich\n * één keer verkeerd aankleden — en zonder eigen re-render zo blijven staan. FormBlock leert\n * dezelfde les op dezelfde manier.\n */\n const [demo, setDemo] = useState(false);\n useEffect(() => setDemo(isDemoActive()), []);\n const useXicmsForm = !block.actionUrl && (Boolean(block.formId) || demo);\n\n async function submitViaXicms(e: FormEvent) {\n e.preventDefault();\n if (mode === \"preview\") return;\n // Zonder formId kan er alleen in demo iets \"verzonden\" worden — de guard hieronder vangt dat af\n // met een lokaal succes vóór submitFormValues ooit wordt bereikt.\n if (!block.formId && !demo) return;\n // Zelfde vangnet als het formulier-blok: in demo of zonder Firebase-backend lokaal \"slagen\"\n // i.p.v. een foutmelding tonen op een statische (referentie)site.\n if (isDemoActive() || !isFirebaseConfigured()) {\n setStatus(\"ok\");\n return;\n }\n setStatus(\"busy\");\n try {\n // Niet-demo-pad; hierboven afgevangen als formId ontbreekt, dus hier bestaat hij.\n await submitFormValues(siteId, block.formId as string, { email });\n setStatus(\"ok\");\n setEmail(\"\");\n } catch {\n setStatus(\"error\");\n }\n }\n\n const consentBox = block.consentText ? (\n <div style={{ textAlign: \"left\", maxWidth: 440, width: \"100%\" }}>\n <CheckboxInput label={block.consentText} isRequired value={agreed} onChange={setAgreed} />\n </div>\n ) : null;\n\n const emailInput = (\n <div style={{ flex: 1, minWidth: 200, textAlign: \"left\" }}>\n <TextInput\n label=\"E-mailadres\"\n isLabelHidden\n type=\"email\"\n htmlName=\"EMAIL\"\n isRequired\n placeholder=\"je@email.nl\"\n value={email}\n onChange={setEmail}\n />\n </div>\n );\n const rowStyle = { display: \"flex\", gap: \"0.75rem\", flexWrap: \"wrap\", alignItems: \"end\", justifyContent: \"center\", maxWidth: 440, width: \"100%\" } as const;\n\n return (\n <Card padding={5} variant=\"muted\">\n <VStack gap={2} hAlign=\"center\" style={{ textAlign: \"center\" }}>\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {block.text && <Text color=\"secondary\" data-xicms-edit=\"text\">{block.text}</Text>}\n {status === \"ok\" ? (\n <Text role=\"status\">Bedankt voor je aanmelding!</Text>\n ) : block.actionUrl ? (\n <form\n action={block.actionUrl}\n method=\"post\"\n target=\"_blank\"\n style={rowStyle}\n // AVG: zonder deze guard kon de native POST doorgaan zonder aangevinkte toestemming\n // (Astryx CheckboxInput heeft geen htmlName, dus de browser hield 'm niet tegen).\n onSubmit={(e) => {\n if (block.consentText && !agreed) e.preventDefault();\n }}\n >\n {emailInput}\n {consentBox}\n {/* De toestemmingswaarde ook daadwerkelijk meesturen naar de provider. */}\n {block.consentText && <input type=\"hidden\" name=\"CONSENT\" value={agreed ? \"yes\" : \"\"} />}\n <Button label={block.buttonLabel} variant=\"primary\" size=\"lg\" type=\"submit\">\n <span data-xicms-edit=\"buttonLabel\">{block.buttonLabel}</span>\n </Button>\n </form>\n ) : useXicmsForm ? (\n <form onSubmit={submitViaXicms} style={rowStyle}>\n {emailInput}\n {consentBox}\n <Button\n label={status === \"busy\" ? \"Aanmelden…\" : block.buttonLabel}\n variant=\"primary\"\n size=\"lg\"\n type=\"submit\"\n isLoading={status === \"busy\"}\n >\n {/* Tijdens versturen wint het wachtlabel: `undefined` laat Astryx op `label` terugvallen. */}\n {status === \"busy\" ? undefined : <span data-xicms-edit=\"buttonLabel\">{block.buttonLabel}</span>}\n </Button>\n {status === \"error\" && (\n <Text role=\"alert\" type=\"supporting\" style={{ color: \"var(--color-error, #c0392b)\" }}>\n Aanmelden mislukt. Probeer het later opnieuw.\n </Text>\n )}\n </form>\n ) : (\n <Onvoltooid>Dit nieuwsbrief-blok is nog leeg: kies een aanmeld-URL of een formulier via het tandwiel. Bezoekers zien dit blok niet.</Onvoltooid>\n )}\n </VStack>\n </Card>\n );\n}\n\nexport const newsletterBlock = defineBlock({\n type: \"newsletter\",\n
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/Newsletter.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState, type FormEvent } from \"react\";\nimport type { z } from \"zod\";\nimport type { NewsletterBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Button } from \"@astryxdesign/core/Button\";\nimport { TextInput } from \"@astryxdesign/core/TextInput\";\nimport { CheckboxInput } from \"@astryxdesign/core/CheckboxInput\";\nimport { VStack, Card, Heading, Text } from \"../ui\";\nimport { isDemoActive } from \"../../demo/demoStore\";\nimport { Onvoltooid } from \"./lib/Onvoltooid\";\nimport { isFirebaseConfigured } from \"../../firebase/config\";\nimport { submitFormValues } from \"./lib/submitForm\";\n\ntype Newsletter = z.infer<typeof NewsletterBlockSchema>;\n\n/**\n * Nieuwsbrief-aanmelding. Twee modi:\n * - `actionUrl` (bv. de embed-action van Mailchimp/Brevo): native form-POST naar de provider.\n * - `formId` (zonder actionUrl): opslag via het XiCMS-formulier (submitForm-callable) — inzendingen\n * verschijnen in /beheer → Formulieren, met dezelfde rate-limiting/validatie als het formulier-blok.\n * Optionele AVG-checkbox via `consentText`.\n */\nfunction NewsletterRender({ block, siteId, mode }: BlockRenderProps<Newsletter>) {\n const [email, setEmail] = useState(\"\");\n const [agreed, setAgreed] = useState(false);\n const [status, setStatus] = useState<\"idle\" | \"busy\" | \"ok\" | \"error\">(\"idle\");\n /**\n * In een demo is er altijd een werkend formulier: het slaat lokaal \"op\" (zie submitViaXicms),\n * precies zoals het formulier-blok. Een demo die om configuratie vraagt verkoopt niets.\n *\n * Via state + effect en niet rechtstreeks in de render: `setDemoActive` draait in een effect van\n * de provider, dus tijdens de eerste render is `isDemoActive()` nog false en zou dit blok zich\n * één keer verkeerd aankleden — en zonder eigen re-render zo blijven staan. FormBlock leert\n * dezelfde les op dezelfde manier.\n */\n const [demo, setDemo] = useState(false);\n useEffect(() => setDemo(isDemoActive()), []);\n const useXicmsForm = !block.actionUrl && (Boolean(block.formId) || demo);\n\n async function submitViaXicms(e: FormEvent) {\n e.preventDefault();\n if (mode === \"preview\") return;\n // Zonder formId kan er alleen in demo iets \"verzonden\" worden — de guard hieronder vangt dat af\n // met een lokaal succes vóór submitFormValues ooit wordt bereikt.\n if (!block.formId && !demo) return;\n // Zelfde vangnet als het formulier-blok: in demo of zonder Firebase-backend lokaal \"slagen\"\n // i.p.v. een foutmelding tonen op een statische (referentie)site.\n if (isDemoActive() || !isFirebaseConfigured()) {\n setStatus(\"ok\");\n return;\n }\n setStatus(\"busy\");\n try {\n // Niet-demo-pad; hierboven afgevangen als formId ontbreekt, dus hier bestaat hij.\n await submitFormValues(siteId, block.formId as string, { email });\n setStatus(\"ok\");\n setEmail(\"\");\n } catch {\n setStatus(\"error\");\n }\n }\n\n const consentBox = block.consentText ? (\n <div style={{ textAlign: \"left\", maxWidth: 440, width: \"100%\" }}>\n <CheckboxInput label={block.consentText} isRequired value={agreed} onChange={setAgreed} />\n </div>\n ) : null;\n\n const emailInput = (\n <div style={{ flex: 1, minWidth: 200, textAlign: \"left\" }}>\n <TextInput\n label=\"E-mailadres\"\n isLabelHidden\n type=\"email\"\n htmlName=\"EMAIL\"\n isRequired\n placeholder=\"je@email.nl\"\n value={email}\n onChange={setEmail}\n />\n </div>\n );\n const rowStyle = { display: \"flex\", gap: \"0.75rem\", flexWrap: \"wrap\", alignItems: \"end\", justifyContent: \"center\", maxWidth: 440, width: \"100%\" } as const;\n\n return (\n <Card padding={5} variant=\"muted\">\n <VStack gap={2} hAlign=\"center\" style={{ textAlign: \"center\" }}>\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {block.text && <Text color=\"secondary\" data-xicms-edit=\"text\">{block.text}</Text>}\n {status === \"ok\" ? (\n <Text role=\"status\">Bedankt voor je aanmelding!</Text>\n ) : block.actionUrl ? (\n <form\n action={block.actionUrl}\n method=\"post\"\n target=\"_blank\"\n style={rowStyle}\n // AVG: zonder deze guard kon de native POST doorgaan zonder aangevinkte toestemming\n // (Astryx CheckboxInput heeft geen htmlName, dus de browser hield 'm niet tegen).\n onSubmit={(e) => {\n if (block.consentText && !agreed) e.preventDefault();\n }}\n >\n {emailInput}\n {consentBox}\n {/* De toestemmingswaarde ook daadwerkelijk meesturen naar de provider. */}\n {block.consentText && <input type=\"hidden\" name=\"CONSENT\" value={agreed ? \"yes\" : \"\"} />}\n <Button label={block.buttonLabel} variant=\"primary\" size=\"lg\" type=\"submit\">\n <span data-xicms-edit=\"buttonLabel\">{block.buttonLabel}</span>\n </Button>\n </form>\n ) : useXicmsForm ? (\n <form onSubmit={submitViaXicms} style={rowStyle}>\n {emailInput}\n {consentBox}\n <Button\n label={status === \"busy\" ? \"Aanmelden…\" : block.buttonLabel}\n variant=\"primary\"\n size=\"lg\"\n type=\"submit\"\n isLoading={status === \"busy\"}\n >\n {/* Tijdens versturen wint het wachtlabel: `undefined` laat Astryx op `label` terugvallen. */}\n {status === \"busy\" ? undefined : <span data-xicms-edit=\"buttonLabel\">{block.buttonLabel}</span>}\n </Button>\n {status === \"error\" && (\n <Text role=\"alert\" type=\"supporting\" style={{ color: \"var(--color-error, #c0392b)\" }}>\n Aanmelden mislukt. Probeer het later opnieuw.\n </Text>\n )}\n </form>\n ) : (\n <Onvoltooid>Dit nieuwsbrief-blok is nog leeg: kies een aanmeld-URL of een formulier via het tandwiel. Bezoekers zien dit blok niet.</Onvoltooid>\n )}\n </VStack>\n </Card>\n );\n}\n\nexport const newsletterBlock = defineBlock({\n type: \"newsletter\",\n Render: NewsletterRender,\n label: \"Nieuwsbrief\",\n icon: \"✉️\",\n defaults: () => ({ heading: \"Blijf op de hoogte\", buttonLabel: \"Aanmelden\" }),\n});\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { jsonLdHtml } from "./jsonld.js";
|
|
3
|
-
import { OpeningHoursBlockSchema } from "../../schema/blocks.js";
|
|
4
3
|
import { defineBlock } from "../registry.js";
|
|
5
4
|
import { Heading } from "../ui/index.js";
|
|
6
5
|
import { OpenNowBadge } from "./OpenNowBadge.js";
|
|
@@ -29,7 +28,6 @@ function OpeningHoursRender({ block }) {
|
|
|
29
28
|
}
|
|
30
29
|
const openingHoursBlock = defineBlock({
|
|
31
30
|
type: "openingHours",
|
|
32
|
-
schema: OpeningHoursBlockSchema,
|
|
33
31
|
Render: OpeningHoursRender,
|
|
34
32
|
label: "Openingstijden",
|
|
35
33
|
icon: "\u{1F552}",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/OpeningHours.tsx"],"sourcesContent":["import type { z } from \"zod\";\nimport { jsonLdHtml } from \"./jsonld\";\nimport { OpeningHoursBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Heading } from \"../ui\";\nimport { OpenNowBadge } from \"./OpenNowBadge\";\nimport { openingHoursSpecJsonLd } from \"./lib/openingHours\";\n\ntype OpeningHours = z.infer<typeof OpeningHoursBlockSchema>;\n\n/** Openingstijden-tabel. Server-safe; de optionele \"Nu geopend\"-badge is een client-eiland. */\nfunction OpeningHoursRender({ block }: BlockRenderProps<OpeningHours>) {\n const rows = block.rows ?? [];\n const spec = block.jsonLd ? openingHoursSpecJsonLd(rows) : [];\n return (\n <section className=\"xicms-hours\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {block.showStatus && <OpenNowBadge rows={rows} />}\n <table className=\"xicms-hours-table\">\n <tbody>\n {rows.map((r, i) => (\n <tr key={i}>\n <th scope=\"row\" data-xicms-edit={`rows.${i}.day`}>{r.day}</th>\n <td data-xicms-edit={`rows.${i}.hours`}>{r.hours}</td>\n </tr>\n ))}\n </tbody>\n </table>\n {spec.length > 0 && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{\n // Zelfde @id als contact-/layout-JSON-LD → Google voegt de drie samen tot één bedrijf.\n __html: jsonLdHtml({ \"@context\": \"https://schema.org\", \"@type\": \"LocalBusiness\", \"@id\": \"#business\", openingHoursSpecification: spec }),\n }}\n />\n )}\n </section>\n );\n}\n\nexport const openingHoursBlock = defineBlock({\n type: \"openingHours\",\n
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/OpeningHours.tsx"],"sourcesContent":["import type { z } from \"zod\";\nimport { jsonLdHtml } from \"./jsonld\";\nimport type { OpeningHoursBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Heading } from \"../ui\";\nimport { OpenNowBadge } from \"./OpenNowBadge\";\nimport { openingHoursSpecJsonLd } from \"./lib/openingHours\";\n\ntype OpeningHours = z.infer<typeof OpeningHoursBlockSchema>;\n\n/** Openingstijden-tabel. Server-safe; de optionele \"Nu geopend\"-badge is een client-eiland. */\nfunction OpeningHoursRender({ block }: BlockRenderProps<OpeningHours>) {\n const rows = block.rows ?? [];\n const spec = block.jsonLd ? openingHoursSpecJsonLd(rows) : [];\n return (\n <section className=\"xicms-hours\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {block.showStatus && <OpenNowBadge rows={rows} />}\n <table className=\"xicms-hours-table\">\n <tbody>\n {rows.map((r, i) => (\n <tr key={i}>\n <th scope=\"row\" data-xicms-edit={`rows.${i}.day`}>{r.day}</th>\n <td data-xicms-edit={`rows.${i}.hours`}>{r.hours}</td>\n </tr>\n ))}\n </tbody>\n </table>\n {spec.length > 0 && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{\n // Zelfde @id als contact-/layout-JSON-LD → Google voegt de drie samen tot één bedrijf.\n __html: jsonLdHtml({ \"@context\": \"https://schema.org\", \"@type\": \"LocalBusiness\", \"@id\": \"#business\", openingHoursSpecification: spec }),\n }}\n />\n )}\n </section>\n );\n}\n\nexport const openingHoursBlock = defineBlock({\n type: \"openingHours\",\n Render: OpeningHoursRender,\n label: \"Openingstijden\",\n icon: \"🕒\",\n defaults: () => ({\n heading: \"Openingstijden\",\n rows: [\n { day: \"Maandag t/m vrijdag\", hours: \"09:00 – 17:00\" },\n { day: \"Zaterdag\", hours: \"10:00 – 16:00\" },\n { day: \"Zondag\", hours: \"Gesloten\" },\n ],\n }),\n});\n"],"mappings":"AAgBwB,cAKZ,YALY;AAfxB,SAAS,kBAAkB;AAE3B,SAAS,mBAA0C;AACnD,SAAS,eAAe;AACxB,SAAS,oBAAoB;AAC7B,SAAS,8BAA8B;AAKvC,SAAS,mBAAmB,EAAE,MAAM,GAAmC;AACrE,QAAM,OAAO,MAAM,QAAQ,CAAC;AAC5B,QAAM,OAAO,MAAM,SAAS,uBAAuB,IAAI,IAAI,CAAC;AAC5D,SACE,qBAAC,aAAQ,WAAU,eAChB;AAAA,UAAM,WAAW,oBAAC,WAAQ,OAAO,GAAG,mBAAgB,WAAW,gBAAM,SAAQ;AAAA,IAC7E,MAAM,cAAc,oBAAC,gBAAa,MAAY;AAAA,IAC/C,oBAAC,WAAM,WAAU,qBACf,8BAAC,WACE,eAAK,IAAI,CAAC,GAAG,MACZ,qBAAC,QACC;AAAA,0BAAC,QAAG,OAAM,OAAM,mBAAiB,QAAQ,CAAC,QAAS,YAAE,KAAI;AAAA,MACzD,oBAAC,QAAG,mBAAiB,QAAQ,CAAC,UAAW,YAAE,OAAM;AAAA,SAF1C,CAGT,CACD,GACH,GACF;AAAA,IACC,KAAK,SAAS,KACb;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QAEL,yBAAyB;AAAA;AAAA,UAEvB,QAAQ,WAAW,EAAE,YAAY,sBAAsB,SAAS,iBAAiB,OAAO,aAAa,2BAA2B,KAAK,CAAC;AAAA,QACxI;AAAA;AAAA,IACF;AAAA,KAEJ;AAEJ;AAEO,MAAM,oBAAoB,YAAY;AAAA,EAC3C,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO;AAAA,IACf,SAAS;AAAA,IACT,MAAM;AAAA,MACJ,EAAE,KAAK,uBAAuB,OAAO,qBAAgB;AAAA,MACrD,EAAE,KAAK,YAAY,OAAO,qBAAgB;AAAA,MAC1C,EAAE,KAAK,UAAU,OAAO,WAAW;AAAA,IACrC;AAAA,EACF;AACF,CAAC;","names":[]}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { Onvoltooid } from "./lib/Onvoltooid.js";
|
|
5
|
-
import { PdfBlockSchema } from "../../schema/blocks.js";
|
|
6
5
|
import { defineBlock } from "../registry.js";
|
|
7
6
|
import { VStack, HStack, Heading, Text } from "../ui/index.js";
|
|
8
7
|
function PdfRender({ block, mode }) {
|
|
@@ -77,7 +76,6 @@ function PdfRender({ block, mode }) {
|
|
|
77
76
|
}
|
|
78
77
|
const pdfBlock = defineBlock({
|
|
79
78
|
type: "pdf",
|
|
80
|
-
schema: PdfBlockSchema,
|
|
81
79
|
Render: PdfRender,
|
|
82
80
|
label: "PDF-lezer (menu/document)",
|
|
83
81
|
icon: "\u{1F4C4}",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/PdfBlock.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport type { z } from \"zod\";\nimport { Onvoltooid } from \"./lib/Onvoltooid\";\nimport { PdfBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { VStack, HStack, Heading, Text } from \"../ui\";\n\ntype Pdf = z.infer<typeof PdfBlockSchema>;\n\n/**\n * Inline PDF-lezer voor bv. menukaarten. Eén of meer PDF's; bij meerdere verschijnen tabs\n * (lunchkaart/dinerkaart/wijnkaart…). De klant vervangt de PDF's zelf in de editor (URL of\n * media-upload). In de editor (mode=\"preview\") tonen we een lichte placeholder i.p.v. de iframe.\n */\nfunction PdfRender({ block, mode }: BlockRenderProps<Pdf>) {\n // _i = index in de OORSPRONKELIJKE array — de lijst wordt gefilterd op url.\n const docs = (block.documents ?? []).map((d, i) => ({ ...d, _i: i })).filter((d) => d.url);\n const [active, setActive] = useState(0);\n if (docs.length === 0 && !block.heading) return null;\n const current = docs[Math.min(active, docs.length - 1)];\n\n return (\n <VStack gap={3} as=\"section\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n\n {/* Gewone toggle-knoppen: een echt tabs-patroon vereist tabpanel-koppeling + pijltjesnavigatie. */}\n {docs.length > 1 && (\n <HStack gap={1} style={{ flexWrap: \"wrap\" }} role=\"group\" aria-label=\"Kies document\">\n {docs.map((d, i) => (\n <button\n key={i}\n type=\"button\"\n aria-pressed={i === active}\n onClick={() => setActive(i)}\n style={{\n padding: \"0.4rem 0.9rem\",\n borderRadius: \"var(--radius-full, 999px)\",\n border: \"1px solid var(--color-border)\",\n cursor: \"pointer\",\n fontWeight: 600,\n fontSize: \"0.9rem\",\n background: i === active ? \"var(--color-accent)\" : \"transparent\",\n color: i === active ? \"var(--color-on-accent)\" : \"var(--color-text-primary)\",\n }}\n >\n <span data-xicms-edit={`documents.${d._i}.label`}>{d.label || `Document ${i + 1}`}</span>\n </button>\n ))}\n </HStack>\n )}\n\n {current ? (\n <div\n style={{\n borderRadius: \"var(--radius-container)\",\n overflow: \"hidden\",\n border: \"1px solid var(--color-border)\",\n background: \"var(--color-background-muted, rgba(0,0,0,0.03))\",\n }}\n >\n {mode === \"preview\" ? (\n <div className=\"xicms-embed-consent xicms-embed-preview\" role=\"note\" style={{ height: Math.min(block.height, 360) }}>\n <p>📄 PDF “{current.label || \"document\"}” — wordt op de live site geladen (niet in de editor).</p>\n </div>\n ) : (\n // <object> i.p.v. <iframe>: browsers zonder inline PDF-viewer (veel mobiel) tonen\n // dan de fallback-inhoud met een open/download-knop i.p.v. een leeg vlak.\n <object\n data={`${current.url}#view=FitH`}\n type=\"application/pdf\"\n aria-label={current.label || block.heading || \"PDF\"}\n style={{ border: 0, width: \"100%\", height: block.height, display: \"block\" }}\n >\n <div style={{ display: \"grid\", placeItems: \"center\", gap: \"0.6rem\", padding: \"2.5rem 1rem\", textAlign: \"center\" }}>\n <Text color=\"secondary\">Je browser kan deze PDF niet inline tonen.</Text>\n <a className=\"xicms-btn\" href={current.url} target=\"_blank\" rel=\"noopener noreferrer\">\n 📄 Open “{current.label || \"PDF\"}”\n </a>\n </div>\n </object>\n )}\n </div>\n ) : (\n <Onvoltooid>Dit PDF-blok is nog leeg: kies een document via het tandwiel. Bezoekers zien dit blok niet.</Onvoltooid>\n )}\n\n {block.showDownload && current && (\n <a href={current.url} target=\"_blank\" rel=\"noopener noreferrer\" style={{ color: \"var(--color-accent)\" }}>\n <Text type=\"label\">↗ Open “{current.label || \"PDF\"}” in een nieuw tabblad</Text>\n </a>\n )}\n </VStack>\n );\n}\n\nexport const pdfBlock = defineBlock({\n type: \"pdf\",\n
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/PdfBlock.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport type { z } from \"zod\";\nimport { Onvoltooid } from \"./lib/Onvoltooid\";\nimport type { PdfBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { VStack, HStack, Heading, Text } from \"../ui\";\n\ntype Pdf = z.infer<typeof PdfBlockSchema>;\n\n/**\n * Inline PDF-lezer voor bv. menukaarten. Eén of meer PDF's; bij meerdere verschijnen tabs\n * (lunchkaart/dinerkaart/wijnkaart…). De klant vervangt de PDF's zelf in de editor (URL of\n * media-upload). In de editor (mode=\"preview\") tonen we een lichte placeholder i.p.v. de iframe.\n */\nfunction PdfRender({ block, mode }: BlockRenderProps<Pdf>) {\n // _i = index in de OORSPRONKELIJKE array — de lijst wordt gefilterd op url.\n const docs = (block.documents ?? []).map((d, i) => ({ ...d, _i: i })).filter((d) => d.url);\n const [active, setActive] = useState(0);\n if (docs.length === 0 && !block.heading) return null;\n const current = docs[Math.min(active, docs.length - 1)];\n\n return (\n <VStack gap={3} as=\"section\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n\n {/* Gewone toggle-knoppen: een echt tabs-patroon vereist tabpanel-koppeling + pijltjesnavigatie. */}\n {docs.length > 1 && (\n <HStack gap={1} style={{ flexWrap: \"wrap\" }} role=\"group\" aria-label=\"Kies document\">\n {docs.map((d, i) => (\n <button\n key={i}\n type=\"button\"\n aria-pressed={i === active}\n onClick={() => setActive(i)}\n style={{\n padding: \"0.4rem 0.9rem\",\n borderRadius: \"var(--radius-full, 999px)\",\n border: \"1px solid var(--color-border)\",\n cursor: \"pointer\",\n fontWeight: 600,\n fontSize: \"0.9rem\",\n background: i === active ? \"var(--color-accent)\" : \"transparent\",\n color: i === active ? \"var(--color-on-accent)\" : \"var(--color-text-primary)\",\n }}\n >\n <span data-xicms-edit={`documents.${d._i}.label`}>{d.label || `Document ${i + 1}`}</span>\n </button>\n ))}\n </HStack>\n )}\n\n {current ? (\n <div\n style={{\n borderRadius: \"var(--radius-container)\",\n overflow: \"hidden\",\n border: \"1px solid var(--color-border)\",\n background: \"var(--color-background-muted, rgba(0,0,0,0.03))\",\n }}\n >\n {mode === \"preview\" ? (\n <div className=\"xicms-embed-consent xicms-embed-preview\" role=\"note\" style={{ height: Math.min(block.height, 360) }}>\n <p>📄 PDF “{current.label || \"document\"}” — wordt op de live site geladen (niet in de editor).</p>\n </div>\n ) : (\n // <object> i.p.v. <iframe>: browsers zonder inline PDF-viewer (veel mobiel) tonen\n // dan de fallback-inhoud met een open/download-knop i.p.v. een leeg vlak.\n <object\n data={`${current.url}#view=FitH`}\n type=\"application/pdf\"\n aria-label={current.label || block.heading || \"PDF\"}\n style={{ border: 0, width: \"100%\", height: block.height, display: \"block\" }}\n >\n <div style={{ display: \"grid\", placeItems: \"center\", gap: \"0.6rem\", padding: \"2.5rem 1rem\", textAlign: \"center\" }}>\n <Text color=\"secondary\">Je browser kan deze PDF niet inline tonen.</Text>\n <a className=\"xicms-btn\" href={current.url} target=\"_blank\" rel=\"noopener noreferrer\">\n 📄 Open “{current.label || \"PDF\"}”\n </a>\n </div>\n </object>\n )}\n </div>\n ) : (\n <Onvoltooid>Dit PDF-blok is nog leeg: kies een document via het tandwiel. Bezoekers zien dit blok niet.</Onvoltooid>\n )}\n\n {block.showDownload && current && (\n <a href={current.url} target=\"_blank\" rel=\"noopener noreferrer\" style={{ color: \"var(--color-accent)\" }}>\n <Text type=\"label\">↗ Open “{current.label || \"PDF\"}” in een nieuw tabblad</Text>\n </a>\n )}\n </VStack>\n );\n}\n\nexport const pdfBlock = defineBlock({\n type: \"pdf\",\n Render: PdfRender,\n label: \"PDF-lezer (menu/document)\",\n icon: \"📄\",\n defaults: () => ({\n heading: \"Onze kaart\",\n documents: [\n { label: \"Lunchkaart\", url: \"\" },\n { label: \"Dinerkaart\", url: \"\" },\n ],\n height: 820,\n showDownload: true,\n }),\n});\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useRef, useState } from "react";
|
|
4
|
-
import { PortfolioBlockSchema } from "../../schema/blocks.js";
|
|
5
4
|
import { defineBlock } from "../registry.js";
|
|
6
5
|
import { VStack, Grid, Heading, Text } from "../ui/index.js";
|
|
7
6
|
import { Img } from "../../media/ImageContext.js";
|
|
@@ -76,7 +75,6 @@ function PortfolioRender({ block }) {
|
|
|
76
75
|
}
|
|
77
76
|
const portfolioBlock = defineBlock({
|
|
78
77
|
type: "portfolio",
|
|
79
|
-
schema: PortfolioBlockSchema,
|
|
80
78
|
Render: PortfolioRender,
|
|
81
79
|
label: "Portfolio / projecten",
|
|
82
80
|
icon: "\u{1F5C2}\uFE0F",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/Portfolio.tsx"],"sourcesContent":["\"use client\";\n\nimport { useRef, useState } from \"react\";\nimport type { z } from \"zod\";\nimport { PortfolioBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { VStack, Grid, Heading, Text } from \"../ui\";\nimport { Img } from \"../../media/ImageContext\";\nimport { LightboxOverlay } from \"./Gallery\";\n\ntype Portfolio = z.infer<typeof PortfolioBlockSchema>;\n\n/** Projecten/cases-grid met categorie-filter en lightbox (hergebruikt de galerij-lightbox). */\nfunction PortfolioRender({ block }: BlockRenderProps<Portfolio>) {\n // _i = index in de OORSPRONKELIJKE array — de lijst wordt gefilterd op imageUrl én op de\n // gekozen categorie, dus de render-index wijst niet naar block.items[i].\n const allItems = (block.items ?? []).map((it, i) => ({ ...it, _i: i })).filter((i) => i.imageUrl);\n const [activeCat, setActiveCat] = useState<string | null>(null);\n const [open, setOpen] = useState(false);\n const [index, setIndex] = useState(0);\n const triggerRef = useRef<HTMLButtonElement | null>(null);\n\n const cats = block.showFilter ? [...new Set(allItems.map((i) => i.category).filter((c): c is string => Boolean(c)))] : [];\n const items = activeCat ? allItems.filter((i) => i.category === activeCat) : allItems;\n if (allItems.length === 0) return null;\n\n const lightboxItems = items.map((i) => ({ url: i.imageUrl, alt: i.title, caption: i.description ? `${i.title} — ${i.description}` : i.title }));\n const close = () => {\n setOpen(false);\n triggerRef.current?.focus({ preventScroll: true });\n };\n\n return (\n <VStack gap={4} as=\"section\" className=\"xicms-portfolio\">\n {(block.heading || block.intro) && (\n <VStack gap={1} hAlign=\"center\" style={{ textAlign: \"center\" }}>\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {block.intro && <Text type=\"large\" color=\"secondary\" data-xicms-edit=\"intro\">{block.intro}</Text>}\n </VStack>\n )}\n {cats.length > 0 && (\n <div className=\"xicms-gallery__filter\" role=\"group\" aria-label=\"Filter op categorie\" style={{ justifyContent: \"center\", marginBottom: 0 }}>\n <button type=\"button\" className=\"xicms-gallery__filterbtn\" aria-pressed={activeCat === null} data-active={activeCat === null ? \"1\" : \"0\"} onClick={() => setActiveCat(null)}>\n Alles\n </button>\n {cats.map((c) => (\n <button key={c} type=\"button\" className=\"xicms-gallery__filterbtn\" aria-pressed={activeCat === c} data-active={activeCat === c ? \"1\" : \"0\"} onClick={() => setActiveCat(c)}>\n {c}\n </button>\n ))}\n </div>\n )}\n <Grid columns={{ minWidth: 240, max: block.columns }} gap={3}>\n {items.map((it, idx) => {\n const media = (\n <Img\n src={it.imageUrl}\n alt={it.title}\n width={720}\n height={540}\n sizes=\"(max-width: 720px) 100vw, 33vw\"\n style={{ width: \"100%\", aspectRatio: \"4 / 3\", objectFit: \"cover\", display: \"block\" }}\n />\n );\n const body = (\n <>\n {media}\n <div className=\"xicms-portfolio__body\">\n {it.category && <span className=\"xicms-portfolio__cat\" data-xicms-edit={`items.${it._i}.category`}>{it.category}</span>}\n <strong className=\"xicms-portfolio__title\" data-xicms-edit={`items.${it._i}.title`}>{it.title}</strong>\n {it.description && <span className=\"xicms-portfolio__desc\" data-xicms-edit={`items.${it._i}.description`}>{it.description}</span>}\n </div>\n </>\n );\n if (it.href) {\n return (\n <a key={idx} className=\"xicms-portfolio__item\" href={it.href}>\n {body}\n </a>\n );\n }\n if (block.lightbox) {\n return (\n <button\n key={idx}\n type=\"button\"\n className=\"xicms-portfolio__item\"\n onClick={(e) => { triggerRef.current = e.currentTarget; setIndex(idx); setOpen(true); }}\n aria-label={`${it.title} vergroten`}\n >\n {body}\n </button>\n );\n }\n return (\n <div key={idx} className=\"xicms-portfolio__item\">\n {body}\n </div>\n );\n })}\n </Grid>\n {block.lightbox && open && (\n <LightboxOverlay items={lightboxItems} index={index} onIndex={setIndex} onClose={close} />\n )}\n </VStack>\n );\n}\n\nexport const portfolioBlock = defineBlock({\n type: \"portfolio\",\n
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/Portfolio.tsx"],"sourcesContent":["\"use client\";\n\nimport { useRef, useState } from \"react\";\nimport type { z } from \"zod\";\nimport type { PortfolioBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { VStack, Grid, Heading, Text } from \"../ui\";\nimport { Img } from \"../../media/ImageContext\";\nimport { LightboxOverlay } from \"./Gallery\";\n\ntype Portfolio = z.infer<typeof PortfolioBlockSchema>;\n\n/** Projecten/cases-grid met categorie-filter en lightbox (hergebruikt de galerij-lightbox). */\nfunction PortfolioRender({ block }: BlockRenderProps<Portfolio>) {\n // _i = index in de OORSPRONKELIJKE array — de lijst wordt gefilterd op imageUrl én op de\n // gekozen categorie, dus de render-index wijst niet naar block.items[i].\n const allItems = (block.items ?? []).map((it, i) => ({ ...it, _i: i })).filter((i) => i.imageUrl);\n const [activeCat, setActiveCat] = useState<string | null>(null);\n const [open, setOpen] = useState(false);\n const [index, setIndex] = useState(0);\n const triggerRef = useRef<HTMLButtonElement | null>(null);\n\n const cats = block.showFilter ? [...new Set(allItems.map((i) => i.category).filter((c): c is string => Boolean(c)))] : [];\n const items = activeCat ? allItems.filter((i) => i.category === activeCat) : allItems;\n if (allItems.length === 0) return null;\n\n const lightboxItems = items.map((i) => ({ url: i.imageUrl, alt: i.title, caption: i.description ? `${i.title} — ${i.description}` : i.title }));\n const close = () => {\n setOpen(false);\n triggerRef.current?.focus({ preventScroll: true });\n };\n\n return (\n <VStack gap={4} as=\"section\" className=\"xicms-portfolio\">\n {(block.heading || block.intro) && (\n <VStack gap={1} hAlign=\"center\" style={{ textAlign: \"center\" }}>\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {block.intro && <Text type=\"large\" color=\"secondary\" data-xicms-edit=\"intro\">{block.intro}</Text>}\n </VStack>\n )}\n {cats.length > 0 && (\n <div className=\"xicms-gallery__filter\" role=\"group\" aria-label=\"Filter op categorie\" style={{ justifyContent: \"center\", marginBottom: 0 }}>\n <button type=\"button\" className=\"xicms-gallery__filterbtn\" aria-pressed={activeCat === null} data-active={activeCat === null ? \"1\" : \"0\"} onClick={() => setActiveCat(null)}>\n Alles\n </button>\n {cats.map((c) => (\n <button key={c} type=\"button\" className=\"xicms-gallery__filterbtn\" aria-pressed={activeCat === c} data-active={activeCat === c ? \"1\" : \"0\"} onClick={() => setActiveCat(c)}>\n {c}\n </button>\n ))}\n </div>\n )}\n <Grid columns={{ minWidth: 240, max: block.columns }} gap={3}>\n {items.map((it, idx) => {\n const media = (\n <Img\n src={it.imageUrl}\n alt={it.title}\n width={720}\n height={540}\n sizes=\"(max-width: 720px) 100vw, 33vw\"\n style={{ width: \"100%\", aspectRatio: \"4 / 3\", objectFit: \"cover\", display: \"block\" }}\n />\n );\n const body = (\n <>\n {media}\n <div className=\"xicms-portfolio__body\">\n {it.category && <span className=\"xicms-portfolio__cat\" data-xicms-edit={`items.${it._i}.category`}>{it.category}</span>}\n <strong className=\"xicms-portfolio__title\" data-xicms-edit={`items.${it._i}.title`}>{it.title}</strong>\n {it.description && <span className=\"xicms-portfolio__desc\" data-xicms-edit={`items.${it._i}.description`}>{it.description}</span>}\n </div>\n </>\n );\n if (it.href) {\n return (\n <a key={idx} className=\"xicms-portfolio__item\" href={it.href}>\n {body}\n </a>\n );\n }\n if (block.lightbox) {\n return (\n <button\n key={idx}\n type=\"button\"\n className=\"xicms-portfolio__item\"\n onClick={(e) => { triggerRef.current = e.currentTarget; setIndex(idx); setOpen(true); }}\n aria-label={`${it.title} vergroten`}\n >\n {body}\n </button>\n );\n }\n return (\n <div key={idx} className=\"xicms-portfolio__item\">\n {body}\n </div>\n );\n })}\n </Grid>\n {block.lightbox && open && (\n <LightboxOverlay items={lightboxItems} index={index} onIndex={setIndex} onClose={close} />\n )}\n </VStack>\n );\n}\n\nexport const portfolioBlock = defineBlock({\n type: \"portfolio\",\n Render: PortfolioRender,\n label: \"Portfolio / projecten\",\n icon: \"🗂️\",\n defaults: () => ({\n heading: \"Ons werk\",\n columns: 3,\n items: [{ imageUrl: \"\", title: \"Project\", category: \"\" }],\n showFilter: true,\n lightbox: true,\n }),\n});\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { z } from "zod";
|
|
2
|
-
import { PricingBlockSchema } from "../../schema/blocks.js";
|
|
2
|
+
import type { PricingBlockSchema } from "../../schema/blocks.js";
|
|
3
3
|
type Pricing = z.infer<typeof PricingBlockSchema>;
|
|
4
4
|
/** "€ 120", "120,50 p/m" → "120.50"; null als er geen bedrag in te herkennen is. */
|
|
5
5
|
export declare function parsePriceAmount(price: string): string | null;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { PricingBlockSchema } from "../../schema/blocks.js";
|
|
4
3
|
import { defineBlock } from "../registry.js";
|
|
5
4
|
import { VStack, Grid, Card, Heading, Text, Btn } from "../ui/index.js";
|
|
6
5
|
import { jsonLdHtml } from "./jsonld.js";
|
|
@@ -71,7 +70,6 @@ function PricingRender({ block }) {
|
|
|
71
70
|
}
|
|
72
71
|
const pricingBlock = defineBlock({
|
|
73
72
|
type: "pricing",
|
|
74
|
-
schema: PricingBlockSchema,
|
|
75
73
|
Render: PricingRender,
|
|
76
74
|
label: "Prijzen",
|
|
77
75
|
icon: "\u{1F4B6}",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/Pricing.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport { PricingBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { VStack, Grid, Card, Heading, Text, Btn } from \"../ui\";\nimport { jsonLdHtml } from \"./jsonld\";\n\ntype Pricing = z.infer<typeof PricingBlockSchema>;\n\n/** \"€ 120\", \"120,50 p/m\" → \"120.50\"; null als er geen bedrag in te herkennen is. */\nexport function parsePriceAmount(price: string): string | null {\n const m = price.replace(/\\./g, \"\").replace(\",\", \".\").match(/(\\d+(?:\\.\\d{1,2})?)/);\n return m ? m[1] : null;\n}\n\nexport function pricingJsonLd(items: Pricing[\"items\"], currency = \"EUR\"): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"ItemList\",\n itemListElement: items\n .filter((it) => it.label)\n .map((it, i) => {\n // schema.org-`price` moet een kaal getal zijn (\"120\" of \"120.50\"); vrije tekst zoals\n // \"€ 120/mnd\" keurt Google af. Geen herkenbaar bedrag → dan geen Offer.\n const amount = parsePriceAmount(it.price);\n return {\n \"@type\": \"ListItem\",\n position: i + 1,\n item: {\n \"@type\": \"Product\",\n name: it.label,\n ...(it.detail ? { description: it.detail } : {}),\n ...(amount ? { offers: { \"@type\": \"Offer\", price: amount, priceCurrency: currency } } : {}),\n },\n };\n }),\n };\n}\n\n/** Prijzen als kaarten met optionele features, uitgelichte tier en per-tier knop (Astryx). */\nfunction PricingRender({ block }: BlockRenderProps<Pricing>) {\n const items = block.items ?? [];\n return (\n <VStack gap={4} as=\"section\" className=\"xicms-pricing\">\n {block.heading && (\n <Heading level={2} justify=\"center\" data-xicms-edit=\"heading\" style={{ textAlign: \"center\" }}>{block.heading}</Heading>\n )}\n <Grid columns={{ minWidth: 240, max: 4 }} gap={4}>\n {items.map((it, i) => (\n <Card\n key={i}\n padding={5}\n height=\"100%\"\n className={`xicms-pricing__card${it.highlighted ? \" xicms-pricing__card--highlighted\" : \"\"}`}\n style={it.highlighted ? { outline: \"2px solid var(--color-accent)\", outlineOffset: -2 } : undefined}\n >\n <VStack gap={2} style={{ height: \"100%\" }}>\n <Text type=\"label\" color=\"secondary\" className=\"xicms-pricing__label\" data-xicms-edit={`items.${i}.label`} style={{ textTransform: \"uppercase\", letterSpacing: \"0.05em\" }}>\n {it.label}\n </Text>\n <VStack gap={0}>\n <Heading level={3} className=\"xicms-pricing__price\" data-xicms-edit={`items.${i}.price`} style={{ fontSize: \"clamp(1.75rem, 4vw, 2.25rem)\", lineHeight: 1.1, fontWeight: 700 }}>\n {it.price}\n </Heading>\n {it.billingPeriod && <Text type=\"supporting\" color=\"secondary\" data-xicms-edit={`items.${i}.billingPeriod`}>{it.billingPeriod}</Text>}\n </VStack>\n {it.detail && <Text color=\"secondary\" data-xicms-edit={`items.${i}.detail`}>{it.detail}</Text>}\n {it.features && it.features.length > 0 && (\n <VStack gap={1} as=\"ul\" style={{ listStyle: \"none\", margin: 0, padding: 0, flex: 1 }}>\n {it.features.map((f, j) => (\n // Echte <li> binnen de <ul> (geldige HTML; screenreaders kondigen de lijst aan).\n // Inline edit op een span BINNEN de tekst: anders zou het ✓-teken mee worden\n // teruggeschreven in de opgeslagen feature (zelfde patroon als Testimonials).\n <li key={j}>\n <Text display=\"block\">✓ <span data-xicms-edit={`items.${i}.features.${j}`}>{f}</span></Text>\n </li>\n ))}\n </VStack>\n )}\n {it.ctaLabel && (\n <Btn label={it.ctaLabel} href={it.ctaHref ?? \"#\"} variant={it.highlighted ? \"primary\" : \"outline\"} editField={`items.${i}.ctaLabel`} />\n )}\n </VStack>\n </Card>\n ))}\n </Grid>\n {block.note && <Text color=\"secondary\" justify=\"center\" style={{ textAlign: \"center\" }} data-xicms-edit=\"note\">{block.note}</Text>}\n {block.jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: jsonLdHtml(pricingJsonLd(items, block.currency)) }}\n />\n )}\n </VStack>\n );\n}\n\nexport const pricingBlock = defineBlock({\n type: \"pricing\",\n
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/Pricing.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport type { PricingBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { VStack, Grid, Card, Heading, Text, Btn } from \"../ui\";\nimport { jsonLdHtml } from \"./jsonld\";\n\ntype Pricing = z.infer<typeof PricingBlockSchema>;\n\n/** \"€ 120\", \"120,50 p/m\" → \"120.50\"; null als er geen bedrag in te herkennen is. */\nexport function parsePriceAmount(price: string): string | null {\n const m = price.replace(/\\./g, \"\").replace(\",\", \".\").match(/(\\d+(?:\\.\\d{1,2})?)/);\n return m ? m[1] : null;\n}\n\nexport function pricingJsonLd(items: Pricing[\"items\"], currency = \"EUR\"): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"ItemList\",\n itemListElement: items\n .filter((it) => it.label)\n .map((it, i) => {\n // schema.org-`price` moet een kaal getal zijn (\"120\" of \"120.50\"); vrije tekst zoals\n // \"€ 120/mnd\" keurt Google af. Geen herkenbaar bedrag → dan geen Offer.\n const amount = parsePriceAmount(it.price);\n return {\n \"@type\": \"ListItem\",\n position: i + 1,\n item: {\n \"@type\": \"Product\",\n name: it.label,\n ...(it.detail ? { description: it.detail } : {}),\n ...(amount ? { offers: { \"@type\": \"Offer\", price: amount, priceCurrency: currency } } : {}),\n },\n };\n }),\n };\n}\n\n/** Prijzen als kaarten met optionele features, uitgelichte tier en per-tier knop (Astryx). */\nfunction PricingRender({ block }: BlockRenderProps<Pricing>) {\n const items = block.items ?? [];\n return (\n <VStack gap={4} as=\"section\" className=\"xicms-pricing\">\n {block.heading && (\n <Heading level={2} justify=\"center\" data-xicms-edit=\"heading\" style={{ textAlign: \"center\" }}>{block.heading}</Heading>\n )}\n <Grid columns={{ minWidth: 240, max: 4 }} gap={4}>\n {items.map((it, i) => (\n <Card\n key={i}\n padding={5}\n height=\"100%\"\n className={`xicms-pricing__card${it.highlighted ? \" xicms-pricing__card--highlighted\" : \"\"}`}\n style={it.highlighted ? { outline: \"2px solid var(--color-accent)\", outlineOffset: -2 } : undefined}\n >\n <VStack gap={2} style={{ height: \"100%\" }}>\n <Text type=\"label\" color=\"secondary\" className=\"xicms-pricing__label\" data-xicms-edit={`items.${i}.label`} style={{ textTransform: \"uppercase\", letterSpacing: \"0.05em\" }}>\n {it.label}\n </Text>\n <VStack gap={0}>\n <Heading level={3} className=\"xicms-pricing__price\" data-xicms-edit={`items.${i}.price`} style={{ fontSize: \"clamp(1.75rem, 4vw, 2.25rem)\", lineHeight: 1.1, fontWeight: 700 }}>\n {it.price}\n </Heading>\n {it.billingPeriod && <Text type=\"supporting\" color=\"secondary\" data-xicms-edit={`items.${i}.billingPeriod`}>{it.billingPeriod}</Text>}\n </VStack>\n {it.detail && <Text color=\"secondary\" data-xicms-edit={`items.${i}.detail`}>{it.detail}</Text>}\n {it.features && it.features.length > 0 && (\n <VStack gap={1} as=\"ul\" style={{ listStyle: \"none\", margin: 0, padding: 0, flex: 1 }}>\n {it.features.map((f, j) => (\n // Echte <li> binnen de <ul> (geldige HTML; screenreaders kondigen de lijst aan).\n // Inline edit op een span BINNEN de tekst: anders zou het ✓-teken mee worden\n // teruggeschreven in de opgeslagen feature (zelfde patroon als Testimonials).\n <li key={j}>\n <Text display=\"block\">✓ <span data-xicms-edit={`items.${i}.features.${j}`}>{f}</span></Text>\n </li>\n ))}\n </VStack>\n )}\n {it.ctaLabel && (\n <Btn label={it.ctaLabel} href={it.ctaHref ?? \"#\"} variant={it.highlighted ? \"primary\" : \"outline\"} editField={`items.${i}.ctaLabel`} />\n )}\n </VStack>\n </Card>\n ))}\n </Grid>\n {block.note && <Text color=\"secondary\" justify=\"center\" style={{ textAlign: \"center\" }} data-xicms-edit=\"note\">{block.note}</Text>}\n {block.jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: jsonLdHtml(pricingJsonLd(items, block.currency)) }}\n />\n )}\n </VStack>\n );\n}\n\nexport const pricingBlock = defineBlock({\n type: \"pricing\",\n Render: PricingRender,\n label: \"Prijzen\",\n icon: \"💶\",\n defaults: () => ({\n heading: \"Prijzen\",\n items: [{ label: \"Pakket\", price: \"€ 00\", billingPeriod: \"/maand\", features: [], highlighted: false }],\n }),\n});\n"],"mappings":"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","names":[]}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { RichTextBlockSchema } from "../../schema/blocks.js";
|
|
3
2
|
import { defineBlock } from "../registry.js";
|
|
4
3
|
function RichTextRender({ block }) {
|
|
5
4
|
return /* @__PURE__ */ jsx("div", { className: "xicms-richtext", dangerouslySetInnerHTML: { __html: block.html } });
|
|
6
5
|
}
|
|
7
6
|
const richTextBlock = defineBlock({
|
|
8
7
|
type: "richText",
|
|
9
|
-
schema: RichTextBlockSchema,
|
|
10
8
|
Render: RichTextRender,
|
|
11
9
|
label: "Tekst",
|
|
12
10
|
icon: "\xB6",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/blocks/builtins/RichTextBlock.tsx"],"sourcesContent":["import type { z } from \"zod\";\nimport { RichTextBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\n\ntype RichText = z.infer<typeof RichTextBlockSchema>;\n\nfunction RichTextRender({ block }: BlockRenderProps<RichText>) {\n // Twee bronnen, twee saneringen — allebei vóór dit punt:\n // - GEPUBLICEERD: `publishPage` haalt de blokken door `sanitizeBlocks`, en de Firestore-rules\n // maken `pages` Functions-only. Wat hier binnenkomt is dus al server-side gesaneerd.\n // - WERKVERSIE: die schrijven editors rechtstreeks naar `drafts`, waar rules geen HTML kunnen\n // saneren. Daarom saneert de editor bij het INLEZEN (admin/lib/sanitizeDraft.ts).\n // Ga hier dus niet vanuit \"opslaan heeft het al gedaan\" — dat gold nooit voor werkversies.\n return <div className=\"xicms-richtext\" dangerouslySetInnerHTML={{ __html: block.html }} />;\n}\n\nexport const richTextBlock = defineBlock({\n type: \"richText\",\n
|
|
1
|
+
{"version":3,"sources":["../../../src/blocks/builtins/RichTextBlock.tsx"],"sourcesContent":["import type { z } from \"zod\";\nimport type { RichTextBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\n\ntype RichText = z.infer<typeof RichTextBlockSchema>;\n\nfunction RichTextRender({ block }: BlockRenderProps<RichText>) {\n // Twee bronnen, twee saneringen — allebei vóór dit punt:\n // - GEPUBLICEERD: `publishPage` haalt de blokken door `sanitizeBlocks`, en de Firestore-rules\n // maken `pages` Functions-only. Wat hier binnenkomt is dus al server-side gesaneerd.\n // - WERKVERSIE: die schrijven editors rechtstreeks naar `drafts`, waar rules geen HTML kunnen\n // saneren. Daarom saneert de editor bij het INLEZEN (admin/lib/sanitizeDraft.ts).\n // Ga hier dus niet vanuit \"opslaan heeft het al gedaan\" — dat gold nooit voor werkversies.\n return <div className=\"xicms-richtext\" dangerouslySetInnerHTML={{ __html: block.html }} />;\n}\n\nexport const richTextBlock = defineBlock({\n type: \"richText\",\n Render: RichTextRender,\n label: \"Tekst\",\n icon: \"¶\",\n defaults: () => ({ html: \"<p></p>\" }),\n});\n"],"mappings":"AAaS;AAXT,SAAS,mBAA0C;AAInD,SAAS,eAAe,EAAE,MAAM,GAA+B;AAO7D,SAAO,oBAAC,SAAI,WAAU,kBAAiB,yBAAyB,EAAE,QAAQ,MAAM,KAAK,GAAG;AAC1F;AAEO,MAAM,gBAAgB,YAAY;AAAA,EACvC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,MAAM,UAAU;AACrC,CAAC;","names":[]}
|