blume 0.6.0 → 0.6.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.
- package/dist/cli/index.js +384 -254
- package/dist/cli/index.js.map +11 -10
- package/dist/types/core/config-input.d.ts +759 -0
- package/dist/types/core/config.d.ts +126 -3
- package/dist/types/core/schema.d.ts +6 -23
- package/dist/types/index.d.ts +2 -1
- package/docs/advanced/changelog.mdx +10 -2
- package/docs/configuration/index.mdx +0 -2
- package/docs/content/syntax.mdx +4 -8
- package/package.json +1 -1
- package/src/astro/generate.ts +36 -11
- package/src/astro/templates.ts +66 -5
- package/src/cli/commands/build.ts +26 -1
- package/src/components/content/Update.astro +12 -2
- package/src/components/content/changelog-element.ts +62 -0
- package/src/components/layout/RootLayout.astro +31 -8
- package/src/core/config-input.ts +933 -0
- package/src/core/config.ts +126 -3
- package/src/core/schema.ts +6 -13
- package/src/deploy/adapter-output.ts +82 -0
- package/src/index.ts +1 -1
- package/src/markdown/index.ts +28 -30
- package/src/markdown/math.ts +3 -2
- package/src/registry/eject.ts +21 -14
- package/src/theme/entry.ts +7 -3
package/src/core/config.ts
CHANGED
|
@@ -1,16 +1,139 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
|
|
3
|
+
import type { BlumeConfig } from "./config-input.ts";
|
|
3
4
|
import { applyDeploymentEnv } from "./deployment-env.ts";
|
|
4
5
|
import { BlumeError, diagnosticsFromZod } from "./diagnostics.ts";
|
|
5
6
|
import { createModuleLoader } from "./load-module.ts";
|
|
6
7
|
import { findConfigFile } from "./project.ts";
|
|
7
8
|
import { blumeConfigSchema } from "./schema.ts";
|
|
8
|
-
import type {
|
|
9
|
+
import type { ResolvedConfig } from "./schema.ts";
|
|
9
10
|
import type { Diagnostic } from "./types.ts";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
13
|
+
* Define a Blume site's configuration with full type-checking and editor
|
|
14
|
+
* autocomplete. Place the call in `blume.config.ts` at your project root and
|
|
15
|
+
* `export default` the result:
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { defineConfig } from "blume";
|
|
19
|
+
*
|
|
20
|
+
* export default defineConfig({
|
|
21
|
+
* title: "Acme Docs",
|
|
22
|
+
* description: "Everything you need to build with Acme.",
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* Every field is optional — an empty `defineConfig({})` produces a working
|
|
27
|
+
* site from the Markdown/MDX in your `docs/` directory. Configure only what you
|
|
28
|
+
* want to change; sensible defaults fill in the rest.
|
|
29
|
+
*
|
|
30
|
+
* This is an identity helper: it returns its input unchanged and exists purely
|
|
31
|
+
* for type inference (and as a stable home for future plugin hooks). The object
|
|
32
|
+
* is validated against the Blume schema when the CLI loads it.
|
|
33
|
+
*
|
|
34
|
+
* ## Top-level fields
|
|
35
|
+
*
|
|
36
|
+
* **Site identity**
|
|
37
|
+
* - `title` — site title, shown in the header, `<title>`, and OG images.
|
|
38
|
+
* Defaults to `"Documentation"`.
|
|
39
|
+
* - `description` — default meta description, used where a page sets none.
|
|
40
|
+
* - `logo` — brand mark. A string is an image path/URL; the object form splits
|
|
41
|
+
* an `image` mark from wordmark `text` and can override the brand `href`.
|
|
42
|
+
* - `banner` — site-wide announcement bar; a string, or `{ content, link,
|
|
43
|
+
* dismissible }`.
|
|
44
|
+
*
|
|
45
|
+
* **Content & navigation**
|
|
46
|
+
* - `content` — where content lives (`root`, defaults to `docs`) and pluggable
|
|
47
|
+
* `sources` (filesystem, remote MDX, GitHub Releases, Sanity, Notion, or a
|
|
48
|
+
* custom `ContentSource`). Omit `sources` and the top-level `root` becomes one
|
|
49
|
+
* implicit filesystem source.
|
|
50
|
+
* - `navigation` — sidebar, header `tabs`, `selectors` (version/language/product
|
|
51
|
+
* switchers), pinned `featured` links, and the `repo` link toggle. Omit
|
|
52
|
+
* `sidebar` to generate it from the content tree.
|
|
53
|
+
* - `redirects` — `{ from, to, status }` rules (301 by default).
|
|
54
|
+
* - `github` — `{ owner, repo, branch, dir }`, powering "Edit this page" links
|
|
55
|
+
* and the header repo link.
|
|
56
|
+
*
|
|
57
|
+
* **Appearance**
|
|
58
|
+
* - `theme` — `accent` color, `fonts` (curated Google Font slugs), `radius`,
|
|
59
|
+
* `mode` (`system`/`light`/`dark`), `background`, and `strict` token mode.
|
|
60
|
+
* - `markdown` — `code` (language icons, inline highlighting, line wrap),
|
|
61
|
+
* `headingAnchors`, `imageZoom`, and opt-in KaTeX `math`.
|
|
62
|
+
* - `toc` — on-page table of contents; `true`/`false` or a heading-level range.
|
|
63
|
+
* - `lastModified` — "Last updated" stamps from `git` history or frontmatter.
|
|
64
|
+
* - `feedback` — the per-page "Was this helpful?" widget (on by default).
|
|
65
|
+
* - `export` — reader-facing PDF/EPUB export actions (off by default).
|
|
66
|
+
*
|
|
67
|
+
* **Reference docs**
|
|
68
|
+
* - `openapi` — native OpenAPI reference: one real page per operation, woven
|
|
69
|
+
* into the sidebar and search. Point `sources`/`spec` at your spec.
|
|
70
|
+
* - `asyncapi` — AsyncAPI reference via the embedded Scalar renderer.
|
|
71
|
+
*
|
|
72
|
+
* **Search & AI**
|
|
73
|
+
* - `search` — search backend `provider` (`orama` by default; `pagefind`,
|
|
74
|
+
* `algolia`, `typesense`, `orama-cloud`, `mixedbread`, or `none`) plus its
|
|
75
|
+
* credential block.
|
|
76
|
+
* - `ai` — `ask` (the Ask AI chat endpoint and its provider/model) and `llmsTxt`
|
|
77
|
+
* (emit `llms.txt`).
|
|
78
|
+
* - `mcp` — expose the docs as an MCP server for connecting agents.
|
|
79
|
+
*
|
|
80
|
+
* **SEO, feeds & analytics**
|
|
81
|
+
* - `seo` — `og` images, `sitemap`, `robots`, `rss` feeds, `structuredData`
|
|
82
|
+
* JSON-LD, `agentReadability`, and robots `contentSignals`.
|
|
83
|
+
* - `analytics` — PostHog, Vercel, or arbitrary `scripts` (Plausible, Fathom,
|
|
84
|
+
* GA, …).
|
|
85
|
+
*
|
|
86
|
+
* **Deployment & i18n**
|
|
87
|
+
* - `deployment` — `site` URL (needed for absolute links, sitemaps, and OG),
|
|
88
|
+
* `adapter` (`vercel`/`node`/`netlify`/`cloudflare`), `output`
|
|
89
|
+
* (`static`/`server`), and `base` path. Auto-detected on Vercel/Netlify/
|
|
90
|
+
* Cloudflare from the platform env.
|
|
91
|
+
* - `i18n` — opt-in multi-locale: `locales`, `defaultLocale`, `parser`
|
|
92
|
+
* (`dir` vs filename `dot` suffix), and per-locale UI overrides.
|
|
93
|
+
*
|
|
94
|
+
* - `examples` — where `<Component path>` previews resolve their source from
|
|
95
|
+
* (defaults to `examples/`; supports a glob for colocated registries).
|
|
96
|
+
*
|
|
97
|
+
* @example Zero-config — just render the Markdown under `docs/`.
|
|
98
|
+
* ```ts
|
|
99
|
+
* export default defineConfig({});
|
|
100
|
+
* ```
|
|
101
|
+
*
|
|
102
|
+
* @example A production docs site with theming, search, and deployment.
|
|
103
|
+
* ```ts
|
|
104
|
+
* export default defineConfig({
|
|
105
|
+
* title: "Acme Docs",
|
|
106
|
+
* description: "Build faster with Acme.",
|
|
107
|
+
* logo: { image: "/logo.svg", text: "Acme" },
|
|
108
|
+
* github: { owner: "acme", repo: "acme" },
|
|
109
|
+
* theme: { accent: "violet", fonts: { body: "inter" }, radius: "lg" },
|
|
110
|
+
* navigation: {
|
|
111
|
+
* tabs: [
|
|
112
|
+
* { label: "Guides", path: "/guides" },
|
|
113
|
+
* { label: "API", path: "/api" },
|
|
114
|
+
* ],
|
|
115
|
+
* },
|
|
116
|
+
* search: { provider: "orama" },
|
|
117
|
+
* deployment: { site: "https://docs.acme.com", adapter: "vercel" },
|
|
118
|
+
* });
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* @example An OpenAPI reference with the Ask AI assistant enabled.
|
|
122
|
+
* ```ts
|
|
123
|
+
* export default defineConfig({
|
|
124
|
+
* title: "Acme API",
|
|
125
|
+
* openapi: {
|
|
126
|
+
* enabled: true,
|
|
127
|
+
* route: "/reference",
|
|
128
|
+
* sources: [{ label: "Core", spec: "./openapi.json" }],
|
|
129
|
+
* },
|
|
130
|
+
* ai: { ask: { enabled: true }, llmsTxt: true },
|
|
131
|
+
* });
|
|
132
|
+
* ```
|
|
133
|
+
*
|
|
134
|
+
* @param config - The site configuration. All fields are optional.
|
|
135
|
+
* @returns The same config object, typed for inference.
|
|
136
|
+
* @see https://useblume.dev/docs for the full configuration reference.
|
|
14
137
|
*/
|
|
15
138
|
export const defineConfig = (config: BlumeConfig): BlumeConfig => config;
|
|
16
139
|
|
package/src/core/schema.ts
CHANGED
|
@@ -925,12 +925,6 @@ const codeConfigSchema = z
|
|
|
925
925
|
* …). On by default; recognized languages only.
|
|
926
926
|
*/
|
|
927
927
|
icons: z.boolean().default(true),
|
|
928
|
-
/**
|
|
929
|
-
* Syntax-highlight inline `` `code{:lang}` `` snippets. Off by default — most
|
|
930
|
-
* inline code (flags, file names) reads better plain; opt a snippet in with
|
|
931
|
-
* a trailing `{:lang}` marker.
|
|
932
|
-
*/
|
|
933
|
-
inline: z.boolean().default(false),
|
|
934
928
|
/**
|
|
935
929
|
* Wrap long lines instead of scrolling horizontally. Off by default, so
|
|
936
930
|
* code keeps its original line breaks and overflows into a scroll area.
|
|
@@ -955,11 +949,6 @@ const markdownConfigSchema = z
|
|
|
955
949
|
* opt a single image out with `data-no-zoom`.
|
|
956
950
|
*/
|
|
957
951
|
imageZoom: z.boolean().default(true),
|
|
958
|
-
/**
|
|
959
|
-
* Enable LaTeX math (`$…$` inline, `$$…$$` block) rendered with KaTeX.
|
|
960
|
-
* Off by default since `$` is common in prose, shell, and code. MDX only.
|
|
961
|
-
*/
|
|
962
|
-
math: z.boolean().default(false),
|
|
963
952
|
})
|
|
964
953
|
.strict();
|
|
965
954
|
|
|
@@ -1097,8 +1086,12 @@ export type ResolvedConfig = z.infer<typeof blumeConfigSchema>;
|
|
|
1097
1086
|
export type ResolvedI18nConfig = z.infer<typeof i18nConfigSchema>;
|
|
1098
1087
|
/** A configured locale with display metadata. */
|
|
1099
1088
|
export type LocaleConfig = z.infer<typeof localeSchema>;
|
|
1100
|
-
/**
|
|
1101
|
-
|
|
1089
|
+
/**
|
|
1090
|
+
* User-authored config, straight off the schema. The public, hand-documented
|
|
1091
|
+
* authoring type is `BlumeConfig` in `./config-input.ts`, which a compile-time
|
|
1092
|
+
* guard keeps structurally identical to this.
|
|
1093
|
+
*/
|
|
1094
|
+
export type BlumeConfigInput = z.input<typeof blumeConfigSchema>;
|
|
1102
1095
|
/** A configured search backend. */
|
|
1103
1096
|
export type SearchProvider = (typeof searchProviders)[number];
|
|
1104
1097
|
/** Resolved robots.txt `Content-Signal` preferences (`null` when disabled). */
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { cp, mkdir, rm } from "node:fs/promises";
|
|
3
|
+
|
|
4
|
+
import { dirname, join } from "pathe";
|
|
5
|
+
|
|
6
|
+
import type { ResolvedConfig } from "../core/schema.ts";
|
|
7
|
+
import type { ProjectContext } from "../core/types.ts";
|
|
8
|
+
|
|
9
|
+
type Adapter = NonNullable<ResolvedConfig["deployment"]["adapter"]>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Server adapters whose deploy bundle lands *outside* Astro's `outDir`, at a
|
|
13
|
+
* path relative to the Astro project root. Blume points the Astro root at the
|
|
14
|
+
* hidden `<root>/.blume` runtime, so these adapters write their bundle to
|
|
15
|
+
* `<root>/.blume/<path>` — where the deploy platform never looks. Each value is
|
|
16
|
+
* the sub-path to surface up to the real project root.
|
|
17
|
+
*
|
|
18
|
+
* `vercel` writes a Build Output API v3 tree at `.vercel/output`; only that
|
|
19
|
+
* subtree is moved, so a `vercel pull`-ed `.vercel/project.json` sitting at the
|
|
20
|
+
* project root survives the relocation. `netlify` owns its whole `.netlify`
|
|
21
|
+
* dir. `node` and `cloudflare` emit into `dist/` (already at the project root),
|
|
22
|
+
* so they are absent here and need no relocation.
|
|
23
|
+
*/
|
|
24
|
+
export const ADAPTER_OUTPUT_PATHS: Partial<Record<Adapter, string>> = {
|
|
25
|
+
netlify: ".netlify",
|
|
26
|
+
vercel: ".vercel/output",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Directory whose contents the deploy platform serves as static files. Build
|
|
31
|
+
* artifacts (robots.txt, sitemap.xml, llms.txt, …) must be written here to be
|
|
32
|
+
* served. For a Vercel server build that is the adapter's
|
|
33
|
+
* `.vercel/output/static`; every other build serves `dist/`.
|
|
34
|
+
*/
|
|
35
|
+
export const deployStaticDir = (
|
|
36
|
+
config: ResolvedConfig,
|
|
37
|
+
context: ProjectContext
|
|
38
|
+
): string => {
|
|
39
|
+
const { adapter, output } = config.deployment;
|
|
40
|
+
if (output === "server" && adapter === "vercel") {
|
|
41
|
+
return join(context.root, ".vercel", "output", "static");
|
|
42
|
+
}
|
|
43
|
+
return context.distDir ?? join(context.root, "dist");
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** Outcome of {@link surfaceAdapterOutput}, for logging and `.gitignore`. */
|
|
47
|
+
export type SurfaceResult =
|
|
48
|
+
| { moved: false }
|
|
49
|
+
| { from: string; ignore: string; moved: true; to: string };
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Move a server adapter's deploy bundle out of the hidden `.blume` runtime and
|
|
53
|
+
* up to the project root, where the deploy platform (and `vercel deploy
|
|
54
|
+
* --prebuilt`) expects it. A no-op for static builds, for adapters that emit
|
|
55
|
+
* into `dist/`, and when the expected output is absent.
|
|
56
|
+
*/
|
|
57
|
+
export const surfaceAdapterOutput = async (
|
|
58
|
+
config: ResolvedConfig,
|
|
59
|
+
context: ProjectContext
|
|
60
|
+
): Promise<SurfaceResult> => {
|
|
61
|
+
const { adapter, output } = config.deployment;
|
|
62
|
+
if (output !== "server" || !adapter) {
|
|
63
|
+
return { moved: false };
|
|
64
|
+
}
|
|
65
|
+
const rel = ADAPTER_OUTPUT_PATHS[adapter];
|
|
66
|
+
if (!rel) {
|
|
67
|
+
return { moved: false };
|
|
68
|
+
}
|
|
69
|
+
const from = join(context.outDir, rel);
|
|
70
|
+
const to = join(context.root, rel);
|
|
71
|
+
if (!existsSync(from)) {
|
|
72
|
+
return { moved: false };
|
|
73
|
+
}
|
|
74
|
+
await mkdir(dirname(to), { recursive: true });
|
|
75
|
+
await rm(to, { force: true, recursive: true });
|
|
76
|
+
await cp(from, to, { recursive: true });
|
|
77
|
+
await rm(from, { force: true, recursive: true });
|
|
78
|
+
// The `.gitignore` entry is the surfaced top-level dir (`.vercel`/`.netlify`),
|
|
79
|
+
// never the moved sub-path — Vercel's own `.vercel/project.json` lives there
|
|
80
|
+
// too and must also be ignored.
|
|
81
|
+
return { from, ignore: `${rel.split("/")[0]}/`, moved: true, to };
|
|
82
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -22,8 +22,8 @@ export type {
|
|
|
22
22
|
FolderMetaFactory,
|
|
23
23
|
} from "./core/define-meta.ts";
|
|
24
24
|
export type { UIStrings } from "./core/i18n-ui.ts";
|
|
25
|
+
export type { BlumeConfig } from "./core/config-input.ts";
|
|
25
26
|
export type {
|
|
26
|
-
BlumeConfig,
|
|
27
27
|
FolderMeta,
|
|
28
28
|
HydrationMode,
|
|
29
29
|
ResolvedConfig,
|
package/src/markdown/index.ts
CHANGED
|
@@ -46,15 +46,16 @@ type HastPlugin = NonNullable<
|
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Hast plugins enabled by config. Inline `` `code`{:lang} `` highlighting is
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
49
|
+
* always on: it only fires on an explicit trailing `{:lang}` marker, so plain
|
|
50
|
+
* inline code is untouched and there's nothing to opt out of. Self-linking
|
|
51
|
+
* heading anchors (`<h2>`–`<h6>` wrapped in an `<a>` to their own id) are on
|
|
52
|
+
* unless `markdown.headingAnchors` is `false`. Inline code runs first so the
|
|
53
|
+
* anchor wrap re-refs already-highlighted code.
|
|
52
54
|
*/
|
|
53
55
|
const blumeHastPlugins = (options: BlumeMarkdownOptions): HastPlugin[] => {
|
|
54
|
-
const plugins: HastPlugin[] = [
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
56
|
+
const plugins: HastPlugin[] = [
|
|
57
|
+
inlineCodeHighlightPlugin() as unknown as HastPlugin,
|
|
58
|
+
];
|
|
58
59
|
if (options.headingAnchors !== false) {
|
|
59
60
|
plugins.push(headingAnchorPlugin() as unknown as HastPlugin);
|
|
60
61
|
}
|
|
@@ -204,8 +205,6 @@ export interface BlumeMarkdownOptions {
|
|
|
204
205
|
* On unless explicitly `false`.
|
|
205
206
|
*/
|
|
206
207
|
headingAnchors?: boolean;
|
|
207
|
-
/** Highlight inline `` `code`{:lang} `` snippets (`markdown.code.inline`). */
|
|
208
|
-
inline?: boolean;
|
|
209
208
|
}
|
|
210
209
|
|
|
211
210
|
/** Sätteri processor for plain `.md`, with Blume's curated feature set. */
|
|
@@ -215,38 +214,37 @@ export const blumeMarkdownProcessor = (options: BlumeMarkdownOptions = {}) =>
|
|
|
215
214
|
hastPlugins: blumeHastPlugins(options),
|
|
216
215
|
});
|
|
217
216
|
|
|
218
|
-
export
|
|
219
|
-
/** Enable KaTeX math parsing and rendering. */
|
|
220
|
-
math?: boolean;
|
|
221
|
-
}
|
|
217
|
+
export type BlumeMdxOptions = BlumeMarkdownOptions;
|
|
222
218
|
|
|
223
219
|
/**
|
|
224
220
|
* Sätteri MDX processor: Blume's feature set plus the MDAST plugins that target
|
|
225
221
|
* components — `package-install` → package-manager tabs, `:::note` →
|
|
226
|
-
* `<Callout>`,
|
|
227
|
-
*
|
|
228
|
-
* `.mdx` only (plain `.md` uses
|
|
229
|
-
*
|
|
222
|
+
* `<Callout>`, ` ```mermaid ` → a `<blume-mermaid>` element, and block math
|
|
223
|
+
* (`$$…$$`) → the `<Math>` component. Used as the `processor` for
|
|
224
|
+
* `@astrojs/mdx` so these apply to `.mdx` only (plain `.md` uses
|
|
225
|
+
* {@link blumeMarkdownProcessor}).
|
|
226
|
+
*
|
|
227
|
+
* Math is always on but block-only: `singleDollarTextMath: false` keeps a bare
|
|
228
|
+
* `$` (currency, shell, code) as literal text and only parses `$$…$$`. The
|
|
229
|
+
* generated runtime imports the `<Math>` component (and KaTeX's stylesheet) only
|
|
230
|
+
* when content actually uses `$$`, so a math-free site ships no KaTeX CSS.
|
|
230
231
|
*
|
|
231
232
|
* The plugins are modeled with minimal structural types; bridge them to
|
|
232
233
|
* Satteri's full `MdastPlugin` type at this single boundary.
|
|
233
234
|
*/
|
|
234
|
-
export const blumeMdxProcessor = (options: BlumeMdxOptions = {}) =>
|
|
235
|
-
|
|
236
|
-
packageInstallPlugin(),
|
|
237
|
-
directiveToCalloutPlugin(),
|
|
238
|
-
mermaidPlugin(),
|
|
239
|
-
];
|
|
240
|
-
if (options.math) {
|
|
241
|
-
plugins.push(mathPlugin());
|
|
242
|
-
}
|
|
243
|
-
return satteri({
|
|
235
|
+
export const blumeMdxProcessor = (options: BlumeMdxOptions = {}) =>
|
|
236
|
+
satteri({
|
|
244
237
|
features: {
|
|
245
238
|
...FEATURES,
|
|
246
239
|
directive: true,
|
|
247
|
-
|
|
240
|
+
// Block-only: `$$…$$` parses, a bare `$` stays literal text.
|
|
241
|
+
math: { singleDollarTextMath: false },
|
|
248
242
|
},
|
|
249
243
|
hastPlugins: blumeHastPlugins(options),
|
|
250
|
-
mdastPlugins:
|
|
244
|
+
mdastPlugins: [
|
|
245
|
+
packageInstallPlugin(),
|
|
246
|
+
directiveToCalloutPlugin(),
|
|
247
|
+
mermaidPlugin(),
|
|
248
|
+
mathPlugin(),
|
|
249
|
+
] as unknown as MdastPlugin[],
|
|
251
250
|
});
|
|
252
|
-
};
|
package/src/markdown/math.ts
CHANGED
|
@@ -8,8 +8,9 @@ interface MathNode extends MdastNode {
|
|
|
8
8
|
/**
|
|
9
9
|
* Satteri MDAST plugin that turns math nodes into Blume's `<Math>` component,
|
|
10
10
|
* which renders them with KaTeX at build time. Block math (`$$…$$`) becomes a
|
|
11
|
-
* block element
|
|
12
|
-
*
|
|
11
|
+
* block element. Blume runs the parser block-only (`singleDollarTextMath:
|
|
12
|
+
* false`), so a bare `$` stays literal and no `inlineMath` nodes are produced;
|
|
13
|
+
* the `inlineMath` visitor remains as a harmless safety net.
|
|
13
14
|
*/
|
|
14
15
|
export const mathPlugin = () => ({
|
|
15
16
|
inlineMath(node: MathNode, ctx: MdastVisitorContext) {
|
package/src/registry/eject.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
buildRuntimeData,
|
|
13
13
|
collectStaged,
|
|
14
14
|
detectNeedsReact,
|
|
15
|
+
detectUsesMath,
|
|
15
16
|
} from "../astro/generate.ts";
|
|
16
17
|
import { discoverIslands } from "../astro/islands.ts";
|
|
17
18
|
import { customOgRoutes, discoverPages, routeIsTaken } from "../astro/pages.ts";
|
|
@@ -102,19 +103,25 @@ export const eject = async (root: string): Promise<string[]> => {
|
|
|
102
103
|
const exportPdf = config.export.pdf;
|
|
103
104
|
const exportEpub = config.export.epub;
|
|
104
105
|
|
|
105
|
-
const [
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
106
|
+
const [
|
|
107
|
+
pages,
|
|
108
|
+
needsReactRaw,
|
|
109
|
+
usesMath,
|
|
110
|
+
userTheme,
|
|
111
|
+
rawMarkdown,
|
|
112
|
+
islands,
|
|
113
|
+
examples,
|
|
114
|
+
] = await Promise.all([
|
|
115
|
+
context.pagesRoot ? discoverPages(context.pagesRoot) : Promise.resolve([]),
|
|
116
|
+
detectNeedsReact(root),
|
|
117
|
+
detectUsesMath(root),
|
|
118
|
+
context.themeFile
|
|
119
|
+
? readFile(context.themeFile, "utf-8")
|
|
120
|
+
: Promise.resolve(""),
|
|
121
|
+
buildRawMarkdown(project),
|
|
122
|
+
discoverIslands(root),
|
|
123
|
+
discoverExamples(root, config.examples),
|
|
124
|
+
]);
|
|
118
125
|
// Island/example frameworks drive which Astro renderers the ejected config
|
|
119
126
|
// wires in; React also switches on for project `.tsx`/`.jsx` and Ask AI.
|
|
120
127
|
const frameworks = new Set<string>([
|
|
@@ -192,7 +199,7 @@ export const eject = async (root: string): Promise<string[]> => {
|
|
|
192
199
|
askEnabled,
|
|
193
200
|
exportEpub,
|
|
194
201
|
exportPdf,
|
|
195
|
-
mathEnabled:
|
|
202
|
+
mathEnabled: usesMath,
|
|
196
203
|
needsReact,
|
|
197
204
|
}),
|
|
198
205
|
path: join(srcDir, "pages", "[...slug].astro"),
|
package/src/theme/entry.ts
CHANGED
|
@@ -435,6 +435,10 @@ blume-diff {
|
|
|
435
435
|
font-size: 0.8125rem;
|
|
436
436
|
}
|
|
437
437
|
|
|
438
|
+
.prose :where(td, th) :not(pre) > code {
|
|
439
|
+
white-space: nowrap;
|
|
440
|
+
}
|
|
441
|
+
|
|
438
442
|
blume-tabs pre,
|
|
439
443
|
.not-prose > div > pre {
|
|
440
444
|
background: var(--blume-code-background);
|
|
@@ -610,9 +614,9 @@ pre:has(.line.focused):hover .line:not(.focused) {
|
|
|
610
614
|
content: none;
|
|
611
615
|
}
|
|
612
616
|
|
|
613
|
-
/* Inline code highlighting
|
|
614
|
-
|
|
615
|
-
|
|
617
|
+
/* Inline code highlighting: Shiki colors the tokens of a \`code\`{:lang} snippet
|
|
618
|
+
via the same dual-theme CSS variables as fenced blocks, keeping the inline
|
|
619
|
+
pill background. Always on — it only fires on the trailing {:lang} marker. */
|
|
616
620
|
.prose code.blume-inline-code span {
|
|
617
621
|
color: var(--shiki-light);
|
|
618
622
|
}
|