@reblu/site-blocks 0.17.0 → 0.18.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/CHANGELOG.md +12 -0
- package/README.md +44 -0
- package/dist/index.cjs +67 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.js +67 -6
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @reblu/site-blocks
|
|
2
2
|
|
|
3
|
+
## 0.18.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 801983f: New opt-in `normalizeHeadings` prop on `MarkdownRenderer` and `SafeHtml`: the body's shallowest heading lands at `1 + headingOffset`, every other heading keeps its depth relative to it, and levels clamp at h6. Standard Markdown (`##` sections) then renders `<h2>` under the page `<h1>` just like the Reblu convention (`#` sections), which renders byte-identically. Use it where the body sits directly under the page `<h1>` (a blog post, a legal document); leave it off where the body sits under its own section heading (a product or course description). Default `false`: nothing changes until a SITE opts in.
|
|
8
|
+
|
|
9
|
+
## 0.17.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 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.
|
|
14
|
+
|
|
3
15
|
## 0.17.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -105,6 +105,34 @@ To force a visible break, use any of:
|
|
|
105
105
|
| A hard break inside one paragraph | Two spaces at the end of the first line |
|
|
106
106
|
| A hard break, visibly | A trailing `\` at the end of the first line |
|
|
107
107
|
|
|
108
|
+
### Heading levels: `headingOffset` and `normalizeHeadings`
|
|
109
|
+
|
|
110
|
+
`headingOffset` shifts every heading by a fixed amount: with `headingOffset={1}`
|
|
111
|
+
under the page's own `<h1>`, `#` renders `<h2>` and `##` renders `<h3>`. That
|
|
112
|
+
assumes the Reblu convention — `#` is a section — and standard Markdown pasted
|
|
113
|
+
or imported from elsewhere (`##` sections) then renders `<h3>` with no `<h2>`.
|
|
114
|
+
|
|
115
|
+
`normalizeHeadings` lands the body's **shallowest** heading at
|
|
116
|
+
`1 + headingOffset` instead, keeps every other heading's depth relative to it,
|
|
117
|
+
and clamps at h6:
|
|
118
|
+
|
|
119
|
+
| Body | `headingOffset={1}` | `+ normalizeHeadings` |
|
|
120
|
+
| --------------------------- | ------------------- | --------------------- |
|
|
121
|
+
| `#` / `##` (convention) | h2 / h3 | h2 / h3 (identical) |
|
|
122
|
+
| `##` / `###` (standard) | h3 / h4 | h2 / h3 |
|
|
123
|
+
| no headings | — | — (identical) |
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
// A blog post or legal document directly under the page <h1>:
|
|
127
|
+
<MarkdownRenderer content={post.content} headingOffset={1} normalizeHeadings />
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
It is **opt-in**, because the renderer cannot see what sits above the body.
|
|
131
|
+
Leave it off where the body sits under its own section heading — e.g. a product
|
|
132
|
+
description under «Sobre este producto» (`h2`), whose `##` subsections must stay
|
|
133
|
+
one level below that heading. `SafeHtml` takes the same prop; `CourseDescription`
|
|
134
|
+
does not use it (its `##` sits below its own heading by contract).
|
|
135
|
+
|
|
108
136
|
### `hidden` hides a block — it does not withhold it
|
|
109
137
|
|
|
110
138
|
An element marked `hidden` is not rendered and is not announced to a screen
|
|
@@ -127,6 +155,22 @@ of the page.** Draft it somewhere that is not the document body.
|
|
|
127
155
|
4" still points at a `4`. The marker style (`type`) and an item's explicit number
|
|
128
156
|
(`value`) survive too.
|
|
129
157
|
|
|
158
|
+
### Tables scroll inside their own box
|
|
159
|
+
|
|
160
|
+
GFM tables (`| Cookie | Purpose | Duration |` plus a `| --- | --- | --- |`
|
|
161
|
+
delimiter row) render with theme-token borders (`border-border`), cell padding,
|
|
162
|
+
a `bg-muted` header row with semibold header text, and `text-sm` body text. The
|
|
163
|
+
alignment written in the delimiter row (`:--`, `:-:`, `--:`) is kept.
|
|
164
|
+
|
|
165
|
+
Each table sits in its own `overflow-x-auto` wrapper, so a table wider than the
|
|
166
|
+
reading column scrolls sideways on a phone while the page itself does not.
|
|
167
|
+
|
|
168
|
+
### Links
|
|
169
|
+
|
|
170
|
+
Internal links (`/privacidad`, `#clause-4`) and `mailto:` links open in the same
|
|
171
|
+
tab. External links (`https://…`, `//…`) open in a new tab and always carry
|
|
172
|
+
`rel="noopener noreferrer"`.
|
|
173
|
+
|
|
130
174
|
### Which authored attributes survive
|
|
131
175
|
|
|
132
176
|
The renderer rebuilds each element to apply the tenant's design tokens and to
|
package/dist/index.cjs
CHANGED
|
@@ -13193,8 +13193,25 @@ function slotClassName(structural, defaults, consumer) {
|
|
|
13193
13193
|
// src/components/markdown-renderer.tsx
|
|
13194
13194
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
13195
13195
|
var HTML_TO_DOM_OPTIONS = { lowerCaseAttributeNames: false };
|
|
13196
|
-
function
|
|
13197
|
-
|
|
13196
|
+
function shallowestHeadingLevel(nodes) {
|
|
13197
|
+
let shallowest = null;
|
|
13198
|
+
for (const node of nodes) {
|
|
13199
|
+
if (!(node instanceof import_html_react_parser.Element)) continue;
|
|
13200
|
+
const match = /^h([1-6])$/.exec(node.name);
|
|
13201
|
+
const level = match ? Number(match[1]) : shallowestHeadingLevel(node.children);
|
|
13202
|
+
if (level !== null && (shallowest === null || level < shallowest)) shallowest = level;
|
|
13203
|
+
}
|
|
13204
|
+
return shallowest;
|
|
13205
|
+
}
|
|
13206
|
+
function headingShift(nodes, headingOffset, normalize2) {
|
|
13207
|
+
if (!normalize2) return headingOffset;
|
|
13208
|
+
const shallowest = shallowestHeadingLevel(nodes);
|
|
13209
|
+
return shallowest === null ? headingOffset : 1 + headingOffset - shallowest;
|
|
13210
|
+
}
|
|
13211
|
+
function parse3(html5, headingOffset, normalizeHeadings, images) {
|
|
13212
|
+
const nodes = (0, import_html_react_parser.htmlToDOM)(html5, HTML_TO_DOM_OPTIONS);
|
|
13213
|
+
const shift = headingShift(nodes, headingOffset, normalizeHeadings);
|
|
13214
|
+
return (0, import_html_react_parser.domToReact)(nodes, buildOptions(shift, images));
|
|
13198
13215
|
}
|
|
13199
13216
|
function isAllowedImage(src, prefixes) {
|
|
13200
13217
|
if (!src) return false;
|
|
@@ -13252,6 +13269,14 @@ function authorAttributes(el) {
|
|
|
13252
13269
|
}
|
|
13253
13270
|
return preserved;
|
|
13254
13271
|
}
|
|
13272
|
+
var CELL_ALIGN_CLASSES = {
|
|
13273
|
+
left: "text-left",
|
|
13274
|
+
center: "text-center",
|
|
13275
|
+
right: "text-right"
|
|
13276
|
+
};
|
|
13277
|
+
function cellAlignClass(align) {
|
|
13278
|
+
return align ? CELL_ALIGN_CLASSES[align.toLowerCase()] : void 0;
|
|
13279
|
+
}
|
|
13255
13280
|
function isExternalHref(href) {
|
|
13256
13281
|
return !!href && /^(https?:)?\/\//.test(href);
|
|
13257
13282
|
}
|
|
@@ -13303,7 +13328,7 @@ function buildOptions(headingOffset, images) {
|
|
|
13303
13328
|
const author = authorAttributes(node);
|
|
13304
13329
|
const headingMatch = /^h([1-6])$/.exec(name);
|
|
13305
13330
|
if (headingMatch) {
|
|
13306
|
-
const rendered = Math.min(6, Number(headingMatch[1]) + headingOffset);
|
|
13331
|
+
const rendered = Math.max(1, Math.min(6, Number(headingMatch[1]) + headingOffset));
|
|
13307
13332
|
const Tag = `h${rendered}`;
|
|
13308
13333
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tag, { ...author, className: HEADING_CLASSES[rendered], children: children2() });
|
|
13309
13334
|
}
|
|
@@ -13385,6 +13410,36 @@ function buildOptions(headingOffset, images) {
|
|
|
13385
13410
|
className: "absolute inset-0 h-full w-full border-0"
|
|
13386
13411
|
}
|
|
13387
13412
|
) });
|
|
13413
|
+
// GFM tables (a cookie policy's cookie list, a price table). The table scrolls
|
|
13414
|
+
// inside its own wrapper so a wide one never pushes the page sideways on a phone.
|
|
13415
|
+
case "table":
|
|
13416
|
+
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() }) });
|
|
13417
|
+
case "thead":
|
|
13418
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { ...author, className: "bg-muted", children: children2() });
|
|
13419
|
+
case "tbody":
|
|
13420
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { ...author, children: children2() });
|
|
13421
|
+
case "tr":
|
|
13422
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { ...author, children: children2() });
|
|
13423
|
+
case "th":
|
|
13424
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
13425
|
+
"th",
|
|
13426
|
+
{
|
|
13427
|
+
...author,
|
|
13428
|
+
className: `border border-border px-3 py-2 font-semibold text-foreground ${cellAlignClass(attribs.align) ?? "text-left"}`,
|
|
13429
|
+
children: children2()
|
|
13430
|
+
}
|
|
13431
|
+
);
|
|
13432
|
+
case "td": {
|
|
13433
|
+
const align = cellAlignClass(attribs.align);
|
|
13434
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
13435
|
+
"td",
|
|
13436
|
+
{
|
|
13437
|
+
...author,
|
|
13438
|
+
className: `border border-border px-3 py-2 align-top text-foreground/90${align ? ` ${align}` : ""}`,
|
|
13439
|
+
children: children2()
|
|
13440
|
+
}
|
|
13441
|
+
);
|
|
13442
|
+
}
|
|
13388
13443
|
case "hr":
|
|
13389
13444
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("hr", { ...author, className: "my-8 border-muted-foreground/30" });
|
|
13390
13445
|
case "strong":
|
|
@@ -13404,15 +13459,21 @@ function MarkdownRenderer({
|
|
|
13404
13459
|
content,
|
|
13405
13460
|
className,
|
|
13406
13461
|
headingOffset = 0,
|
|
13462
|
+
normalizeHeadings = false,
|
|
13407
13463
|
images
|
|
13408
13464
|
}) {
|
|
13409
13465
|
const html5 = markdownToSafeHtml(content);
|
|
13410
13466
|
if (!html5) return null;
|
|
13411
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: slotClassName("max-w-none", "", className), "data-slot": "markdown", children: parse3(html5,
|
|
13467
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: slotClassName("max-w-none", "", className), "data-slot": "markdown", children: parse3(html5, headingOffset, normalizeHeadings, images) });
|
|
13412
13468
|
}
|
|
13413
|
-
function SafeHtml({
|
|
13469
|
+
function SafeHtml({
|
|
13470
|
+
html: html5,
|
|
13471
|
+
className,
|
|
13472
|
+
headingOffset = 0,
|
|
13473
|
+
normalizeHeadings = false
|
|
13474
|
+
}) {
|
|
13414
13475
|
if (!html5) return null;
|
|
13415
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: slotClassName("max-w-none", "", className), "data-slot": "safe-html", children: parse3(html5,
|
|
13476
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: slotClassName("max-w-none", "", className), "data-slot": "safe-html", children: parse3(html5, headingOffset, normalizeHeadings) });
|
|
13416
13477
|
}
|
|
13417
13478
|
|
|
13418
13479
|
// src/components/course-description.tsx
|