hd-garden 0.14.7 → 0.14.10

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.
@@ -0,0 +1,23 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { useLayoutEffect, useRef } from "react";
5
+ import { mountFeatureTitle } from "../../ui/garden/feature-title-fit.mjs";
6
+ import runtimeSource from "../../ui/garden/feature-title-fit.mjs?raw";
7
+
8
+ // Preserve one authored runtime across SSR/browser builds; function.toString()
9
+ // would be changed independently by server and client optimizers.
10
+ const FEATURE_TITLE_BOOT = `(()=>{${runtimeSource.replace(/^export /gm, "")}\nmountFeatureTitle(document.currentScript.parentElement);})();`;
11
+
12
+ type FeaturedTitleProps = { title: string; href: string; description?: string };
13
+
14
+ export function FeaturedTitle({ title, href, description }: FeaturedTitleProps) {
15
+ const heading = useRef<HTMLHeadingElement>(null);
16
+ useLayoutEffect(() => mountFeatureTitle(heading.current?.parentElement), [title, href, description]);
17
+ return <>
18
+ <h2 id="ft" ref={heading} data-feature-title=""><Link href={href}>{title}</Link></h2>
19
+ {description ? <p data-feature-summary="">{description}</p> : null}
20
+ <style data-feature-title-rules="">{""}</style>
21
+ <script data-feature-title-boot="" dangerouslySetInnerHTML={{ __html: FEATURE_TITLE_BOOT }} />
22
+ </>;
23
+ }
@@ -15,6 +15,7 @@ import Link from "next/link";
15
15
  import ReactMarkdown from "react-markdown";
16
16
  import rehypeRaw from "rehype-raw";
17
17
  import rehypeKatex from "rehype-katex";
18
+ import {rehypeKeepPunctuation} from "../../ui/garden/rehype-keep-punctuation.mjs";
18
19
  import rehypeSlug from "rehype-slug";
19
20
  import rehypeHighlight from "rehype-highlight";
20
21
  import { rehypeHeadingAnchors } from "../../ui/garden/heading-anchors.mjs";
@@ -254,6 +255,7 @@ export function MarkdownArticle({ content, lang, fieldTables, refDemos, handbook
254
255
  rehypeRaw,
255
256
  rehypeGardenBlocks,
256
257
  rehypeKatex,
258
+ rehypeKeepPunctuation,
257
259
  rehypeSlug,
258
260
  // Not rehype-autolink-headings "wrap": a heading with a link in it would nest <a> in <a> (React #418).
259
261
  [rehypeHeadingAnchors, { className: ["h-anchor"] }],
@@ -4,39 +4,28 @@ import {
4
4
  useCallback,
5
5
  useEffect,
6
6
  useId,
7
+ useMemo,
7
8
  useRef,
8
9
  useState,
9
10
  useSyncExternalStore,
10
11
  type MouseEvent,
11
12
  type ReactNode,
12
13
  } from "react";
14
+ import { usePathname } from "next/navigation";
15
+ import { nestHeadings, expandedTocSections, toggleTocSection, type TocHeading, type TocNode, type TocExpansion } from "../lib/toc-expansion";
13
16
  import { createPortal } from "react-dom";
14
17
  import { TOC_PANEL_ID, TOC_CONTROL_ID, TOC_CLOSED_ID } from "../lib/reading-navigation";
15
- import { X } from "@phosphor-icons/react";
18
+ import { CaretUp, CaretDown, X } from "@phosphor-icons/react";
16
19
  import { ui, type UiLang } from "../lib/i18n";
17
20
 
18
- type Heading = { depth: number; text: string; id: string; no?: string };
21
+ type TocProps = { headings: TocHeading[]; label?: string; lang?: UiLang };
19
22
 
20
- type TocNode = Heading & { children: TocNode[] };
21
-
22
- const nestHeadings = (headings: Heading[]) => {
23
- const roots: TocNode[] = [];
24
- const stack: TocNode[] = [];
25
-
26
- for (const heading of headings) {
27
- const node: TocNode = { ...heading, children: [] };
28
- while (stack.at(-1) && stack.at(-1)!.depth >= heading.depth) {
29
- stack.pop();
30
- }
31
-
32
- const parent = stack.at(-1);
33
- if (parent) parent.children.push(node);
34
- else roots.push(node);
35
- stack.push(node);
36
- }
37
-
38
- return roots;
39
- };
23
+ // A route/content change starts a fresh reading session, including when React
24
+ // reuses the component during normal client navigation.
25
+ export function TableOfContents(props: TocProps) {
26
+ const pathname = usePathname();
27
+ return <ContentsSession key={`${pathname}:${JSON.stringify(props.headings)}`} {...props} />;
28
+ }
40
29
 
41
30
  const getCssScrollPaddingTop = () => {
42
31
  const value = window.getComputedStyle(document.documentElement).scrollPaddingTop;
@@ -131,22 +120,15 @@ function subscribeControlHost(changed: () => void) {
131
120
  return () => observer.disconnect();
132
121
  }
133
122
 
134
- export function TableOfContents({
123
+ function ContentsSession({
135
124
  headings,
136
125
  label,
137
126
  lang,
138
- }: {
139
- headings: Heading[];
140
- label?: string;
141
- /** Defaults to the site language; the bilingual handbook passes its own. */
142
- lang?: UiLang;
143
- }) {
127
+ }: TocProps) {
144
128
  const t = ui(lang);
145
129
  label ??= t.toc;
146
- const [activeId, setActiveId] = useState(headings[0]?.id);
147
- const [collapsedSections, setCollapsedSections] = useState<Set<string>>(
148
- () => new Set(),
149
- );
130
+ const [activeId, setActiveId] = useState<string>();
131
+ const [expansion, setExpansion] = useState<TocExpansion>({ mode: "follow" });
150
132
  const [mobileOpen, setMobileOpen] = useState(false);
151
133
  const panelId = TOC_PANEL_ID;
152
134
  const controlHost = useSyncExternalStore(subscribeControlHost, readControlHost, serverControlHost);
@@ -157,7 +139,8 @@ export function TableOfContents({
157
139
  const mobileCloseRef = useRef<HTMLButtonElement>(null);
158
140
  const restoreMobileTriggerFocusRef = useRef(false);
159
141
  const anchorCleanupRef = useRef<(() => void) | undefined>(undefined);
160
- const tree = nestHeadings(headings);
142
+ const tree = useMemo(() => nestHeadings(headings), [headings]);
143
+ const expanded = useMemo(() => expandedTocSections(tree, expansion, activeId), [tree, expansion, activeId]);
161
144
 
162
145
  const closeMobileToc = useCallback((restoreFocus = false) => {
163
146
  restoreMobileTriggerFocusRef.current = restoreFocus;
@@ -250,9 +233,9 @@ export function TableOfContents({
250
233
  const current = reachedPageEnd
251
234
  ? visibleHeadings.at(-1)?.id
252
235
  : visibleHeadings.filter((heading) => heading.top <= readingProbe).at(-1)
253
- ?.id ?? visibleHeadings[0]?.id;
236
+ ?.id;
254
237
 
255
- if (current) setActiveId((previous) => (previous === current ? previous : current));
238
+ setActiveId((previous) => (previous === current ? previous : current));
256
239
  };
257
240
  const scheduleUpdate = () => {
258
241
  if (!frame) frame = window.requestAnimationFrame(updateActiveHeading);
@@ -341,7 +324,7 @@ export function TableOfContents({
341
324
  const activeItem = list?.querySelector<HTMLElement>(
342
325
  '[aria-current="location"]',
343
326
  );
344
- if (!list || !activeItem) return;
327
+ if (!list || !activeItem || !activeItem.getClientRects().length) return;
345
328
 
346
329
  const edge = 10;
347
330
  const listBounds = list.getBoundingClientRect();
@@ -358,16 +341,20 @@ export function TableOfContents({
358
341
  const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
359
342
  list.scrollTo({ top: nextTop, behavior: reduceMotion ? "auto" : "smooth" });
360
343
  }
361
- }, [activeId, collapsedSections, mobileOpen]);
362
-
363
- const toggleSection = (id: string) => {
364
- setCollapsedSections((current) => {
365
- const next = new Set(current);
366
- if (next.has(id)) next.delete(id);
367
- else next.add(id);
368
- return next;
369
- });
370
- };
344
+ }, [activeId, expanded, mobileOpen]);
345
+
346
+ useEffect(() => {
347
+ const focused = document.activeElement;
348
+ if (!(focused instanceof HTMLElement) || !listRef.current?.contains(focused) || focused.getClientRects().length) return;
349
+ let section = focused.closest(".toc-section");
350
+ while (section) {
351
+ const link = section.querySelector<HTMLAnchorElement>(":scope > .toc-item-row > a");
352
+ if (link?.getClientRects().length) { link.focus({ preventScroll: true }); break; }
353
+ section = section.parentElement?.closest(".toc-section") ?? null;
354
+ }
355
+ }, [expanded]);
356
+
357
+ const toggleSection = (id: string) => setExpansion((current) => toggleTocSection(expandedTocSections(tree, current, activeId), id));
371
358
 
372
359
  const navigateToHeading = (event: MouseEvent<HTMLAnchorElement>, id: string) => {
373
360
  event.preventDefault();
@@ -390,7 +377,7 @@ export function TableOfContents({
390
377
  };
391
378
 
392
379
  const renderNode = (node: TocNode, path: number[]): ReactNode => {
393
- const collapsed = collapsedSections.has(node.id);
380
+ const collapsed = !expanded.has(node.id);
394
381
  const childListId = `${listId}-${path.join("-")}`;
395
382
 
396
383
  return (
@@ -422,7 +409,7 @@ export function TableOfContents({
422
409
  ) : null}
423
410
  </div>
424
411
  {node.children.length > 0 ? (
425
- <ol id={childListId} className="toc-sublist" hidden={collapsed}>
412
+ <ol id={childListId} className="toc-sublist" role="list" data-collapsed={collapsed}>
426
413
  {node.children.map((child, index) => renderNode(child, [...path, index]))}
427
414
  </ol>
428
415
  ) : null}
@@ -465,9 +452,23 @@ export function TableOfContents({
465
452
  id={panelId}
466
453
  aria-label={label}
467
454
  data-mobile-open={mobileOpen}
455
+ data-toc-mode={expansion.mode}
456
+ data-toc-active={activeId || ""}
468
457
  >
469
458
  <div className="toc-header">
470
459
  <p>{label}</p>
460
+ {tree.some((node) => node.children.length > 0) ? <div className="toc-mode-controls">
461
+ <button
462
+ type="button"
463
+ data-toc-mode-toggle=""
464
+ aria-controls={listId}
465
+ aria-label={expansion.mode === "follow" ? t.tocExpandAll : t.tocResumeFollow}
466
+ title={expansion.mode === "follow" ? t.tocExpandAll : t.tocResumeFollow}
467
+ onClick={() => setExpansion({ mode: expansion.mode === "follow" ? "all" : "follow" })}
468
+ >
469
+ {expansion.mode === "follow" ? <CaretDown size={14} aria-hidden="true" /> : <CaretUp size={14} aria-hidden="true" />}
470
+ </button>
471
+ </div> : null}
471
472
  <button
472
473
  ref={mobileCloseRef}
473
474
  className="toc-mobile-close"
@@ -481,7 +482,7 @@ export function TableOfContents({
481
482
  <X size={18} aria-hidden="true" />
482
483
  </a>
483
484
  </div>
484
- <ol id={listId} ref={listRef} className="toc-list">
485
+ <ol id={listId} ref={listRef} className="toc-list" role="list">
485
486
  {tree.map((node, index) => renderNode(node, [index]))}
486
487
  </ol>
487
488
  </aside>
package/app/globals.css CHANGED
@@ -1,6 +1,7 @@
1
1
  @import "tailwindcss";
2
2
  @import "../ui/react/ui.css";
3
3
  @import "../ui/compose/compose.css";
4
+ @import "../ui/garden/inline-math.css";
4
5
  @import "./admin-ui.css";
5
6
 
6
7
  :root {
@@ -1734,6 +1735,8 @@ button {
1734
1735
  }
1735
1736
 
1736
1737
  .toc-header > p {
1738
+ flex: 1 1 auto;
1739
+ min-width: 0;
1737
1740
  margin: 0;
1738
1741
  color: var(--accent);
1739
1742
  font-size: 12px;
@@ -1754,10 +1757,13 @@ button {
1754
1757
 
1755
1758
  .toc .toc-sublist {
1756
1759
  margin: 0;
1757
- padding: 0 0 0 12px;
1760
+ padding: 0;
1761
+ padding-inline-start: 1.25em;
1758
1762
  list-style: none;
1759
1763
  }
1760
1764
 
1765
+ .toc .toc-sublist[data-collapsed="true"] { display: none; }
1766
+
1761
1767
  .toc-item-row {
1762
1768
  display: flex;
1763
1769
  align-items: flex-start;
@@ -1804,10 +1810,11 @@ button {
1804
1810
  line-height: 1.6;
1805
1811
  }
1806
1812
 
1807
- .toc li.toc-depth-4,
1808
- .toc li.toc-depth-5 {
1809
- font-size: 11px;
1810
- }
1813
+ .toc-list > .toc-section > .toc-item-row > a { font-weight: 600; }
1814
+ .toc-sublist > .toc-section > .toc-item-row > a { font-weight: 400; }
1815
+ .toc-mode-controls { display: flex; align-items: center; flex: 0 0 auto; margin-left: auto; }
1816
+ .toc-mode-controls button { display: grid; place-items: center; width: 24px; height: 24px; padding: 0; border: 0; background: transparent; color: var(--muted); cursor: pointer; touch-action: manipulation; }
1817
+ .toc-mode-controls button:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
1811
1818
 
1812
1819
  .toc a:hover {
1813
1820
  color: var(--ink);
@@ -2557,6 +2564,7 @@ button {
2557
2564
  overflow-wrap: normal;
2558
2565
  }
2559
2566
 
2567
+
2560
2568
  .markdown-body .footnotes {
2561
2569
  margin-top: 64px;
2562
2570
  padding-top: 22px;
@@ -3868,13 +3876,16 @@ html[data-theme="dark"] .search-footer {
3868
3876
  padding-right: max(20px, env(safe-area-inset-right));
3869
3877
  }
3870
3878
 
3871
- .toc .toc-item-row > a { display: flex; align-items: center; min-height: 44px; padding-block: 8px; }
3872
- .toc .toc-section-toggle { min-width: 44px; min-height: 44px; }
3879
+ .toc .toc-item-row > a { display: flex; align-items: center; min-height: 40px; padding-block: 4px; }
3880
+ .toc .toc-section-toggle { min-width: 40px; min-height: 40px; }
3881
+ .toc .toc-mode-controls button { width: 44px; height: 44px; }
3873
3882
 
3874
- .toc li {
3875
- margin: 10px 0;
3883
+ .toc .toc-list .toc-section {
3884
+ margin: 2px 0;
3876
3885
  font-size: 13px;
3886
+ line-height: 1.5;
3877
3887
  }
3888
+ .toc-list > .toc-section + .toc-section { margin-top: 6px; }
3878
3889
 
3879
3890
  .post-reading-page {
3880
3891
  width: min(var(--content), calc(100% - 48px));
@@ -5393,9 +5404,10 @@ html[data-theme="dark"] .search-footer {
5393
5404
  /* Without JavaScript every sub-list stays open and the collapse toggles do
5394
5405
  nothing, so hide them at every width. */
5395
5406
  @media (scripting: none) {
5396
- .toc-section-toggle {
5407
+ .toc-section-toggle, .toc-mode-controls {
5397
5408
  display: none;
5398
5409
  }
5410
+ .toc .toc-sublist[data-collapsed="true"] { display: block; }
5399
5411
  }
5400
5412
 
5401
5413
  /* No JavaScript: the drawer button cannot open anything, so swap in plain
package/app/lib/i18n.ts CHANGED
@@ -38,6 +38,8 @@ const zh = {
38
38
  columnNewestFirst: (title: string | undefined) => `${title},从新到旧`,
39
39
  toc: "本篇目录",
40
40
  tocShort: "目录",
41
+ tocResumeFollow: "恢复跟读",
42
+ tocExpandAll: "全部展开",
41
43
  tocPage: "本页目录",
42
44
  open: "打开",
43
45
  close: "关闭",
@@ -296,6 +298,8 @@ const en: typeof zh = {
296
298
  columnNewestFirst: (title: string | undefined) => `${title}, newest first`,
297
299
  toc: "Contents",
298
300
  tocShort: "TOC",
301
+ tocResumeFollow: "Resume following",
302
+ tocExpandAll: "Expand all",
299
303
  tocPage: "On this page",
300
304
  open: "Open ",
301
305
  close: "Close ",
@@ -0,0 +1,49 @@
1
+ export type TocHeading = { depth: number; text: string; id: string; no?: string };
2
+ export type TocNode = TocHeading & { children: TocNode[] };
3
+ export type TocExpansion =
4
+ | { mode: "follow" }
5
+ | { mode: "all" }
6
+ | { mode: "manual"; expanded: ReadonlySet<string> };
7
+
8
+ export function nestHeadings(headings: readonly TocHeading[]): TocNode[] {
9
+ const roots: TocNode[] = [], stack: TocNode[] = [];
10
+ for (const heading of headings) {
11
+ const node: TocNode = { ...heading, children: [] };
12
+ let parent = stack.at(-1);
13
+ while (parent && parent.depth >= node.depth) {
14
+ stack.pop();
15
+ parent = stack.at(-1);
16
+ }
17
+ if (parent) parent.children.push(node);
18
+ else roots.push(node);
19
+ stack.push(node);
20
+ }
21
+ return roots;
22
+ }
23
+
24
+ // The nearest shallower heading is the parent even when heading ranks skip.
25
+ // Opening only this path exposes the current heading and its immediate children.
26
+ export function expandedTocSections(tree: readonly TocNode[], state: TocExpansion, activeId?: string): ReadonlySet<string> {
27
+ if (state.mode === "manual") return state.expanded;
28
+ const expanded = new Set<string>();
29
+ function visit(nodes: readonly TocNode[]): boolean {
30
+ let containsActive = false;
31
+ for (const node of nodes) {
32
+ const onPath = visit(node.children) || node.id === activeId;
33
+ if (node.children.length && (state.mode === "all" || onPath)) expanded.add(node.id);
34
+ containsActive ||= onPath;
35
+ }
36
+ return containsActive;
37
+ }
38
+ visit(tree);
39
+ return expanded;
40
+ }
41
+
42
+ // An individual disclosure is an explicit manual browsing choice. Freeze the
43
+ // current visible branches instead of fighting the next scroll-spy update.
44
+ export function toggleTocSection(expanded: ReadonlySet<string>, id: string): TocExpansion {
45
+ const next = new Set(expanded);
46
+ if (next.has(id)) next.delete(id);
47
+ else next.add(id);
48
+ return { mode: "manual", expanded: next };
49
+ }
package/app/page.tsx CHANGED
@@ -1,4 +1,5 @@
1
1
  import Link from "next/link";
2
+ import { FeaturedTitle } from "./components/FeaturedTitle";
2
3
  import { CoverImage } from "./components/CoverImage";
3
4
  import { fullListDate, homeListDate } from "./lib/a2-meta";
4
5
  import { columnStatusLabel, siteLang, ui } from "./lib/i18n";
@@ -65,8 +66,7 @@ export default function Home() {
65
66
  <span>{t.homeLatestBadge}</span>
66
67
  <time className="num" dateTime={day(feature.date)}>{fullListDate(feature.date, siteLang)}</time>
67
68
  </p>
68
- <h2 id="ft"><Link href={postHref(feature)}>{feature.title}</Link></h2>
69
- {feature.description ? <p>{feature.description}</p> : null}
69
+ <FeaturedTitle title={feature.title} href={postHref(feature)} description={feature.description} />
70
70
  {feature.tags?.length ? (
71
71
  <p className="tags">{feature.tags.map((tag) => <span key={tag}>#{tag}</span>)}</p>
72
72
  ) : null}
package/app/raw.d.ts CHANGED
@@ -2,3 +2,8 @@ declare module "*.md?raw" {
2
2
  const source: string;
3
3
  export default source;
4
4
  }
5
+
6
+ declare module "*.mjs?raw" {
7
+ const source: string;
8
+ export default source;
9
+ }
@@ -166,7 +166,8 @@ html[data-theme="dark"] .sh-rail{color:var(--muted)}
166
166
  .sh-feature .cover{border-radius:var(--r-lg);aspect-ratio:auto;display:block;overflow:hidden;background:none}
167
167
  .sh-feature .cover img{display:block;width:100%;height:auto}
168
168
  .sh-feature .kicker{display:flex;flex-wrap:wrap;gap:4px 14px;font-size:var(--fs-small);color:var(--muted);margin:0 0 12px}
169
- .sh-feature h2{font:700 30px/1.35 var(--sans);color:var(--ink);margin:0;text-wrap:balance}
169
+ .sh-feature>div{min-width:0}
170
+ .sh-feature h2{font:700 max(1.875rem,1.125em)/1.35 var(--sans);color:var(--ink);margin:0;text-wrap:balance;overflow-wrap:anywhere}
170
171
  .sh-feature h2 a{text-decoration:none}
171
172
  .sh-feature h2 a::after{content:"";position:absolute;inset:0;border-radius:var(--r-lg)}
172
173
  .sh-feature:hover h2 a{color:var(--blue);text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:4px}
@@ -289,7 +290,7 @@ html[data-theme="dark"] .sh-pal-scrim{background:rgba(0,0,0,.55)}
289
290
  .sh-intro{padding:44px 0 0}
290
291
  .sh-intro p{font-size:var(--fs-body)}
291
292
  .sh-feature{grid-template-columns:minmax(0,1fr);gap:20px;margin-top:36px}
292
- .sh-feature h2{font-size:24px}
293
+ .sh-feature h2{font-size:max(1.5rem,1.125em)}
293
294
  .sh-section{margin-top:56px}
294
295
  .sh-cols{grid-template-columns:minmax(0,1fr);gap:16px}
295
296
  .sh-row,.sh-home-grid .sh-row{grid-template-columns:minmax(0,1fr) auto;gap:2px 12px;padding:16px 0 17px}
@@ -5,7 +5,7 @@
5
5
  "author": "hedon",
6
6
  "authorUrl": "https://github.com/hedon954",
7
7
  "repo": "hedon954/garden",
8
- "version": "0.14.7",
8
+ "version": "0.14.10",
9
9
  "minAppVersion": "1.5.0",
10
10
  "minCoreVersion": "2.10.52",
11
11
  "platforms": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hd-garden",
3
- "version": "0.14.7",
3
+ "version": "0.14.10",
4
4
  "private": false,
5
5
  "description": "用 Markdown 写作、用 GitHub Pages 发布的个人博客引擎。",
6
6
  "license": "MIT",
@@ -66,7 +66,7 @@
66
66
  "start": "node bin/garden.mjs start",
67
67
  "admin:local": "node --env-file=.env.admin.local bin/garden.mjs start --host 0.0.0.0 --port 3000",
68
68
  "admin:cloudflare": "npm run build && wrangler deploy --config dist/server/wrangler.json",
69
- "test": "npm run build && node --experimental-strip-types --test tests/*.test.mjs",
69
+ "test": "npm run build && node --experimental-strip-types --test --test-concurrency=2 tests/*.test.mjs",
70
70
  "ui:build": "node scripts/build-ui-preview.mjs",
71
71
  "editors:build": "node editors/vscode/build.mjs && node editors/obsidian/build.mjs && node editors/typora/build.mjs",
72
72
  "editors:pack": "node scripts/pack-editors.mjs",
@@ -15,6 +15,7 @@
15
15
  import fs from "node:fs";
16
16
  import rehypeHighlight from "rehype-highlight";
17
17
  import rehypeKatex from "rehype-katex";
18
+ import { rehypeKeepPunctuation } from "../ui/garden/rehype-keep-punctuation.mjs";
18
19
  import rehypeRaw from "rehype-raw";
19
20
  import rehypeSlug from "rehype-slug";
20
21
  import rehypeStringify from "rehype-stringify";
@@ -104,6 +105,7 @@ export function renderFilePreview(markdown, { idPrefix = "gf-", lang = "zh" } =
104
105
  .use(remarkRehype, { allowDangerousHtml: true })
105
106
  .use(rehypeRaw)
106
107
  .use(rehypeKatex)
108
+ .use(rehypeKeepPunctuation)
107
109
  .use(rehypeSlug, { prefix: idPrefix })
108
110
  .use(rehypeHighlight)
109
111
  .use(rehypePreviewSafety)
@@ -190,6 +192,7 @@ export function previewStyleSheet() {
190
192
  `:not(pre)>code{font:400 14px/1 ${mono};padding:3px 6px;border-radius:5px}`,
191
193
  ".table-wrap{overflow-x:auto}table{border-collapse:collapse}th,td{border:1px solid;padding:6px 10px}",
192
194
  "a{color:inherit}.katex-html{display:none}",
195
+ fs.readFileSync(new URL("../ui/garden/inline-math.css", import.meta.url), "utf8"),
193
196
  theme(light),
194
197
  ...rules.flatMap((sheet) => sheet.light),
195
198
  `@media (prefers-color-scheme:dark){${theme(dark)}${rules.flatMap((sheet) => sheet.dark).join("")}}`,