@reblu/site-blocks 0.17.1 → 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 +6 -0
- package/README.md +28 -0
- package/dist/index.cjs +29 -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 +29 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
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
|
+
|
|
3
9
|
## 0.17.1
|
|
4
10
|
|
|
5
11
|
### Patch 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
|
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;
|
|
@@ -13311,7 +13328,7 @@ function buildOptions(headingOffset, images) {
|
|
|
13311
13328
|
const author = authorAttributes(node);
|
|
13312
13329
|
const headingMatch = /^h([1-6])$/.exec(name);
|
|
13313
13330
|
if (headingMatch) {
|
|
13314
|
-
const rendered = Math.min(6, Number(headingMatch[1]) + headingOffset);
|
|
13331
|
+
const rendered = Math.max(1, Math.min(6, Number(headingMatch[1]) + headingOffset));
|
|
13315
13332
|
const Tag = `h${rendered}`;
|
|
13316
13333
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tag, { ...author, className: HEADING_CLASSES[rendered], children: children2() });
|
|
13317
13334
|
}
|
|
@@ -13442,15 +13459,21 @@ function MarkdownRenderer({
|
|
|
13442
13459
|
content,
|
|
13443
13460
|
className,
|
|
13444
13461
|
headingOffset = 0,
|
|
13462
|
+
normalizeHeadings = false,
|
|
13445
13463
|
images
|
|
13446
13464
|
}) {
|
|
13447
13465
|
const html5 = markdownToSafeHtml(content);
|
|
13448
13466
|
if (!html5) return null;
|
|
13449
|
-
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) });
|
|
13450
13468
|
}
|
|
13451
|
-
function SafeHtml({
|
|
13469
|
+
function SafeHtml({
|
|
13470
|
+
html: html5,
|
|
13471
|
+
className,
|
|
13472
|
+
headingOffset = 0,
|
|
13473
|
+
normalizeHeadings = false
|
|
13474
|
+
}) {
|
|
13452
13475
|
if (!html5) return null;
|
|
13453
|
-
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) });
|
|
13454
13477
|
}
|
|
13455
13478
|
|
|
13456
13479
|
// src/components/course-description.tsx
|