blume 0.0.0 → 0.1.0

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 (263) hide show
  1. package/bin/blume.mjs +18 -0
  2. package/dist/cli/index.js +11989 -0
  3. package/dist/cli/index.js.map +141 -0
  4. package/docs/01-quickstart.mdx +99 -0
  5. package/docs/02-deployment.mdx +129 -0
  6. package/docs/advanced/api-reference.mdx +114 -0
  7. package/docs/advanced/blog.mdx +121 -0
  8. package/docs/advanced/changelog.mdx +113 -0
  9. package/docs/advanced/custom-pages.mdx +187 -0
  10. package/docs/advanced/meta.ts +7 -0
  11. package/docs/changelog/v0-1-0.mdx +12 -0
  12. package/docs/changelog/v0-2-0.mdx +16 -0
  13. package/docs/configuration/ai.mdx +228 -0
  14. package/docs/configuration/analytics.mdx +98 -0
  15. package/docs/configuration/customization.mdx +91 -0
  16. package/docs/configuration/export.mdx +70 -0
  17. package/docs/configuration/index.mdx +271 -0
  18. package/docs/configuration/meta.ts +15 -0
  19. package/docs/configuration/search.mdx +172 -0
  20. package/docs/configuration/seo.mdx +196 -0
  21. package/docs/configuration/theming.mdx +178 -0
  22. package/docs/content/components.mdx +565 -0
  23. package/docs/content/i18n.mdx +205 -0
  24. package/docs/content/index.mdx +161 -0
  25. package/docs/content/islands.mdx +94 -0
  26. package/docs/content/meta.mdx +119 -0
  27. package/docs/content/meta.ts +15 -0
  28. package/docs/content/navigation.mdx +168 -0
  29. package/docs/content/sources.mdx +216 -0
  30. package/docs/content/syntax.mdx +445 -0
  31. package/docs/index.mdx +112 -0
  32. package/docs/reference/cli.mdx +43 -0
  33. package/docs/reference/frontmatter.mdx +74 -0
  34. package/docs/reference/meta.ts +7 -0
  35. package/package.json +140 -6
  36. package/src/ai/ask.ts +93 -0
  37. package/src/ai/llms.ts +65 -0
  38. package/src/ai/markdown.ts +31 -0
  39. package/src/ai/mcp/data.ts +74 -0
  40. package/src/ai/mcp/discovery.ts +49 -0
  41. package/src/ai/mcp/server.ts +225 -0
  42. package/src/ai/mcp/tools.ts +47 -0
  43. package/src/assets/icon.png +0 -0
  44. package/src/astro/generate.ts +878 -0
  45. package/src/astro/index.ts +4 -0
  46. package/src/astro/integration.ts +74 -0
  47. package/src/astro/islands.ts +131 -0
  48. package/src/astro/markdown-negotiation.ts +68 -0
  49. package/src/astro/pages.ts +28 -0
  50. package/src/astro/templates.ts +1199 -0
  51. package/src/cli/commands/add.ts +81 -0
  52. package/src/cli/commands/build.ts +103 -0
  53. package/src/cli/commands/dev.ts +108 -0
  54. package/src/cli/commands/doctor.ts +74 -0
  55. package/src/cli/commands/eject.ts +57 -0
  56. package/src/cli/commands/init.ts +98 -0
  57. package/src/cli/commands/migrate.ts +39 -0
  58. package/src/cli/commands/preview.ts +39 -0
  59. package/src/cli/commands/sync.ts +52 -0
  60. package/src/cli/commands/validate.ts +60 -0
  61. package/src/cli/index.ts +35 -0
  62. package/src/cli/log.ts +37 -0
  63. package/src/cli/prepare.ts +80 -0
  64. package/src/components/Icon.astro +99 -0
  65. package/src/components/content/Accordion.astro +8 -0
  66. package/src/components/content/AccordionItem.astro +121 -0
  67. package/src/components/content/AutoTypeTable.astro +51 -0
  68. package/src/components/content/Badge.astro +124 -0
  69. package/src/components/content/Callout.astro +73 -0
  70. package/src/components/content/Card.astro +104 -0
  71. package/src/components/content/CardGroup.astro +14 -0
  72. package/src/components/content/CodeGroup.astro +13 -0
  73. package/src/components/content/Color.astro +15 -0
  74. package/src/components/content/ColorItem.astro +87 -0
  75. package/src/components/content/ColorRow.astro +10 -0
  76. package/src/components/content/Column.astro +6 -0
  77. package/src/components/content/Columns.astro +9 -0
  78. package/src/components/content/Expandable.astro +11 -0
  79. package/src/components/content/FileTree.astro +8 -0
  80. package/src/components/content/Frame.astro +70 -0
  81. package/src/components/content/GithubInfo.astro +110 -0
  82. package/src/components/content/Math.astro +24 -0
  83. package/src/components/content/Panel.astro +20 -0
  84. package/src/components/content/Prompt.astro +129 -0
  85. package/src/components/content/Step.astro +34 -0
  86. package/src/components/content/Steps.astro +20 -0
  87. package/src/components/content/Tab.astro +40 -0
  88. package/src/components/content/Tabs.astro +273 -0
  89. package/src/components/content/Tile.astro +42 -0
  90. package/src/components/content/Tooltip.astro +68 -0
  91. package/src/components/content/Tree.astro +300 -0
  92. package/src/components/content/TreeFile.astro +15 -0
  93. package/src/components/content/TreeFolder.astro +62 -0
  94. package/src/components/content/TypeTable.astro +106 -0
  95. package/src/components/content/Update.astro +66 -0
  96. package/src/components/content/Visibility.astro +12 -0
  97. package/src/components/content/Warning.astro +9 -0
  98. package/src/components/content/auto-type-table.ts +141 -0
  99. package/src/components/content/github-info.ts +79 -0
  100. package/src/components/content/mermaid-element.ts +68 -0
  101. package/src/components/github-mark.ts +9 -0
  102. package/src/components/index.ts +14 -0
  103. package/src/components/islands/AskAI.astro +12 -0
  104. package/src/components/islands/ask-ai.tsx +156 -0
  105. package/src/components/layout/Analytics.astro +63 -0
  106. package/src/components/layout/Banner.astro +50 -0
  107. package/src/components/layout/Breadcrumbs.astro +31 -0
  108. package/src/components/layout/Favicon.astro +15 -0
  109. package/src/components/layout/Fonts.astro +14 -0
  110. package/src/components/layout/Header.astro +188 -0
  111. package/src/components/layout/LanguageSwitcher.astro +56 -0
  112. package/src/components/layout/NavTree.astro +462 -0
  113. package/src/components/layout/PageActions.astro +438 -0
  114. package/src/components/layout/PageFeedback.astro +58 -0
  115. package/src/components/layout/Pagination.astro +56 -0
  116. package/src/components/layout/ReferenceLayout.astro +102 -0
  117. package/src/components/layout/RootLayout.astro +533 -0
  118. package/src/components/layout/Search.astro +608 -0
  119. package/src/components/layout/TableOfContents.astro +68 -0
  120. package/src/components/layout/analytics-client.ts +38 -0
  121. package/src/components/layout/nav-utils.ts +87 -0
  122. package/src/components/layout/overrides.ts +32 -0
  123. package/src/components/layout/search/algolia.ts +43 -0
  124. package/src/components/layout/search/endpoint.ts +22 -0
  125. package/src/components/layout/search/flexsearch.ts +52 -0
  126. package/src/components/layout/search/orama-cloud.ts +41 -0
  127. package/src/components/layout/search/orama.ts +26 -0
  128. package/src/components/layout/search/pagefind.ts +43 -0
  129. package/src/components/layout/search/types.ts +163 -0
  130. package/src/components/layout/search/typesense.ts +60 -0
  131. package/src/components/layout/toc-element.ts +108 -0
  132. package/src/core/bridge.ts +92 -0
  133. package/src/core/config.ts +112 -0
  134. package/src/core/content.ts +50 -0
  135. package/src/core/define-components.ts +34 -0
  136. package/src/core/define-meta.ts +20 -0
  137. package/src/core/deployment-env.ts +73 -0
  138. package/src/core/diagnostics.ts +104 -0
  139. package/src/core/graph.ts +128 -0
  140. package/src/core/i18n-ui.ts +171 -0
  141. package/src/core/i18n.ts +169 -0
  142. package/src/core/last-modified.ts +88 -0
  143. package/src/core/links.ts +336 -0
  144. package/src/core/load-module.ts +15 -0
  145. package/src/core/manifest.ts +126 -0
  146. package/src/core/meta.ts +97 -0
  147. package/src/core/navigation.ts +392 -0
  148. package/src/core/package-root.ts +37 -0
  149. package/src/core/project-graph.ts +153 -0
  150. package/src/core/project.ts +56 -0
  151. package/src/core/schema.ts +1057 -0
  152. package/src/core/server-features.ts +23 -0
  153. package/src/core/sources/assets.ts +77 -0
  154. package/src/core/sources/cache.ts +122 -0
  155. package/src/core/sources/filesystem.ts +99 -0
  156. package/src/core/sources/mdx-remote.ts +216 -0
  157. package/src/core/sources/mintlify.ts +161 -0
  158. package/src/core/sources/normalize.ts +227 -0
  159. package/src/core/sources/notion.ts +440 -0
  160. package/src/core/sources/portable-text.ts +143 -0
  161. package/src/core/sources/read.ts +36 -0
  162. package/src/core/sources/resolve.ts +158 -0
  163. package/src/core/sources/sanity.ts +218 -0
  164. package/src/core/sources/types.ts +105 -0
  165. package/src/core/types.ts +261 -0
  166. package/src/core/ui-packs/ar.ts +47 -0
  167. package/src/core/ui-packs/bg.ts +47 -0
  168. package/src/core/ui-packs/bn.ts +47 -0
  169. package/src/core/ui-packs/ca.ts +47 -0
  170. package/src/core/ui-packs/cs.ts +47 -0
  171. package/src/core/ui-packs/da.ts +47 -0
  172. package/src/core/ui-packs/de.ts +47 -0
  173. package/src/core/ui-packs/el.ts +47 -0
  174. package/src/core/ui-packs/es.ts +47 -0
  175. package/src/core/ui-packs/fa.ts +47 -0
  176. package/src/core/ui-packs/fi.ts +47 -0
  177. package/src/core/ui-packs/fr.ts +47 -0
  178. package/src/core/ui-packs/he.ts +47 -0
  179. package/src/core/ui-packs/hi.ts +47 -0
  180. package/src/core/ui-packs/hr.ts +47 -0
  181. package/src/core/ui-packs/hu.ts +47 -0
  182. package/src/core/ui-packs/id.ts +47 -0
  183. package/src/core/ui-packs/index.ts +87 -0
  184. package/src/core/ui-packs/it.ts +47 -0
  185. package/src/core/ui-packs/ja.ts +47 -0
  186. package/src/core/ui-packs/ko.ts +47 -0
  187. package/src/core/ui-packs/nl.ts +47 -0
  188. package/src/core/ui-packs/no.ts +47 -0
  189. package/src/core/ui-packs/pl.ts +47 -0
  190. package/src/core/ui-packs/pt-br.ts +47 -0
  191. package/src/core/ui-packs/pt.ts +47 -0
  192. package/src/core/ui-packs/ro.ts +47 -0
  193. package/src/core/ui-packs/ru.ts +47 -0
  194. package/src/core/ui-packs/sk.ts +47 -0
  195. package/src/core/ui-packs/sr.ts +47 -0
  196. package/src/core/ui-packs/sv.ts +47 -0
  197. package/src/core/ui-packs/th.ts +47 -0
  198. package/src/core/ui-packs/tr.ts +47 -0
  199. package/src/core/ui-packs/uk.ts +47 -0
  200. package/src/core/ui-packs/vi.ts +47 -0
  201. package/src/core/ui-packs/zh-tw.ts +47 -0
  202. package/src/core/ui-packs/zh.ts +47 -0
  203. package/src/core/version.ts +23 -0
  204. package/src/deploy/robots.ts +20 -0
  205. package/src/deploy/rss.ts +128 -0
  206. package/src/deploy/sitemap.ts +28 -0
  207. package/src/index.ts +27 -0
  208. package/src/markdown/code-title.ts +71 -0
  209. package/src/markdown/directives.ts +83 -0
  210. package/src/markdown/heading-anchors.ts +137 -0
  211. package/src/markdown/index.ts +159 -0
  212. package/src/markdown/inline-code.ts +108 -0
  213. package/src/markdown/language-icon.ts +172 -0
  214. package/src/markdown/math.ts +32 -0
  215. package/src/markdown/mdast.ts +48 -0
  216. package/src/markdown/mermaid.ts +37 -0
  217. package/src/markdown/package-commands.ts +159 -0
  218. package/src/markdown/package-install.ts +40 -0
  219. package/src/migrate/fumadocs/config.ts +106 -0
  220. package/src/migrate/fumadocs/content.ts +365 -0
  221. package/src/migrate/fumadocs/frontmatter.ts +18 -0
  222. package/src/migrate/fumadocs/index.ts +252 -0
  223. package/src/migrate/fumadocs/meta.ts +114 -0
  224. package/src/migrate/migrate.ts +53 -0
  225. package/src/migrate/mintlify/config.ts +1040 -0
  226. package/src/migrate/mintlify/content.ts +98 -0
  227. package/src/migrate/mintlify/frontmatter.ts +126 -0
  228. package/src/migrate/mintlify/i18n.ts +51 -0
  229. package/src/migrate/mintlify/icons.ts +128 -0
  230. package/src/migrate/mintlify/index.ts +266 -0
  231. package/src/migrate/mintlify/snippets.ts +305 -0
  232. package/src/migrate/mintlify/transform.ts +81 -0
  233. package/src/migrate/nextra/content.ts +46 -0
  234. package/src/migrate/nextra/frontmatter.ts +40 -0
  235. package/src/migrate/nextra/index.ts +374 -0
  236. package/src/migrate/nextra/meta.ts +266 -0
  237. package/src/migrate/shared.ts +623 -0
  238. package/src/migrate/starlight/config.ts +459 -0
  239. package/src/migrate/starlight/content.ts +78 -0
  240. package/src/migrate/starlight/frontmatter.ts +111 -0
  241. package/src/migrate/starlight/i18n.ts +54 -0
  242. package/src/migrate/starlight/index.ts +131 -0
  243. package/src/og/card.ts +92 -0
  244. package/src/og/index.ts +2 -0
  245. package/src/openapi/scalar.ts +246 -0
  246. package/src/registry/eject.ts +263 -0
  247. package/src/registry/registry.ts +100 -0
  248. package/src/registry/rewrite-imports.ts +39 -0
  249. package/src/runtime/index.ts +14 -0
  250. package/src/search/build.ts +23 -0
  251. package/src/search/documents.ts +165 -0
  252. package/src/search/orama-index.ts +66 -0
  253. package/src/search/providers.ts +91 -0
  254. package/src/search/sync/algolia.ts +30 -0
  255. package/src/search/sync/index.ts +50 -0
  256. package/src/search/sync/orama-cloud.ts +40 -0
  257. package/src/search/sync/typesense.ts +65 -0
  258. package/src/seo/jsonld.ts +113 -0
  259. package/src/theme/entry.ts +608 -0
  260. package/src/theme/fonts.ts +198 -0
  261. package/src/theme/icons.ts +184 -0
  262. package/src/theme/palette.ts +143 -0
  263. package/src/theme/twoslash.ts +81 -0
@@ -0,0 +1,104 @@
1
+ ---
2
+ import Icon from "../Icon.astro";
3
+
4
+ interface Props {
5
+ title?: string;
6
+ href?: string;
7
+ icon?: string;
8
+ iconType?: string;
9
+ img?: string;
10
+ horizontal?: boolean | string;
11
+ cta?: string;
12
+ arrow?: boolean | string;
13
+ type?: "info" | "warning" | "note" | "tip" | "check" | "danger";
14
+ color?: string;
15
+ }
16
+
17
+ const { arrow, color, cta, horizontal, href, icon, iconType, img, title, type } =
18
+ Astro.props;
19
+ const Tag = href ? "a" : "div";
20
+ const external = href?.startsWith("http");
21
+ const isHorizontal = horizontal === true || horizontal === "true";
22
+ const showArrow =
23
+ href && arrow === undefined ? external : arrow === true || arrow === "true";
24
+ const iconByType = {
25
+ check: "circle-check",
26
+ danger: "circle-x",
27
+ info: "info",
28
+ note: "info",
29
+ tip: "lightbulb",
30
+ warning: "triangle-alert",
31
+ };
32
+ const iconClass = {
33
+ check: "text-green-600 dark:text-green-400",
34
+ danger: "text-red-600 dark:text-red-400",
35
+ info: "text-blue-600 dark:text-blue-400",
36
+ note: "text-muted-foreground/70",
37
+ tip: "text-accent",
38
+ warning: "text-amber-600 dark:text-amber-400",
39
+ };
40
+ const iconName = icon ?? (type ? iconByType[type] : undefined);
41
+ const variantClass = {
42
+ check: "border-green-500/30 bg-green-500/10",
43
+ danger: "border-red-500/30 bg-red-500/10",
44
+ info: "border-blue-500/30 bg-blue-500/10",
45
+ note: "border-border bg-muted/40",
46
+ tip: "border-accent/30 bg-accent/10",
47
+ warning: "border-amber-500/30 bg-amber-500/10",
48
+ }[type ?? "note"];
49
+ ---
50
+
51
+ <Tag
52
+ class:list={[
53
+ "not-prose block overflow-hidden rounded-blume border text-inherit no-underline! transition-colors hover:no-underline! hover:[&[href]]:border-accent",
54
+ type ? variantClass : "border-border bg-background",
55
+ isHorizontal ? "sm:flex" : "",
56
+ ]}
57
+ data-blume-card
58
+ href={href}
59
+ rel={external ? "noreferrer" : undefined}
60
+ target={external ? "_blank" : undefined}
61
+ >
62
+ {
63
+ img && (
64
+ <img
65
+ alt=""
66
+ class:list={[
67
+ "w-full object-cover",
68
+ isHorizontal ? "sm:w-40 sm:shrink-0" : "max-h-48",
69
+ ]}
70
+ src={img}
71
+ />
72
+ )
73
+ }
74
+ <div class="p-5">
75
+ {
76
+ iconName && (
77
+ <div class:list={["mb-2.5", color ? "" : type ? iconClass[type] : "text-accent"]}>
78
+ <Icon color={color} iconType={iconType} name={iconName} size={20} />
79
+ </div>
80
+ )
81
+ }
82
+ {
83
+ title && (
84
+ <p class="mt-0! mb-1.5! flex items-center gap-1.5 font-medium text-foreground">
85
+ {title}
86
+ {showArrow && (
87
+ <Icon class="text-muted-foreground" name="arrow-right" size={14} />
88
+ )}
89
+ </p>
90
+ )
91
+ }
92
+ <div class="font-normal text-muted-foreground text-sm [&>:first-child]:mt-0! [&>:last-child]:mb-0!">
93
+ <slot />
94
+ </div>
95
+ {
96
+ cta && (
97
+ <p class="mt-4! mb-0! flex items-center gap-1.5 font-medium text-accent text-sm">
98
+ {cta}
99
+ {showArrow && <Icon name="arrow-right" size={14} />}
100
+ </p>
101
+ )
102
+ }
103
+ </div>
104
+ </Tag>
@@ -0,0 +1,14 @@
1
+ ---
2
+ interface Props {
3
+ cols?: number;
4
+ }
5
+
6
+ const { cols = 2 } = Astro.props;
7
+ ---
8
+
9
+ <div
10
+ class="not-prose my-6 grid grid-cols-1 gap-4 sm:grid-cols-[repeat(var(--blume-cols),minmax(0,1fr))]"
11
+ style={`--blume-cols:${cols}`}
12
+ >
13
+ <slot />
14
+ </div>
@@ -0,0 +1,13 @@
1
+ ---
2
+ import Tabs from "./Tabs.astro";
3
+
4
+ interface Props {
5
+ dropdown?: boolean;
6
+ }
7
+
8
+ const { dropdown = false } = Astro.props;
9
+ ---
10
+
11
+ <Tabs dropdown={dropdown} hash={false}>
12
+ <slot />
13
+ </Tabs>
@@ -0,0 +1,15 @@
1
+ ---
2
+ const { variant = "compact" } = Astro.props;
3
+ ---
4
+
5
+ <div
6
+ class:list={[
7
+ "not-prose my-6",
8
+ variant === "table"
9
+ ? "space-y-4"
10
+ : "grid grid-cols-2 gap-x-5 gap-y-4 sm:grid-cols-4",
11
+ ]}
12
+ data-blume-color={variant}
13
+ >
14
+ <slot />
15
+ </div>
@@ -0,0 +1,87 @@
1
+ ---
2
+ interface ColorValue {
3
+ dark?: string;
4
+ light?: string;
5
+ }
6
+
7
+ interface Props {
8
+ name: string;
9
+ value: string | ColorValue;
10
+ }
11
+
12
+ const { name, value } = Astro.props;
13
+
14
+ const lightValue = typeof value === "string" ? value : value.light ?? value.dark;
15
+ const darkValue = typeof value === "string" ? value : value.dark ?? value.light;
16
+ const displayValue =
17
+ lightValue && darkValue && lightValue !== darkValue
18
+ ? `${lightValue} / ${darkValue}`
19
+ : lightValue;
20
+ ---
21
+
22
+ <button
23
+ aria-label={`Copy ${name} color ${displayValue}`}
24
+ class="group flex w-full min-w-0 cursor-pointer flex-col gap-2.5 text-center focus:outline-none"
25
+ data-blume-color-copy={displayValue}
26
+ style={`--blume-color-light:${lightValue};--blume-color-dark:${darkValue};`}
27
+ type="button"
28
+ >
29
+ <span
30
+ class="block aspect-square w-full rounded-2xl border border-border/60 shadow-sm transition group-hover:ring-2 group-hover:ring-accent/30 group-focus-visible:ring-2 group-focus-visible:ring-accent/40"
31
+ data-blume-color-swatch
32
+ style="background:var(--blume-color-light)"
33
+ >
34
+ </span>
35
+ <span class="block min-w-0 leading-tight">
36
+ <span class="block truncate font-semibold text-foreground text-sm">
37
+ {name}
38
+ </span>
39
+ <span
40
+ class="mt-0.5 block truncate font-mono text-muted-foreground text-xs uppercase"
41
+ data-blume-color-status
42
+ >
43
+ {displayValue}
44
+ </span>
45
+ </span>
46
+ </button>
47
+
48
+ <style>
49
+ :global(.dark) [data-blume-color-swatch] {
50
+ background: var(--blume-color-dark, var(--blume-color-light));
51
+ }
52
+ </style>
53
+
54
+ <script>
55
+ const state = window as Window & { __blumeColorCopy?: boolean };
56
+ if (!state.__blumeColorCopy) {
57
+ state.__blumeColorCopy = true;
58
+ document.addEventListener("click", async (event) => {
59
+ const target = event.target;
60
+ if (!(target instanceof Element)) {
61
+ return;
62
+ }
63
+
64
+ const button = target.closest<HTMLButtonElement>("[data-blume-color-copy]");
65
+ if (!button) {
66
+ return;
67
+ }
68
+
69
+ try {
70
+ await navigator.clipboard.writeText(button.dataset.blumeColorCopy ?? "");
71
+ } catch {
72
+ return;
73
+ }
74
+
75
+ const status = button.querySelector<HTMLElement>("[data-blume-color-status]");
76
+ if (!status) {
77
+ return;
78
+ }
79
+
80
+ const original = status.textContent;
81
+ status.textContent = "Copied";
82
+ window.setTimeout(() => {
83
+ status.textContent = original;
84
+ }, 1500);
85
+ });
86
+ }
87
+ </script>
@@ -0,0 +1,10 @@
1
+ ---
2
+ const { title } = Astro.props;
3
+ ---
4
+
5
+ <div class="rounded-blume border border-border bg-background p-4">
6
+ {title && <p class="mt-0! mb-3! font-medium text-foreground text-sm">{title}</p>}
7
+ <div class="grid grid-cols-2 gap-x-5 gap-y-4 sm:grid-cols-4">
8
+ <slot />
9
+ </div>
10
+ </div>
@@ -0,0 +1,6 @@
1
+ ---
2
+ ---
3
+
4
+ <div class="min-w-0">
5
+ <slot />
6
+ </div>
@@ -0,0 +1,9 @@
1
+ ---
2
+ import CardGroup from "./CardGroup.astro";
3
+
4
+ const { cols = 2 } = Astro.props;
5
+ ---
6
+
7
+ <CardGroup cols={cols}>
8
+ <slot />
9
+ </CardGroup>
@@ -0,0 +1,11 @@
1
+ ---
2
+ import AccordionItem from "./AccordionItem.astro";
3
+
4
+ const { defaultOpen = false, title = "Show more" } = Astro.props;
5
+ ---
6
+
7
+ <div class="not-prose my-4 overflow-hidden rounded-blume border border-border">
8
+ <AccordionItem defaultOpen={defaultOpen} title={title}>
9
+ <slot />
10
+ </AccordionItem>
11
+ </div>
@@ -0,0 +1,8 @@
1
+ ---
2
+ ---
3
+
4
+ <div
5
+ class="not-prose my-6 rounded-blume border border-border p-5 text-sm [&_ul]:m-0! [&_ul]:list-none [&_ul]:ps-4 [&>ul]:ps-0"
6
+ >
7
+ <slot />
8
+ </div>
@@ -0,0 +1,70 @@
1
+ ---
2
+ import { createSatteriMarkdownProcessor } from "@astrojs/markdown-satteri";
3
+
4
+ interface Props {
5
+ caption?: string;
6
+ hint?: string;
7
+ }
8
+
9
+ const { caption, hint } = Astro.props;
10
+
11
+ const escapeRawHtml = (value: string): string =>
12
+ value.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
13
+
14
+ const unwrapParagraph = (html: string): string => {
15
+ const trimmed = html.trim();
16
+ const match = trimmed.match(/^<p>(?<content>[\s\S]*)<\/p>$/u);
17
+ return match?.groups?.content ?? trimmed;
18
+ };
19
+
20
+ const markdownProcessor = await createSatteriMarkdownProcessor();
21
+ const captionHtml = caption
22
+ ? unwrapParagraph((await markdownProcessor.render(escapeRawHtml(caption))).code)
23
+ : undefined;
24
+ ---
25
+
26
+ <blume-frame class="not-prose my-6 block">
27
+ <figure class="m-0">
28
+ {
29
+ hint && (
30
+ <p class="mb-2 text-muted-foreground text-sm leading-6">
31
+ {hint}
32
+ </p>
33
+ )
34
+ }
35
+ <div
36
+ class="flex justify-center overflow-hidden rounded-blume border border-border bg-muted/30 p-6 [&>:first-child]:m-0! [&_img]:h-auto [&_img]:max-w-full [&_video]:h-auto [&_video]:max-w-full"
37
+ >
38
+ <slot />
39
+ </div>
40
+ {
41
+ captionHtml && (
42
+ <figcaption
43
+ class="mt-2 text-center text-muted-foreground text-xs leading-5 [&_a]:font-medium [&_a]:text-foreground [&_a]:underline [&_strong]:font-semibold [&_strong]:text-foreground"
44
+ set:html={captionHtml}
45
+ />
46
+ )
47
+ }
48
+ </figure>
49
+ </blume-frame>
50
+
51
+ <script>
52
+ class BlumeFrame extends HTMLElement {
53
+ connectedCallback() {
54
+ for (const video of this.querySelectorAll<HTMLVideoElement>(
55
+ "video[autoplay]"
56
+ )) {
57
+ video.playsInline = true;
58
+ video.loop = true;
59
+ video.muted = true;
60
+ video.setAttribute("playsinline", "");
61
+ video.setAttribute("loop", "");
62
+ video.setAttribute("muted", "");
63
+ }
64
+ }
65
+ }
66
+
67
+ if (!customElements.get("blume-frame")) {
68
+ customElements.define("blume-frame", BlumeFrame);
69
+ }
70
+ </script>
@@ -0,0 +1,110 @@
1
+ ---
2
+ // A card linking to a GitHub repository with its star and fork counts, fetched
3
+ // at build time (no client JS). Pass `owner`/`repo`, or omit them to use the
4
+ // repo from blume.config. A `GITHUB_TOKEN` env var lifts the API rate limit.
5
+ // If the API is unreachable the card still renders, just without counts.
6
+ import data from "blume:data";
7
+ import { GITHUB_MARK } from "../github-mark.ts";
8
+ import { fetchRepositoryInfo } from "./github-info.ts";
9
+
10
+ interface Props {
11
+ owner?: string;
12
+ repo?: string;
13
+ token?: string;
14
+ }
15
+
16
+ // The resolved config exposes the repo as `repoUrl`, not `github`; parse the
17
+ // owner/repo back out so <GithubInfo /> can default to the configured repo.
18
+ const repoMatch = data.config.repoUrl?.match(
19
+ /github\.com\/(?<owner>[^/]+)\/(?<repo>[^/]+)/u
20
+ );
21
+ const owner = Astro.props.owner ?? repoMatch?.groups?.owner;
22
+ const repo = Astro.props.repo ?? repoMatch?.groups?.repo;
23
+ const token = Astro.props.token ?? process.env.GITHUB_TOKEN;
24
+
25
+ const info =
26
+ owner && repo ? await fetchRepositoryInfo({ owner, repo, token }) : null;
27
+
28
+ // Compact notation matches GitHub's own counts (e.g. 1.2k, 34.5k).
29
+ const numbers = new Intl.NumberFormat("en", {
30
+ maximumFractionDigits: 1,
31
+ notation: "compact",
32
+ });
33
+
34
+ const statIcon =
35
+ "size-3.5 shrink-0 text-muted-foreground"; /* shared classes for stat icons */
36
+ ---
37
+
38
+ {
39
+ owner && repo && (
40
+ <a
41
+ class="not-prose my-6 flex flex-col gap-2 rounded-blume border border-border p-4 no-underline! transition-colors hover:border-foreground/30 hover:bg-muted/40"
42
+ href={`https://github.com/${owner}/${repo}`}
43
+ rel="noreferrer"
44
+ target="_blank"
45
+ >
46
+ <span class="flex items-center gap-2 font-medium text-foreground text-sm">
47
+ <svg
48
+ aria-hidden="true"
49
+ class="shrink-0 text-muted-foreground"
50
+ fill="currentColor"
51
+ height="16"
52
+ set:html={GITHUB_MARK}
53
+ viewBox="0 0 16 16"
54
+ width="16"
55
+ xmlns="http://www.w3.org/2000/svg"
56
+ />
57
+ <span class="truncate">
58
+ {owner}/{repo}
59
+ </span>
60
+ </span>
61
+
62
+ {info?.description && (
63
+ <span class="text-muted-foreground text-sm">{info.description}</span>
64
+ )}
65
+
66
+ {info && (
67
+ <span class="flex items-center gap-4 text-muted-foreground text-xs">
68
+ <span class="flex items-center gap-1.5">
69
+ <svg
70
+ aria-hidden="true"
71
+ class={statIcon}
72
+ fill="none"
73
+ height="14"
74
+ stroke="currentColor"
75
+ stroke-linecap="round"
76
+ stroke-linejoin="round"
77
+ stroke-width="2"
78
+ viewBox="0 0 24 24"
79
+ width="14"
80
+ >
81
+ <path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" />
82
+ </svg>
83
+ <span>{numbers.format(info.stars)}</span>
84
+ </span>
85
+ <span class="flex items-center gap-1.5">
86
+ <svg
87
+ aria-hidden="true"
88
+ class={statIcon}
89
+ fill="none"
90
+ height="14"
91
+ stroke="currentColor"
92
+ stroke-linecap="round"
93
+ stroke-linejoin="round"
94
+ stroke-width="2"
95
+ viewBox="0 0 24 24"
96
+ width="14"
97
+ >
98
+ <circle cx="12" cy="18" r="3" />
99
+ <circle cx="6" cy="6" r="3" />
100
+ <circle cx="18" cy="6" r="3" />
101
+ <path d="M18 9v2c0 .6-.4 1-1 1H7c-.6 0-1-.4-1-1V9" />
102
+ <path d="M12 12v3" />
103
+ </svg>
104
+ <span>{numbers.format(info.forks)}</span>
105
+ </span>
106
+ </span>
107
+ )}
108
+ </a>
109
+ )
110
+ }
@@ -0,0 +1,24 @@
1
+ ---
2
+ import "katex/dist/katex.min.css";
3
+ import katex from "katex";
4
+
5
+ interface Props {
6
+ code: string;
7
+ display?: boolean;
8
+ }
9
+
10
+ const { code, display = false } = Astro.props;
11
+
12
+ const html = katex.renderToString(code, {
13
+ displayMode: display,
14
+ throwOnError: false,
15
+ });
16
+ ---
17
+
18
+ {
19
+ display ? (
20
+ <div class="not-prose my-4 overflow-x-auto" set:html={html} />
21
+ ) : (
22
+ <Fragment set:html={html} />
23
+ )
24
+ }
@@ -0,0 +1,20 @@
1
+ ---
2
+ const { title } = Astro.props;
3
+ ---
4
+
5
+ <aside
6
+ class="not-prose my-6 overflow-hidden rounded-blume border border-border/80 bg-background shadow-sm shadow-border/30"
7
+ data-blume-panel
8
+ data-blume-right-rail-item
9
+ >
10
+ {
11
+ title && (
12
+ <p class="m-0! border-border/70 border-b bg-muted/35 px-3.5 py-2 font-medium text-muted-foreground text-xs">
13
+ {title}
14
+ </p>
15
+ )
16
+ }
17
+ <div class="px-3.5 py-3 text-sm leading-6 [&>:first-child]:mt-0! [&>:last-child]:mb-0!">
18
+ <slot />
19
+ </div>
20
+ </aside>
@@ -0,0 +1,129 @@
1
+ ---
2
+ import { createSatteriMarkdownProcessor } from "@astrojs/markdown-satteri";
3
+
4
+ interface Props {
5
+ actions?: boolean | string | string[];
6
+ description: string;
7
+ }
8
+
9
+ const { actions = ["copy"], description } = Astro.props;
10
+
11
+ const escapeRawHtml = (value: string): string =>
12
+ value.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
13
+
14
+ const unwrapParagraph = (html: string): string => {
15
+ const trimmed = html.trim();
16
+ const match = trimmed.match(/^<p>(?<content>[\s\S]*)<\/p>$/u);
17
+ return match?.groups?.content ?? trimmed;
18
+ };
19
+
20
+ const normalizeActions = (value: Props["actions"]): string[] => {
21
+ if (Array.isArray(value)) {
22
+ return value;
23
+ }
24
+ if (typeof value === "string") {
25
+ return [value];
26
+ }
27
+ return value === false ? [] : ["copy"];
28
+ };
29
+
30
+ const enabledActions = new Set(normalizeActions(actions));
31
+ const markdownProcessor = await createSatteriMarkdownProcessor();
32
+ const renderedDescription = await markdownProcessor.render(
33
+ escapeRawHtml(description)
34
+ );
35
+ const descriptionHtml = unwrapParagraph(renderedDescription.code);
36
+
37
+ const primaryButton =
38
+ "inline-flex h-8 cursor-pointer items-center rounded-lg bg-accent px-3.5 font-medium text-accent-foreground text-xs transition hover:bg-accent/90";
39
+ const secondaryButton =
40
+ "inline-flex h-8 cursor-pointer items-center rounded-lg border border-border bg-background px-3.5 font-medium text-muted-foreground text-xs transition hover:border-accent hover:text-accent";
41
+ ---
42
+
43
+ <blume-prompt
44
+ class="not-prose my-6 block rounded-blume border border-border bg-muted/30"
45
+ >
46
+ <div class="flex items-center justify-between gap-3 px-4 py-3">
47
+ <p
48
+ class="m-0! min-w-0 text-foreground text-sm [&_a]:font-medium [&_a]:text-foreground [&_a]:underline [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_strong]:font-semibold [&_strong]:text-foreground"
49
+ data-blume-prompt-description
50
+ set:html={descriptionHtml}
51
+ />
52
+ {
53
+ enabledActions.size > 0 && (
54
+ <div class="flex shrink-0 items-center gap-2">
55
+ {enabledActions.has("copy") && (
56
+ <button
57
+ aria-label="Copy prompt"
58
+ class={primaryButton}
59
+ data-blume-prompt-copy
60
+ type="button"
61
+ >
62
+ <span data-blume-prompt-copy-label>Copy prompt</span>
63
+ </button>
64
+ )}
65
+ {enabledActions.has("cursor") && (
66
+ <a
67
+ aria-label="Open prompt in Cursor"
68
+ class={secondaryButton}
69
+ data-blume-prompt-cursor
70
+ href="cursor://anysphere.cursor-deeplink/prompt"
71
+ >
72
+ Open in Cursor
73
+ </a>
74
+ )}
75
+ </div>
76
+ )
77
+ }
78
+ </div>
79
+ <div data-blume-prompt-content hidden>
80
+ <slot />
81
+ </div>
82
+ </blume-prompt>
83
+
84
+ <script>
85
+ class BlumePrompt extends HTMLElement {
86
+ connectedCallback() {
87
+ const content = this.querySelector<HTMLElement>(
88
+ "[data-blume-prompt-content]"
89
+ );
90
+ const promptText = () => content?.textContent?.trim() ?? "";
91
+
92
+ const cursor = this.querySelector<HTMLAnchorElement>(
93
+ "[data-blume-prompt-cursor]"
94
+ );
95
+ if (cursor) {
96
+ cursor.href = `cursor://anysphere.cursor-deeplink/prompt?text=${encodeURIComponent(promptText())}`;
97
+ }
98
+
99
+ const copy = this.querySelector<HTMLButtonElement>(
100
+ "[data-blume-prompt-copy]"
101
+ );
102
+ copy?.addEventListener("click", async () => {
103
+ const text = promptText();
104
+ if (!text) {
105
+ return;
106
+ }
107
+ try {
108
+ await navigator.clipboard.writeText(text);
109
+ } catch {
110
+ return;
111
+ }
112
+
113
+ const label = copy.querySelector("[data-blume-prompt-copy-label]");
114
+ if (!label) {
115
+ return;
116
+ }
117
+ const previous = label.textContent ?? "Copy prompt";
118
+ label.textContent = "Copied";
119
+ setTimeout(() => {
120
+ label.textContent = previous;
121
+ }, 1500);
122
+ });
123
+ }
124
+ }
125
+
126
+ if (!customElements.get("blume-prompt")) {
127
+ customElements.define("blume-prompt", BlumePrompt);
128
+ }
129
+ </script>
@@ -0,0 +1,34 @@
1
+ ---
2
+ import Icon from "../Icon.astro";
3
+ import { hasIcon } from "../../theme/icons.ts";
4
+
5
+ interface Props {
6
+ icon?: string;
7
+ iconType?: string;
8
+ title?: string;
9
+ }
10
+
11
+ const { icon, iconType, title } = Astro.props;
12
+ const markerClass =
13
+ "absolute top-0 -start-[2.75rem] flex size-6 items-center justify-center rounded-full bg-accent text-center font-semibold text-accent-foreground text-xs";
14
+ const isImageIcon = (value: string): boolean =>
15
+ /^(?:https?:\/\/|data:image\/|\/|\.{1,2}\/)/u.test(value) ||
16
+ /\.(?:avif|gif|jpe?g|png|svg|webp)$/iu.test(value);
17
+ const canRenderIcon =
18
+ icon !== undefined &&
19
+ (/^\s*<svg[\s\S]*<\/svg>\s*$/u.test(icon) ||
20
+ isImageIcon(icon) ||
21
+ hasIcon(icon, iconType));
22
+ ---
23
+
24
+ <div
25
+ class="relative [counter-increment:blume-step]"
26
+ >
27
+ <span class:list={[markerClass, !canRenderIcon && "before:[content:counter(blume-step)]"]}>
28
+ {canRenderIcon && <Icon iconType={iconType} name={icon} size={14} />}
29
+ </span>
30
+ {title && <p class="mb-1.5 font-semibold">{title}</p>}
31
+ <div class="[&>:first-child]:mt-0! [&>:last-child]:mb-0!">
32
+ <slot />
33
+ </div>
34
+ </div>