@dogsbay/format-astro 0.2.0-beta.127 → 0.2.0-beta.128

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.
Files changed (2) hide show
  1. package/dist/serialize.js +44 -9
  2. package/package.json +7 -7
package/dist/serialize.js CHANGED
@@ -176,6 +176,7 @@ export function escapeTemplate(s) {
176
176
  export function treeToAstro(nodes, options) {
177
177
  const ctx = {
178
178
  imports: new Set(),
179
+ diagramIds: new Map(),
179
180
  indent: 0,
180
181
  scripts: new Set(),
181
182
  mode: options?.mode ?? "template",
@@ -495,6 +496,9 @@ function templateInlineEmitters() {
495
496
  strike: (s) => `<s>${s}</s>`,
496
497
  }),
497
498
  link: (node, ctx) => {
499
+ // Inside a whole-card link, a link is its text (wholeCardLink)
500
+ if (cardLinkDepth > 0)
501
+ return ctx.recurse(node.children);
498
502
  // A link node an importer built directly never passed markdown-it's
499
503
  // scheme check: a `javascript:` href keeps its text and loses the link.
500
504
  if (!isSafeHref(node.href)) {
@@ -565,6 +569,8 @@ function htmlModeInlineEmitters() {
565
569
  // EXPRESSION, inside a string that becomes a `set:html` value. Astro never
566
570
  // parses it as markup, so the reader saw the braces and quotes.
567
571
  link: (node, ctx) => {
572
+ if (cardLinkDepth > 0)
573
+ return ctx.recurse(node.children);
568
574
  if (!isSafeHref(node.href)) {
569
575
  ctx.report("link", `unsafe link target dropped (${node.href.slice(0, 40)})`);
570
576
  return ctx.recurse(node.children);
@@ -1141,8 +1147,18 @@ function diagramToAstro(node, ctx) {
1141
1147
  ctx.imports.add("diagram");
1142
1148
  const lang = node.props?.lang || "mermaid";
1143
1149
  const code = node.props?.code ?? "";
1144
- const svg = node.props?.svg ?? "";
1150
+ let svg = node.props?.svg ?? "";
1145
1151
  const title = node.props?.title;
1152
+ // A Pele SVG's ids (`<prefix>-title`, `<prefix>-desc`, for aria-labelledby) are unique within
1153
+ // its source page. A book view or an include can show the same diagram twice on one page, so
1154
+ // the later copies are renamed; the prefix appears nowhere else in the SVG.
1155
+ const prefix = /^<svg[^>]*\bclass="pele\b/.test(svg) ? /\sid="(dg-[0-9a-f]{12}(?:-\d+)?)-/.exec(svg)?.[1] : undefined;
1156
+ if (prefix) {
1157
+ const n = (ctx.diagramIds.get(prefix) ?? 0) + 1;
1158
+ ctx.diagramIds.set(prefix, n);
1159
+ if (n > 1)
1160
+ svg = svg.split(prefix).join(`${prefix}-p${n}`);
1161
+ }
1146
1162
  const attrs = [`lang="${escapeAttr(lang)}"`, `code={${escapeExpr(code)}}`, `svg={${escapeExpr(svg)}}`];
1147
1163
  if (title)
1148
1164
  attrs.push(componentAttr("title", title).trimStart());
@@ -1364,6 +1380,25 @@ function endpointToAstro(node, ctx) {
1364
1380
  ` <ApiCodePanel slot="code">${codeBody}</ApiCodePanel>\n` +
1365
1381
  `</ApiLayout>`);
1366
1382
  }
1383
+ /**
1384
+ * Whole-card links being emitted. Inside one, a link in the card's body is its text: a browser
1385
+ * ends the outer <a> at an inner one, so the rest of the card fell out of the link (and lost
1386
+ * not-prose). The serializer is synchronous, so a depth count is enough.
1387
+ */
1388
+ let cardLinkDepth = 0;
1389
+ function wholeCardLink(yes, fn) {
1390
+ if (!yes)
1391
+ return fn();
1392
+ cardLinkDepth++;
1393
+ try {
1394
+ return fn();
1395
+ }
1396
+ finally {
1397
+ cardLinkDepth--;
1398
+ }
1399
+ }
1400
+ /** A card's title: not-prose, or docs.css's h3 rule (1.25rem, 2rem above) applies inside the card. */
1401
+ const CARD_TITLE_CLASS = "not-prose m-0 border-0 p-0 text-base font-semibold";
1367
1402
  function cardsToAstro(node, ctx) {
1368
1403
  ctx.imports.add("grid");
1369
1404
  ctx.imports.add("card");
@@ -1382,9 +1417,9 @@ function cardsToAstro(node, ctx) {
1382
1417
  const icon = card.props?.icon;
1383
1418
  // Description sources: inline content, children, or props.description.
1384
1419
  // Child blocks carry their own <p>, so they get a <div>: <p><p>…</p></p> is invalid HTML.
1385
- const blocks = childrenToAstro(card, ctx);
1420
+ const blocks = wholeCardLink(!!href, () => childrenToAstro(card, ctx));
1386
1421
  const inner = blocks
1387
- || (card.inline ? inlineToTemplate(card.inline) : "")
1422
+ || (card.inline ? wholeCardLink(!!href, () => inlineToTemplate(card.inline)) : "")
1388
1423
  || (description ? escapeTemplate(description) : "");
1389
1424
  const descTag = blocks ? "div" : "p";
1390
1425
  // Icon renders inline before the title — picks up Lucide
@@ -1396,7 +1431,7 @@ function cardsToAstro(node, ctx) {
1396
1431
  if (icon) {
1397
1432
  iconHtml = cardIconHtml(icon, title, ctx);
1398
1433
  }
1399
- const titleHtml = title ? `<h3 class="text-base font-semibold">${escapeTemplate(title)}</h3>` : "";
1434
+ const titleHtml = title ? `<h3 class="${CARD_TITLE_CLASS}">${escapeTemplate(title)}</h3>` : "";
1400
1435
  // A description and a body are both shown (the description used to be dropped when a body existed).
1401
1436
  const lead = blocks && description
1402
1437
  ? `<p class="text-sm text-muted-foreground mt-1 [text-decoration:none]">${escapeTemplate(description)}</p>`
@@ -1415,7 +1450,7 @@ function cardsToAstro(node, ctx) {
1415
1450
  "title", "href", "description", "icon", "variant", "class", "tone",
1416
1451
  ]);
1417
1452
  if (href) {
1418
- return `<Card${cardClassAttr}${cardPass}>\n<CardContent class="p-0">\n<a href="${escapeAttr(href)}" class="[text-decoration:none] text-foreground">\n${iconHtml}\n${titleHtml}\n${desc}\n</a>\n</CardContent>\n</Card>`;
1453
+ return `<Card${cardClassAttr}${cardPass}>\n<CardContent class="p-0">\n<a href="${escapeAttr(href)}" class="not-prose [text-decoration:none] text-foreground">\n${iconHtml}\n${titleHtml}\n${desc}\n</a>\n</CardContent>\n</Card>`;
1419
1454
  }
1420
1455
  return `<Card${cardClassAttr}${cardPass}>\n<CardContent class="p-0">\n${iconHtml}\n${titleHtml}\n${desc}\n</CardContent>\n</Card>`;
1421
1456
  }).join("\n");
@@ -1448,9 +1483,9 @@ function standaloneCardToAstro(node, ctx) {
1448
1483
  const description = node.props?.description;
1449
1484
  const icon = node.props?.icon;
1450
1485
  // Child blocks carry their own <p>, so they get a <div>: <p><p>…</p></p> is invalid HTML.
1451
- const blocks = childrenToAstro(node, ctx);
1486
+ const blocks = wholeCardLink(!!href, () => childrenToAstro(node, ctx));
1452
1487
  const inner = blocks
1453
- || (node.inline ? inlineToTemplate(node.inline) : "")
1488
+ || (node.inline ? wholeCardLink(!!href, () => inlineToTemplate(node.inline)) : "")
1454
1489
  || (description ? escapeTemplate(description) : "");
1455
1490
  const descTag = blocks ? "div" : "p";
1456
1491
  // Icon renders inline before the title (same shape as the
@@ -1461,7 +1496,7 @@ function standaloneCardToAstro(node, ctx) {
1461
1496
  iconHtml = `<Icon name="${escapeAttr(icon)}" class="size-5 mb-2 text-muted-foreground" />`;
1462
1497
  }
1463
1498
  const titleHtml = title
1464
- ? `<h3 class="text-base font-semibold">${escapeTemplate(title)}</h3>`
1499
+ ? `<h3 class="${CARD_TITLE_CLASS}">${escapeTemplate(title)}</h3>`
1465
1500
  : "";
1466
1501
  // A description and a body are both shown (the description used to be dropped when a body existed).
1467
1502
  const lead = blocks && description
@@ -1480,7 +1515,7 @@ function standaloneCardToAstro(node, ctx) {
1480
1515
  "title", "href", "description", "icon", "variant", "class", "tone",
1481
1516
  ]);
1482
1517
  if (href) {
1483
- return `<Card${classAttr}${passthrough}>\n<CardContent class="p-0">\n<a href="${escapeAttr(href)}" class="[text-decoration:none] text-foreground">\n${iconHtml}\n${titleHtml}\n${desc}\n</a>\n</CardContent>\n</Card>`;
1518
+ return `<Card${classAttr}${passthrough}>\n<CardContent class="p-0">\n<a href="${escapeAttr(href)}" class="not-prose [text-decoration:none] text-foreground">\n${iconHtml}\n${titleHtml}\n${desc}\n</a>\n</CardContent>\n</Card>`;
1484
1519
  }
1485
1520
  return `<Card${classAttr}${passthrough}>\n<CardContent class="p-0">\n${iconHtml}\n${titleHtml}\n${desc}\n</CardContent>\n</Card>`;
1486
1521
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dogsbay/format-astro",
3
- "version": "0.2.0-beta.127",
3
+ "version": "0.2.0-beta.128",
4
4
  "description": "Astro format plugin for Dogsbay — export TreeNode[] to Astro component markup",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -32,12 +32,12 @@
32
32
  "license": "MIT",
33
33
  "dependencies": {
34
34
  "markdown-it": "^14.3.0",
35
- "@dogsbay/format-dogsbay-md": "0.2.0-beta.127",
36
- "@dogsbay/serialize-core": "0.2.0-beta.127",
37
- "@dogsbay/site-files": "0.2.0-beta.127",
38
- "@dogsbay/render-html": "0.2.0-beta.127",
39
- "@dogsbay/types": "0.2.0-beta.127",
40
- "@dogsbay/ui": "0.2.0-beta.127"
35
+ "@dogsbay/format-dogsbay-md": "0.2.0-beta.128",
36
+ "@dogsbay/render-html": "0.2.0-beta.128",
37
+ "@dogsbay/serialize-core": "0.2.0-beta.128",
38
+ "@dogsbay/site-files": "0.2.0-beta.128",
39
+ "@dogsbay/types": "0.2.0-beta.128",
40
+ "@dogsbay/ui": "0.2.0-beta.128"
41
41
  },
42
42
  "repository": {
43
43
  "type": "git",