From af6d08c9dd8ea1b67b19c986cdb1497501e56952 Mon Sep 17 00:00:00 2001 From: Cato <41178744+catomean@users.noreply.github.com> Date: Sat, 5 Sep 2026 18:02:06 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20v0.2=20=E2=80=94=20inline=20model,=20ex?= =?UTF-8?q?tended=20block=20vocabulary,=20react=20reference=20renderer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One vocabulary + one reference renderer so every fleet site reads long-form at the same standard, while typography tokens stay per-site. Parser (root export, still zero-dep; the v0.1 ten block types unchanged): - Inline model: parseInline → text/strong/em/code/link/footnoteRef spans; every text-bearing block keeps `text` and gains additive `spans`. - New blocks: h4, hr, figure (caption title), gallery (adjacent images merge), callout (GitHub > [!NOTE] syntax, nestable), pullquote (>> with — cite), mermaid, chart (tiny declarative spec; malformed specs THROW), math ($$), footnote defs, stats fence. - Headings gain slugified, de-duplicated, umlaut-aware ids (kivvi's scheme). - parseFrontmatter learns YAML arrays (inline + block lists), still dep-free. - Helpers: extractToc, readingTime, slugify/createSlugger, parseChartSpec. Renderer (new bip-kit/react subpath + bip-kit/styles.css): - ArticleBody: async RSC that pre-awaits shiki/katex, then emits a fully synchronous tree (renderToString-safe). Server components for every block; client islands: Toc (scroll-spy, hidden under 3 headings), ReadingProgress, Lightbox, CopyButton. MermaidBlock on its own bip-kit/react/mermaid subpath so the mermaid peer never enters uninterested bundles (components override). - shiki/katex are optional peers loaded via bundler-invisible dynamic import; honest degradation without them (mono
, TeX source with data-math). - styles.css: complete neutral theme — 70ch measure, real vertical rhythm, dark mode via vars only (data-theme wins over prefers-color-scheme), mobile-first, print-safe; every color/radius/font is a --bp-* custom property (theming contract documented in the README). CI: ci.yml gains workflow_dispatch (re-arm strand — a GITHUB_TOKEN merge cannot trigger workflows on its own; threadkit#28 pattern). 96 tests: v0.1 compat goldens (original shapes byte-identical after stripping additive fields), goldens for every new syntax form and edge case, renderToString smoke tests for every block type with and without the optional peers (BIPKIT_DISABLE_PEERS seam), consumer-install pack test now probing ./react, ./react/mermaid and ./styles.css. Co-Authored-By: Claude Fable 5Claude-Session: https://claude.ai/code/session_01WqKqMnHQHSmkGFfc5t7Rxn --- .github/workflows/ci.yml | 3 + README.md | 293 ++++++-- eslint.config.mjs | 6 +- package.json | 45 +- pnpm-lock.yaml | 1201 ++++++++++++++++++++++++++++++++ src/chart.ts | 130 ++++ src/index.ts | 10 + src/inline.ts | 125 ++++ src/parse-content.ts | 320 +++++++-- src/react/article-body.tsx | 214 ++++++ src/react/blocks.tsx | 200 ++++++ src/react/chart.tsx | 184 +++++ src/react/code-block.tsx | 74 ++ src/react/copy-button.tsx | 39 ++ src/react/index.ts | 33 + src/react/inline.tsx | 61 ++ src/react/lightbox.tsx | 54 ++ src/react/math.tsx | 52 ++ src/react/mermaid-peer.d.ts | 19 + src/react/mermaid.tsx | 106 +++ src/react/peers.ts | 38 + src/react/reading-progress.tsx | 38 + src/react/toc.tsx | 77 ++ src/slug.ts | 35 + src/toc.ts | 83 +++ src/types.ts | 50 +- styles.css | 852 ++++++++++++++++++++++ test/helpers.test.js | 69 ++ test/inline.test.js | 94 +++ test/package.test.js | 69 +- test/parse-content-v02.test.js | 286 ++++++++ test/parse-content.test.js | 41 +- test/react-degrade.test.js | 57 ++ test/react-render.test.js | 224 ++++++ tsconfig.json | 4 +- 35 files changed, 5049 insertions(+), 137 deletions(-) create mode 100644 src/chart.ts create mode 100644 src/inline.ts create mode 100644 src/react/article-body.tsx create mode 100644 src/react/blocks.tsx create mode 100644 src/react/chart.tsx create mode 100644 src/react/code-block.tsx create mode 100644 src/react/copy-button.tsx create mode 100644 src/react/index.ts create mode 100644 src/react/inline.tsx create mode 100644 src/react/lightbox.tsx create mode 100644 src/react/math.tsx create mode 100644 src/react/mermaid-peer.d.ts create mode 100644 src/react/mermaid.tsx create mode 100644 src/react/peers.ts create mode 100644 src/react/reading-progress.tsx create mode 100644 src/react/toc.tsx create mode 100644 src/slug.ts create mode 100644 src/toc.ts create mode 100644 styles.css create mode 100644 test/helpers.test.js create mode 100644 test/inline.test.js create mode 100644 test/parse-content-v02.test.js create mode 100644 test/react-degrade.test.js create mode 100644 test/react-render.test.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c9f9573..f22e4d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,9 @@ on: branches: [main] pull_request: branches: [main] + # Re-arm strand: a GITHUB_TOKEN merge does not trigger workflows on its own, + # so the sweep (or a human) must be able to start CI on main explicitly. + workflow_dispatch: {} jobs: verify: diff --git a/README.md b/README.md index 24d4363..881860f 100644 --- a/README.md +++ b/README.md @@ -1,85 +1,276 @@ # bip-kit — Building in Public -**Blog · Roadmap · Changelog** for product sites — one content contract instead of five blog stacks. +**Blog · Roadmap · Changelog** for product sites — one content contract, one reference renderer, instead of five blog stacks. -You want to build in public. What you don't want is a CMS, a markdown pipeline, three renderers, and a security review every time a product site needs a blog. bip-kit is the small, sharp core of that stack: it turns repo-authored markdown into **typed blocks** your components render, and gives you the **types** for a roadmap and a user-facing changelog. Bring your own design system. +You want to build in public. What you don't want is a CMS, a markdown pipeline, three renderers, and a security review every time a product site needs a blog. bip-kit is the small, sharp core of that stack: + +- **`bip-kit`** — a zero-dependency parser that turns repo-authored markdown into **typed blocks** (plus the types for a roadmap and a user-facing changelog). +- **`bip-kit/react`** — a reference renderer: RSC-first server components, tiny client islands, semantic `bp-*` classes, every color a CSS variable. +- **`bip-kit/styles.css`** — a complete neutral theme (measured ~70ch line length, real vertical rhythm, dark mode, mobile-first, print-safe) that you retheme var-by-var. + +Same vocabulary everywhere, your design tokens on top. ```bash npm i bip-kit +# optional, only if your content uses them: +npm i shiki # syntax highlighting +npm i katex # math +npm i mermaid # diagrams (client-side) ``` -Zero dependencies. ESM, typed, ~200 lines you can read in one sitting. +Core parser: zero dependencies. Renderer: `react >= 18` as the only required peer; shiki/katex/mermaid are **optional** peers — everything degrades gracefully without them. + +## Quick start (Next.js App Router) -## The idea +```tsx +// app/blog/[slug]/page.tsx — a server component; ArticleBody is async (RSC) +import { readFileSync } from "node:fs"; +import { parseFrontmatter, parseContentBlocks, extractToc, readingTime } from "bip-kit"; +import { ArticleBody, Toc, ReadingProgress } from "bip-kit/react"; +import "bip-kit/styles.css"; -Building in public is a *content contract*, not a platform: +export default async function Post({ params }: { params: Promise<{ slug: string }> }) { + const { slug } = await params; + const raw = readFileSync(`content/blog/${slug}.md`, "utf8"); + const { meta, body } = parseFrontmatter(raw); + const blocks = parseContentBlocks(body); + const toc = extractToc(blocks); + const { minutes } = readingTime(blocks); -- **Blog** — long-form posts, written as markdown files in your repo, reviewed like code. -- **Roadmap** — a typed document (`RoadmapDoc`) your site renders, not a screenshot of a kanban board. -- **Changelog** — user-facing entries (`ChangelogEntry`), not a git log. + return ( + <> + + {meta.title}
+{minutes} min read
++ + > + ); +} +``` -bip-kit owns the parsing and the types. Your app owns the routes, the rendering, and the look. That split is why the same kit serves sites with completely different design systems. +Diagrams? Mermaid is client-side and heavyweight, so it lives on its **own subpath** — it never enters your bundle unless you ask: -## Quick start +```tsx +import { MermaidBlock } from "bip-kit/react/mermaid"; // requires the mermaid peer -```ts -import { parseFrontmatter, parseContentBlocks } from "bip-kit"; -import { readFileSync } from "node:fs"; + ; +``` + +Without the override, mermaid blocks render their source in a styled ` ` — honest degradation, never a broken build. + +## The block vocabulary -const raw = readFileSync("content/blog/my-post.md", "utf8"); -const { meta, body } = parseFrontmatter(raw); -const blocks = parseContentBlocks(body); +One syntax example per block. Everything the v0.1 parser understood still parses identically; v0.2 adds the rest of what long-form writing needs. -// blocks is a typed array — switch on block.type in your renderer: -// h2 · h3 · p · ul · ol · blockquote · code · table · image · embed +### Headings — `h2` / `h3` / `h4` + +```md +## Section → { type: "h2", text, id: "section", spans } +### Subsection → h3 +#### Detail → h4 ``` -A minimal React renderer: +Headings carry a slugified, de-duplicated, umlaut-aware `id` (`## Über uns` → `ueber-uns`) — anchors and TOC for free. -```tsx -function PostBody({ blocks }: { blocks: ContentBlock[] }) { - return blocks.map((b, i) => { - switch (b.type) { - case "h2": return{b.text}
; - case "p": return{b.text}
; - case "ul": return{b.items.map((it) =>
; - case "code": return- {it}
)}; - case "embed": return{b.text}; - // …handle the rest with your own components - } - }); -} +### Paragraphs & inline markup — `p` + +```md +Body text with **bold**, *emphasis*, `code`, [links](https://example.com) and footnote refs[^1]. ``` -Video embeds are **allowlisted, never arbitrary**: a lone YouTube/Vimeo URL on its own line becomes an `embed` block, and `parseVideoEmbed` refuses everything else — so a markdown file can never inject an iframe you didn't intend: +Every text-bearing block keeps its raw `text` AND carries `spans: Inline[]` — a parsed tree of `text | strong | em | code | link | footnoteRef` nodes. Consumers stop re-parsing `**bold**` themselves. -```ts -import { parseVideoEmbed, videoEmbedSrc } from "bip-kit"; +### Lists — `ul` / `ol` + +```md +- unordered item +1. ordered item +``` + +Items come with parallel `itemSpans` for inline markup. + +### Quotes — `blockquote`, `pullquote` + +```md +> An ordinary quote. + +>> A big centered pull quote for the one sentence that matters. +>> — Attribution +``` + +The trailing `— Name` line (on a multi-line pull quote) becomes `cite`. + +### Callouts — `callout` (GitHub syntax, nestable) + +```md +> [!TIP] Optional custom title +> Callout body — full blocks, callouts can nest. +``` + +Kinds: `[!NOTE]` `[!TIP]` `[!WARN]`/`[!WARNING]` `[!CAUTION]`/`[!DANGER]` → `note | tip | warn | danger`. + +### Images — `image`, `figure`, `gallery` + +```md + → image (v0.1, unchanged) + → figure (caption supports inline markup) + + → adjacent image lines merge into a gallery +``` + +A blank line between images keeps them separate. + +### Code — `code` + +````md +```ts src/example.ts +const x: number = 1; +``` +```` + +The first word of the fence info is the language; the rest becomes a filename label. With the `shiki` peer, blocks are highlighted **server-side once** with both themes emitted as CSS variables (`--shiki-light`/`--shiki-dark`) — dark mode is pure CSS, zero client JS. Without shiki: clean mono fallback. Copy button included either way. + +### Diagrams — `mermaid` -const parsed = parseVideoEmbed(url); // { provider, id } | null -if (parsed) iframeSrc = videoEmbedSrc(parsed); // youtube-nocookie / player.vimeo +````md +```mermaid +graph TD; A-->B ``` +```` + +First-class block since v0.2 (v0.1 parsed these as `code` with lang `mermaid` — update your switch if you special-cased that). + +### Charts — `chart` + +````md +```chart +kind: bar +title: Weekly signups +ylabel: signups +series Organic: Jan=12, Feb=30, Mar=41 +series Paid: Jan=4, Feb=9 +``` +```` + +Or strict JSON: `{ "kind": "line", "series": [{ "name": "A", "points": [["W1", 10]] }] }`. Kinds: `bar | line | area`. The renderer draws an inline, theme-aware SVG — axis, gridlines, legend, categorical palette from CSS vars, no chart library. **A malformed spec throws at parse time** with the reason — committed content is the trust boundary, and a broken chart should fail the build like a broken import, never vanish silently. + +### Math — `math` + +```md +$$e = mc^2$$ + +$$ +\sum_{i=1}^n i = \frac{n(n+1)}{2} +$$ +``` + +With the `katex` peer: server-rendered (import KaTeX's stylesheet in your app: `import "katex/dist/katex.min.css"`). Without it: the TeX source in a styled block with `data-math` — no crash. + +### Footnotes — `footnote` + +```md +A claim[^src] in running text. + +[^src]: The definition, with **inline markup**. + Continuation lines are indented. +``` + +Refs render as superscript links; definitions collect into a footnotes section at the end with backlinks. + +### Stats — `stats` + +````md +```stats +68 | essays shipped +99.9% | uptime +``` +```` + +A row of value/label tiles. + +### The rest + +```md +--- → hr +| A | B | → table (GFM, separator row required) +|---|---| +https://youtu.be/VIDEO_ID → embed (lone URL on its own line, allowlisted) +``` + +Video embeds are **allowlisted, never arbitrary**: only YouTube/Vimeo URLs become `embed` blocks, rendered via `youtube-nocookie.com` / `player.vimeo.com`. Everything else stays a paragraph. + +## Frontmatter + +```md +--- +title: My post +tags: [bitcoin, "build in public"] +authors: + - Mao + - George +--- +``` + +`parseFrontmatter` is still dependency-free `key: value` parsing — v0.2 adds inline `[a, b]` arrays and block `- item` lists. Scalar values parse exactly as before. + +## Helpers + +```ts +extractToc(blocks); // → { id, text, level }[] (h2/h3/h4) +readingTime(blocks); // → { words, minutes } (200 wpm, min 1) +parseInline(text); // → Inline[] (the inline parser, standalone) +slugify("Über uns"); // → "ueber-uns" +``` + +## The renderer — `bip-kit/react` + +Server components (RSC-first): `ArticleBody` (the one you usually need — an **async** server component that pre-awaits shiki/katex and emits a fully synchronous tree), plus `Figure`, `Gallery`, `Callout`, `PullQuote`, `Stats`, `Footnotes`, `CodeBlock`, `Chart`, `TableBlock`, `VideoEmbed`. + +Client islands (each tiny, dependency-free): `Toc` (sticky scroll-spy; hides itself under 3 headings), `ReadingProgress` (top-of-page hairline), `Lightbox` (figure/gallery zoom, Escape to close), `CopyButton`. And `MermaidBlock` on `bip-kit/react/mermaid` (theme colors resolved from your CSS vars at render time). + +`ArticleBody` props: `blocks`, `components` (`{ mermaid }` override), `lightbox` (default `true`), `className`. + +## Theming contract + +`bip-kit/styles.css` is a complete neutral theme; every visual decision is a CSS custom property you may override — light, and dark via `@media (prefers-color-scheme)` plus an explicit `data-theme="dark" | "light"` attribute on `` that always wins. + +| Variable | Role | +| --- | --- | +| `--bp-font-sans` / `--bp-font-mono` | Type stacks | +| `--bp-size` | Base font size (17px mobile / 18px ≥720px) | +| `--bp-leading` | Body line-height (1.7) | +| `--bp-measure` | Line length (70ch) | +| `--bp-flow` | Vertical rhythm unit (1.5rem) | +| `--bp-radius` / `--bp-radius-sm` | Corner radii | +| `--bp-bg` / `--bp-surface` | Page & raised-surface backgrounds | +| `--bp-fg` / `--bp-fg-muted` / `--bp-fg-faint` | Text hierarchy | +| `--bp-border` | Hairlines & frames | +| `--bp-accent` / `--bp-accent-contrast` | Links, active TOC, progress bar | +| `--bp-note` / `--bp-note-bg` | Note callout | +| `--bp-tip` / `--bp-tip-bg` | Tip callout | +| `--bp-warn` / `--bp-warn-bg` | Warning callout | +| `--bp-danger` / `--bp-danger-bg` | Danger callout | +| `--bp-chart-1` … `--bp-chart-6` | Categorical chart palette | -## What the parser understands +Retheme by redefining vars on `:root` (and your dark scope) — zero rule overrides needed. Components emit **semantic classes only** (`bp-p`, `bp-h2`, `bp-callout bp-callout--warn`, `bp-figure`, …), so a from-scratch stylesheet is equally supported. -Ordinary markdown, deliberately scoped to what long-form product writing needs: +## Security model -| Input | Block | -|-------|-------| -| `## …` / `### …` | `h2` / `h3` | -| Plain lines | `p` (consecutive lines join) | -| `- …` / `1. …` | `ul` / `ol` | -| `> …` | `blockquote` | -| ` ```lang ` fences (incl. `mermaid`) | `code` | -| GFM tables | `table` | -| `` | `image` | -| Lone YouTube/Vimeo URL | `embed` | +Typed blocks are the security model: -Trust boundary: **committed content only** — this parses your repo's markdown, not user input. +- **No raw HTML, ever.** Markdown becomes a discriminated union; the renderer emits React elements from typed data. There is no HTML passthrough for content to hide in — which makes the renderer safe for **database/user-submitted content** too, not just committed files. +- **Link hrefs are guarded at render.** `javascript:` and every unknown scheme degrade to plain text (`safeHref` allows http/https/mailto/tel/relative/#). +- **Embeds are allowlisted.** Only YouTube/Vimeo, always via the privacy player. A markdown file can never inject an arbitrary iframe. +- The only `dangerouslySetInnerHTML` sinks are shiki/KaTeX **output generated from escaped source** — never author-supplied markup. +- Parse errors in chart/stats fences **throw** instead of guessing. ## Types you'll actually use - `ContentBlock` — the discriminated union your renderer switches on +- `Inline` — the inline span union (`text | strong | em | code | link | footnoteRef`) +- `ChartSpec` / `ChartSeries` — the tiny declarative chart contract +- `TocEntry`, `ReadingTimeResult` - `BlogPostMeta` — minimal post frontmatter (slug, title, summary, tags, …) - `RoadmapDoc` / `RoadmapBucket` / `RoadmapItem` — a renderable roadmap - `ChangelogEntry` / `ChangelogTag` — user-facing changelog entries @@ -87,7 +278,7 @@ Trust boundary: **committed content only** — this parses your repo's markdown, ## Starter routes -[`templates/next-app`](./templates/next-app) has copy-paste Next.js route stubs for `/blog`, `/roadmap`, and `/changelog`. They're intentionally thin — copy them into your app and style with your own tokens. The kit never ships UI. +[`templates/next-app`](./templates/next-app) has copy-paste Next.js route stubs for `/blog`, `/roadmap`, and `/changelog`. They're intentionally thin — copy them into your app and style with your own tokens. ## Company vs users diff --git a/eslint.config.mjs b/eslint.config.mjs index e170142..e29ed54 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -15,8 +15,10 @@ export default tseslint.config( js.configs.recommended, ...tseslint.configs.recommended, { - files: ["**/*.ts"], - languageOptions: { globals: globals.node }, + files: ["**/*.ts", "**/*.tsx"], + // Client islands touch browser APIs; the parser and server components + // run under Node. Both global sets, one config. + languageOptions: { globals: { ...globals.node, ...globals.browser } }, }, { // Tests are plain Node running under `node --test`. diff --git a/package.json b/package.json index 30ccab2..2a041a3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "bip-kit", - "version": "0.1.0", - "description": "Building in Public, as a content contract: parse markdown into typed blocks and ship a blog, roadmap, and changelog on your product site — bring your own design system.", + "version": "0.2.0", + "description": "Building in Public, as a content contract: parse markdown into typed blocks and render them with a reference React renderer (bip-kit/react) — blog, roadmap, changelog on your product site, bring your own design tokens.", "license": "MIT", "author": "Mao Nakamoto", "homepage": "https://github.com/bitbaum/bip-kit#readme", @@ -20,7 +20,10 @@ "markdown", "headless", "content", - "nextjs" + "nextjs", + "react", + "rsc", + "renderer" ], "type": "module", "sideEffects": false, @@ -33,13 +36,23 @@ "provenance": true }, "files": [ - "dist" + "dist", + "styles.css" ], "exports": { ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" - } + }, + "./react": { + "types": "./dist/react/index.d.ts", + "default": "./dist/react/index.js" + }, + "./react/mermaid": { + "types": "./dist/react/mermaid.d.ts", + "default": "./dist/react/mermaid.js" + }, + "./styles.css": "./styles.css" }, "scripts": { "build": "tsc -p tsconfig.json", @@ -54,10 +67,32 @@ "devDependencies": { "@eslint/js": "^10.0.1", "@types/node": "^26.4.0", + "@types/react": "^19.2.18", "eslint": "^10.9.1", "globals": "^17.11.0", + "katex": "^0.16.47", "prettier": "^3.9.6", + "react": "^19.2.8", + "react-dom": "^19.2.8", + "shiki": "^3.23.0", "typescript": "^6.0.3", "typescript-eslint": "^8.68.0" + }, + "peerDependencies": { + "react": ">=18", + "shiki": ">=1", + "mermaid": ">=10", + "katex": ">=0.16" + }, + "peerDependenciesMeta": { + "shiki": { + "optional": true + }, + "mermaid": { + "optional": true + }, + "katex": { + "optional": true + } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6f6eec1..ae009bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,10 @@ settings: importers: .: + dependencies: + mermaid: + specifier: '>=10' + version: 11.17.2 devDependencies: '@eslint/js': specifier: ^10.0.1 @@ -14,15 +18,30 @@ importers: '@types/node': specifier: ^26.4.0 version: 26.4.0 + '@types/react': + specifier: ^19.2.18 + version: 19.2.18 eslint: specifier: ^10.9.1 version: 10.9.1 globals: specifier: ^17.11.0 version: 17.11.0 + katex: + specifier: ^0.16.47 + version: 0.16.47 prettier: specifier: ^3.9.6 version: 3.9.6 + react: + specifier: ^19.2.8 + version: 19.2.8 + react-dom: + specifier: ^19.2.8 + version: 19.2.8(react@19.2.8) + shiki: + specifier: ^3.23.0 + version: 3.23.0 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -32,6 +51,15 @@ importers: packages: + '@antfu/install-pkg@2.0.1': + resolution: {integrity: sha512-iCKVQcIC0e3oDxEfs3SHQGW+ovhBMZmS1TE+bTk50rVyMCBmCfClv7Qi3HQKlumYwvjb/iIMeWCW2i67q6kFfQ==} + + '@braintree/sanitize-url@7.1.2': + resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} + + '@chevrotain/types@11.1.2': + resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==} + '@eslint-community/eslint-utils@4.10.1': resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -91,18 +119,159 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/utils@3.1.5': + resolution: {integrity: sha512-nT+AWyh5caHY6xa0PdLXpUooIz6l7qsT+adqsCTYBfbdn5Q18VrWs2fvuTa7KEoJnPBfEPqWkmAIht0EHtCi2A==} + + '@mermaid-js/parser@1.2.1': + resolution: {integrity: sha512-n12NohV3mrUyUL2o93IgG/ifeW9FTyeJn3zDxkhwa8MJ9Fxg3HQMlA3RiGmD/3UnJvheztkjjQAjA2T4LmUcpw==} + + '@shikijs/core@3.23.0': + resolution: {integrity: sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==} + + '@shikijs/engine-javascript@3.23.0': + resolution: {integrity: sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==} + + '@shikijs/engine-oniguruma@3.23.0': + resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==} + + '@shikijs/langs@3.23.0': + resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==} + + '@shikijs/themes@3.23.0': + resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==} + + '@shikijs/types@3.23.0': + resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.1': + resolution: {integrity: sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.4': + resolution: {integrity: sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + + '@types/d3-shape@3.2.0': + resolution: {integrity: sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw==} + + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + '@types/esrecurse@4.3.1': resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + + '@types/hast@3.0.5': + resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/node@26.4.0': resolution: {integrity: sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==} + '@types/react@19.2.18': + resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@typescript-eslint/eslint-plugin@8.68.0': resolution: {integrity: sha512-WASHDpCm6qO5jj9g1a+8NiW5+GCkAyLReR56/4VruYmNgfUmqpxOfZ2Yfb8xGfJPWv5Qi6LSD8sXdces3vbp/Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -162,6 +331,12 @@ packages: resolution: {integrity: sha512-YR65gGdGvTUAWLldC3xLOvOzamdGzB4A5/N8rehEaHs3Zvoe39BhgY+u0SPch1OvrVTfLcc55wsSgK2NcnTS/A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.4.0': + resolution: {integrity: sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==} + + '@upsetjs/venn.js@2.0.0': + resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -183,10 +358,198 @@ packages: resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} engines: {node: 20 || >=22} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + + cose-base@2.2.0: + resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + cytoscape-cose-bilkent@4.1.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape-fcose@2.2.0: + resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.34.2: + resolution: {integrity: sha512-Cm2jaj1X/PBNlzV9yH8zcfGOxO7U+CJ/+mxSBVPSchLaugdp4jtlGx5qaHtPRZ6tgiZ5P+o1XoRfJA+ba6KM3g==} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + dagre-d3-es@7.0.14: + resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==} + + dayjs@1.11.23: + resolution: {integrity: sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -199,6 +562,22 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + delaunator@5.1.0: + resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + dompurify@3.4.14: + resolution: {integrity: sha512-dVoH9z+MY+C9IilgGCk3YfFqjLi3fChm2OiKJMzh6axrJ5qwxqWaZamgmHrpv22CN/KdbZJuGEGgfQoL00LTdg==} + + es-toolkit@1.52.0: + resolution: {integrity: sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -254,6 +633,9 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fastdom@1.0.12: + resolution: {integrity: sha512-LB+xjSTEbjHE1cWsxu+tN2Xqr1kpi+V9aADI7sVM5ZMaXyYGPHULQMzpJMYqOTULK/73pUkWVzzObFRBkPr+hg==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -286,6 +668,22 @@ packages: resolution: {integrity: sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==} engines: {node: '>=18'} + hachure-fill@0.5.2: + resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -294,10 +692,20 @@ packages: resolution: {integrity: sha512-dML0wP6oak21rsNYCJpJB6O1BJIEwNpGrTw0URPfAk4hm0e3pRfCtzkfB6olBcXcVlU2rouCyz7lCyRB0OMVCA==} engines: {node: '>= 4'} + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -318,9 +726,22 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + katex@0.16.47: + resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} + hasBin: true + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + + layout-base@2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -329,6 +750,35 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + + marked@16.4.2: + resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} + engines: {node: '>= 20'} + hasBin: true + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mermaid@11.17.2: + resolution: {integrity: sha512-V6K3C8EBdEsPFZXSKMJe6ppQOENxuHARr9GvHX4hh47lAbhMRD9qf4oEK7LoaRQxULMa80/qt5gHO73aCleBBg==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + minimatch@10.2.6: resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} @@ -339,6 +789,12 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + oniguruma-parser@0.12.2: + resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} + + oniguruma-to-es@4.3.6: + resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -351,6 +807,12 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + package-manager-detector@1.8.0: + resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} + + path-data-parser@0.1.0: + resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -363,6 +825,12 @@ packages: resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} engines: {node: '>=12'} + points-on-curve@0.2.0: + resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} + + points-on-path@0.2.1: + resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -372,10 +840,46 @@ packages: engines: {node: '>=14'} hasBin: true + property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + react-dom@19.2.8: + resolution: {integrity: sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==} + peerDependencies: + react: ^19.2.8 + + react@19.2.8: + resolution: {integrity: sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==} + engines: {node: '>=0.10.0'} + + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + + robust-predicates@3.0.3: + resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} + + roughjs@4.6.6: + resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + semver@7.8.5: resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} @@ -389,16 +893,42 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shiki@3.23.0: + resolution: {integrity: sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + strictdom@1.0.1: + resolution: {integrity: sha512-cEmp9QeXXRmjj/rVp9oyiqcvyocWab/HaoN4+bwFeZ7QzykJD6L3yD4v12K1x0tHpqRqVpJevN3gW7kyM39Bqg==} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + stylis@4.4.0: + resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} + + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + engines: {node: '>=18'} + tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' + ts-dedent@2.3.0: + resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==} + engines: {node: '>=6.10'} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -418,9 +948,34 @@ packages: undici-types@8.3.0: resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + uuid@14.0.2: + resolution: {integrity: sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ==} + hasBin: true + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -434,8 +989,20 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: + '@antfu/install-pkg@2.0.1': + dependencies: + package-manager-detector: 1.8.0 + tinyexec: 1.3.1 + + '@braintree/sanitize-url@7.1.2': {} + + '@chevrotain/types@11.1.2': {} + '@eslint-community/eslint-utils@4.10.1(eslint@10.9.1)': dependencies: eslint: 10.9.1 @@ -486,16 +1053,197 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@iconify/types@2.0.0': {} + + '@iconify/utils@3.1.5': + dependencies: + '@antfu/install-pkg': 2.0.1 + '@iconify/types': 2.0.0 + import-meta-resolve: 4.2.0 + + '@mermaid-js/parser@1.2.1': + dependencies: + '@chevrotain/types': 11.1.2 + + '@shikijs/core@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.6 + + '@shikijs/engine-oniguruma@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + + '@shikijs/themes@3.23.0': + dependencies: + '@shikijs/types': 3.23.0 + + '@shikijs/types@3.23.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@types/d3-array@3.2.2': {} + + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.1': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.4': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-shape@3.2.0': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@4.0.3': {} + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.1 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.2.0 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + '@types/esrecurse@4.3.1': {} '@types/estree@1.0.9': {} + '@types/geojson@7946.0.16': {} + + '@types/hast@3.0.5': + dependencies: + '@types/unist': 3.0.3 + '@types/json-schema@7.0.15': {} + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/node@26.4.0': dependencies: undici-types: 8.3.0 + '@types/react@19.2.18': + dependencies: + csstype: 3.2.3 + + '@types/trusted-types@2.0.7': + optional: true + + '@types/unist@3.0.3': {} + '@typescript-eslint/eslint-plugin@8.68.0(@typescript-eslint/parser@8.68.0(eslint@10.9.1)(typescript@6.0.3))(eslint@10.9.1)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -587,6 +1335,13 @@ snapshots: '@typescript-eslint/types': 8.68.0 eslint-visitor-keys: 5.0.1 + '@ungap/structured-clone@1.4.0': {} + + '@upsetjs/venn.js@2.0.0': + optionalDependencies: + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + acorn-jsx@5.3.2(acorn@8.18.0): dependencies: acorn: 8.18.0 @@ -606,18 +1361,242 @@ snapshots: dependencies: balanced-match: 4.0.4 + ccount@2.0.1: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + comma-separated-tokens@2.0.3: {} + + commander@7.2.0: {} + + commander@8.3.0: {} + + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + + cose-base@2.2.0: + dependencies: + layout-base: 2.0.1 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + csstype@3.2.3: {} + + cytoscape-cose-bilkent@4.1.0(cytoscape@3.34.2): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.34.2 + + cytoscape-fcose@2.2.0(cytoscape@3.34.2): + dependencies: + cose-base: 2.2.0 + cytoscape: 3.34.2 + + cytoscape@3.34.2: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + + d3-color@3.1.0: {} + + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.1.0 + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.2: {} + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@1.0.9: {} + + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.2 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + dagre-d3-es@7.0.14: + dependencies: + d3: 7.9.0 + lodash-es: 4.18.1 + + dayjs@1.11.23: {} + debug@4.4.3: dependencies: ms: 2.1.3 deep-is@0.1.4: {} + delaunator@5.1.0: + dependencies: + robust-predicates: 3.0.3 + + dequal@2.0.3: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + dompurify@3.4.14: + optionalDependencies: + '@types/trusted-types': 2.0.7 + + es-toolkit@1.52.0: {} + escape-string-regexp@4.0.0: {} eslint-scope@9.1.2: @@ -690,6 +1669,10 @@ snapshots: fast-levenshtein@2.0.6: {} + fastdom@1.0.12: + dependencies: + strictdom: 1.0.1 + fdir@6.5.0(picomatch@4.0.7): optionalDependencies: picomatch: 4.0.7 @@ -716,12 +1699,44 @@ snapshots: globals@17.11.0: {} + hachure-fill@0.5.2: {} + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.5 + + html-void-elements@3.0.0: {} + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} ignore@7.0.7: {} + import-meta-resolve@4.2.0: {} + imurmurhash@0.1.4: {} + internmap@1.0.1: {} + + internmap@2.0.3: {} + is-extglob@2.1.1: {} is-glob@4.0.3: @@ -736,10 +1751,20 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + katex@0.16.47: + dependencies: + commander: 8.3.0 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 + khroma@2.1.0: {} + + layout-base@1.0.2: {} + + layout-base@2.0.1: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -749,6 +1774,64 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash-es@4.18.1: {} + + marked@16.4.2: {} + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.4.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mermaid@11.17.2: + dependencies: + '@braintree/sanitize-url': 7.1.2 + '@iconify/utils': 3.1.5 + '@mermaid-js/parser': 1.2.1 + '@types/d3': 7.4.3 + '@upsetjs/venn.js': 2.0.0 + cytoscape: 3.34.2 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.34.2) + cytoscape-fcose: 2.2.0(cytoscape@3.34.2) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.14 + dayjs: 1.11.23 + dompurify: 3.4.14 + es-toolkit: 1.52.0 + fastdom: 1.0.12 + katex: 0.16.47 + khroma: 2.1.0 + marked: 16.4.2 + roughjs: 4.6.6 + stylis: 4.4.0 + ts-dedent: 2.3.0 + uuid: 14.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-encode@2.0.1: {} + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + minimatch@10.2.6: dependencies: brace-expansion: 5.0.9 @@ -757,6 +1840,14 @@ snapshots: natural-compare@1.4.0: {} + oniguruma-parser@0.12.2: {} + + oniguruma-to-es@4.3.6: + dependencies: + oniguruma-parser: 0.12.2 + regex: 6.1.0 + regex-recursion: 6.0.2 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -774,18 +1865,63 @@ snapshots: dependencies: p-limit: 3.1.0 + package-manager-detector@1.8.0: {} + + path-data-parser@0.1.0: {} + path-exists@4.0.0: {} path-key@3.1.1: {} picomatch@4.0.7: {} + points-on-curve@0.2.0: {} + + points-on-path@0.2.1: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + prelude-ls@1.2.1: {} prettier@3.9.6: {} + property-information@7.2.0: {} + punycode@2.3.1: {} + react-dom@19.2.8(react@19.2.8): + dependencies: + react: 19.2.8 + scheduler: 0.27.0 + + react@19.2.8: {} + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + + robust-predicates@3.0.3: {} + + roughjs@4.6.6: + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + + rw@1.3.3: {} + + safer-buffer@2.1.2: {} + + scheduler@0.27.0: {} + semver@7.8.5: {} shebang-command@2.0.0: @@ -794,15 +1930,43 @@ snapshots: shebang-regex@3.0.0: {} + shiki@3.23.0: + dependencies: + '@shikijs/core': 3.23.0 + '@shikijs/engine-javascript': 3.23.0 + '@shikijs/engine-oniguruma': 3.23.0 + '@shikijs/langs': 3.23.0 + '@shikijs/themes': 3.23.0 + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + + space-separated-tokens@2.0.2: {} + + strictdom@1.0.1: {} + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + stylis@4.4.0: {} + + tinyexec@1.3.1: {} + tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.7) picomatch: 4.0.7 + trim-lines@3.0.1: {} + ts-api-utils@2.5.0(typescript@6.0.3): dependencies: typescript: 6.0.3 + ts-dedent@2.3.0: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -822,10 +1986,45 @@ snapshots: undici-types@8.3.0: {} + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + uri-js@4.4.1: dependencies: punycode: 2.3.1 + uuid@14.0.2: {} + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -833,3 +2032,5 @@ snapshots: word-wrap@1.2.5: {} yocto-queue@0.1.0: {} + + zwitch@2.0.4: {} diff --git a/src/chart.ts b/src/chart.ts new file mode 100644 index 0000000..e15cef4 --- /dev/null +++ b/src/chart.ts @@ -0,0 +1,130 @@ +/** + * ```chart fences carry a deliberately tiny declarative spec. + * Two accepted bodies: strict JSON, or a line format: + * + * kind: bar + * title: Weekly signups + * ylabel: signups + * series Organic: Jan=12, Feb=30, Mar=41 + * series Paid: Jan=4, Feb=9, Mar=8 + * + * Errors THROW with the reason — committed content is the trust boundary, + * and a broken chart spec should fail the build like a broken import, + * never silently drop the block. + */ + +export interface ChartSeries { + name: string; + points: [string | number, number][]; +} + +export interface ChartSpec { + kind: "bar" | "line" | "area"; + title?: string; + yLabel?: string; + series: ChartSeries[]; +} + +const KINDS = new Set(["bar", "line", "area"]); + +export function parseChartSpec(source: string): ChartSpec { + const trimmed = source.trim(); + if (!trimmed) throw new Error("chart block: empty spec"); + + if (trimmed.startsWith("{")) { + let parsed: unknown; + try { + parsed = JSON.parse(trimmed); + } catch (err) { + throw new Error( + `chart block: invalid JSON — ${err instanceof Error ? err.message : String(err)}`, + { cause: err }, + ); + } + return validateChartSpec(parsed); + } + + const spec: { kind?: string; title?: string; yLabel?: string; series: ChartSeries[] } = { + series: [], + }; + for (const rawLine of trimmed.split("\n")) { + const line = rawLine.trim(); + if (!line) continue; + const seriesMatch = line.match(/^series\s+(.+?)\s*:\s*(.+)$/); + if (seriesMatch) { + const points: [string | number, number][] = []; + for (const pair of seriesMatch[2].split(",")) { + const m = pair.trim().match(/^(.+?)\s*=\s*(-?\d+(?:\.\d+)?)$/); + if (!m) { + throw new Error( + `chart block: bad point "${pair.trim()}" in series "${seriesMatch[1]}" — expected label=number`, + ); + } + points.push([m[1], Number(m[2])]); + } + spec.series.push({ name: seriesMatch[1], points }); + continue; + } + const kv = line.match(/^(kind|title|ylabel)\s*:\s*(.+)$/i); + if (kv) { + const key = kv[1].toLowerCase(); + if (key === "kind") spec.kind = kv[2].trim(); + else if (key === "title") spec.title = kv[2].trim(); + else spec.yLabel = kv[2].trim(); + continue; + } + throw new Error(`chart block: unrecognized line "${line}"`); + } + return validateChartSpec(spec); +} + +export function validateChartSpec(value: unknown): ChartSpec { + if (typeof value !== "object" || value === null) { + throw new Error("chart block: spec must be an object"); + } + const v = value as Record ; + if (typeof v.kind !== "string" || !KINDS.has(v.kind)) { + throw new Error( + `chart block: kind must be "bar" | "line" | "area", got ${JSON.stringify(v.kind)}`, + ); + } + if (v.title !== undefined && typeof v.title !== "string") { + throw new Error("chart block: title must be a string"); + } + if (v.yLabel !== undefined && typeof v.yLabel !== "string") { + throw new Error("chart block: yLabel must be a string"); + } + if (!Array.isArray(v.series) || v.series.length === 0) { + throw new Error("chart block: series must be a non-empty array"); + } + const series: ChartSeries[] = v.series.map((s: unknown, i: number) => { + if (typeof s !== "object" || s === null) + throw new Error(`chart block: series[${i}] must be an object`); + const sv = s as Record ; + if (typeof sv.name !== "string" || sv.name.length === 0) { + throw new Error(`chart block: series[${i}].name must be a non-empty string`); + } + if (!Array.isArray(sv.points) || sv.points.length === 0) { + throw new Error(`chart block: series "${sv.name}" has no points`); + } + const points: [string | number, number][] = sv.points.map((p: unknown, j: number) => { + if ( + !Array.isArray(p) || + p.length !== 2 || + (typeof p[0] !== "string" && typeof p[0] !== "number") || + typeof p[1] !== "number" || + Number.isNaN(p[1]) + ) { + throw new Error(`chart block: series "${sv.name}" point ${j} must be [label, number]`); + } + return [p[0], p[1]]; + }); + return { name: sv.name, points }; + }); + return { + kind: v.kind as ChartSpec["kind"], + title: v.title as string | undefined, + yLabel: v.yLabel as string | undefined, + series, + }; +} diff --git a/src/index.ts b/src/index.ts index 9df0ca0..f4a73ca 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,8 @@ export type { ContentBlock, + CalloutKind, + TocEntry, + ReadingTimeResult, BlogPostMeta, RoadmapItem, RoadmapBucket, @@ -9,5 +12,12 @@ export type { ReleaseEntry, } from "./types.js"; +export type { Inline } from "./inline.js"; +export type { ChartSpec, ChartSeries } from "./chart.js"; + export { parseContentBlocks, parseFrontmatter } from "./parse-content.js"; +export { parseInline, inlineToText } from "./inline.js"; +export { parseChartSpec, validateChartSpec } from "./chart.js"; +export { slugify, createSlugger } from "./slug.js"; +export { extractToc, readingTime } from "./toc.js"; export { parseVideoEmbed, videoEmbedSrc } from "./video-embed.js"; diff --git a/src/inline.ts b/src/inline.ts new file mode 100644 index 0000000..8717ada --- /dev/null +++ b/src/inline.ts @@ -0,0 +1,125 @@ +/** + * Inline markup model — consumers stop re-parsing `**bold**` themselves. + * + * Deliberately small: strong, em, code spans, links, footnote references. + * No raw HTML ever — unknown syntax stays literal text, which is the + * security model (typed nodes only, nothing to inject). + */ + +export type Inline = + | { t: "text"; text: string } + | { t: "strong"; children: Inline[] } + | { t: "em"; children: Inline[] } + | { t: "code"; text: string } + | { t: "link"; href: string; children: Inline[] } + | { t: "footnoteRef"; id: string }; + +const WORD_CHAR = /[\p{L}\p{N}]/u; + +/** Parse one line/run of text into inline spans. Never throws. */ +export function parseInline(text: string): Inline[] { + const out: Inline[] = []; + let buf = ""; + const flush = () => { + if (buf) { + out.push({ t: "text", text: buf }); + buf = ""; + } + }; + + let i = 0; + while (i < text.length) { + const ch = text[i]; + + // Code span first: its contents are raw, `**` inside stays literal. + if (ch === "`") { + const close = text.indexOf("`", i + 1); + if (close !== -1) { + flush(); + out.push({ t: "code", text: text.slice(i + 1, close) }); + i = close + 1; + continue; + } + } + + // Strong: **...** (checked before single-char em). + if (ch === "*" && text[i + 1] === "*") { + const close = text.indexOf("**", i + 2); + if (close !== -1 && close > i + 2) { + flush(); + out.push({ t: "strong", children: parseInline(text.slice(i + 2, close)) }); + i = close + 2; + continue; + } + } + + // Em: *...* or _..._ — not intraword (2*3*4, snake_case stay literal). + if (ch === "*" || ch === "_") { + const prev = i > 0 ? text[i - 1] : ""; + const next = text[i + 1] ?? ""; + const intraword = prev !== "" && WORD_CHAR.test(prev); + if (!intraword && next !== "" && next !== ch && !/\s/.test(next)) { + const close = text.indexOf(ch, i + 1); + if (close !== -1 && !/\s/.test(text[close - 1])) { + flush(); + out.push({ t: "em", children: parseInline(text.slice(i + 1, close)) }); + i = close + 1; + continue; + } + } + } + + if (ch === "[") { + // Footnote reference: [^id] + if (text[i + 1] === "^") { + const close = text.indexOf("]", i + 2); + if (close !== -1) { + const id = text.slice(i + 2, close); + if (id.length > 0 && !/\s/.test(id)) { + flush(); + out.push({ t: "footnoteRef", id }); + i = close + 1; + continue; + } + } + } + // Link: [text](href) + const closeBracket = text.indexOf("]", i + 1); + if (closeBracket !== -1 && text[closeBracket + 1] === "(") { + const closeParen = text.indexOf(")", closeBracket + 2); + if (closeParen !== -1) { + flush(); + out.push({ + t: "link", + href: text.slice(closeBracket + 2, closeParen).trim(), + children: parseInline(text.slice(i + 1, closeBracket)), + }); + i = closeParen + 1; + continue; + } + } + } + + buf += ch; + i += 1; + } + flush(); + return out; +} + +/** Plain text of a span tree (for aria labels, word counts, TOC text). */ +export function inlineToText(spans: Inline[]): string { + return spans + .map((s) => { + switch (s.t) { + case "text": + case "code": + return s.text; + case "footnoteRef": + return ""; + default: + return inlineToText(s.children); + } + }) + .join(""); +} diff --git a/src/parse-content.ts b/src/parse-content.ts index f1bebe9..a453300 100644 --- a/src/parse-content.ts +++ b/src/parse-content.ts @@ -1,29 +1,70 @@ -import type { ContentBlock } from "./types.js"; +import type { CalloutKind, ContentBlock } from "./types.js"; +import { parseInline } from "./inline.js"; +import { parseChartSpec } from "./chart.js"; +import { createSlugger } from "./slug.js"; /** * Parse repo-authored markdown into typed blocks. - * Trust boundary: committed content only — not user input. + * Trust boundary: committed content only — not user input. A malformed + * chart/stats fence THROWS (fail the build, don't silently drop content). */ + +const isBlank = (line: string) => line.trim().length === 0; +const isTableRow = (line: string) => /^\|.+\|/.test(line.trim()); +const isTableSep = (line: string) => /^\|[\s:|-]+\|$/.test(line.trim()); +const isHr = (line: string) => /^(?:-{3,}|\*{3,}|_{3,})$/.test(line.trim()); +const isFootnoteDef = (line: string) => /^\[\^[^\]\s]+\]:/.test(line); +const IMAGE_RE = /^!\[([^\]]*)\]\(\s*(\S+?)(?:\s+"([^"]*)")?\s*\)$/; + +const splitCells = (line: string) => + line + .trim() + .replace(/^\|/, "") + .replace(/\|$/, "") + .split("|") + .map((c) => c.trim()); + +const isEmbedUrl = (line: string) => + /^(https?:\/\/)?(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|vimeo\.com\/)\S+$/i.test( + line.trim(), + ); + +const CALLOUT_KINDS: Record = { + NOTE: "note", + INFO: "note", + IMPORTANT: "note", + TIP: "tip", + HINT: "tip", + WARN: "warn", + WARNING: "warn", + CAUTION: "danger", + DANGER: "danger", +}; + export function parseContentBlocks(body: string): ContentBlock[] { - const lines = body.replace(/\r\n/g, "\n").split("\n"); + const slug = createSlugger(); + return parseBlocks(body.replace(/\r\n/g, "\n").split("\n"), slug); +} + +function parseBlocks(lines: string[], slug: (text: string) => string): ContentBlock[] { const blocks: ContentBlock[] = []; let i = 0; - const isBlank = (line: string) => line.trim().length === 0; - const isTableRow = (line: string) => /^\|.+\|/.test(line.trim()); - const isTableSep = (line: string) => /^\|[\s:|-]+\|$/.test(line.trim()); - const splitCells = (line: string) => - line - .trim() - .replace(/^\|/, "") - .replace(/\|$/, "") - .split("|") - .map((c) => c.trim()); - - const isEmbedUrl = (line: string) => - /^(https?:\/\/)?(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|vimeo\.com\/)\S+$/i.test( - line.trim(), - ); + const startsNewBlock = (line: string, next: string | undefined) => + line.startsWith("## ") || + line.startsWith("### ") || + line.startsWith("#### ") || + isHr(line) || + line.startsWith("- ") || + /^\d+\.\s+/.test(line) || + line.startsWith("> ") || + line.startsWith(">> ") || + line.startsWith("```") || + line.startsWith("$$") || + isFootnoteDef(line) || + /^!\[/.test(line) || + (isTableRow(line) && next !== undefined && isTableSep(next)) || + isEmbedUrl(line); while (i < lines.length) { const line = lines[i]; @@ -33,14 +74,17 @@ export function parseContentBlocks(body: string): ContentBlock[] { continue; } - if (line.startsWith("## ")) { - blocks.push({ type: "h2", text: line.replace(/^##\s+/, "").trim() }); + const heading = line.match(/^(##|###|####)\s+(.*)$/); + if (heading) { + const text = heading[2].trim(); + const type = heading[1] === "##" ? "h2" : heading[1] === "###" ? "h3" : "h4"; + blocks.push({ type, text, id: slug(text), spans: parseInline(text) }); i += 1; continue; } - if (line.startsWith("### ")) { - blocks.push({ type: "h3", text: line.replace(/^###\s+/, "").trim() }); + if (isHr(line)) { + blocks.push({ type: "hr" }); i += 1; continue; } @@ -63,13 +107,51 @@ export function parseContentBlocks(body: string): ContentBlock[] { continue; } + // Display math: $$..$$ on one line, or a $$-fenced run of lines. + if (line.trim().startsWith("$$")) { + const single = line.trim().match(/^\$\$(.+?)\$\$$/); + if (single) { + blocks.push({ type: "math", tex: single[1].trim(), display: true }); + i += 1; + continue; + } + const texLines: string[] = []; + const first = line.trim().replace(/^\$\$/, ""); + if (first) texLines.push(first); + i += 1; + while (i < lines.length && !lines[i].trim().endsWith("$$")) { + texLines.push(lines[i]); + i += 1; + } + if (i < lines.length) { + const last = lines[i].trim().replace(/\$\$$/, ""); + if (last) texLines.push(last); + i += 1; + } + blocks.push({ type: "math", tex: texLines.join("\n").trim(), display: true }); + continue; + } + + // Footnote definition: [^id]: text, continued on indented lines. + const fnDef = line.match(/^\[\^([^\]\s]+)\]:\s?(.*)$/); + if (fnDef) { + const bodyLines: string[] = [fnDef[2]]; + i += 1; + while (i < lines.length && /^(?: {2,}|\t)\S/.test(lines[i])) { + bodyLines.push(lines[i].replace(/^(?: {2,}|\t)/, "")); + i += 1; + } + blocks.push({ type: "footnote", id: fnDef[1], blocks: parseBlocks(bodyLines, slug) }); + continue; + } + if (line.startsWith("- ")) { const items: string[] = []; while (i < lines.length && lines[i].startsWith("- ")) { items.push(lines[i].replace(/^-\s+/, "").trim()); i += 1; } - blocks.push({ type: "ul", items }); + blocks.push({ type: "ul", items, itemSpans: items.map(parseInline) }); continue; } @@ -79,17 +161,49 @@ export function parseContentBlocks(body: string): ContentBlock[] { items.push(lines[i].replace(/^\d+\.\s+/, "").trim()); i += 1; } - blocks.push({ type: "ol", items }); + blocks.push({ type: "ol", items, itemSpans: items.map(parseInline) }); continue; } - if (line.startsWith("> ")) { + // Pull quote: >> lines; a trailing "— Name" line becomes the citation. + if (line.startsWith(">> ")) { const quote: string[] = []; - while (i < lines.length && lines[i].startsWith("> ")) { - quote.push(lines[i].replace(/^>\s+/, "").trim()); + while (i < lines.length && lines[i].startsWith(">> ")) { + quote.push(lines[i].replace(/^>>\s+/, "").trim()); i += 1; } - blocks.push({ type: "blockquote", text: quote }); + let cite: string | undefined; + const last = quote[quote.length - 1]; + const citeMatch = quote.length > 1 ? last.match(/^(?:—|–|--)\s*(.+)$/) : null; + if (citeMatch) { + cite = citeMatch[1]; + quote.pop(); + } + const text = quote.join(" "); + blocks.push({ type: "pullquote", text, spans: parseInline(text), cite }); + continue; + } + + // Blockquote or callout (GitHub `> [!NOTE]` syntax; callouts nest). + if (line.startsWith("> ") || line.trim() === ">") { + const inner: string[] = []; + while (i < lines.length && (lines[i].startsWith("> ") || lines[i].trim() === ">")) { + inner.push(lines[i].trim() === ">" ? "" : lines[i].replace(/^>\s/, "")); + i += 1; + } + const marker = inner[0]?.trim().match(/^\[!(\w+)\]\s*(.*)$/); + const kind = marker ? CALLOUT_KINDS[marker[1].toUpperCase()] : undefined; + if (marker && kind) { + blocks.push({ + type: "callout", + kind, + title: marker[2].trim() || undefined, + blocks: parseBlocks(inner.slice(1), slug), + }); + } else { + const text = inner.filter((l) => l.trim().length > 0).map((l) => l.trim()); + blocks.push({ type: "blockquote", text, spans: text.map(parseInline) }); + } continue; } @@ -102,60 +216,154 @@ export function parseContentBlocks(body: string): ContentBlock[] { i += 1; } i += 1; - blocks.push({ type: "code", lang, text: codeLines.join("\n") }); + const code = codeLines.join("\n"); + const langId = lang.split(/\s+/)[0]; + if (langId === "mermaid") { + blocks.push({ type: "mermaid", code }); + } else if (langId === "chart") { + blocks.push({ type: "chart", spec: parseChartSpec(code) }); + } else if (langId === "stats") { + blocks.push({ type: "stats", items: parseStats(code) }); + } else { + blocks.push({ type: "code", lang, text: code }); + } continue; } - const imgMatch = line.match(/^!\[([^\]]*)\]\(([^)]+)\)$/); - if (imgMatch) { - blocks.push({ type: "image", alt: imgMatch[1], src: imgMatch[2] }); - i += 1; + // Images: adjacent image lines (no blank line between) merge to a gallery; + // one image with a "caption" title is a figure; a bare one stays `image`. + if (IMAGE_RE.test(line)) { + const images: { src: string; alt: string; caption?: string }[] = []; + while (i < lines.length) { + const m = lines[i].match(IMAGE_RE); + if (!m) break; + images.push({ src: m[2], alt: m[1], caption: m[3] || undefined }); + i += 1; + } + if (images.length >= 2) { + blocks.push({ type: "gallery", images }); + } else if (images[0].caption !== undefined) { + blocks.push({ + type: "figure", + src: images[0].src, + alt: images[0].alt, + caption: images[0].caption, + spans: parseInline(images[0].caption), + }); + } else { + blocks.push({ type: "image", alt: images[0].alt, src: images[0].src }); + } continue; } const paragraph: string[] = []; - while ( - i < lines.length && - !isBlank(lines[i]) && - !lines[i].startsWith("## ") && - !lines[i].startsWith("### ") && - !lines[i].startsWith("- ") && - !/^\d+\.\s+/.test(lines[i]) && - !lines[i].startsWith("> ") && - !lines[i].startsWith("```") && - !lines[i].match(/^!\[/) && - !(isTableRow(lines[i]) && i + 1 < lines.length && isTableSep(lines[i + 1])) && - !isEmbedUrl(lines[i]) - ) { + while (i < lines.length && !isBlank(lines[i]) && !startsNewBlock(lines[i], lines[i + 1])) { + paragraph.push(lines[i].trim()); + i += 1; + } + if (paragraph.length === 0) { + // Defensive: a line that claims to start a block but fell through + // (should not happen) must not loop forever. paragraph.push(lines[i].trim()); i += 1; } - blocks.push({ type: "p", text: paragraph.join(" ") }); + const text = paragraph.join(" "); + blocks.push({ type: "p", text, spans: parseInline(text) }); } return blocks; } -/** Simple YAML-ish frontmatter (`key: value`); strips matching quotes. */ -export function parseFrontmatter(raw: string): { meta: Record ; body: string } { +/** ```stats fence: one `value | label` per line. Malformed lines throw. */ +function parseStats(source: string): { value: string; label: string }[] { + const items: { value: string; label: string }[] = []; + for (const rawLine of source.split("\n")) { + const line = rawLine.trim(); + if (!line) continue; + const idx = line.indexOf("|"); + if (idx <= 0 || idx === line.length - 1) { + throw new Error(`stats block: each line must be "value | label", got "${line}"`); + } + items.push({ value: line.slice(0, idx).trim(), label: line.slice(idx + 1).trim() }); + } + if (items.length === 0) throw new Error("stats block: empty"); + return items; +} + +/** + * Simple YAML-ish frontmatter. `key: value` (matching quotes stripped), plus + * two dependency-free array forms: + * + * tags: [a, b] → ["a", "b"] + * tags: → ["a", "b"] + * - a + * - b + * + * Existing scalar files keep parsing exactly as before (values stay strings). + */ +export function parseFrontmatter(raw: string): { + meta: Record ; + body: string; +} { if (!raw.startsWith("---\n")) return { meta: {}, body: raw }; const end = raw.indexOf("\n---\n", 4); if (end === -1) return { meta: {}, body: raw }; const header = raw.slice(4, end); const body = raw.slice(end + 5); - const meta: Record = {}; - for (const line of header.split("\n")) { - const idx = line.indexOf(":"); - if (idx <= 0) continue; - let value = line.slice(idx + 1).trim(); + const meta: Record = {}; + + const stripQuotes = (value: string) => { if ( value.length >= 2 && ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) ) { - value = value.slice(1, -1); + return value.slice(1, -1); } - meta[line.slice(0, idx).trim()] = value; + return value; + }; + + const lines = header.split("\n"); + let i = 0; + while (i < lines.length) { + const line = lines[i]; + const idx = line.indexOf(":"); + if (idx <= 0 || /^\s*-\s/.test(line)) { + i += 1; + continue; + } + const key = line.slice(0, idx).trim(); + const value = line.slice(idx + 1).trim(); + + if (value === "") { + const items: string[] = []; + let j = i + 1; + while (j < lines.length && /^\s*-\s+\S/.test(lines[j])) { + items.push(stripQuotes(lines[j].replace(/^\s*-\s+/, "").trim())); + j += 1; + } + if (items.length > 0) { + meta[key] = items; + i = j; + continue; + } + meta[key] = ""; + i += 1; + continue; + } + + if (value.startsWith("[") && value.endsWith("]")) { + meta[key] = value + .slice(1, -1) + .split(",") + .map((item) => stripQuotes(item.trim())) + .filter((item) => item.length > 0); + i += 1; + continue; + } + + meta[key] = stripQuotes(value); + i += 1; } return { meta, body }; } diff --git a/src/react/article-body.tsx b/src/react/article-body.tsx new file mode 100644 index 0000000..680f436 --- /dev/null +++ b/src/react/article-body.tsx @@ -0,0 +1,214 @@ +import type { ComponentType, ReactNode } from "react"; +import type { ContentBlock } from "../types.js"; +import { renderInline } from "./inline.js"; +import { parseInline } from "../inline.js"; +import { highlightCode, splitFenceInfo, CodeBlockView } from "./code-block.js"; +import { renderMath, MathView } from "./math.js"; +import { Chart } from "./chart.js"; +import { + Figure, + Gallery, + Callout, + PullQuote, + Stats, + Footnotes, + VideoEmbed, + TableBlock, + spansOf, + type FootnoteEntry, +} from "./blocks.js"; + +/** + * The reference renderer. RSC-first: `ArticleBody` is an async server + * component — it awaits every optional-peer render (shiki, katex) up front, + * then emits a fully synchronous tree. That keeps the whole article + * renderable by react-dom/server too (tests, RSS, OG pipelines). + * + * Mermaid is a CLIENT concern and an optional peer, so it is NOT wired by + * default (a bundler would fail on the import for consumers without it). + * Pass it in: `components={{ mermaid: MermaidBlock }}` with + * `import { MermaidBlock } from "bip-kit/react/mermaid"`. + */ + +export interface ArticleBodyComponents { + /** Renderer for mermaid blocks; default is an honest source fallback. */ + mermaid?: ComponentType<{ code: string }>; +} + +export interface ArticleBodyProps { + blocks: ContentBlock[]; + components?: ArticleBodyComponents; + /** Wrap figure/gallery images in the Lightbox island (default true). */ + lightbox?: boolean; + className?: string; +} + +/** Pre-rendered HTML (shiki/katex) keyed by block identity. */ +type Enriched = WeakMap