@dogsbay/render-html 0.2.0-beta.127 → 0.2.0-beta.129
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/capabilities.js +1 -1
- package/dist/render.js +50 -9
- package/package.json +4 -4
package/dist/capabilities.js
CHANGED
|
@@ -40,7 +40,7 @@ export const RENDER_HTML_CAPABILITIES = [
|
|
|
40
40
|
note: "Deliberately no embed: the page must work offline and make no request to YouTube. The Astro pipeline embeds on click.",
|
|
41
41
|
},
|
|
42
42
|
// ── Components ───────────────────────────────────────────
|
|
43
|
-
{ feature: "callout", support: "full", rendersAs: "Alert markup (12 variants, aliases mapped); collapsible → CollapsibleAlert `<details>`" },
|
|
43
|
+
{ feature: "callout", support: "full", rendersAs: "Alert markup (12 variants, aliases mapped, a custom `icon`); collapsible → CollapsibleAlert `<details>`" },
|
|
44
44
|
{ feature: "details", support: "full", rendersAs: "CollapsibleAlert `<details>`, as the Astro serializer" },
|
|
45
45
|
{ feature: "details-sheet", support: "full", rendersAs: "`<db-details-sheet>` around the CollapsibleAlert `<details>` (a right-hand native `<dialog>` when the element script runs; a collapsible block with `js: none`)" },
|
|
46
46
|
{ feature: "theme", support: "full", rendersAs: "a `<div class=\"dark …\" data-theme>` (THEME_SCOPES); an unknown name keeps the content and is reported" },
|
package/dist/render.js
CHANGED
|
@@ -98,6 +98,10 @@ function inline(n, ctx) {
|
|
|
98
98
|
case "code":
|
|
99
99
|
return decorate(`<code>${esc(n.text)}</code>`, n);
|
|
100
100
|
case "link": {
|
|
101
|
+
// Inside a whole-card link, a link is its text: a browser ends the outer <a> at an inner one,
|
|
102
|
+
// and the rest of the card falls out of the link
|
|
103
|
+
if (ctx.inLink)
|
|
104
|
+
return renderInline(n.children, ctx);
|
|
101
105
|
const href = linkUrl(n.href, ctx);
|
|
102
106
|
const external = /^[a-z][a-z0-9+.-]*:|^\/\//i.test(href) && !href.startsWith("mailto:");
|
|
103
107
|
// An inline `:button`: Button.astro's classes at its small size, as the Astro pipeline renders it.
|
|
@@ -749,23 +753,52 @@ function calloutLabel(type, ctx) {
|
|
|
749
753
|
}
|
|
750
754
|
function callout(n, ctx) {
|
|
751
755
|
const p = (n.props ?? {});
|
|
752
|
-
|
|
756
|
+
// `variant` first, as format-astro reads it (Starlight and MkDocs importers write it)
|
|
757
|
+
const type = (str(p.variant) ?? str(p.type) ?? "note").toLowerCase();
|
|
753
758
|
const variant = ALERT_VARIANT[VARIANT_ALIAS[type] ?? type] ? VARIANT_ALIAS[type] ?? type : "note";
|
|
754
759
|
const title = str(p.title);
|
|
755
|
-
const titleHtml = title ?
|
|
760
|
+
const titleHtml = title ? titleMarkup(title, ctx) : esc(calloutLabel(type, ctx));
|
|
756
761
|
const body = renderBlocks(n.children, ctx);
|
|
757
|
-
if (p.collapsible
|
|
758
|
-
const open = p.collapsed === false || p.collapsed === "false" || p.open
|
|
759
|
-
return collapsibleAlert(variant, titleHtml, body, open, p);
|
|
762
|
+
if (flagOn(p.collapsible) || p.collapsed != null) {
|
|
763
|
+
const open = p.collapsed === false || p.collapsed === "false" || flagOn(p.open);
|
|
764
|
+
return collapsibleAlert(variant, titleHtml, body, open, { ...p, "data-type": p["data-type"] ?? type }, ctx);
|
|
760
765
|
}
|
|
761
766
|
return (`<div${attrs({ ...passthrough(p, `${ALERT_BASE} ${ALERT_VARIANT[variant]} my-4`), "data-variant": variant, "data-type": type, role: "note" })}>` +
|
|
762
|
-
(
|
|
767
|
+
alertIcon(variant, p, ctx) +
|
|
763
768
|
`<div data-part="title" class="mb-1 text-sm font-semibold leading-none tracking-tight">${titleHtml}</div>` +
|
|
764
769
|
`<div class="text-sm [&_p]:leading-relaxed">${body}</div></div>\n`);
|
|
765
770
|
}
|
|
766
|
-
|
|
771
|
+
/** Formatting tags with no attributes, the HTML a title keeps (format-astro `TITLE_TAG`). */
|
|
772
|
+
const TITLE_TAG = /^<\/?(?:code|kbd|b|strong|i|em|s|del|mark|sub|sup|br)\s*\/?>$/i;
|
|
773
|
+
/**
|
|
774
|
+
* A callout or details title: inline Markdown, and raw HTML other than bare formatting tags as text
|
|
775
|
+
* (format-astro `titleInline`). `Map<K, V>` from an importer lost words as HTML.
|
|
776
|
+
*/
|
|
777
|
+
function titleMarkup(title, ctx) {
|
|
778
|
+
return renderInline(parseInline(title).map((n) => (n.type === "html-inline" && !TITLE_TAG.test(n.html.trim()) ? { type: "text", text: n.html } : n)), ctx);
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* A presence flag: `open`, `open=""`, `open="true"` and `true` are on; absent, `false` and `"false"` are
|
|
782
|
+
* off. format-astro reads flags with the same rule (`flagOn`); `open="false"` used to open here.
|
|
783
|
+
*/
|
|
784
|
+
function flagOn(v) {
|
|
785
|
+
return v !== undefined && v !== null && v !== false && v !== "false";
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* The icon an alert draws: the author's `icon` when it resolves (Alert.astro's rule, through the same
|
|
789
|
+
* resolver as `:icon`), else the variant's. An `icon` that resolves to nothing is reported, as `:icon` is.
|
|
790
|
+
*/
|
|
791
|
+
function alertIcon(variant, p, ctx) {
|
|
792
|
+
const name = str(p.icon);
|
|
793
|
+
const custom = name ? resolveIconMarkup(name, ctx) : undefined;
|
|
794
|
+
if (name && custom === undefined)
|
|
795
|
+
(ctx.missingIcons ??= new Set()).add(name);
|
|
796
|
+
const svg = custom ?? ALERT_ICONS[variant] ?? "";
|
|
797
|
+
return svg.startsWith("<svg") && !/^<svg[^>]*aria-hidden/.test(svg) ? svg.replace("<svg ", '<svg aria-hidden="true" focusable="false" ') : svg;
|
|
798
|
+
}
|
|
799
|
+
function collapsibleAlert(variant, titleHtml, body, open, p, ctx) {
|
|
767
800
|
const c = COLLAPSIBLE[variant] ?? COLLAPSIBLE.note;
|
|
768
|
-
const icon = (
|
|
801
|
+
const icon = alertIcon(variant, p, ctx);
|
|
769
802
|
return (`<details${attrs({ ...passthrough(p, `group/collapsible relative my-4 w-full overflow-hidden rounded-lg border [&>summary]:list-none [&>summary::-webkit-details-marker]:hidden ${c.border}`), "data-variant": variant, open })}>` +
|
|
770
803
|
`<summary class="flex cursor-pointer select-none items-center gap-2 px-4 py-3 text-sm font-semibold ${c.summary}"><span class="shrink-0 [&>svg]:size-4 ${c.icon}">${icon}</span><span class="flex-1 text-foreground" data-part="title">${titleHtml}</span><span class="shrink-0 text-muted-foreground transition-transform duration-200 group-open/collapsible:rotate-180">${ALERT_CHEVRON.replace("<svg ", '<svg aria-hidden="true" focusable="false" ')}</span></summary>` +
|
|
771
804
|
`<div class="px-4 py-3 text-sm [&_p]:leading-relaxed ${c.body}">${body}</div></details>\n`);
|
|
@@ -895,7 +928,7 @@ function details(n, ctx) {
|
|
|
895
928
|
// `variant` as documented, then `type`, as the Astro pipeline reads it.
|
|
896
929
|
const type = (str(p.variant) ?? str(p.type) ?? "").toLowerCase();
|
|
897
930
|
const variant = ALERT_VARIANT[VARIANT_ALIAS[type] ?? type] ? VARIANT_ALIAS[type] ?? type : "note";
|
|
898
|
-
const html = collapsibleAlert(variant,
|
|
931
|
+
const html = collapsibleAlert(variant, titleMarkup(title, ctx), renderBlocks(n.children, ctx), flagOn(p.open), p, ctx);
|
|
899
932
|
// `sheet`: the same details, which <db-details-sheet> opens as a side
|
|
900
933
|
// sheet when its script runs (and which stays a details when it does not)
|
|
901
934
|
if (!isDetailsSheet(p))
|
|
@@ -930,6 +963,14 @@ function insideLink(yes, ctx, fn) {
|
|
|
930
963
|
* gives, so pages match the Astro pipeline's. Without one: the built-in Lucide set. An icon neither knows is
|
|
931
964
|
* recorded for a build warning and leaves a marked, empty span.
|
|
932
965
|
*/
|
|
966
|
+
/** An icon's markup without a wrapper: the host's resolver (an SVG, or an emoji in Alert.astro's span), then the built-in set. */
|
|
967
|
+
function resolveIconMarkup(name, ctx) {
|
|
968
|
+
const resolved = ctx.icons?.(name);
|
|
969
|
+
if (resolved)
|
|
970
|
+
return resolved.startsWith("<") ? resolved : `<span aria-hidden="true" class="inline-block w-[1em] text-center leading-none">${esc(resolved)}</span>`;
|
|
971
|
+
const builtin = name.replace(/^lucide:/, "");
|
|
972
|
+
return hasIcon(builtin) ? icon(builtin) : undefined;
|
|
973
|
+
}
|
|
933
974
|
function namedIcon(name, cls, ctx) {
|
|
934
975
|
const resolved = ctx.icons?.(name);
|
|
935
976
|
if (resolved)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dogsbay/render-html",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.129",
|
|
4
4
|
"description": "Dogsbay MD TreeNode → HTML with the Astro components' markup and classes. No highlighter, math or CSS compiler unless you pass one; Worker- and browser-safe",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"htmlparser2": "^10.1.0",
|
|
32
32
|
"markdown-it": "^14.3.0",
|
|
33
|
-
"@dogsbay/format-dogsbay-md": "0.2.0-beta.
|
|
34
|
-
"@dogsbay/serialize-core": "0.2.0-beta.
|
|
35
|
-
"@dogsbay/types": "0.2.0-beta.
|
|
33
|
+
"@dogsbay/format-dogsbay-md": "0.2.0-beta.129",
|
|
34
|
+
"@dogsbay/serialize-core": "0.2.0-beta.129",
|
|
35
|
+
"@dogsbay/types": "0.2.0-beta.129"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"katex": "^0.16.0"
|