@dogsbay/format-astro 0.2.0-beta.152 → 0.2.0-beta.153

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.
@@ -113,7 +113,7 @@ export function openAsPageNode(slug, hrefPrefix) {
113
113
  const route = hrefPrefix ? `${hrefPrefix}/${slug}` : `/${slug}`;
114
114
  return {
115
115
  type: "paragraph",
116
- props: { class: "dsb-open-as-page" },
116
+ props: { class: "dsb-open-as-page not-prose" },
117
117
  children: [{ type: "prose", inline: [{ type: "link", href: route, children: [{ type: "text", text: "Open as page ↗" }] }] }],
118
118
  };
119
119
  }
package/dist/project.d.ts CHANGED
@@ -168,6 +168,12 @@ export interface AstroProjectOptions {
168
168
  * See plans/search-facets.md.
169
169
  */
170
170
  taxonomyDisplay?: import("@dogsbay/types").SiteConfig["taxonomyDisplay"];
171
+ /**
172
+ * Declared taxonomies with `hierarchical: true`: a page's nested value in one
173
+ * of them is a search facet at every level (`ai/llm` → `ai`, `ai/llm`), as
174
+ * `pageFacets` (@dogsbay/site-files) maps it for DocsLayout and dogsbay-ssg.
175
+ */
176
+ hierarchicalTaxonomies?: string[];
171
177
  /** Canonical site URL — required for sitemap.xml + canonical tags */
172
178
  siteUrl?: string;
173
179
  /** Site-wide description used as default <meta name="description"> */
@@ -685,7 +691,29 @@ export interface SwitcherMap {
685
691
  label: string;
686
692
  home: string;
687
693
  }>>;
694
+ /**
695
+ * The version banner each unit's pages carry, keyed by site-files `unitKeyString` (product,
696
+ * locale, version; just the version for site-wide versions): the version's own text, or a kind
697
+ * the layout words (older, no longer maintained, not released), and the default version it points
698
+ * to with its home URL (where the link goes when the page is not in that version). As dogsbay-ssg:
699
+ * with a `products:` block from site-files `unitOf`; site-wide, an older version only. Left out
700
+ * when no page has one.
701
+ */
702
+ banners?: Record<string, VersionBannerData>;
688
703
  }
704
+ /** A unit's version banner in switcherMap.json (site-files `UnitBanner`, `latest.home` as served). */
705
+ export type VersionBannerData = ({
706
+ kind: "custom";
707
+ text: string;
708
+ } | {
709
+ kind: "eol" | "prerelease" | "old";
710
+ }) & {
711
+ latest?: {
712
+ id: string;
713
+ label: string;
714
+ home: string;
715
+ };
716
+ };
689
717
  export interface SwitcherVariant {
690
718
  /** Present when the locale axis is active for this site. */
691
719
  locale?: string;
package/dist/project.js CHANGED
@@ -26,10 +26,10 @@ function pageLiteral(value, indent) {
26
26
  }
27
27
  import { emitPluginRuntime } from "./plugins.js";
28
28
  import { buildLlmsTxt, buildSectionLlmsTxt, buildLlmsFullTxt } from "./llms-txt.js";
29
- import { buildSitemapFiles, buildSitemapIndex } from "./sitemap.js";
29
+ import { buildSitemapFiles, buildSitemapIndex, pageHref } from "./sitemap.js";
30
30
  import { canonicalPlan } from "./canonical.js";
31
31
  import { headMetadata } from "./head-metadata.js";
32
- import { buildHeadersFile, buildRobotsTxt as buildRobotsTxtFile } from "@dogsbay/site-files";
32
+ import { buildHeadersFile, buildRobotsTxt as buildRobotsTxtFile, pageFacets } from "@dogsbay/site-files";
33
33
  import { mergeHeaders, mergeRedirects, productRootRedirects, siteFiles, siteRootTarget, switcher as productSwitcherRows, productSwitcher, isVersionMenuHref, versionMenuHref, toPagesScript, toScript, unitKeyString, unitManifest, unitOf, } from "@dogsbay/site-files";
34
34
  import { assembleBook, estimateTreeBytes } from "./granularity.js";
35
35
  /**
@@ -41,7 +41,7 @@ function singlePageLinkLines(href, indent) {
41
41
  if (!href)
42
42
  return [];
43
43
  const pad = " ".repeat(indent);
44
- return [`${pad}<a class="dsb-read-single-page" href="${href}">Read as single page →</a>`];
44
+ return [`${pad}<a class="dsb-read-single-page not-prose" href="${href}">Read as single page →</a>`];
45
45
  }
46
46
  import { normalizeBasePath, basePathSegments, buildCurrentPath, withBasePath, parseSiteUrl, combinePrefix, } from "./base-path.js";
47
47
  import { buildBlogData, blogAdjacency } from "./blog.js";
@@ -1933,6 +1933,14 @@ export async function emitAstroPages(pages, nav, outputDir, options) {
1933
1933
  const pageTaxonomies = pageMeta?.taxonomies && Object.keys(pageMeta.taxonomies).length > 0
1934
1934
  ? pageMeta.taxonomies
1935
1935
  : undefined;
1936
+ // The page's search facets, by the mapping dogsbay-ssg's index uses
1937
+ // too; DocsLayout writes them as Pagefind filters. Computed here
1938
+ // because only the build knows which taxonomies are hierarchical.
1939
+ const pageFacetValues = pageFacets(pageMeta, {
1940
+ tagPrefixes: options.tagPrefixes,
1941
+ hierarchical: options.hierarchicalTaxonomies,
1942
+ unit: { product: page.multiSource?.namespace, version: page.multiSource?.version },
1943
+ });
1936
1944
  // `tagsIndexPath` flows to `<TagList>` for chip hrefs
1937
1945
  // (`${indexPath}/${tag}/`). Caller passes the raw config value
1938
1946
  // (e.g. `/tags`); we bake the COMBINED prefix (urlBase from
@@ -2095,6 +2103,7 @@ export async function emitAstroPages(pages, nav, outputDir, options) {
2095
2103
  `const pageAudience = ${pageLiteral(pageAudience ?? null)};`,
2096
2104
  `const pageCategory = ${pageLiteral(pageCategory ?? null)};`,
2097
2105
  `const pageTaxonomies = ${pageLiteral(pageTaxonomies ?? null)};`,
2106
+ `const pageFacets = ${pageLiteral(pageFacetValues)};`,
2098
2107
  `const tagsIndexPath = ${pageLiteral(tagsIndexPath)};`,
2099
2108
  `const lastUpdated = ${pageLiteral(pageLastUpdated)} || undefined;`,
2100
2109
  `const llmActionsProps = ${pageLiteral(llmActionsEnabled
@@ -2150,6 +2159,7 @@ export async function emitAstroPages(pages, nav, outputDir, options) {
2150
2159
  ` category={pageCategory ?? undefined}`,
2151
2160
  ` lastUpdated={lastUpdated}`,
2152
2161
  ` taxonomies={pageTaxonomies ?? undefined}`,
2162
+ ` facets={pageFacets}`,
2153
2163
  ` autoH1={${autoH1}}`,
2154
2164
  ` autoLede={${autoLede}}`,
2155
2165
  ` llmActions={llmActionsProps}`,
@@ -2636,13 +2646,17 @@ export function emitSwitcherMap(pages, outputDir, options) {
2636
2646
  const variant = {
2637
2647
  ...(ms.locale !== undefined ? { locale: ms.locale } : {}),
2638
2648
  ...(ms.version !== undefined ? { version: ms.version } : {}),
2639
- url: `${combined}/${page.slug}`,
2649
+ // As the site serves the page (a folder, with the slash): a raw slug cost each menu link a redirect
2650
+ url: pageHref(page.slug, combined),
2640
2651
  };
2641
2652
  if (!byLogicalKey[key])
2642
2653
  byLogicalKey[key] = [];
2643
2654
  byLogicalKey[key].push(variant);
2644
2655
  }
2645
2656
  const data = { versions, locales, byLogicalKey };
2657
+ const banners = versionBanners(options, combined, pages);
2658
+ if (banners)
2659
+ data.banners = banners;
2646
2660
  const menus = versionMenus(options, combined);
2647
2661
  if (menus)
2648
2662
  data.versionMenu = menus;
@@ -2656,6 +2670,61 @@ export function emitSwitcherMap(pages, outputDir, options) {
2656
2670
  mkdirSync(dataDir, { recursive: true });
2657
2671
  writeFileSync(outPath, JSON.stringify(data, null, 2));
2658
2672
  }
2673
+ /**
2674
+ * Each unit's version banner, as dogsbay-ssg shows it: with a `products:` block the shared model's
2675
+ * (site-files `unitOf`), none on a hidden version (the number a `latest` alias serves); site-wide,
2676
+ * an older version's, pointing at the default version's unit with the same source and language
2677
+ * (its home read from that unit's pages), and none when the default version has no such unit.
2678
+ */
2679
+ function versionBanners(options, combined, pages) {
2680
+ const out = {};
2681
+ if (options.products) {
2682
+ const model = options.products;
2683
+ for (const u of model.units) {
2684
+ if (u.version === undefined)
2685
+ continue;
2686
+ const product = model.products.find((p) => p.name === u.product);
2687
+ if (product?.versions.find((v) => v.id === u.version)?.hidden)
2688
+ continue;
2689
+ const b = unitOf(model, u).banner;
2690
+ if (b)
2691
+ out[unitKeyString(u)] = b.latest ? { ...b, latest: { ...b.latest, home: `${combined}${b.latest.home}` } } : b;
2692
+ }
2693
+ }
2694
+ else if (options.defaultVersion) {
2695
+ const def = options.versions?.find((v) => v.id === options.defaultVersion);
2696
+ if (!def)
2697
+ return undefined;
2698
+ const hidden = new Set((options.versions ?? []).filter((v) => v.hidden).map((v) => v.id));
2699
+ // Each unit (source, language, version) and the URL prefix its pages are served under
2700
+ const home = new Map();
2701
+ const units = new Map();
2702
+ const bare = (x) => x.replace(/^\/+|\/+$/g, "").replace(/(^|\/)index$/, "");
2703
+ for (const page of pages) {
2704
+ const ms = page.multiSource;
2705
+ if (!ms?.version || page.redirect)
2706
+ continue;
2707
+ const u = { ...(ms.namespace ? { product: ms.namespace } : {}), ...(ms.locale ? { locale: ms.locale } : {}), version: ms.version };
2708
+ const key = unitKeyString(u);
2709
+ units.set(key, u);
2710
+ if (home.has(key))
2711
+ continue;
2712
+ const slug = bare(page.slug);
2713
+ const own = bare(ms.originalSlug);
2714
+ const prefix = own === "" ? slug : slug.endsWith(`/${own}`) ? slug.slice(0, -own.length - 1) : slug === own ? "" : undefined;
2715
+ if (prefix !== undefined)
2716
+ home.set(key, `${combined}/${prefix ? `${prefix}/` : ""}`);
2717
+ }
2718
+ for (const [key, u] of units) {
2719
+ if (u.version === def.id || hidden.has(u.version))
2720
+ continue;
2721
+ const target = home.get(unitKeyString({ ...u, version: def.id }));
2722
+ if (target)
2723
+ out[key] = { kind: "old", latest: { id: def.id, label: def.label ?? def.id, home: target } };
2724
+ }
2725
+ }
2726
+ return Object.keys(out).length ? out : undefined;
2727
+ }
2659
2728
  /** The product menu per locale (`<locale|_>`), or nothing with fewer than two products. */
2660
2729
  function productMenuRows(model, combined) {
2661
2730
  const locales = model.localeAxis ? [...new Set(model.units.map((u) => u.locale))] : [undefined];
package/dist/serialize.js CHANGED
@@ -308,9 +308,13 @@ function nodeToAstroBase(node, ctx) {
308
308
  case "tr":
309
309
  ctx.imports.add("table");
310
310
  return `<TableRow>\n${childrenToAstro(node, ctx)}\n</TableRow>`;
311
- case "th":
311
+ case "th": {
312
312
  ctx.imports.add("table");
313
- return `<TableHead>${leafContent(node, ctx)}</TableHead>`;
313
+ // An empty header cell is no header (axe `empty-table-header`): a plain cell in the header row
314
+ const content = leafContent(node, ctx);
315
+ // Written here, not through TableHead: a site keeps the component it was scaffolded with
316
+ return content.trim() ? `<TableHead>${content}</TableHead>` : `<td class="${EMPTY_HEADER_CELL_CLASS}"></td>`;
317
+ }
314
318
  case "td":
315
319
  ctx.imports.add("table");
316
320
  return `<TableCell>${leafContent(node, ctx)}</TableCell>`;
@@ -640,8 +644,15 @@ function proseToAstro(node, ctx) {
640
644
  return renderHtml(node.html, ctx);
641
645
  return "";
642
646
  }
647
+ /** TableHead's classes, for a header row's empty cell written as a plain `td`. */
648
+ const EMPTY_HEADER_CELL_CLASS = "px-2 py-2 text-left align-bottom font-semibold text-muted-foreground";
649
+ /** A card title's heading: one level below the last heading on the page, h6 at most. */
650
+ function cardTitleTag(ctx) {
651
+ return `h${Math.min((ctx.lastHeading ?? 1) + 1, 6)}`;
652
+ }
643
653
  function headingToAstro(node, ctx) {
644
654
  const level = node.props?.level ?? 1;
655
+ ctx.lastHeading = level;
645
656
  // `id` takes precedence over `slug` (slug is auto-generated, id is explicit user override)
646
657
  const id = node.props?.id ?? node.props?.slug ?? "";
647
658
  const anchor = id
@@ -1347,6 +1358,7 @@ function apiSymbolToAstro(node, ctx) {
1347
1358
  // By position, not by type (apiSymbolLevels): a module-level function was
1348
1359
  // an h3 straight under the page's h1, and a class inside a class an h2.
1349
1360
  const level = ctx.apiSymbolLevels.get(node) ?? (node.type === "api-class" ? 2 : 3);
1361
+ ctx.lastHeading = level;
1350
1362
  const attrs = [
1351
1363
  componentAttr("name", name).trimStart(),
1352
1364
  componentAttr("kind", kind).trimStart(),
@@ -1534,9 +1546,14 @@ function cardsToAstro(node, ctx) {
1534
1546
  const href = safeHref(card.props?.href);
1535
1547
  const description = card.props?.description;
1536
1548
  const icon = card.props?.icon;
1549
+ // The title's level before the body is written: a heading inside it must not move this
1550
+ // card's title, nor the next card's (each starts from the level before the group)
1551
+ const tag = cardTitleTag(ctx);
1552
+ const before = ctx.lastHeading;
1537
1553
  // Description sources: inline content, children, or props.description.
1538
1554
  // Child blocks carry their own <p>, so they get a <div>: <p><p>…</p></p> is invalid HTML.
1539
1555
  const blocks = wholeCardLink(!!href, () => childrenToAstro(card, ctx));
1556
+ ctx.lastHeading = before;
1540
1557
  const inner = blocks
1541
1558
  || (card.inline ? wholeCardLink(!!href, () => inlineToTemplate(card.inline)) : "")
1542
1559
  || (description ? escapeTemplate(description) : "");
@@ -1550,7 +1567,7 @@ function cardsToAstro(node, ctx) {
1550
1567
  if (icon) {
1551
1568
  iconHtml = cardIconHtml(icon, title, ctx);
1552
1569
  }
1553
- const titleHtml = title ? `<h3 class="${CARD_TITLE_CLASS}">${escapeTemplate(title)}</h3>` : "";
1570
+ const titleHtml = title ? `<${tag} class="${CARD_TITLE_CLASS}">${escapeTemplate(title)}</${tag}>` : "";
1554
1571
  // A description and a body are both shown (the description used to be dropped when a body existed).
1555
1572
  const lead = blocks && description
1556
1573
  ? `<p class="text-sm text-muted-foreground mt-1 [text-decoration:none]">${escapeTemplate(description)}</p>`
@@ -1569,7 +1586,7 @@ function cardsToAstro(node, ctx) {
1569
1586
  "title", "href", "description", "icon", "variant", "class", "tone",
1570
1587
  ]);
1571
1588
  if (href) {
1572
- 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>`;
1589
+ return `<Card${cardClassAttr}${cardPass}>\n<CardContent class="p-0">\n<a href="${escapeAttr(href)}" data-variant="card" class="[text-decoration:none] text-foreground">\n${iconHtml}\n${titleHtml}\n${desc}\n</a>\n</CardContent>\n</Card>`;
1573
1590
  }
1574
1591
  return `<Card${cardClassAttr}${cardPass}>\n<CardContent class="p-0">\n${iconHtml}\n${titleHtml}\n${desc}\n</CardContent>\n</Card>`;
1575
1592
  }).join("\n");
@@ -1601,8 +1618,12 @@ function standaloneCardToAstro(node, ctx) {
1601
1618
  const href = safeHref(node.props?.href);
1602
1619
  const description = node.props?.description;
1603
1620
  const icon = node.props?.icon;
1621
+ // The title's level before the body is written (a heading inside it must not move it)
1622
+ const tag = cardTitleTag(ctx);
1623
+ const before = ctx.lastHeading;
1604
1624
  // Child blocks carry their own <p>, so they get a <div>: <p><p>…</p></p> is invalid HTML.
1605
1625
  const blocks = wholeCardLink(!!href, () => childrenToAstro(node, ctx));
1626
+ ctx.lastHeading = before;
1606
1627
  const inner = blocks
1607
1628
  || (node.inline ? wholeCardLink(!!href, () => inlineToTemplate(node.inline)) : "")
1608
1629
  || (description ? escapeTemplate(description) : "");
@@ -1610,7 +1631,7 @@ function standaloneCardToAstro(node, ctx) {
1610
1631
  // Icon renders inline before the title, a name or an image path, as in a card group
1611
1632
  const iconHtml = icon ? cardIconHtml(icon, title, ctx) : "";
1612
1633
  const titleHtml = title
1613
- ? `<h3 class="${CARD_TITLE_CLASS}">${escapeTemplate(title)}</h3>`
1634
+ ? `<${tag} class="${CARD_TITLE_CLASS}">${escapeTemplate(title)}</${tag}>`
1614
1635
  : "";
1615
1636
  // A description and a body are both shown (the description used to be dropped when a body existed).
1616
1637
  const lead = blocks && description
@@ -1629,7 +1650,7 @@ function standaloneCardToAstro(node, ctx) {
1629
1650
  "title", "href", "description", "icon", "variant", "class", "tone",
1630
1651
  ]);
1631
1652
  if (href) {
1632
- 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>`;
1653
+ return `<Card${classAttr}${passthrough}>\n<CardContent class="p-0">\n<a href="${escapeAttr(href)}" data-variant="card" class="[text-decoration:none] text-foreground">\n${iconHtml}\n${titleHtml}\n${desc}\n</a>\n</CardContent>\n</Card>`;
1633
1654
  }
1634
1655
  return `<Card${classAttr}${passthrough}>\n<CardContent class="p-0">\n${iconHtml}\n${titleHtml}\n${desc}\n</CardContent>\n</Card>`;
1635
1656
  }
package/dist/theme-css.js CHANGED
@@ -90,21 +90,27 @@ export function generateGlobalCss() {
90
90
  @source inline("${buildVariantSafelist()}"); /* ASTRO-ONLY: variants, tones, theme scopes */
91
91
 
92
92
  /* Prose typography for rendered content.
93
- Every element rule is guarded with :not(.dba-api *): API-reference
94
- regions (ApiLayout root carries the dba-api marker) compose their own
95
- typography from utility classes, and unguarded .docs-prose rules would
96
- out-specify them. The guard lets mixed prose+endpoint pages (MDX
97
- imports) keep full typography outside the cards. */
93
+ Every element rule is guarded with :not(.dba-api *, .not-prose, .not-prose *).
94
+ API-reference regions (ApiLayout root carries the dba-api marker) compose
95
+ their own typography from utility classes, and unguarded .docs-prose rules
96
+ would out-specify them (these rules are unlayered, so they beat every
97
+ utility). The guard lets mixed prose+endpoint pages (MDX imports) keep full
98
+ typography outside the cards. .not-prose means what it means in Tailwind
99
+ Typography: that element and everything inside it is UI, not prose. Only
100
+ the heading and link rules skipped it, and only the element itself, so a
101
+ search page's facet list and result list inside an article got bullets,
102
+ indents and underlines. A wrapper around AUTHORED content (a card group, an
103
+ API symbol) must therefore not carry it. */
98
104
  .docs-prose {
99
105
  line-height: 1.7;
100
106
 
101
107
  /* A heading that is part of a component (a card title: h3.not-prose) keeps the component look */
102
- & h1:not(.dba-api *):not(.not-prose) { font-family: var(--font-heading); font-size: 2rem; font-weight: 700; margin-top: 0; margin-bottom: 1rem; line-height: 1.2; letter-spacing: -0.025em; }
103
- & h2:not(.dba-api *):not(.not-prose) { font-family: var(--font-heading); font-size: 1.5rem; font-weight: 600; margin-top: 2.5rem; margin-bottom: 0.75rem; line-height: 1.3; border-bottom: 1px solid var(--border); padding-bottom: 0.5rem; letter-spacing: -0.015em; }
104
- & h3:not(.dba-api *):not(.not-prose) { font-family: var(--font-heading); font-size: 1.25rem; font-weight: 600; margin-top: 2rem; margin-bottom: 0.5rem; line-height: 1.4; }
105
- & h4:not(.dba-api *):not(.not-prose) { font-family: var(--font-heading); font-size: 1.1rem; font-weight: 600; margin-top: 1.5rem; margin-bottom: 0.5rem; }
108
+ & h1:not(.dba-api *, .not-prose, .not-prose *) { font-family: var(--font-heading); font-size: 2rem; font-weight: 700; margin-top: 0; margin-bottom: 1rem; line-height: 1.2; letter-spacing: -0.025em; }
109
+ & h2:not(.dba-api *, .not-prose, .not-prose *) { font-family: var(--font-heading); font-size: 1.5rem; font-weight: 600; margin-top: 2.5rem; margin-bottom: 0.75rem; line-height: 1.3; border-bottom: 1px solid var(--border); padding-bottom: 0.5rem; letter-spacing: -0.015em; }
110
+ & h3:not(.dba-api *, .not-prose, .not-prose *) { font-family: var(--font-heading); font-size: 1.25rem; font-weight: 600; margin-top: 2rem; margin-bottom: 0.5rem; line-height: 1.4; }
111
+ & h4:not(.dba-api *, .not-prose, .not-prose *) { font-family: var(--font-heading); font-size: 1.1rem; font-weight: 600; margin-top: 1.5rem; margin-bottom: 0.5rem; }
106
112
 
107
- & p:not(.dba-api *) { margin-top: 0.75rem; margin-bottom: 0.75rem; }
113
+ & p:not(.dba-api *, .not-prose, .not-prose *) { margin-top: 0.75rem; margin-bottom: 0.75rem; }
108
114
 
109
115
  /* The :not([data-variant]) guard exempts anchors that ARE components. A
110
116
  link-button renders as an <a> carrying bg-primary + text-primary-foreground
@@ -116,48 +122,45 @@ export function generateGlobalCss() {
116
122
  (No backticks in this comment: the whole stylesheet is a template
117
123
  literal.) */
118
124
  /* Not a whole-card link (LinkCard, the video card: \`a.not-prose\`): its underline ran under every line of the card */
119
- & a:not(.dba-api *):not([data-variant]):not(.not-prose) { color: var(--primary); text-decoration: underline; text-underline-offset: 2px; }
120
- & a:hover:not(.dba-api *):not([data-variant]):not(.not-prose) { opacity: 0.8; }
125
+ & a:not(.dba-api *, .not-prose, .not-prose *):not([data-variant]) { color: var(--primary); text-decoration: underline; text-underline-offset: 2px; }
126
+ & a:hover:not(.dba-api *, .not-prose, .not-prose *):not([data-variant]) { opacity: 0.8; }
121
127
 
122
- & strong:not(.dba-api *) { font-weight: 600; }
123
- & code:not(.dba-api *) { font-size: 0.875em; padding: 0.15em 0.35em; border-radius: 0.25rem; background: var(--muted); font-family: var(--font-code, ui-monospace, monospace); }
124
- & pre code:not(.dba-api *) { padding: 0; background: none; font-size: 1em; }
128
+ & strong:not(.dba-api *, .not-prose, .not-prose *) { font-weight: 600; }
129
+ & code:not(.dba-api *, .not-prose, .not-prose *) { font-size: 0.875em; padding: 0.15em 0.35em; border-radius: 0.25rem; background: var(--muted); font-family: var(--font-code, ui-monospace, monospace); }
130
+ & pre code:not(.dba-api *, .not-prose, .not-prose *) { padding: 0; background: none; font-size: 1em; }
125
131
 
126
132
  /* Spacing between consecutive block elements (code blocks, alerts, etc.) */
127
133
  & > * + * { margin-top: 0.75rem; }
128
- & li > * + *:not(.dba-api *) { margin-top: 0.75rem; }
134
+ & li > * + *:not(.dba-api *, .not-prose *) { margin-top: 0.75rem; }
129
135
 
130
- & ul:not(.dba-api *) { list-style: disc; padding-left: 1.5rem; margin: 0.75rem 0; }
136
+ & ul:not(.dba-api *, .not-prose, .not-prose *) { list-style: disc; padding-left: 1.5rem; margin: 0.75rem 0; }
131
137
  /* Not a step list, nor a list inside one: Steps.astro draws their numbers as circles, and this
132
138
  rule (one class and an element) outranked its .steps-list { list-style: none }, so each step
133
139
  showed its "2." beside its circle */
134
- & ol:not(.dba-api *):not(.steps-list, .sub-steps-list, .steps-list ol) { list-style: decimal; padding-left: 1.5rem; margin: 0.75rem 0; }
135
- & li:not(.dba-api *) { margin: 0.25rem 0; }
136
- & li > ul:not(.dba-api *), & li > ol:not(.dba-api *) { margin: 0.25rem 0; }
137
- /* A file tree draws its own folder and file icons (files/markup.ts): the list rules above gave it bullets too */
138
- & [data-component="files"] ul:not(.dba-api *) { list-style: none; padding-left: 0; }
139
- & [data-component="files"] li > ul:not(.dba-api *) { padding-left: 1rem; }
140
+ & ol:not(.dba-api *, .not-prose, .not-prose *):not(.steps-list, .sub-steps-list, .steps-list ol) { list-style: decimal; padding-left: 1.5rem; margin: 0.75rem 0; }
141
+ & li:not(.dba-api *, .not-prose, .not-prose *) { margin: 0.25rem 0; }
142
+ & li > ul:not(.dba-api *, .not-prose, .not-prose *), & li > ol:not(.dba-api *, .not-prose, .not-prose *) { margin: 0.25rem 0; }
140
143
 
141
- & blockquote:not(.dba-api *) { border-left: 4px solid var(--border); padding-left: 1rem; color: var(--muted-foreground); font-style: italic; margin: 1rem 0; }
144
+ & blockquote:not(.dba-api *, .not-prose, .not-prose *) { border-left: 4px solid var(--border); padding-left: 1rem; color: var(--muted-foreground); font-style: italic; margin: 1rem 0; }
142
145
 
143
- & hr:not(.dba-api *) { border: none; border-top: 1px solid var(--border); margin: 2rem 0; }
146
+ & hr:not(.dba-api *, .not-prose, .not-prose *) { border: none; border-top: 1px solid var(--border); margin: 2rem 0; }
144
147
 
145
- & table:not(.dba-api *) { width: 100%; border-collapse: collapse; margin: 1rem 0; font-size: 0.875rem; }
146
- & th:not(.dba-api *) { text-align: left; vertical-align: bottom; font-weight: 600; padding: 0.5rem; border-bottom: 2px solid var(--border); }
147
- & td:not(.dba-api *) { padding: 0.5rem; vertical-align: top; border-bottom: 1px solid var(--border); }
148
+ & table:not(.dba-api *, .not-prose, .not-prose *) { width: 100%; border-collapse: collapse; margin: 1rem 0; font-size: 0.875rem; }
149
+ & th:not(.dba-api *, .not-prose, .not-prose *) { text-align: left; vertical-align: bottom; font-weight: 600; padding: 0.5rem; border-bottom: 2px solid var(--border); }
150
+ & td:not(.dba-api *, .not-prose, .not-prose *) { padding: 0.5rem; vertical-align: top; border-bottom: 1px solid var(--border); }
148
151
  /* A table's caption is its title, above it; the same for Table components and raw HTML tables */
149
- & caption:not(.dba-api *) { caption-side: top; text-align: left; font-weight: 500; color: var(--foreground); padding-bottom: 0.5rem; }
152
+ & caption:not(.dba-api *, .not-prose, .not-prose *) { caption-side: top; text-align: left; font-weight: 500; color: var(--foreground); padding-bottom: 0.5rem; }
150
153
 
151
154
  /* Tailwind Preflight sets display: block on every img, which turns an
152
155
  INLINE icon into its own line and splits the sentence around it —
153
156
  AsciiDoc UI macros render mid-step and were breaking every procedure
154
157
  that used one. inline-block restores the flow and leaves a standalone
155
158
  image (the only child of its paragraph) looking exactly as before. */
156
- & img:not(.dba-api *) { display: inline-block; max-width: 100%; border-radius: 0.5rem; }
159
+ & img:not(.dba-api *, .not-prose, .not-prose *) { display: inline-block; max-width: 100%; border-radius: 0.5rem; }
157
160
  /* A standalone image — the only child of its paragraph — keeps block
158
161
  layout, so the vertical rhythm and the absence of baseline descender
159
162
  space are exactly what they were before inline icons were fixed. */
160
- & p > img:only-child:not(.dba-api *) { display: block; }
163
+ & p > img:only-child:not(.dba-api *, .not-prose, .not-prose *) { display: block; }
161
164
 
162
165
  & .heading-anchor { text-decoration: none; opacity: 0; margin-right: 0.25rem; transition: opacity 0.2s; }
163
166
  & h1:hover .heading-anchor, & h2:hover .heading-anchor, & h3:hover .heading-anchor, & h4:hover .heading-anchor { opacity: 0.4; }
@@ -166,19 +169,19 @@ export function generateGlobalCss() {
166
169
  callouts) is a <details> too, with its own border and spacing; it
167
170
  carries data-variant, and without the exclusion it got a second border
168
171
  and 1rem of padding around its title bar. */
169
- & details:not(.dba-api *):not([data-variant]) { border: 1px solid var(--border); border-radius: 0.5rem; padding: 1rem; margin: 1rem 0; }
170
- & summary:not(.dba-api *) { cursor: pointer; font-weight: 600; }
172
+ & details:not(.dba-api *, .not-prose, .not-prose *):not([data-variant]) { border: 1px solid var(--border); border-radius: 0.5rem; padding: 1rem; margin: 1rem 0; }
173
+ & summary:not(.dba-api *, .not-prose, .not-prose *) { cursor: pointer; font-weight: 600; }
171
174
 
172
175
  /* mark is emitted by the highlight directive AND can arrive as raw HTML
173
176
  from an importer. Without a rule here it renders as the browser default
174
177
  yellow, which clashes in light mode and is unreadable on a dark page
175
178
  (nothing declares color-scheme). Token-based so it follows the theme. */
176
- & mark:not(.dba-api *) { background: var(--dsb-mark-bg, rgb(254 240 138 / 0.6)); color: inherit; border-radius: 0.125rem; padding: 0 0.125rem; }
177
- &:where(.dark *) mark:not(.dba-api *), .dark & mark:not(.dba-api *) { background: var(--dsb-mark-bg-dark, rgb(234 179 8 / 0.3)); }
179
+ & mark:not(.dba-api *, .not-prose, .not-prose *) { background: var(--dsb-mark-bg, rgb(254 240 138 / 0.6)); color: inherit; border-radius: 0.125rem; padding: 0 0.125rem; }
180
+ &:where(.dark *) mark:not(.dba-api *, .not-prose, .not-prose *), .dark & mark:not(.dba-api *, .not-prose, .not-prose *) { background: var(--dsb-mark-bg-dark, rgb(234 179 8 / 0.3)); }
178
181
 
179
- & dl:not(.dba-api *) { margin: 1rem 0; }
180
- & dt:not(.dba-api *) { font-weight: 600; margin-top: 0.75rem; }
181
- & dd:not(.dba-api *) { margin-left: 1.5rem; color: var(--muted-foreground); }
182
+ & dl:not(.dba-api *, .not-prose, .not-prose *) { margin: 1rem 0; }
183
+ & dt:not(.dba-api *, .not-prose, .not-prose *) { font-weight: 600; margin-top: 0.75rem; }
184
+ & dd:not(.dba-api *, .not-prose, .not-prose *) { margin-left: 1.5rem; color: var(--muted-foreground); }
182
185
 
183
186
  /* In-cell admonitions. Inside a raw HTML table cell nothing
184
187
  markdown-native works, so the AsciiDoc engine emits a neutral
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dogsbay/format-astro",
3
- "version": "0.2.0-beta.152",
3
+ "version": "0.2.0-beta.153",
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,13 +32,13 @@
32
32
  "license": "MIT",
33
33
  "dependencies": {
34
34
  "markdown-it": "^14.3.0",
35
- "@dogsbay/format-dogsbay-md": "0.2.0-beta.152",
36
- "@dogsbay/icons": "0.2.0-beta.152",
37
- "@dogsbay/render-html": "0.2.0-beta.152",
38
- "@dogsbay/serialize-core": "0.2.0-beta.152",
39
- "@dogsbay/types": "0.2.0-beta.152",
40
- "@dogsbay/site-files": "0.2.0-beta.152",
41
- "@dogsbay/ui": "0.2.0-beta.152"
35
+ "@dogsbay/format-dogsbay-md": "0.2.0-beta.153",
36
+ "@dogsbay/render-html": "0.2.0-beta.153",
37
+ "@dogsbay/icons": "0.2.0-beta.153",
38
+ "@dogsbay/serialize-core": "0.2.0-beta.153",
39
+ "@dogsbay/site-files": "0.2.0-beta.153",
40
+ "@dogsbay/types": "0.2.0-beta.153",
41
+ "@dogsbay/ui": "0.2.0-beta.153"
42
42
  },
43
43
  "repository": {
44
44
  "type": "git",