@reblu/site-blocks 0.16.0 → 0.17.1
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/CHANGELOG.md +17 -0
- package/README.md +16 -0
- package/dist/index.cjs +96 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -2
- package/dist/index.d.ts +22 -2
- package/dist/index.js +208 -115
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @reblu/site-blocks
|
|
2
2
|
|
|
3
|
+
## 0.17.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 1a3c593: `MarkdownRenderer` and `SafeHtml` now style GFM tables. Tables used to render unstyled (no borders, no padding, no header emphasis); they now get theme-token borders, cell padding, a muted header row with semibold header text and `text-sm` body text, and keep the column alignment from the delimiter row. Each table scrolls horizontally inside its own `overflow-x-auto` wrapper, so a wide table no longer pushes the page sideways on mobile. No props changed.
|
|
8
|
+
|
|
9
|
+
## 0.17.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- a42846c: `MarkdownRenderer` gains an optional `images` prop (`MarkdownImageOptions`): `allowedUrlPrefixes` keeps only images whose normalised URL starts with one of the given prefixes (any other image is dropped), a paragraph holding only an image renders as `<figure>` with the image title as `<figcaption>`, and `render` lets a Next.js consumer render allowed images with `next/image`. Without the prop, images render exactly as before.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated dependencies [7a6f8c1]
|
|
18
|
+
- @reblu/site-contracts@1.18.0
|
|
19
|
+
|
|
3
20
|
## 0.16.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -127,6 +127,22 @@ of the page.** Draft it somewhere that is not the document body.
|
|
|
127
127
|
4" still points at a `4`. The marker style (`type`) and an item's explicit number
|
|
128
128
|
(`value`) survive too.
|
|
129
129
|
|
|
130
|
+
### Tables scroll inside their own box
|
|
131
|
+
|
|
132
|
+
GFM tables (`| Cookie | Purpose | Duration |` plus a `| --- | --- | --- |`
|
|
133
|
+
delimiter row) render with theme-token borders (`border-border`), cell padding,
|
|
134
|
+
a `bg-muted` header row with semibold header text, and `text-sm` body text. The
|
|
135
|
+
alignment written in the delimiter row (`:--`, `:-:`, `--:`) is kept.
|
|
136
|
+
|
|
137
|
+
Each table sits in its own `overflow-x-auto` wrapper, so a table wider than the
|
|
138
|
+
reading column scrolls sideways on a phone while the page itself does not.
|
|
139
|
+
|
|
140
|
+
### Links
|
|
141
|
+
|
|
142
|
+
Internal links (`/privacidad`, `#clause-4`) and `mailto:` links open in the same
|
|
143
|
+
tab. External links (`https://…`, `//…`) open in a new tab and always carry
|
|
144
|
+
`rel="noopener noreferrer"`.
|
|
145
|
+
|
|
130
146
|
### Which authored attributes survive
|
|
131
147
|
|
|
132
148
|
The renderer rebuilds each element to apply the tenant's design tokens and to
|
package/dist/index.cjs
CHANGED
|
@@ -13196,6 +13196,16 @@ var HTML_TO_DOM_OPTIONS = { lowerCaseAttributeNames: false };
|
|
|
13196
13196
|
function parse3(html5, options) {
|
|
13197
13197
|
return (0, import_html_react_parser.domToReact)((0, import_html_react_parser.htmlToDOM)(html5, HTML_TO_DOM_OPTIONS), options);
|
|
13198
13198
|
}
|
|
13199
|
+
function isAllowedImage(src, prefixes) {
|
|
13200
|
+
if (!src) return false;
|
|
13201
|
+
let url;
|
|
13202
|
+
try {
|
|
13203
|
+
url = new URL(src);
|
|
13204
|
+
} catch {
|
|
13205
|
+
return false;
|
|
13206
|
+
}
|
|
13207
|
+
return prefixes.some((prefix) => url.href.startsWith(prefix));
|
|
13208
|
+
}
|
|
13199
13209
|
var HEADING_CLASSES = {
|
|
13200
13210
|
1: "text-3xl font-bold mt-8 mb-4 first:mt-0",
|
|
13201
13211
|
2: "text-2xl font-bold mt-7 mb-3 first:mt-0",
|
|
@@ -13242,13 +13252,60 @@ function authorAttributes(el) {
|
|
|
13242
13252
|
}
|
|
13243
13253
|
return preserved;
|
|
13244
13254
|
}
|
|
13255
|
+
var CELL_ALIGN_CLASSES = {
|
|
13256
|
+
left: "text-left",
|
|
13257
|
+
center: "text-center",
|
|
13258
|
+
right: "text-right"
|
|
13259
|
+
};
|
|
13260
|
+
function cellAlignClass(align) {
|
|
13261
|
+
return align ? CELL_ALIGN_CLASSES[align.toLowerCase()] : void 0;
|
|
13262
|
+
}
|
|
13245
13263
|
function isExternalHref(href) {
|
|
13246
13264
|
return !!href && /^(https?:)?\/\//.test(href);
|
|
13247
13265
|
}
|
|
13248
|
-
function
|
|
13266
|
+
function soleImage(p2) {
|
|
13267
|
+
let image = null;
|
|
13268
|
+
for (const child of p2.children) {
|
|
13269
|
+
if (child instanceof import_html_react_parser.Element) {
|
|
13270
|
+
if (child.name !== "img" || image) return null;
|
|
13271
|
+
image = child;
|
|
13272
|
+
} else if (child.type !== "text" || child.data?.trim()) {
|
|
13273
|
+
return null;
|
|
13274
|
+
}
|
|
13275
|
+
}
|
|
13276
|
+
return image;
|
|
13277
|
+
}
|
|
13278
|
+
function buildOptions(headingOffset, images) {
|
|
13279
|
+
const renderImage = (img) => {
|
|
13280
|
+
if (!images || !isAllowedImage(img.attribs.src, images.allowedUrlPrefixes)) return null;
|
|
13281
|
+
const image = { src: img.attribs.src, alt: img.attribs.alt ?? "" };
|
|
13282
|
+
if (images.render) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: images.render(image) });
|
|
13283
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
13284
|
+
"img",
|
|
13285
|
+
{
|
|
13286
|
+
src: image.src,
|
|
13287
|
+
alt: image.alt,
|
|
13288
|
+
loading: "lazy",
|
|
13289
|
+
decoding: "async",
|
|
13290
|
+
className: "rounded-lg w-full h-auto"
|
|
13291
|
+
}
|
|
13292
|
+
);
|
|
13293
|
+
};
|
|
13249
13294
|
const options = {
|
|
13250
13295
|
replace: (node) => {
|
|
13251
13296
|
if (!(node instanceof import_html_react_parser.Element)) return void 0;
|
|
13297
|
+
if (images && node.name === "p") {
|
|
13298
|
+
const img = soleImage(node);
|
|
13299
|
+
if (img) {
|
|
13300
|
+
const rendered = renderImage(img);
|
|
13301
|
+
if (rendered === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, {});
|
|
13302
|
+
const caption = img.attribs.title?.trim();
|
|
13303
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("figure", { className: "my-8", children: [
|
|
13304
|
+
rendered,
|
|
13305
|
+
caption ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("figcaption", { className: "mt-2 text-center text-sm text-muted-foreground", children: caption }) : null
|
|
13306
|
+
] });
|
|
13307
|
+
}
|
|
13308
|
+
}
|
|
13252
13309
|
const children2 = () => (0, import_html_react_parser.domToReact)(node.children, options);
|
|
13253
13310
|
const { name, attribs } = node;
|
|
13254
13311
|
const author = authorAttributes(node);
|
|
@@ -13307,6 +13364,7 @@ function buildOptions(headingOffset) {
|
|
|
13307
13364
|
);
|
|
13308
13365
|
}
|
|
13309
13366
|
case "img":
|
|
13367
|
+
if (images) return renderImage(node) ?? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, {});
|
|
13310
13368
|
if (!attribs.src) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, {});
|
|
13311
13369
|
return (
|
|
13312
13370
|
// Plain <img> keeps the block portable — a distributable can't assume a
|
|
@@ -13335,6 +13393,36 @@ function buildOptions(headingOffset) {
|
|
|
13335
13393
|
className: "absolute inset-0 h-full w-full border-0"
|
|
13336
13394
|
}
|
|
13337
13395
|
) });
|
|
13396
|
+
// GFM tables (a cookie policy's cookie list, a price table). The table scrolls
|
|
13397
|
+
// inside its own wrapper so a wide one never pushes the page sideways on a phone.
|
|
13398
|
+
case "table":
|
|
13399
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "my-6 w-full overflow-x-auto", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("table", { ...author, className: "w-full border-collapse text-sm", children: children2() }) });
|
|
13400
|
+
case "thead":
|
|
13401
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { ...author, className: "bg-muted", children: children2() });
|
|
13402
|
+
case "tbody":
|
|
13403
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { ...author, children: children2() });
|
|
13404
|
+
case "tr":
|
|
13405
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { ...author, children: children2() });
|
|
13406
|
+
case "th":
|
|
13407
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
13408
|
+
"th",
|
|
13409
|
+
{
|
|
13410
|
+
...author,
|
|
13411
|
+
className: `border border-border px-3 py-2 font-semibold text-foreground ${cellAlignClass(attribs.align) ?? "text-left"}`,
|
|
13412
|
+
children: children2()
|
|
13413
|
+
}
|
|
13414
|
+
);
|
|
13415
|
+
case "td": {
|
|
13416
|
+
const align = cellAlignClass(attribs.align);
|
|
13417
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
13418
|
+
"td",
|
|
13419
|
+
{
|
|
13420
|
+
...author,
|
|
13421
|
+
className: `border border-border px-3 py-2 align-top text-foreground/90${align ? ` ${align}` : ""}`,
|
|
13422
|
+
children: children2()
|
|
13423
|
+
}
|
|
13424
|
+
);
|
|
13425
|
+
}
|
|
13338
13426
|
case "hr":
|
|
13339
13427
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("hr", { ...author, className: "my-8 border-muted-foreground/30" });
|
|
13340
13428
|
case "strong":
|
|
@@ -13350,10 +13438,15 @@ function buildOptions(headingOffset) {
|
|
|
13350
13438
|
};
|
|
13351
13439
|
return options;
|
|
13352
13440
|
}
|
|
13353
|
-
function MarkdownRenderer({
|
|
13441
|
+
function MarkdownRenderer({
|
|
13442
|
+
content,
|
|
13443
|
+
className,
|
|
13444
|
+
headingOffset = 0,
|
|
13445
|
+
images
|
|
13446
|
+
}) {
|
|
13354
13447
|
const html5 = markdownToSafeHtml(content);
|
|
13355
13448
|
if (!html5) return null;
|
|
13356
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: slotClassName("max-w-none", "", className), "data-slot": "markdown", children: parse3(html5, buildOptions(headingOffset)) });
|
|
13449
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: slotClassName("max-w-none", "", className), "data-slot": "markdown", children: parse3(html5, buildOptions(headingOffset, images)) });
|
|
13357
13450
|
}
|
|
13358
13451
|
function SafeHtml({ html: html5, className, headingOffset = 0 }) {
|
|
13359
13452
|
if (!html5) return null;
|