@dogsbay/render-html 0.2.0-beta.121 → 0.2.0-beta.122

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/css/docs.css CHANGED
@@ -91,7 +91,11 @@
91
91
  & .heading-anchor { text-decoration: none; opacity: 0; margin-right: 0.25rem; transition: opacity 0.2s; }
92
92
  & h1:hover .heading-anchor, & h2:hover .heading-anchor, & h3:hover .heading-anchor, & h4:hover .heading-anchor { opacity: 0.4; }
93
93
 
94
- & details:not(.dba-api *) { border: 1px solid var(--border); border-radius: 0.5rem; padding: 1rem; margin: 1rem 0; }
94
+ /* Raw HTML <details> only. CollapsibleAlert (:::details, collapsible
95
+ callouts) is a <details> too, with its own border and spacing; it
96
+ carries data-variant, and without the exclusion it got a second border
97
+ and 1rem of padding around its title bar. */
98
+ & details:not(.dba-api *):not([data-variant]) { border: 1px solid var(--border); border-radius: 0.5rem; padding: 1rem; margin: 1rem 0; }
95
99
  & summary:not(.dba-api *) { cursor: pointer; font-weight: 600; }
96
100
 
97
101
  /* mark is emitted by the highlight directive AND can arrive as raw HTML
@@ -0,0 +1,25 @@
1
+ /**
2
+ * What the HTML renderer can and cannot represent — a CONTRACT (the
3
+ * format-docusaurus pattern): `validateCapabilities` rejects any non-full
4
+ * entry without a note, and `tests/capabilities.test.ts` asserts every
5
+ * `MATRIX_ROWS` type is declared exactly once. Measured against `render.ts`
6
+ * and `api.ts` by rendering real nodes, not read off the switch.
7
+ *
8
+ * This renderer has no export ledger. What it does record goes to the
9
+ * caller's RenderContext: `unknown` (node types and directives it has no
10
+ * case for, the example fallback panel, unresolved includes), `missingIcons`
11
+ * and `unsafeUrls`. dogsbay-ssg and @dogsbay/render turn those into build
12
+ * warnings. A degradation that lands in none of them is SILENT, and the
13
+ * notes below say so where it happens.
14
+ *
15
+ * Several cells depend on what the caller passes: math is `full` only with a
16
+ * `math` function (KaTeX), a diagram only with an `svg` prop or a `diagram`
17
+ * renderer, an icon only when the built-in Lucide set or the `icons`
18
+ * resolver knows it. Those are declared `partial`, because the renderer on
19
+ * its own does not guarantee them.
20
+ *
21
+ * Support levels: full — renders faithfully, with the Astro components'
22
+ * markup · partial — survives with reduced fidelity · none — dropped.
23
+ */
24
+ import type { CapabilityDeclaration } from "@dogsbay/serialize-core";
25
+ export declare const RENDER_HTML_CAPABILITIES: CapabilityDeclaration[];
@@ -0,0 +1,115 @@
1
+ export const RENDER_HTML_CAPABILITIES = [
2
+ // ── Blocks ───────────────────────────────────────────────
3
+ { feature: "heading", support: "full", rendersAs: "`<hN>` with a unique id and a hover pilcrow; h2/h3 join the TOC" },
4
+ { feature: "paragraph", support: "full", rendersAs: "`<p>` (a lone titled image becomes a figure with `imageCaptions: \"title\"`)" },
5
+ { feature: "blockquote", support: "full", rendersAs: "`<blockquote>`" },
6
+ { feature: "unordered-list", support: "full", rendersAs: "`<ul>`, task items as disabled checkboxes" },
7
+ { feature: "ordered-list", support: "full", rendersAs: "`<ol>` with `start`" },
8
+ { feature: "hr", support: "full", rendersAs: "`<hr>`" },
9
+ { feature: "table", support: "full", rendersAs: "scrollable `<table>` with caption, alignment, colspan/rowspan" },
10
+ {
11
+ feature: "code",
12
+ support: "full",
13
+ rendersAs: "`<db-code-block>` (CodeBlock / CodeRich markup); highlighted only when a Highlighter is passed, plain text otherwise",
14
+ },
15
+ {
16
+ feature: "math-block",
17
+ support: "partial",
18
+ rendersAs: "KaTeX HTML + MathML when `ctx.math` is passed",
19
+ note: "Without a math renderer the LaTeX source is shown in a `<pre><code class=\"math-display\">` — readable, not typeset. Not recorded.",
20
+ },
21
+ { feature: "deflist", support: "full", rendersAs: "`<dl>` / `<dt>` / `<dd>`" },
22
+ { feature: "figure", support: "full", rendersAs: "`<figure>` with `<figcaption>`" },
23
+ { feature: "footnote-list", support: "full", rendersAs: "`<section role=\"doc-endnotes\">` with backlinks" },
24
+ {
25
+ feature: "html",
26
+ support: "full",
27
+ rendersAs: "the HTML as authored, links rewritten, script URLs refused (reported in `unsafeUrls`); filtered by `ctx.rawHtml` when the host passes a sanitizer",
28
+ },
29
+ { feature: "html-container", support: "full", rendersAs: "its HTML as authored, or a `div.db-html-container` around its children" },
30
+ {
31
+ feature: "diagram",
32
+ support: "partial",
33
+ rendersAs: "`<figure class=\"diagram\">` around the SVG",
34
+ note: "Rendered only when the node carries an `svg` prop or the host passes `ctx.diagram`; otherwise the diagram SOURCE is shown as a code block. Not recorded.",
35
+ },
36
+ {
37
+ feature: "youtube",
38
+ support: "partial",
39
+ rendersAs: "a link card to youtube.com/watch (optional local poster image)",
40
+ note: "Deliberately no embed: the page must work offline and make no request to YouTube. The Astro pipeline embeds on click.",
41
+ },
42
+ // ── Components ───────────────────────────────────────────
43
+ { feature: "callout", support: "full", rendersAs: "Alert markup (12 variants, aliases mapped); collapsible → CollapsibleAlert `<details>`" },
44
+ { feature: "details", support: "full", rendersAs: "CollapsibleAlert `<details>`, as the Astro serializer" },
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
+ {
47
+ feature: "tabs",
48
+ support: "full",
49
+ rendersAs: "`<db-tabs>` with Tabs.astro markup, synced groups by label; a child that is not a tab stays in reading order around the group",
50
+ },
51
+ { feature: "steps", support: "full", rendersAs: "`<ol class=\"steps-list\">`; a non-step child is wrapped in its own `<li>`" },
52
+ {
53
+ feature: "accordion",
54
+ support: "full",
55
+ rendersAs: "`<db-accordion>` with Accordion.astro markup; a child that is not an item stays in reading order around the group",
56
+ },
57
+ { feature: "cards", support: "full", rendersAs: "a responsive card grid `<div>`" },
58
+ { feature: "card", support: "full", rendersAs: "Card.astro markup; icon, title, description, body; linked when `href` is set" },
59
+ { feature: "link-card", support: "full", rendersAs: "LinkCard.astro markup; a body of several blocks renders as a linked Card" },
60
+ { feature: "grid", support: "full", rendersAs: "a 1–6 column grid; grid-item spans and placeholder cells" },
61
+ { feature: "link-button", support: "full", rendersAs: "an `<a>` with Button.astro's classes and variant" },
62
+ { feature: "related", support: "full", rendersAs: "`<nav class=\"related-resources\">`" },
63
+ {
64
+ feature: "example",
65
+ support: "partial",
66
+ rendersAs: "a figure: the rendered content, then its source in a collapsed panel",
67
+ note: "The Astro component's optional plain-Markdown panel (`fallback=true`) needs a second Markdown renderer and is left out; reported in `ctx.unknown`.",
68
+ },
69
+ {
70
+ feature: "container",
71
+ support: "partial",
72
+ rendersAs: "`<div data-directive=\"name\">` with its title and content",
73
+ note: "A `:::name` directive no renderer knows: content kept, but no component behaviour or styling. Reported in `ctx.unknown` as `:::name`.",
74
+ },
75
+ {
76
+ feature: "annotation-list",
77
+ support: "partial",
78
+ rendersAs: "a tinted box holding the annotation list (format-astro's wrapBlock)",
79
+ note: "The annotations are not attached to the code block's `(1)` markers as pop-ups; they read as a list below the code, as in the Astro pipeline.",
80
+ },
81
+ // ── API reference ────────────────────────────────────────
82
+ { feature: "endpoint", support: "full", rendersAs: "ApiLayout + EndpointCard + code panel; descriptions are CommonMark with raw HTML off" },
83
+ { feature: "api-doc", support: "full", rendersAs: "ApiDoc: prose with its code blocks split out" },
84
+ { feature: "api-class", support: "full", rendersAs: "ApiSymbol: kind badge, heading, signature, bases, body" },
85
+ { feature: "api-member", support: "full", rendersAs: "ApiSymbol, one level below its parent" },
86
+ { feature: "api-params", support: "full", rendersAs: "ApiParams two-column table" },
87
+ { feature: "api-source", support: "full", rendersAs: "ApiSource: a `<details>` with the source code" },
88
+ // ── Inline ───────────────────────────────────────────────
89
+ { feature: "link", support: "full", rendersAs: "`<a>` via `ctx.link`; inline buttons with Button.astro classes; script URLs become `#` and are reported" },
90
+ { feature: "image", support: "full", rendersAs: "`<img>` via `ctx.asset`, lazy; optional zoom marker" },
91
+ { feature: "kbd", support: "full", rendersAs: "one `<kbd>` keycap per key, joined by `+`" },
92
+ {
93
+ feature: "math",
94
+ support: "partial",
95
+ rendersAs: "KaTeX HTML + MathML when `ctx.math` is passed",
96
+ note: "Without a math renderer the LaTeX source is shown as `<code class=\"db-math\">`. Not recorded.",
97
+ },
98
+ { feature: "highlight", support: "full", rendersAs: "`<mark>`" },
99
+ { feature: "badge", support: "full", rendersAs: "Badge.astro `<span>` with its variant" },
100
+ {
101
+ feature: "icon",
102
+ support: "partial",
103
+ rendersAs: "Icon.astro markup around the resolved SVG or emoji",
104
+ note: "Resolved by `ctx.icons` or the built-in Lucide subset only; an unknown name becomes an empty `span.db-icon-missing`, reported in `ctx.missingIcons`.",
105
+ },
106
+ { feature: "footnote-ref", support: "full", rendersAs: "`<sup>` noteref; repeat references get distinct ids" },
107
+ { feature: "html-inline", support: "full", rendersAs: "the HTML as authored, links rewritten, filtered by `ctx.rawHtml` when passed" },
108
+ // ── Concerns the renderer records ────────────────────────
109
+ {
110
+ feature: "unrecognised node types",
111
+ support: "partial",
112
+ rendersAs: "block: its HTML, or a `div.db-<type>` with its inline and children; inline: nothing",
113
+ note: "Reported in `ctx.unknown` (blocks by type, inline as `inline:<type>`). An unknown INLINE node is dropped; an unknown block keeps its content.",
114
+ },
115
+ ];
@@ -1,8 +1,3 @@
1
- /**
2
- * Every class name in the page's class attributes, entity-decoded. Markup
3
- * from other tools (KaTeX, Mermaid), between the foreign markers, is skipped:
4
- * its class names are not Tailwind utilities.
5
- */
6
1
  export declare function pageClasses(html: string, into?: Set<string>): Set<string>;
7
2
  /**
8
3
  * Whitespace and comment removal. Tailwind's own minifier (Lightning CSS)
package/dist/css-tools.js CHANGED
@@ -11,11 +11,20 @@ const FOREIGN_BLOCK = new RegExp(`${FOREIGN_START}[\\s\\S]*?${FOREIGN_END}`, "g"
11
11
  * from other tools (KaTeX, Mermaid), between the foreign markers, is skipped:
12
12
  * its class names are not Tailwind utilities.
13
13
  */
14
+ const TAG = /<[a-zA-Z][^\s/>]*(?:\s+[^\s"'>/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?|\s*\/)*\s*>/g;
14
15
  export function pageClasses(html, into = new Set()) {
15
16
  const page = html.includes(FOREIGN_START) ? html.replace(FOREIGN_BLOCK, "") : html;
16
17
  // Only inside tags: text that merely looks like class="…" (escaped prose, code) must
17
18
  // not become CSS. A class compiled from text once wrote `</style>` into the stylesheet.
18
- for (const tag of page.matchAll(/<[a-zA-Z][^<>]*>/g))
19
+ // A quoted attribute value may contain `>` (`[&>svg]:size-4`, valid HTML):
20
+ // a tag pattern that stopped at the first `>` cut the tag mid-class and lost
21
+ // the WHOLE class list (the accordion trigger, callout icons, diagrams).
22
+ // So tags are read with HTML's attribute grammar: a quote opens a value ONLY
23
+ // right after `=`. Allowing quotes anywhere let an unquoted value with an
24
+ // apostrophe (`title=it's`) run on past the real `>` into the text, and
25
+ // text after it became CSS (review). A tag that does not parse is skipped:
26
+ // a missing style, never text compiled as CSS.
27
+ for (const tag of page.matchAll(TAG))
19
28
  for (const m of tag[0].matchAll(/\sclass\s*=\s*(?:"([^"]*)"|'([^']*)')/g))
20
29
  for (const tok of (m[1] ?? m[2]).split(/\s+/))
21
30
  if (tok)
package/dist/index.d.ts CHANGED
@@ -9,3 +9,4 @@ export * from "./render.js";
9
9
  export * from "./lines.js";
10
10
  export { esc, attrs, passthrough, slugify, unesc } from "./html.js";
11
11
  export { icon, hasIcon, CALLOUTS } from "./icons.js";
12
+ export { RENDER_HTML_CAPABILITIES } from "./capabilities.js";
package/dist/index.js CHANGED
@@ -9,3 +9,4 @@ export * from "./render.js";
9
9
  export * from "./lines.js";
10
10
  export { esc, attrs, passthrough, slugify, unesc } from "./html.js";
11
11
  export { icon, hasIcon, CALLOUTS } from "./icons.js";
12
+ export { RENDER_HTML_CAPABILITIES } from "./capabilities.js";
package/dist/render.d.ts CHANGED
@@ -1,9 +1,3 @@
1
- /**
2
- * TreeNode → HTML. Emits plain semantic HTML plus light-DOM custom
3
- * elements (`db-tabs`, `db-accordion`, `db-code-block`) whose markup
4
- * contract comes from `@dogsbay/elements`. Everything is a string, so
5
- * rendering is synchronous and runs anywhere.
6
- */
7
1
  import type { InlineNode, TreeNode } from "@dogsbay/types";
8
2
  import { FOREIGN_END, FOREIGN_START } from "./html.js";
9
3
  import { type Highlighter } from "./lines.js";
package/dist/render.js CHANGED
@@ -1,3 +1,10 @@
1
+ /**
2
+ * TreeNode → HTML. Emits plain semantic HTML plus light-DOM custom
3
+ * elements (`db-tabs`, `db-accordion`, `db-code-block`) whose markup
4
+ * contract comes from `@dogsbay/elements`. Everything is a string, so
5
+ * rendering is synchronous and runs anywhere.
6
+ */
7
+ import { isDetailsSheet, renderAroundGroup } from "@dogsbay/serialize-core";
1
8
  import { parseInline } from "@dogsbay/format-dogsbay-md/browser";
2
9
  import { attrs, esc, FOREIGN_END, FOREIGN_START, passthrough, slugify, unesc } from "./html.js";
3
10
  import { CALLOUTS, hasIcon, icon } from "./icons.js";
@@ -359,11 +366,11 @@ function block(n, ctx) {
359
366
  // Tight, like the Astro Step: the lead text is not wrapped in <p>.
360
367
  return `<li>${renderInline(n.inline, ctx)}${renderBlocks(n.children, ctx)}</li>`;
361
368
  case "tabs":
362
- return tabs(n, ctx);
369
+ return renderAroundGroup(n, "tab", (g) => tabs(g, ctx), (c) => renderBlocks([c], ctx), "");
363
370
  case "tab":
364
371
  return renderBlocks(n.children, ctx);
365
372
  case "accordion":
366
- return accordion(n, ctx);
373
+ return renderAroundGroup(n, "accordion-item", (g) => accordion(g, ctx), (c) => renderBlocks([c], ctx), "");
367
374
  case "accordion-item":
368
375
  return accordion({ type: "accordion", children: [n] }, ctx);
369
376
  case "details":
@@ -478,6 +485,12 @@ function block(n, ctx) {
478
485
  ctx.unknown.add("include (unresolved)");
479
486
  return `<!-- unresolved include: ${esc(target)} -->\n`;
480
487
  }
488
+ // Raw HTML (a one-line `<details>` body) or an importer's wrapper: known,
489
+ // so not reported as unknown (that warned on content that rendered fine)
490
+ case "html-container":
491
+ if (n.html && !n.children?.length)
492
+ return rewriteHtml(n.html, ctx);
493
+ return `<div${attrs(passthrough(p, "db-html-container"))}>${renderBlocks(n.children, ctx)}</div>\n`;
481
494
  default: {
482
495
  ctx.unknown.add(n.type);
483
496
  if (n.html && !n.children?.length)
@@ -786,7 +799,12 @@ function details(n, ctx) {
786
799
  // `variant` as documented, then `type`, as the Astro pipeline reads it.
787
800
  const type = (str(p.variant) ?? str(p.type) ?? "").toLowerCase();
788
801
  const variant = ALERT_VARIANT[VARIANT_ALIAS[type] ?? type] ? VARIANT_ALIAS[type] ?? type : "note";
789
- return collapsibleAlert(variant, renderInline(parseInline(title), ctx), renderBlocks(n.children, ctx), p.open != null && p.open !== false, p);
802
+ const html = collapsibleAlert(variant, renderInline(parseInline(title), ctx), renderBlocks(n.children, ctx), p.open != null && p.open !== false, p);
803
+ // `sheet`: the same details, which <db-details-sheet> opens as a side
804
+ // sheet when its script runs (and which stays a details when it does not)
805
+ if (!isDetailsSheet(p))
806
+ return html;
807
+ return `<db-details-sheet${attrs({ "close-label": ctx.t("close") || "Close" })}>${html.trimEnd()}</db-details-sheet>\n`;
790
808
  }
791
809
  function figure(n, ctx) {
792
810
  const p = (n.props ?? {});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dogsbay/render-html",
3
- "version": "0.2.0-beta.121",
3
+ "version": "0.2.0-beta.122",
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.121",
34
- "@dogsbay/serialize-core": "0.2.0-beta.121",
35
- "@dogsbay/types": "0.2.0-beta.121"
33
+ "@dogsbay/format-dogsbay-md": "0.2.0-beta.122",
34
+ "@dogsbay/serialize-core": "0.2.0-beta.122",
35
+ "@dogsbay/types": "0.2.0-beta.122"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "katex": "^0.16.0"