@dogsbay/render-html 0.2.0-beta.129 → 0.2.0-beta.131
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/code-rich.css +33 -0
- package/css/docs.css +4 -1
- package/css/steps.css +10 -0
- package/dist/capabilities.js +3 -3
- package/dist/context.js +3 -0
- package/dist/lines.d.ts +9 -0
- package/dist/lines.js +35 -0
- package/dist/render.js +24 -9
- package/package.json +4 -4
package/css/code-rich.css
CHANGED
|
@@ -86,3 +86,36 @@
|
|
|
86
86
|
padding: 1px 4px;
|
|
87
87
|
margin: 0 -1px;
|
|
88
88
|
}
|
|
89
|
+
|
|
90
|
+
/* ── `collapse` fence option: line runs folded behind a toggle ──
|
|
91
|
+
No script: a checkbox inside the label. Lines hide only where :has() works,
|
|
92
|
+
so a browser without it shows every line rather than losing them. */
|
|
93
|
+
.code-rich-body .code-collapse-label {
|
|
94
|
+
display: inline-block;
|
|
95
|
+
width: 100%;
|
|
96
|
+
cursor: pointer;
|
|
97
|
+
user-select: none;
|
|
98
|
+
/* The code's own colour: a muted label fell under 4.5:1 on the code background */
|
|
99
|
+
border-block: 1px dashed color-mix(in oklab, currentColor 35%, transparent);
|
|
100
|
+
font-style: italic;
|
|
101
|
+
}
|
|
102
|
+
.code-rich-body .code-collapse-toggle {
|
|
103
|
+
position: absolute;
|
|
104
|
+
width: 1px;
|
|
105
|
+
height: 1px;
|
|
106
|
+
opacity: 0;
|
|
107
|
+
}
|
|
108
|
+
.code-rich-body .code-collapse-label:has(.code-collapse-toggle:focus-visible) {
|
|
109
|
+
outline: 2px solid var(--ring);
|
|
110
|
+
outline-offset: -2px;
|
|
111
|
+
}
|
|
112
|
+
/* The label stays when open, so focus stays on the checkbox and it folds again */
|
|
113
|
+
.code-rich-body .code-collapse-label::before {
|
|
114
|
+
content: "▸ ";
|
|
115
|
+
}
|
|
116
|
+
.code-rich-body .code-collapse-label:has(.code-collapse-toggle:checked)::before {
|
|
117
|
+
content: "▾ ";
|
|
118
|
+
}
|
|
119
|
+
.code-rich-body .code-collapse:has(.code-collapse-toggle:not(:checked)) > .code-collapse-lines {
|
|
120
|
+
display: none;
|
|
121
|
+
}
|
package/css/docs.css
CHANGED
|
@@ -65,7 +65,10 @@
|
|
|
65
65
|
& li > * + *:not(.dba-api *) { margin-top: 0.75rem; }
|
|
66
66
|
|
|
67
67
|
& ul:not(.dba-api *) { list-style: disc; padding-left: 1.5rem; margin: 0.75rem 0; }
|
|
68
|
-
|
|
68
|
+
/* Not a step list, nor a list inside one: Steps.astro draws their numbers as circles, and this
|
|
69
|
+
rule (one class and an element) outranked its .steps-list { list-style: none }, so each step
|
|
70
|
+
showed its "2." beside its circle */
|
|
71
|
+
& ol:not(.dba-api *):not(.steps-list, .sub-steps-list, .steps-list ol) { list-style: decimal; padding-left: 1.5rem; margin: 0.75rem 0; }
|
|
69
72
|
& li:not(.dba-api *) { margin: 0.25rem 0; }
|
|
70
73
|
& li > ul:not(.dba-api *), & li > ol:not(.dba-api *) { margin: 0.25rem 0; }
|
|
71
74
|
/* A file tree draws its own folder and file icons (files/markup.ts): the list rules above gave it bullets too */
|
package/css/steps.css
CHANGED
|
@@ -98,6 +98,11 @@
|
|
|
98
98
|
opacity: 0.6;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/* A step's title on its own line, above its detail (format-astro and render-html mark it) */
|
|
102
|
+
.steps-list > li > .step-title {
|
|
103
|
+
display: block;
|
|
104
|
+
}
|
|
105
|
+
|
|
101
106
|
|
|
102
107
|
.sub-steps-list {
|
|
103
108
|
position: relative;
|
|
@@ -147,3 +152,8 @@
|
|
|
147
152
|
background: var(--color-border);
|
|
148
153
|
opacity: 0.6;
|
|
149
154
|
}
|
|
155
|
+
|
|
156
|
+
/* A step's title on its own line, above its detail (format-astro and render-html mark it) */
|
|
157
|
+
.sub-steps-list > li > .step-title {
|
|
158
|
+
display: block;
|
|
159
|
+
}
|
package/dist/capabilities.js
CHANGED
|
@@ -10,7 +10,7 @@ export const RENDER_HTML_CAPABILITIES = [
|
|
|
10
10
|
{
|
|
11
11
|
feature: "code",
|
|
12
12
|
support: "full",
|
|
13
|
-
rendersAs: "`<db-code-block>` (CodeBlock / CodeRich markup); highlighted only when a Highlighter is passed, plain text otherwise",
|
|
13
|
+
rendersAs: "`<db-code-block>` (CodeBlock / CodeRich markup, `collapse` runs behind a toggle); highlighted only when a Highlighter is passed, plain text otherwise",
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
feature: "math-block",
|
|
@@ -51,13 +51,13 @@ export const RENDER_HTML_CAPABILITIES = [
|
|
|
51
51
|
support: "full",
|
|
52
52
|
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",
|
|
53
53
|
},
|
|
54
|
-
{ feature: "steps", support: "full", rendersAs: "`<ol class=\"steps-list\">`; a non-step child is wrapped in its own `<li
|
|
54
|
+
{ feature: "steps", support: "full", rendersAs: "`<ol class=\"steps-list\">`; a non-step child is wrapped in its own `<li>`; a step's `title` prop, or its leading bold run (`stepLead`), is its title, a block above the detail" },
|
|
55
55
|
{
|
|
56
56
|
feature: "accordion",
|
|
57
57
|
support: "full",
|
|
58
58
|
rendersAs: "`<db-accordion>` with Accordion.astro markup; a child that is not an item stays in reading order around the group",
|
|
59
59
|
},
|
|
60
|
-
{ feature: "cards", support: "full", rendersAs: "a responsive card grid `<div
|
|
60
|
+
{ feature: "cards", support: "full", rendersAs: "a responsive card grid `<div>`; `cols` 1–6, one column on a phone" },
|
|
61
61
|
{ feature: "card", support: "full", rendersAs: "Card.astro markup; icon, title, description, body; linked when `href` is set" },
|
|
62
62
|
{ feature: "link-card", support: "full", rendersAs: "LinkCard.astro markup; a body of several blocks renders as a linked Card" },
|
|
63
63
|
{ feature: "files", support: "full", rendersAs: "FileTree's markup: folder and file icons, nested" },
|
package/dist/context.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
export const UI_STRINGS = {
|
|
3
3
|
backToContent: "Back to content",
|
|
4
4
|
close: "Close",
|
|
5
|
+
/** A `collapse` fence's toggle; `{n}` is the number of lines it hides (`collapsedLine` for one). */
|
|
6
|
+
collapsedLine: "1 collapsed line",
|
|
7
|
+
collapsedLines: "{n} collapsed lines",
|
|
5
8
|
copyCode: "Copy code",
|
|
6
9
|
details: "Details",
|
|
7
10
|
related: "Related links",
|
package/dist/lines.d.ts
CHANGED
|
@@ -44,6 +44,15 @@ export interface Highlighter {
|
|
|
44
44
|
export declare const PLAIN: Set<string>;
|
|
45
45
|
/** Parse "1,3-5" into [1,3,4,5]. */
|
|
46
46
|
export declare function parseLineRanges(spec: unknown): number[];
|
|
47
|
+
/** Sorted line numbers as contiguous runs: [2,3,4,7] → [[2,4],[7,7]]. */
|
|
48
|
+
export declare function lineRuns(lines: number[]): Array<[number, number]>;
|
|
49
|
+
/**
|
|
50
|
+
* The fence's `collapse` option (Expressive Code's): each run of lines folded behind a toggle that
|
|
51
|
+
* names how many lines it hides, as CodeRich.astro draws it. No script: a checkbox in the label, and
|
|
52
|
+
* code-rich.css shows the lines when it is checked (or always, in a browser without `:has()`).
|
|
53
|
+
* Works on any highlighter's `<pre><code>`, whose lines are `\n`-separated `<span class="line">`s.
|
|
54
|
+
*/
|
|
55
|
+
export declare function collapseLines(pre: string, lines: number[], label: (count: number) => string): string;
|
|
47
56
|
/** CodeRich's gutter: the line number and a diff marker (+, −, or a space) in one column. */
|
|
48
57
|
export declare function gutterHtml(n: number, kind: string, lineNumbers: boolean, diffs: boolean): string;
|
|
49
58
|
/** Code as escaped text, with the annotations and gutter a highlighted block has. `src` has had its unprocessable markers removed. */
|
package/dist/lines.js
CHANGED
|
@@ -26,6 +26,41 @@ export function parseLineRanges(spec) {
|
|
|
26
26
|
}
|
|
27
27
|
return out;
|
|
28
28
|
}
|
|
29
|
+
/** Sorted line numbers as contiguous runs: [2,3,4,7] → [[2,4],[7,7]]. */
|
|
30
|
+
export function lineRuns(lines) {
|
|
31
|
+
const sorted = [...new Set(lines)].filter((n) => n > 0).sort((a, b) => a - b);
|
|
32
|
+
const runs = [];
|
|
33
|
+
for (const n of sorted) {
|
|
34
|
+
const last = runs[runs.length - 1];
|
|
35
|
+
if (last && n === last[1] + 1)
|
|
36
|
+
last[1] = n;
|
|
37
|
+
else
|
|
38
|
+
runs.push([n, n]);
|
|
39
|
+
}
|
|
40
|
+
return runs;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The fence's `collapse` option (Expressive Code's): each run of lines folded behind a toggle that
|
|
44
|
+
* names how many lines it hides, as CodeRich.astro draws it. No script: a checkbox in the label, and
|
|
45
|
+
* code-rich.css shows the lines when it is checked (or always, in a browser without `:has()`).
|
|
46
|
+
* Works on any highlighter's `<pre><code>`, whose lines are `\n`-separated `<span class="line">`s.
|
|
47
|
+
*/
|
|
48
|
+
export function collapseLines(pre, lines, label) {
|
|
49
|
+
const open = pre.indexOf("<code>");
|
|
50
|
+
const close = pre.lastIndexOf("</code>");
|
|
51
|
+
if (open < 0 || close < open || !lines.length)
|
|
52
|
+
return pre;
|
|
53
|
+
const parts = pre.slice(open + 6, close).split("\n");
|
|
54
|
+
for (const [a, b] of lineRuns(lines).reverse()) {
|
|
55
|
+
if (a > parts.length)
|
|
56
|
+
continue;
|
|
57
|
+
const end = Math.min(b, parts.length);
|
|
58
|
+
const group = `<span class="code-collapse"><label class="code-collapse-label" data-pagefind-ignore><input type="checkbox" class="code-collapse-toggle">${label(end - a + 1)}</label>` +
|
|
59
|
+
`<span class="code-collapse-lines">${parts.slice(a - 1, end).join("\n")}</span></span>`;
|
|
60
|
+
parts.splice(a - 1, end - a + 1, group);
|
|
61
|
+
}
|
|
62
|
+
return pre.slice(0, open + 6) + parts.join("\n") + pre.slice(close);
|
|
63
|
+
}
|
|
29
64
|
/** CodeRich's gutter: the line number and a diff marker (+, −, or a space) in one column. */
|
|
30
65
|
export function gutterHtml(n, kind, lineNumbers, diffs) {
|
|
31
66
|
if (!lineNumbers && !diffs)
|
package/dist/render.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* contract comes from `@dogsbay/elements`. Everything is a string, so
|
|
5
5
|
* rendering is synchronous and runs anywhere.
|
|
6
6
|
*/
|
|
7
|
-
import { isDetailsSheet, renderAroundGroup } from "@dogsbay/serialize-core";
|
|
7
|
+
import { isDetailsSheet, renderAroundGroup, stepLead, stepTitle } from "@dogsbay/serialize-core";
|
|
8
8
|
import { parseInline } from "@dogsbay/format-dogsbay-md/browser";
|
|
9
9
|
import { attrs, esc, FOREIGN_END, FOREIGN_START, passthrough, slugify, unesc } from "./html.js";
|
|
10
10
|
import { CALLOUTS, hasIcon, icon } from "./icons.js";
|
|
@@ -14,7 +14,7 @@ import { CARD_TONE_CLASSES, TAB_VARIANTS, THEME_SCOPES, TONE_CLASSES, lookup } f
|
|
|
14
14
|
import { fileTreeHtml } from "./file-tree.js";
|
|
15
15
|
import { stripMarkersForCopy } from "./code-markers.js";
|
|
16
16
|
import { icons as ALERT_ICONS, chevronSvg as ALERT_CHEVRON } from "./alert-icons.js";
|
|
17
|
-
import { parseLineRanges, plainHighlighter } from "./lines.js";
|
|
17
|
+
import { collapseLines, parseLineRanges, plainHighlighter } from "./lines.js";
|
|
18
18
|
import { decorateDiffHtml, diffMarksOf, hasDiffMarks, inlineSegmentsToHtml, TAB_GLYPH, tabDiffSignal } from "./diff.js";
|
|
19
19
|
import { scriptUrl } from "./url.js";
|
|
20
20
|
import { API_NODE_TYPES, apiCodeInputs, renderApiNode } from "./api.js";
|
|
@@ -425,9 +425,18 @@ function block(n, ctx) {
|
|
|
425
425
|
return callout(n, ctx);
|
|
426
426
|
case "steps":
|
|
427
427
|
return steps(n, ctx);
|
|
428
|
-
case "step":
|
|
429
|
-
// Tight, like the Astro Step: the lead text is not wrapped in <p>.
|
|
430
|
-
|
|
428
|
+
case "step": {
|
|
429
|
+
// Tight, like the Astro Step: the lead text is not wrapped in <p>. A `title` prop (Mintlify
|
|
430
|
+
// `<Step title>`) leads it in bold, as format-astro draws it.
|
|
431
|
+
// The title on its own line (steps.css: `.step-title` is a block): from the prop, or the
|
|
432
|
+
// step's leading bold run (stepLead), which Markdown otherwise runs into the detail
|
|
433
|
+
const title = stepTitle(n);
|
|
434
|
+
const split = title ? undefined : stepLead(n.inline);
|
|
435
|
+
const lead = title ? `<strong class="step-title">${esc(title)}</strong>` : split ? `<strong class="step-title">${renderInline(split.title, ctx)}</strong>` : "";
|
|
436
|
+
const text = renderInline(split ? split.rest : n.inline, ctx);
|
|
437
|
+
// A space after the title: invisible beside a block, but what a text reader (export) needs
|
|
438
|
+
return `<li>${lead}${lead && text ? " " : ""}${text}${renderBlocks(n.children, ctx)}</li>`;
|
|
439
|
+
}
|
|
431
440
|
case "tabs":
|
|
432
441
|
return renderAroundGroup(n, "tab", (g) => tabs(g, ctx), (c) => renderBlocks([c], ctx), "");
|
|
433
442
|
case "tab":
|
|
@@ -456,7 +465,10 @@ function block(n, ctx) {
|
|
|
456
465
|
// A group tone applies to every card that has none of its own
|
|
457
466
|
const tone = str(p.tone);
|
|
458
467
|
const kids = tone ? (n.children ?? []).map((c) => (c.type === "card" && !c.props?.tone ? { ...c, props: { ...c.props, tone } } : c)) : n.children;
|
|
459
|
-
|
|
468
|
+
// `cols` as format-astro's cardCols: one column on a phone, up to `cols` (1–6) wider; default 2
|
|
469
|
+
// A list (`1,3`, `[1,2,3]`: format-astro's own breakpoints) takes its widest
|
|
470
|
+
const cols = Math.min(6, Math.max(1, Math.round(Number(String(p.cols ?? "").replace(/[[\]\s]/g, "").split(",").pop()) || 2)));
|
|
471
|
+
return `<div${attrs(passthrough(p, `not-prose my-4 grid grid-cols-1 gap-4 ${GRID_COLS[cols]}`))}>${renderBlocks(kids, ctx)}</div>\n`;
|
|
460
472
|
}
|
|
461
473
|
// A scoped theme: `.dark` re-declares the tokens for its subtree (THEME_SCOPES)
|
|
462
474
|
case "theme": {
|
|
@@ -690,10 +702,13 @@ function codeBlock(n, ctx) {
|
|
|
690
702
|
const title = str(p.title) ?? str(p.filename);
|
|
691
703
|
const { highlights, lineNumbers, diffAdd, diffRemove } = opts;
|
|
692
704
|
const hl = ctx.highlighter ?? plainHighlighter;
|
|
693
|
-
|
|
705
|
+
// `collapse`: line runs folded behind a toggle (CodeRich.astro's); the label is not code, so copy the source
|
|
706
|
+
const collapse = parseLineRanges(p.collapse);
|
|
707
|
+
const collapsedLabel = (count) => esc((count === 1 ? ctx.t("collapsedLine") : ctx.t("collapsedLines")).replace("{n}", String(count)));
|
|
708
|
+
const pre = collapseLines(hl.code(code, lang, opts), collapse, collapsedLabel);
|
|
694
709
|
// A block with annotations takes CodeRich's styles, as the Astro pipeline routes it to CodeRich.
|
|
695
710
|
const diffs = diffAdd.length > 0 || diffRemove.length > 0;
|
|
696
|
-
const rich = lineNumbers || highlights.length > 0 || diffs || code.includes("[!code");
|
|
711
|
+
const rich = lineNumbers || highlights.length > 0 || diffs || code.includes("[!code") || collapse.length > 0;
|
|
697
712
|
// Plain by its TAG, as CodeBlock.astro: a language the highlighter has no grammar for (mermaid) keeps its label
|
|
698
713
|
const plain = !lang || plainHighlighter.resolve(lang) === "text";
|
|
699
714
|
const copyLabel = esc(ctx.t("copyCode"));
|
|
@@ -716,7 +731,7 @@ function codeBlock(n, ctx) {
|
|
|
716
731
|
"data-lang": lang,
|
|
717
732
|
"data-line-numbers": lineNumbers || undefined,
|
|
718
733
|
// With a gutter or [!code …] markers the text on the page is not the code: copy the source, markers removed.
|
|
719
|
-
"data-copy-text": str(p.copyText) ?? (lineNumbers || diffs || code.includes("[!code") ? stripMarkersForCopy(code.replace(/\n$/, "")) : undefined),
|
|
734
|
+
"data-copy-text": str(p.copyText) ?? (lineNumbers || diffs || collapse.length > 0 || code.includes("[!code") ? stripMarkersForCopy(code.replace(/\n$/, "")) : undefined),
|
|
720
735
|
})}>` +
|
|
721
736
|
header +
|
|
722
737
|
floating +
|
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.131",
|
|
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.131",
|
|
34
|
+
"@dogsbay/serialize-core": "0.2.0-beta.131",
|
|
35
|
+
"@dogsbay/types": "0.2.0-beta.131"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"katex": "^0.16.0"
|