blume 1.6.6 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/cli/{chunk-etsqspj6.js → chunk-12dzsn9b.js} +140 -23
  3. package/dist/cli/{chunk-etsqspj6.js.map → chunk-12dzsn9b.js.map} +6 -5
  4. package/dist/cli/{chunk-aerwpe14.js → chunk-4ae4f395.js} +164 -51
  5. package/dist/cli/chunk-4ae4f395.js.map +15 -0
  6. package/dist/cli/{chunk-m3p3wahd.js → chunk-52cwcqvp.js} +4 -4
  7. package/dist/cli/{chunk-j00ezcg5.js → chunk-5gfw0q4j.js} +9 -9
  8. package/dist/cli/{chunk-bawgnt8x.js → chunk-6mq7qkve.js} +3 -3
  9. package/dist/cli/{chunk-nyqzjdhj.js → chunk-82atea4k.js} +5 -5
  10. package/dist/cli/{chunk-tc89yh2r.js → chunk-8p3xe5jv.js} +2 -2
  11. package/dist/cli/{chunk-s4k1pnvf.js → chunk-90pdhkpm.js} +11 -11
  12. package/dist/cli/{chunk-n0y172hf.js → chunk-aqjvpd03.js} +4 -4
  13. package/dist/cli/{chunk-x1vrdjyk.js → chunk-h9ekmtz7.js} +5 -5
  14. package/dist/cli/{chunk-f75cqye8.js → chunk-he2zfgah.js} +10 -10
  15. package/dist/cli/{chunk-s4jn7f1q.js → chunk-j5f2wrj5.js} +2 -2
  16. package/dist/cli/{chunk-wkq5tbtq.js → chunk-k0v1f8bb.js} +3 -3
  17. package/dist/cli/{chunk-n4qjabmt.js → chunk-ka5k7cz9.js} +6 -19
  18. package/dist/cli/{chunk-n4qjabmt.js.map → chunk-ka5k7cz9.js.map} +3 -4
  19. package/dist/cli/{chunk-5yvt556e.js → chunk-kmx2mydj.js} +2 -2
  20. package/dist/cli/{chunk-ag1zyr5x.js → chunk-mfm4sjwx.js} +11 -11
  21. package/dist/cli/{chunk-0ewz4trd.js → chunk-np8dmfb0.js} +6 -6
  22. package/dist/cli/{chunk-cnvm6k3e.js → chunk-pdwg3q9g.js} +11 -11
  23. package/dist/cli/{chunk-vv237fp3.js → chunk-q56730e0.js} +26 -12
  24. package/dist/cli/{chunk-vv237fp3.js.map → chunk-q56730e0.js.map} +3 -3
  25. package/dist/cli/{chunk-3k0kzs6d.js → chunk-qvvpnwaz.js} +2 -2
  26. package/dist/cli/{chunk-vv3f8mb6.js → chunk-r99hynxh.js} +25 -24
  27. package/dist/cli/{chunk-vv3f8mb6.js.map → chunk-r99hynxh.js.map} +4 -4
  28. package/dist/cli/{chunk-wb067mv3.js → chunk-vyqj481z.js} +19 -7
  29. package/dist/cli/chunk-vyqj481z.js.map +13 -0
  30. package/dist/cli/{chunk-62qsssnh.js → chunk-x1wvw7a8.js} +411 -145
  31. package/dist/cli/chunk-x1wvw7a8.js.map +40 -0
  32. package/dist/cli/{chunk-9sh49q0h.js → chunk-ywn7t0pb.js} +2 -2
  33. package/dist/cli/index.js +13 -13
  34. package/dist/types/components/layout/nav-utils.d.ts +46 -1
  35. package/dist/types/core/config-input.d.ts +7 -0
  36. package/dist/types/core/schema.d.ts +2 -0
  37. package/dist/types/theme/fonts.d.ts +22 -22
  38. package/docs/02-deployment.mdx +21 -0
  39. package/docs/08-faq.mdx +21 -0
  40. package/docs/configuration/ask-ai.mdx +16 -0
  41. package/docs/content/navigation.mdx +2 -0
  42. package/docs/content/sources.mdx +2 -2
  43. package/docs/content/syntax.mdx +1 -1
  44. package/docs/discoverability/open-graph.mdx +4 -0
  45. package/package.json +1 -1
  46. package/src/ai/ask.ts +31 -4
  47. package/src/astro/generate.ts +142 -5
  48. package/src/astro/integration.ts +12 -1
  49. package/src/astro/module-types.ts +9 -0
  50. package/src/astro/templates.ts +260 -56
  51. package/src/cli/commands/build.ts +28 -0
  52. package/src/components/Icon.astro +24 -0
  53. package/src/components/content/YouTube.astro +1 -1
  54. package/src/components/icon-sprite-middleware.ts +41 -0
  55. package/src/components/icon-sprite.ts +93 -0
  56. package/src/components/layout/Header.astro +1 -1
  57. package/src/components/layout/IconSprite.astro +11 -0
  58. package/src/components/layout/NavTree.astro +156 -188
  59. package/src/components/layout/NavTreeCache.astro +45 -0
  60. package/src/components/layout/NavTreeScript.astro +256 -0
  61. package/src/components/layout/PageActions.astro +11 -5
  62. package/src/components/layout/PageLayout.astro +7 -0
  63. package/src/components/layout/ReferenceLayout.astro +7 -0
  64. package/src/components/layout/RootLayout.astro +30 -2
  65. package/src/components/layout/Search.astro +11 -0
  66. package/src/components/layout/nav-cache.ts +49 -0
  67. package/src/components/layout/nav-utils.ts +87 -1
  68. package/src/core/config-input.ts +7 -0
  69. package/src/core/schema.ts +5 -0
  70. package/src/core/sources/assets.ts +162 -26
  71. package/src/core/sources/notion.ts +60 -1
  72. package/src/markdown/language-icon.ts +64 -20
  73. package/src/markdown/mermaid.ts +11 -0
  74. package/src/og/cache.ts +236 -0
  75. package/src/og/card.ts +12 -4
  76. package/src/og/index.ts +8 -1
  77. package/src/registry/eject.ts +24 -8
  78. package/src/theme/entry.ts +50 -7
  79. package/src/theme/fonts.ts +30 -23
  80. package/dist/cli/chunk-62qsssnh.js.map +0 -36
  81. package/dist/cli/chunk-aerwpe14.js.map +0 -15
  82. package/dist/cli/chunk-wb067mv3.js.map +0 -13
  83. /package/dist/cli/{chunk-m3p3wahd.js.map → chunk-52cwcqvp.js.map} +0 -0
  84. /package/dist/cli/{chunk-j00ezcg5.js.map → chunk-5gfw0q4j.js.map} +0 -0
  85. /package/dist/cli/{chunk-bawgnt8x.js.map → chunk-6mq7qkve.js.map} +0 -0
  86. /package/dist/cli/{chunk-nyqzjdhj.js.map → chunk-82atea4k.js.map} +0 -0
  87. /package/dist/cli/{chunk-tc89yh2r.js.map → chunk-8p3xe5jv.js.map} +0 -0
  88. /package/dist/cli/{chunk-s4k1pnvf.js.map → chunk-90pdhkpm.js.map} +0 -0
  89. /package/dist/cli/{chunk-n0y172hf.js.map → chunk-aqjvpd03.js.map} +0 -0
  90. /package/dist/cli/{chunk-x1vrdjyk.js.map → chunk-h9ekmtz7.js.map} +0 -0
  91. /package/dist/cli/{chunk-f75cqye8.js.map → chunk-he2zfgah.js.map} +0 -0
  92. /package/dist/cli/{chunk-s4jn7f1q.js.map → chunk-j5f2wrj5.js.map} +0 -0
  93. /package/dist/cli/{chunk-wkq5tbtq.js.map → chunk-k0v1f8bb.js.map} +0 -0
  94. /package/dist/cli/{chunk-5yvt556e.js.map → chunk-kmx2mydj.js.map} +0 -0
  95. /package/dist/cli/{chunk-ag1zyr5x.js.map → chunk-mfm4sjwx.js.map} +0 -0
  96. /package/dist/cli/{chunk-0ewz4trd.js.map → chunk-np8dmfb0.js.map} +0 -0
  97. /package/dist/cli/{chunk-cnvm6k3e.js.map → chunk-pdwg3q9g.js.map} +0 -0
  98. /package/dist/cli/{chunk-3k0kzs6d.js.map → chunk-qvvpnwaz.js.map} +0 -0
  99. /package/dist/cli/{chunk-9sh49q0h.js.map → chunk-ywn7t0pb.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # blume
2
2
 
3
+ ## 1.7.1
4
+
5
+ ### Patch Changes
6
+
7
+ - f52d4b7: Add `ai.ask.headers` to send static request headers to the Ask AI provider on every backend (gateway, OpenRouter, and the OpenAI-compatible providers), so a shared LLM backend can identify the caller without an external endpoint or an eject.
8
+ - b9645bf: Render callouts, steps, and every other Blume component inside changelog entries on the `/changelog` index. The index rendered each entry's MDX body without the component map the docs pages use, so a `:::info` directive or `<Steps>` in a release note failed the build (and the dev route) with "Expected component `Callout` to be defined".
9
+ - c25c859: Prevent the page behind the search dialog from scrolling while Command K search is open, and restore any existing page scroll lock when search closes.
10
+ - 39f683c: Fix the grouped sidebar 404ing its deferred section fragments in `blume dev` on sites whose default locale is unprefixed (`i18n.hideDefaultLocalePrefix`, the default). Astro's i18n routing rejects any page URL that carries the default locale's code as a segment, so `/blume-nav/current/de/…` never resolved for a site with `defaultLocale: "de"`; those fragments now live under `default`, the same way the locale's pages drop the prefix.
11
+ - 2575166: Import video blocks from Notion pages. A video block previously rendered as an `unsupported Notion block` comment, so neither uploaded videos nor pasted YouTube links appeared in the output. A YouTube link now becomes a `<YouTube>` embed, and any other video becomes a `<video>` player whose source is downloaded at build time — Notion's uploaded-file URLs are signed and expire, so they would otherwise rot the build. Downloads stream to disk through a shared concurrency gate with a timeout, a file already on disk is reused instead of fetched again, an extension-less asset is named by the response's media type, and a video link that answers with a web page (a Vimeo or Loom URL) is reported as a warning rather than written out as a broken player. String props the adapter writes (captions, toggle titles) use JSX expression form, so a double quote in a Notion caption no longer breaks the page.
12
+
13
+ ## 1.7.0
14
+
15
+ ### Minor Changes
16
+
17
+ - 78aadc8: Bundle Mermaid and the EPUB generator only for sites that use them. Both were dynamic imports, so they never loaded on a page that didn't need them, but they were still built on every `blume build` — over 3 MB of client chunks (ELK, Cytoscape, KaTeX, every diagram type) that most sites never serve, and most of the client build's memory. The generated runtime now decides at generation time whether any page has a mermaid fence and whether `export.epub` is on, and leaves the libraries out of the module graph otherwise; the dev server also stops pre-bundling them for sites that don't need them.
18
+ - 51eb2ac: Leave collapsed sidebar sections out of the page. In `group` and `page` display modes, a section that isn't open on the current page no longer ships its rows in that page's HTML; the empty disclosure or panel fetches a prerendered fragment on first open (prefetched on hover or focus, and kept for the visit), so a large site's pages carry only the open section instead of every section on every page. Group ids are now stable across tab-scoped views of the sidebar.
19
+
20
+ ### Patch Changes
21
+
22
+ - ede3c94: Cache rendered OG cards between builds. Each card is stored under `node_modules/.cache/blume/og`, keyed by everything that decides its pixels (title, description, branding, palette, fonts, and the Blume version), so a rebuild renders only the cards whose inputs changed and reads the rest back from disk. The build log reports how many cards were reused, and cards no page asks for any more are pruned after each build.
23
+ - a43c1d9: Paint code-block language icons from the theme instead of inlining an SVG in every block. A block now carries `data-icon="<slug>"` and the theme masks the brand path onto it, with one rule per language the site's Markdown uses, so a page with twenty TypeScript blocks no longer repeats the same 1 kB logo twenty times. Set icons rendered by the Icon component are deduplicated the same way, through a per-page sprite.
24
+ - 7b7d585: Prerender pages concurrently. The build now renders up to eight pages at once (one per available CPU below that), so the main thread renders the next page while a page's OG card renders on a native thread and its HTML is written to disk, instead of idling behind each page's off-thread work. A 1,400-page site's route generation phase runs in roughly half the time.
25
+ - 903eec0: Render the sidebar's group rows (collapsible summaries, flat headings, drill-in buttons, and the links inside them) through theme utility classes, as page rows already are, so a sidebar with many groups adds a few dozen bytes per group to every page instead of several hundred.
26
+ - 7b7d585: Shrink the sidebar's per-page cost. Every page's HTML carries the whole sidebar, so on a large site the sidebar was most of every page: each row now renders as a single theme utility class (`blume-nav-link`) instead of a dozen Tailwind classes, a bare row drops its inner spans, and groups that don't contain the current page are rendered once per build and reused across pages. A 1,400-page site's pages go from about 570 kB to under 190 kB each, and the build's HTML output shrinks and renders accordingly.
27
+ - 4005c27: Load the curated Google fonts that ship as variable fonts (every preset except IBM Plex Mono, IBM Plex Serif, and Space Mono) as one weight range, so each page declares one `@font-face` per style instead of one per weight — the same font files, a quarter of the inline font CSS. A configured `"100..900"` range now reaches Astro in the form it understands; it previously loaded nothing for that weight.
28
+ - 071e35d: Fix `blume dev` and `blume build` failing on Windows with `Cannot find module '@astrojs/mdx'` under Bun's isolated linker. The hidden runtime's `node_modules` link is now a directory symlink (falling back to a junction where symlinks need elevated privileges), because Windows can't follow the relative symlinks Bun writes into Blume's dependency directory when they're reached through a junction.
29
+
3
30
  ## 1.6.6
4
31
 
5
32
  ### Patch Changes
@@ -10,7 +10,7 @@ import {
10
10
  pageMetaSchema,
11
11
  resolveProjectContext,
12
12
  slugify
13
- } from "./chunk-vv3f8mb6.js";
13
+ } from "./chunk-r99hynxh.js";
14
14
  import {
15
15
  isInternalPath,
16
16
  stripBasePath,
@@ -3803,21 +3803,81 @@ import { setTimeout as sleep2 } from "node:timers/promises";
3803
3803
  import pLimit from "p-limit";
3804
3804
  import { join as join6 } from "pathe";
3805
3805
 
3806
+ // src/components/content/youtube.ts
3807
+ var BARE_ID = /^[\w-]{11}$/u;
3808
+ var URL_ID = /(?:youtu\.be\/|\/embed\/|\/shorts\/|\/live\/|[?&]v=)(?<id>[\w-]{11})/u;
3809
+ var parseYouTubeId = (input) => {
3810
+ const value = input.trim();
3811
+ if (!value) {
3812
+ return null;
3813
+ }
3814
+ if (BARE_ID.test(value)) {
3815
+ return value;
3816
+ }
3817
+ return URL_ID.exec(value)?.groups?.id ?? null;
3818
+ };
3819
+
3806
3820
  // src/core/sources/assets.ts
3807
- import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
3821
+ import { randomUUID } from "node:crypto";
3822
+ import {
3823
+ access,
3824
+ mkdir as mkdir3,
3825
+ readdir,
3826
+ rename,
3827
+ rm,
3828
+ writeFile as writeFile3
3829
+ } from "node:fs/promises";
3808
3830
  import { extname as extname5, join as join5 } from "pathe";
3809
3831
  var MD_IMAGE2 = /!\[(?<alt>[^\]]*)\]\((?<url>[^)\s]+)\)/gu;
3832
+ var HTML_VIDEO_SRC = /(?<open><video\b[^>]*?\ssrc=")(?<url>[^"]+)(?<close>")/gu;
3810
3833
  var REMOTE = /^https?:\/\//u;
3811
3834
  var SAFE_EXT = /^\.[a-z0-9]+$/iu;
3812
3835
  var CODE_FENCE_BLOCK = /^(?<fence>`{3,}|~{3,})[^\n]*\n[\s\S]*?^\k<fence>[^\n]*(?=\n|$)/gmu;
3813
3836
  var FENCE_TOKEN = /\u0000blume-fence-(?<index>\d+)\u0000/gu;
3814
- var extFor = (url) => {
3837
+ var EXT_BY_MIME = new Map([
3838
+ ["image/avif", ".avif"],
3839
+ ["image/gif", ".gif"],
3840
+ ["image/jpeg", ".jpg"],
3841
+ ["image/png", ".png"],
3842
+ ["image/svg+xml", ".svg"],
3843
+ ["image/webp", ".webp"],
3844
+ ["video/mp4", ".mp4"],
3845
+ ["video/ogg", ".ogv"],
3846
+ ["video/quicktime", ".mov"],
3847
+ ["video/webm", ".webm"]
3848
+ ]);
3849
+ var UNKNOWN_EXT = ".bin";
3850
+ var NON_MEDIA_TYPE = /^(?:text\/|application\/(?:[\w.-]+\+)?(?:json|xml|javascript))/u;
3851
+ var PART_SUFFIX = ".part";
3852
+ var DEFAULT_TIMEOUT_MS = 120000;
3853
+ var extFromUrl = (url) => {
3815
3854
  const clean = url.split("?")[0] ?? url;
3816
3855
  const ext = extname5(clean);
3817
- return SAFE_EXT.test(ext) ? ext.toLowerCase() : ".png";
3856
+ return SAFE_EXT.test(ext) ? ext.toLowerCase() : null;
3857
+ };
3858
+ var mediaType = (res) => res.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() ?? "";
3859
+ var exists = async (path) => {
3860
+ try {
3861
+ await access(path);
3862
+ return true;
3863
+ } catch {
3864
+ return false;
3865
+ }
3866
+ };
3867
+ var completedFor = async (dir, stem) => {
3868
+ let names;
3869
+ try {
3870
+ names = await readdir(dir);
3871
+ } catch {
3872
+ return null;
3873
+ }
3874
+ const candidates = names.filter((name) => name.startsWith(`${stem}.`) && !name.endsWith(PART_SUFFIX));
3875
+ return candidates.length === 1 ? candidates[0] ?? null : null;
3818
3876
  };
3819
3877
  var materializeAssets = async (markdown, ctx) => {
3820
3878
  const doFetch = ctx.fetchImpl ?? globalThis.fetch;
3879
+ const limit = ctx.limit ?? ((task) => task());
3880
+ const timeoutMs = ctx.timeoutMs ?? DEFAULT_TIMEOUT_MS;
3821
3881
  const diagnostics = [];
3822
3882
  const fences = [];
3823
3883
  const masked = markdown.replace(CODE_FENCE_BLOCK, (block) => {
@@ -3825,23 +3885,52 @@ var materializeAssets = async (markdown, ctx) => {
3825
3885
  return `\x00blume-fence-${fences.length - 1}\x00`;
3826
3886
  });
3827
3887
  const urls = new Set;
3828
- for (const match of masked.matchAll(MD_IMAGE2)) {
3829
- const url = match.groups?.url;
3830
- if (url && REMOTE.test(url)) {
3831
- urls.add(url);
3888
+ for (const pattern of [MD_IMAGE2, HTML_VIDEO_SRC]) {
3889
+ for (const match of masked.matchAll(pattern)) {
3890
+ const url = match.groups?.url;
3891
+ if (url && REMOTE.test(url)) {
3892
+ urls.add(url);
3893
+ }
3832
3894
  }
3833
3895
  }
3896
+ const download = async (url) => {
3897
+ const stem = hashText(url.split("?")[0] ?? url);
3898
+ const urlExt = extFromUrl(url);
3899
+ if (urlExt && await exists(join5(ctx.assetsDir, `${stem}${urlExt}`))) {
3900
+ return `${stem}${urlExt}`;
3901
+ }
3902
+ const completed = urlExt ? null : await completedFor(ctx.assetsDir, stem);
3903
+ if (completed) {
3904
+ return completed;
3905
+ }
3906
+ const res = await doFetch(url, { signal: AbortSignal.timeout(timeoutMs) });
3907
+ if (!res.ok) {
3908
+ throw new Error(`${res.status}`);
3909
+ }
3910
+ const type = mediaType(res);
3911
+ if (NON_MEDIA_TYPE.test(type)) {
3912
+ throw new Error(`responded with ${type}, not a media file`);
3913
+ }
3914
+ if (!res.body) {
3915
+ throw new Error("empty response body");
3916
+ }
3917
+ const file = `${stem}${urlExt ?? EXT_BY_MIME.get(type) ?? UNKNOWN_EXT}`;
3918
+ const target = join5(ctx.assetsDir, file);
3919
+ await mkdir3(ctx.assetsDir, { recursive: true });
3920
+ const part = `${target}.${randomUUID()}${PART_SUFFIX}`;
3921
+ try {
3922
+ await writeFile3(part, res.body);
3923
+ } catch (error) {
3924
+ await rm(part, { force: true });
3925
+ throw error;
3926
+ }
3927
+ await rename(part, target);
3928
+ return file;
3929
+ };
3834
3930
  const rewrites = new Map;
3835
3931
  await Promise.all([...urls].map(async (url) => {
3836
3932
  try {
3837
- const res = await doFetch(url);
3838
- if (!res.ok) {
3839
- throw new Error(`${res.status}`);
3840
- }
3841
- const bytes = new Uint8Array(await res.arrayBuffer());
3842
- const file = `${hashText(url.split("?")[0] ?? url)}${extFor(url)}`;
3843
- await mkdir3(ctx.assetsDir, { recursive: true });
3844
- await writeFile3(join5(ctx.assetsDir, file), bytes);
3933
+ const file = await limit(() => download(url));
3845
3934
  rewrites.set(url, `${ctx.assetsBaseUrl}/${file}`);
3846
3935
  } catch (error) {
3847
3936
  diagnostics.push({
@@ -3854,6 +3943,9 @@ var materializeAssets = async (markdown, ctx) => {
3854
3943
  const rewritten = masked.replaceAll(MD_IMAGE2, (match, alt, url) => {
3855
3944
  const local = rewrites.get(url);
3856
3945
  return local ? `![${alt}](${local})` : match;
3946
+ }).replaceAll(HTML_VIDEO_SRC, (match, open, url, close) => {
3947
+ const local = rewrites.get(url);
3948
+ return local ? `${open}${local}${close}` : match;
3857
3949
  }).replaceAll(FENCE_TOKEN, (token, index) => fences[Number(index)] ?? token);
3858
3950
  return { diagnostics, markdown: rewritten };
3859
3951
  };
@@ -3886,6 +3978,7 @@ var MAX_RETRIES = 4;
3886
3978
  var BASE_DELAY_MS = 500;
3887
3979
  var SECOND_MS2 = 1000;
3888
3980
  var DEFAULT_CONCURRENCY = 3;
3981
+ var ASSET_DOWNLOAD_CONCURRENCY = 4;
3889
3982
  var withNotionRetry = async (call, attempt = 0) => {
3890
3983
  try {
3891
3984
  return await call();
@@ -3911,6 +4004,25 @@ var LIST_BLOCKS = new Set([
3911
4004
  "to_do"
3912
4005
  ]);
3913
4006
  var isListItem = (block) => block !== undefined && LIST_BLOCKS.has(block.type);
4007
+ var richToPlain = (rich = []) => rich.map((node) => node.plain_text).join("");
4008
+ var jsxString = (value) => `{${JSON.stringify(value)}}`;
4009
+ var YOUTUBE_HOST = /(?:^|\.)(?:youtube(?:-nocookie)?\.com|youtu\.be)$/u;
4010
+ var isYouTubeUrl = (url) => {
4011
+ const host = URL.parse(url)?.hostname ?? "";
4012
+ return YOUTUBE_HOST.test(host) && parseYouTubeId(url) !== null;
4013
+ };
4014
+ var renderVideo = (data) => {
4015
+ const url = data.external?.url ?? data.file?.url;
4016
+ if (!url) {
4017
+ return "";
4018
+ }
4019
+ const caption = richToMarkdown(data.caption);
4020
+ const title = caption ? ` title=${jsxString(richToPlain(data.caption))}` : "";
4021
+ const media = isYouTubeUrl(url) ? `<YouTube${title} url=${JSON.stringify(url)} />` : `<video controls src=${JSON.stringify(url)} />`;
4022
+ return caption ? `<Frame caption=${jsxString(caption)}>
4023
+ ${media}
4024
+ </Frame>` : media;
4025
+ };
3914
4026
  var renderLeaf = (block) => {
3915
4027
  const data = payloadOf(block) ?? {};
3916
4028
  const text = richToMarkdown(blockField(block));
@@ -3951,6 +4063,9 @@ ${text}
3951
4063
  const url = data.external?.url ?? data.file?.url;
3952
4064
  return url ? `![${richToMarkdown(data.caption)}](${url})` : "";
3953
4065
  }
4066
+ case "video": {
4067
+ return renderVideo(data);
4068
+ }
3954
4069
  default: {
3955
4070
  return null;
3956
4071
  }
@@ -3959,6 +4074,7 @@ ${text}
3959
4074
  var notionSource = (options, ctx) => {
3960
4075
  const props = options.properties ?? {};
3961
4076
  const limit = pLimit(Math.max(1, options.concurrency ?? DEFAULT_CONCURRENCY));
4077
+ const downloads = pLimit(ASSET_DOWNLOAD_CONCURRENCY);
3962
4078
  const notionCall = (call) => withNotionRetry(() => limit(call));
3963
4079
  const cache = snapshotCache(ctx?.cacheDir ?? join6(".blume", "cache", options.name));
3964
4080
  const assetsDir = ctx?.assetsDir ?? join6(".blume", "public", "blume-assets", options.name);
@@ -3998,7 +4114,7 @@ ${body}
3998
4114
  </Callout>`;
3999
4115
  }
4000
4116
  if (block.type === "toggle") {
4001
- const title = JSON.stringify(richToMarkdown(blockField(block)));
4117
+ const title = jsxString(richToMarkdown(blockField(block)));
4002
4118
  return `<Accordion>
4003
4119
  <AccordionItem title=${title}>
4004
4120
  ${await children(block)}
@@ -4099,7 +4215,8 @@ ${nested}`;
4099
4215
  const assets = await materializeAssets(mdx, {
4100
4216
  assetsBaseUrl,
4101
4217
  assetsDir,
4102
- fetchImpl: options.fetchImpl
4218
+ fetchImpl: options.fetchImpl,
4219
+ limit: downloads
4103
4220
  });
4104
4221
  const raw = frontmatter_default.stringify(assets.markdown, data);
4105
4222
  return {
@@ -4168,7 +4285,7 @@ ${nested}`;
4168
4285
 
4169
4286
  // src/core/sources/obsidian.ts
4170
4287
  import { existsSync as existsSync3, watch as fsWatch2, statSync } from "node:fs";
4171
- import { readdir, readFile as readFile5, stat } from "node:fs/promises";
4288
+ import { readdir as readdir2, readFile as readFile5, stat } from "node:fs/promises";
4172
4289
  import { basename as basename2, isAbsolute as isAbsolute3, join as join7, relative as relative5, resolve as resolve3 } from "pathe";
4173
4290
  var MARKDOWN_FILE = /\.md$/iu;
4174
4291
  var OBSIDIAN_COMMENT = /%%.*?%%/gu;
@@ -4528,7 +4645,7 @@ var classify = async (dir, entry) => {
4528
4645
  var byVaultOrder = (a, b) => Number(b.directory) - Number(a.directory) || a.name.localeCompare(b.name, "en", { numeric: true, sensitivity: "base" }) || (a.name < b.name ? -1 : 1);
4529
4646
  var walkVault = async (dir, root, exclude) => {
4530
4647
  const found = [];
4531
- const listed = await readdir(dir, { withFileTypes: true });
4648
+ const listed = await readdir2(dir, { withFileTypes: true });
4532
4649
  const entries = await Promise.all(listed.filter((entry) => !(isHidden(entry.name) || exclude.has(entry.name))).map((entry) => classify(dir, entry)));
4533
4650
  for (const entry of entries.toSorted(byVaultOrder)) {
4534
4651
  const full = join7(dir, entry.name);
@@ -5164,7 +5281,7 @@ var scanProject = async (root, options = {}) => {
5164
5281
  };
5165
5282
  };
5166
5283
 
5167
- export { YAML_SCHEMA, frontmatter_default, TOC_HIDDEN_KEY, parseHeadingMarkers, BLUME_IGNORE_DIRS, i18nEnabled, localeCodes, resolveFallbackLocale, localePrefix, localizeRoute, localeTargetPath, validateSearchPopularIcons, validateNavTargets, VERSION_LIKE, versionizeRoute, nextFenceState, HTML_COMMENT, INLINE_CODE, scanBody, MD_IMAGE, targetOffsetIn, hasIncludeStatements, expandIncludes, buildIncludeGraph, contentIndexable, buildManifest, hashText, specOf, operationOf, specAddresses, apiNamePhrase, operationSignature, isOpenApiSource, resolveDocsCollection, isWithin, scanProject };
5284
+ export { YAML_SCHEMA, frontmatter_default, TOC_HIDDEN_KEY, parseHeadingMarkers, BLUME_IGNORE_DIRS, i18nEnabled, localeCodes, resolveFallbackLocale, localePrefix, localizeRoute, localeTargetPath, validateSearchPopularIcons, validateNavTargets, VERSION_LIKE, versionizeRoute, nextFenceState, HTML_COMMENT, INLINE_CODE, scanBody, MD_IMAGE, targetOffsetIn, hasIncludeStatements, expandIncludes, buildIncludeGraph, contentIndexable, buildManifest, hashText, specOf, operationOf, specAddresses, apiNamePhrase, operationSignature, isOpenApiSource, parseYouTubeId, resolveDocsCollection, isWithin, scanProject };
5168
5285
 
5169
- //# debugId=3D5AE0D317E6447664756E2164756E21
5170
- //# sourceMappingURL=chunk-etsqspj6.js.map
5286
+ //# debugId=A3A6EEB99D149F0C64756E2164756E21
5287
+ //# sourceMappingURL=chunk-12dzsn9b.js.map