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.
- package/bin/blume.mjs +18 -0
- package/dist/cli/index.js +11989 -0
- package/dist/cli/index.js.map +141 -0
- package/docs/01-quickstart.mdx +99 -0
- package/docs/02-deployment.mdx +129 -0
- package/docs/advanced/api-reference.mdx +114 -0
- package/docs/advanced/blog.mdx +121 -0
- package/docs/advanced/changelog.mdx +113 -0
- package/docs/advanced/custom-pages.mdx +187 -0
- package/docs/advanced/meta.ts +7 -0
- package/docs/changelog/v0-1-0.mdx +12 -0
- package/docs/changelog/v0-2-0.mdx +16 -0
- package/docs/configuration/ai.mdx +228 -0
- package/docs/configuration/analytics.mdx +98 -0
- package/docs/configuration/customization.mdx +91 -0
- package/docs/configuration/export.mdx +70 -0
- package/docs/configuration/index.mdx +271 -0
- package/docs/configuration/meta.ts +15 -0
- package/docs/configuration/search.mdx +172 -0
- package/docs/configuration/seo.mdx +196 -0
- package/docs/configuration/theming.mdx +178 -0
- package/docs/content/components.mdx +565 -0
- package/docs/content/i18n.mdx +205 -0
- package/docs/content/index.mdx +161 -0
- package/docs/content/islands.mdx +94 -0
- package/docs/content/meta.mdx +119 -0
- package/docs/content/meta.ts +15 -0
- package/docs/content/navigation.mdx +168 -0
- package/docs/content/sources.mdx +216 -0
- package/docs/content/syntax.mdx +445 -0
- package/docs/index.mdx +112 -0
- package/docs/reference/cli.mdx +43 -0
- package/docs/reference/frontmatter.mdx +74 -0
- package/docs/reference/meta.ts +7 -0
- package/package.json +140 -6
- package/src/ai/ask.ts +93 -0
- package/src/ai/llms.ts +65 -0
- package/src/ai/markdown.ts +31 -0
- package/src/ai/mcp/data.ts +74 -0
- package/src/ai/mcp/discovery.ts +49 -0
- package/src/ai/mcp/server.ts +225 -0
- package/src/ai/mcp/tools.ts +47 -0
- package/src/assets/icon.png +0 -0
- package/src/astro/generate.ts +878 -0
- package/src/astro/index.ts +4 -0
- package/src/astro/integration.ts +74 -0
- package/src/astro/islands.ts +131 -0
- package/src/astro/markdown-negotiation.ts +68 -0
- package/src/astro/pages.ts +28 -0
- package/src/astro/templates.ts +1199 -0
- package/src/cli/commands/add.ts +81 -0
- package/src/cli/commands/build.ts +103 -0
- package/src/cli/commands/dev.ts +108 -0
- package/src/cli/commands/doctor.ts +74 -0
- package/src/cli/commands/eject.ts +57 -0
- package/src/cli/commands/init.ts +98 -0
- package/src/cli/commands/migrate.ts +39 -0
- package/src/cli/commands/preview.ts +39 -0
- package/src/cli/commands/sync.ts +52 -0
- package/src/cli/commands/validate.ts +60 -0
- package/src/cli/index.ts +35 -0
- package/src/cli/log.ts +37 -0
- package/src/cli/prepare.ts +80 -0
- package/src/components/Icon.astro +99 -0
- package/src/components/content/Accordion.astro +8 -0
- package/src/components/content/AccordionItem.astro +121 -0
- package/src/components/content/AutoTypeTable.astro +51 -0
- package/src/components/content/Badge.astro +124 -0
- package/src/components/content/Callout.astro +73 -0
- package/src/components/content/Card.astro +104 -0
- package/src/components/content/CardGroup.astro +14 -0
- package/src/components/content/CodeGroup.astro +13 -0
- package/src/components/content/Color.astro +15 -0
- package/src/components/content/ColorItem.astro +87 -0
- package/src/components/content/ColorRow.astro +10 -0
- package/src/components/content/Column.astro +6 -0
- package/src/components/content/Columns.astro +9 -0
- package/src/components/content/Expandable.astro +11 -0
- package/src/components/content/FileTree.astro +8 -0
- package/src/components/content/Frame.astro +70 -0
- package/src/components/content/GithubInfo.astro +110 -0
- package/src/components/content/Math.astro +24 -0
- package/src/components/content/Panel.astro +20 -0
- package/src/components/content/Prompt.astro +129 -0
- package/src/components/content/Step.astro +34 -0
- package/src/components/content/Steps.astro +20 -0
- package/src/components/content/Tab.astro +40 -0
- package/src/components/content/Tabs.astro +273 -0
- package/src/components/content/Tile.astro +42 -0
- package/src/components/content/Tooltip.astro +68 -0
- package/src/components/content/Tree.astro +300 -0
- package/src/components/content/TreeFile.astro +15 -0
- package/src/components/content/TreeFolder.astro +62 -0
- package/src/components/content/TypeTable.astro +106 -0
- package/src/components/content/Update.astro +66 -0
- package/src/components/content/Visibility.astro +12 -0
- package/src/components/content/Warning.astro +9 -0
- package/src/components/content/auto-type-table.ts +141 -0
- package/src/components/content/github-info.ts +79 -0
- package/src/components/content/mermaid-element.ts +68 -0
- package/src/components/github-mark.ts +9 -0
- package/src/components/index.ts +14 -0
- package/src/components/islands/AskAI.astro +12 -0
- package/src/components/islands/ask-ai.tsx +156 -0
- package/src/components/layout/Analytics.astro +63 -0
- package/src/components/layout/Banner.astro +50 -0
- package/src/components/layout/Breadcrumbs.astro +31 -0
- package/src/components/layout/Favicon.astro +15 -0
- package/src/components/layout/Fonts.astro +14 -0
- package/src/components/layout/Header.astro +188 -0
- package/src/components/layout/LanguageSwitcher.astro +56 -0
- package/src/components/layout/NavTree.astro +462 -0
- package/src/components/layout/PageActions.astro +438 -0
- package/src/components/layout/PageFeedback.astro +58 -0
- package/src/components/layout/Pagination.astro +56 -0
- package/src/components/layout/ReferenceLayout.astro +102 -0
- package/src/components/layout/RootLayout.astro +533 -0
- package/src/components/layout/Search.astro +608 -0
- package/src/components/layout/TableOfContents.astro +68 -0
- package/src/components/layout/analytics-client.ts +38 -0
- package/src/components/layout/nav-utils.ts +87 -0
- package/src/components/layout/overrides.ts +32 -0
- package/src/components/layout/search/algolia.ts +43 -0
- package/src/components/layout/search/endpoint.ts +22 -0
- package/src/components/layout/search/flexsearch.ts +52 -0
- package/src/components/layout/search/orama-cloud.ts +41 -0
- package/src/components/layout/search/orama.ts +26 -0
- package/src/components/layout/search/pagefind.ts +43 -0
- package/src/components/layout/search/types.ts +163 -0
- package/src/components/layout/search/typesense.ts +60 -0
- package/src/components/layout/toc-element.ts +108 -0
- package/src/core/bridge.ts +92 -0
- package/src/core/config.ts +112 -0
- package/src/core/content.ts +50 -0
- package/src/core/define-components.ts +34 -0
- package/src/core/define-meta.ts +20 -0
- package/src/core/deployment-env.ts +73 -0
- package/src/core/diagnostics.ts +104 -0
- package/src/core/graph.ts +128 -0
- package/src/core/i18n-ui.ts +171 -0
- package/src/core/i18n.ts +169 -0
- package/src/core/last-modified.ts +88 -0
- package/src/core/links.ts +336 -0
- package/src/core/load-module.ts +15 -0
- package/src/core/manifest.ts +126 -0
- package/src/core/meta.ts +97 -0
- package/src/core/navigation.ts +392 -0
- package/src/core/package-root.ts +37 -0
- package/src/core/project-graph.ts +153 -0
- package/src/core/project.ts +56 -0
- package/src/core/schema.ts +1057 -0
- package/src/core/server-features.ts +23 -0
- package/src/core/sources/assets.ts +77 -0
- package/src/core/sources/cache.ts +122 -0
- package/src/core/sources/filesystem.ts +99 -0
- package/src/core/sources/mdx-remote.ts +216 -0
- package/src/core/sources/mintlify.ts +161 -0
- package/src/core/sources/normalize.ts +227 -0
- package/src/core/sources/notion.ts +440 -0
- package/src/core/sources/portable-text.ts +143 -0
- package/src/core/sources/read.ts +36 -0
- package/src/core/sources/resolve.ts +158 -0
- package/src/core/sources/sanity.ts +218 -0
- package/src/core/sources/types.ts +105 -0
- package/src/core/types.ts +261 -0
- package/src/core/ui-packs/ar.ts +47 -0
- package/src/core/ui-packs/bg.ts +47 -0
- package/src/core/ui-packs/bn.ts +47 -0
- package/src/core/ui-packs/ca.ts +47 -0
- package/src/core/ui-packs/cs.ts +47 -0
- package/src/core/ui-packs/da.ts +47 -0
- package/src/core/ui-packs/de.ts +47 -0
- package/src/core/ui-packs/el.ts +47 -0
- package/src/core/ui-packs/es.ts +47 -0
- package/src/core/ui-packs/fa.ts +47 -0
- package/src/core/ui-packs/fi.ts +47 -0
- package/src/core/ui-packs/fr.ts +47 -0
- package/src/core/ui-packs/he.ts +47 -0
- package/src/core/ui-packs/hi.ts +47 -0
- package/src/core/ui-packs/hr.ts +47 -0
- package/src/core/ui-packs/hu.ts +47 -0
- package/src/core/ui-packs/id.ts +47 -0
- package/src/core/ui-packs/index.ts +87 -0
- package/src/core/ui-packs/it.ts +47 -0
- package/src/core/ui-packs/ja.ts +47 -0
- package/src/core/ui-packs/ko.ts +47 -0
- package/src/core/ui-packs/nl.ts +47 -0
- package/src/core/ui-packs/no.ts +47 -0
- package/src/core/ui-packs/pl.ts +47 -0
- package/src/core/ui-packs/pt-br.ts +47 -0
- package/src/core/ui-packs/pt.ts +47 -0
- package/src/core/ui-packs/ro.ts +47 -0
- package/src/core/ui-packs/ru.ts +47 -0
- package/src/core/ui-packs/sk.ts +47 -0
- package/src/core/ui-packs/sr.ts +47 -0
- package/src/core/ui-packs/sv.ts +47 -0
- package/src/core/ui-packs/th.ts +47 -0
- package/src/core/ui-packs/tr.ts +47 -0
- package/src/core/ui-packs/uk.ts +47 -0
- package/src/core/ui-packs/vi.ts +47 -0
- package/src/core/ui-packs/zh-tw.ts +47 -0
- package/src/core/ui-packs/zh.ts +47 -0
- package/src/core/version.ts +23 -0
- package/src/deploy/robots.ts +20 -0
- package/src/deploy/rss.ts +128 -0
- package/src/deploy/sitemap.ts +28 -0
- package/src/index.ts +27 -0
- package/src/markdown/code-title.ts +71 -0
- package/src/markdown/directives.ts +83 -0
- package/src/markdown/heading-anchors.ts +137 -0
- package/src/markdown/index.ts +159 -0
- package/src/markdown/inline-code.ts +108 -0
- package/src/markdown/language-icon.ts +172 -0
- package/src/markdown/math.ts +32 -0
- package/src/markdown/mdast.ts +48 -0
- package/src/markdown/mermaid.ts +37 -0
- package/src/markdown/package-commands.ts +159 -0
- package/src/markdown/package-install.ts +40 -0
- package/src/migrate/fumadocs/config.ts +106 -0
- package/src/migrate/fumadocs/content.ts +365 -0
- package/src/migrate/fumadocs/frontmatter.ts +18 -0
- package/src/migrate/fumadocs/index.ts +252 -0
- package/src/migrate/fumadocs/meta.ts +114 -0
- package/src/migrate/migrate.ts +53 -0
- package/src/migrate/mintlify/config.ts +1040 -0
- package/src/migrate/mintlify/content.ts +98 -0
- package/src/migrate/mintlify/frontmatter.ts +126 -0
- package/src/migrate/mintlify/i18n.ts +51 -0
- package/src/migrate/mintlify/icons.ts +128 -0
- package/src/migrate/mintlify/index.ts +266 -0
- package/src/migrate/mintlify/snippets.ts +305 -0
- package/src/migrate/mintlify/transform.ts +81 -0
- package/src/migrate/nextra/content.ts +46 -0
- package/src/migrate/nextra/frontmatter.ts +40 -0
- package/src/migrate/nextra/index.ts +374 -0
- package/src/migrate/nextra/meta.ts +266 -0
- package/src/migrate/shared.ts +623 -0
- package/src/migrate/starlight/config.ts +459 -0
- package/src/migrate/starlight/content.ts +78 -0
- package/src/migrate/starlight/frontmatter.ts +111 -0
- package/src/migrate/starlight/i18n.ts +54 -0
- package/src/migrate/starlight/index.ts +131 -0
- package/src/og/card.ts +92 -0
- package/src/og/index.ts +2 -0
- package/src/openapi/scalar.ts +246 -0
- package/src/registry/eject.ts +263 -0
- package/src/registry/registry.ts +100 -0
- package/src/registry/rewrite-imports.ts +39 -0
- package/src/runtime/index.ts +14 -0
- package/src/search/build.ts +23 -0
- package/src/search/documents.ts +165 -0
- package/src/search/orama-index.ts +66 -0
- package/src/search/providers.ts +91 -0
- package/src/search/sync/algolia.ts +30 -0
- package/src/search/sync/index.ts +50 -0
- package/src/search/sync/orama-cloud.ts +40 -0
- package/src/search/sync/typesense.ts +65 -0
- package/src/seo/jsonld.ts +113 -0
- package/src/theme/entry.ts +608 -0
- package/src/theme/fonts.ts +198 -0
- package/src/theme/icons.ts +184 -0
- package/src/theme/palette.ts +143 -0
- package/src/theme/twoslash.ts +81 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { satteri } from "@astrojs/markdown-satteri";
|
|
2
|
+
import {
|
|
3
|
+
transformerMetaHighlight,
|
|
4
|
+
transformerNotationDiff,
|
|
5
|
+
transformerNotationFocus,
|
|
6
|
+
transformerNotationHighlight,
|
|
7
|
+
transformerNotationWordHighlight,
|
|
8
|
+
} from "@shikijs/transformers";
|
|
9
|
+
|
|
10
|
+
import { codeTitleTransformer } from "./code-title.ts";
|
|
11
|
+
import { directiveToCalloutPlugin } from "./directives.ts";
|
|
12
|
+
import { headingAnchorPlugin } from "./heading-anchors.ts";
|
|
13
|
+
import { inlineCodeHighlightPlugin } from "./inline-code.ts";
|
|
14
|
+
import { languageIconTransformer } from "./language-icon.ts";
|
|
15
|
+
import { mathPlugin } from "./math.ts";
|
|
16
|
+
import { mermaidPlugin } from "./mermaid.ts";
|
|
17
|
+
import { packageInstallPlugin } from "./package-install.ts";
|
|
18
|
+
|
|
19
|
+
/** A Shiki transformer, derived from the upstream factories' return type. */
|
|
20
|
+
type ShikiTransformer = ReturnType<typeof transformerNotationDiff>;
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
PACKAGE_MANAGERS,
|
|
24
|
+
type PackageManager,
|
|
25
|
+
toPackageCommands,
|
|
26
|
+
} from "./package-commands.ts";
|
|
27
|
+
export {
|
|
28
|
+
type CodeTitleTransformer,
|
|
29
|
+
codeTitleTransformer,
|
|
30
|
+
} from "./code-title.ts";
|
|
31
|
+
export { calloutTypeFor } from "./directives.ts";
|
|
32
|
+
export { headingAnchorPlugin } from "./heading-anchors.ts";
|
|
33
|
+
export { mermaidPlugin } from "./mermaid.ts";
|
|
34
|
+
export { packageInstallPlugin } from "./package-install.ts";
|
|
35
|
+
|
|
36
|
+
/** Element type of Satteri's `mdastPlugins`, sourced from the (alpha) core. */
|
|
37
|
+
type MdastPlugin = NonNullable<
|
|
38
|
+
NonNullable<Parameters<typeof satteri>[0]>["mdastPlugins"]
|
|
39
|
+
>[number];
|
|
40
|
+
|
|
41
|
+
/** Element type of Satteri's `hastPlugins`. */
|
|
42
|
+
type HastPlugin = NonNullable<
|
|
43
|
+
NonNullable<Parameters<typeof satteri>[0]>["hastPlugins"]
|
|
44
|
+
>[number];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Hast plugins enabled by config. Inline `` `code`{:lang} `` highlighting is
|
|
48
|
+
* opt-in; self-linking heading anchors (`<h2>`–`<h6>` wrapped in an `<a>` to
|
|
49
|
+
* their own id) are on unless `markdown.headingAnchors` is `false`. Inline code
|
|
50
|
+
* runs first so the anchor wrap re-refs already-highlighted code.
|
|
51
|
+
*/
|
|
52
|
+
const blumeHastPlugins = (options: BlumeMarkdownOptions): HastPlugin[] => {
|
|
53
|
+
const plugins: HastPlugin[] = [];
|
|
54
|
+
if (options.inline) {
|
|
55
|
+
plugins.push(inlineCodeHighlightPlugin() as unknown as HastPlugin);
|
|
56
|
+
}
|
|
57
|
+
if (options.headingAnchors !== false) {
|
|
58
|
+
plugins.push(headingAnchorPlugin() as unknown as HastPlugin);
|
|
59
|
+
}
|
|
60
|
+
return plugins;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Shiki transformers enabled by default for every code block. The four upstream
|
|
65
|
+
* notation transformers read GitHub-style comments and strip them from the
|
|
66
|
+
* output: `// [!code highlight]`, `// [!code ++]` / `// [!code --]`,
|
|
67
|
+
* `// [!code word:x]`, and `// [!code focus]`. The v3 match algorithm scopes a
|
|
68
|
+
* notation to the line it sits on (or the next, for a trailing comment).
|
|
69
|
+
* `transformerMetaHighlight` adds numeric range highlighting from the fence meta
|
|
70
|
+
* (` ```ts {1,3-5} `), reusing the same `highlighted` class. Blume's own
|
|
71
|
+
* {@link languageIconTransformer} prepends a brand icon, and
|
|
72
|
+
* {@link codeTitleTransformer} runs last to promote fence-meta (title / line
|
|
73
|
+
* numbers) to `<pre>` attributes. The theme styles the classes these emit
|
|
74
|
+
* (`highlighted`, `diff add/remove`, `highlighted-word`, `focused`,
|
|
75
|
+
* `blume-lang-icon`).
|
|
76
|
+
*/
|
|
77
|
+
export interface BlumeShikiOptions {
|
|
78
|
+
/** Prepend a brand language icon to the header (`markdown.code.icons`). */
|
|
79
|
+
icons?: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const blumeShikiTransformers = (
|
|
83
|
+
options: BlumeShikiOptions = {}
|
|
84
|
+
): ShikiTransformer[] => {
|
|
85
|
+
const transformers: ShikiTransformer[] = [
|
|
86
|
+
transformerNotationHighlight({ matchAlgorithm: "v3" }),
|
|
87
|
+
transformerNotationDiff({ matchAlgorithm: "v3" }),
|
|
88
|
+
transformerNotationWordHighlight({ matchAlgorithm: "v3" }),
|
|
89
|
+
transformerNotationFocus({ matchAlgorithm: "v3" }),
|
|
90
|
+
transformerMetaHighlight(),
|
|
91
|
+
];
|
|
92
|
+
if (options.icons !== false) {
|
|
93
|
+
transformers.push(languageIconTransformer() as unknown as ShikiTransformer);
|
|
94
|
+
}
|
|
95
|
+
// The fence-meta reader (title / line numbers) always runs last.
|
|
96
|
+
transformers.push(codeTitleTransformer() as unknown as ShikiTransformer);
|
|
97
|
+
return transformers;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Sätteri Markdown features Blume enables beyond Astro's defaults. GFM,
|
|
102
|
+
* frontmatter, and smart punctuation are already on; this adds superscript
|
|
103
|
+
* (`^text^`) and subscript (`~text~`), which render to native `<sup>`/`<sub>`.
|
|
104
|
+
*/
|
|
105
|
+
const FEATURES = { subscript: true, superscript: true };
|
|
106
|
+
|
|
107
|
+
/** Options shared by both processors. */
|
|
108
|
+
export interface BlumeMarkdownOptions {
|
|
109
|
+
/**
|
|
110
|
+
* Wrap `<h2>`–`<h6>` in self-linking anchors (`markdown.headingAnchors`).
|
|
111
|
+
* On unless explicitly `false`.
|
|
112
|
+
*/
|
|
113
|
+
headingAnchors?: boolean;
|
|
114
|
+
/** Highlight inline `` `code`{:lang} `` snippets (`markdown.code.inline`). */
|
|
115
|
+
inline?: boolean;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Sätteri processor for plain `.md`, with Blume's curated feature set. */
|
|
119
|
+
export const blumeMarkdownProcessor = (options: BlumeMarkdownOptions = {}) =>
|
|
120
|
+
satteri({
|
|
121
|
+
features: { ...FEATURES },
|
|
122
|
+
hastPlugins: blumeHastPlugins(options),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
export interface BlumeMdxOptions extends BlumeMarkdownOptions {
|
|
126
|
+
/** Enable KaTeX math parsing and rendering. */
|
|
127
|
+
math?: boolean;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Sätteri MDX processor: Blume's feature set plus the MDAST plugins that target
|
|
132
|
+
* components — `package-install` → package-manager tabs, `:::note` →
|
|
133
|
+
* `<Callout>`, and ` ```mermaid ` → a `<blume-mermaid>` element. Used as the
|
|
134
|
+
* `processor` for `@astrojs/mdx` so these apply to
|
|
135
|
+
* `.mdx` only (plain `.md` uses {@link blumeMarkdownProcessor}). Math is opt-in
|
|
136
|
+
* via config since `$` is common in prose and code.
|
|
137
|
+
*
|
|
138
|
+
* The plugins are modeled with minimal structural types; bridge them to
|
|
139
|
+
* Satteri's full `MdastPlugin` type at this single boundary.
|
|
140
|
+
*/
|
|
141
|
+
export const blumeMdxProcessor = (options: BlumeMdxOptions = {}) => {
|
|
142
|
+
const plugins: unknown[] = [
|
|
143
|
+
packageInstallPlugin(),
|
|
144
|
+
directiveToCalloutPlugin(),
|
|
145
|
+
mermaidPlugin(),
|
|
146
|
+
];
|
|
147
|
+
if (options.math) {
|
|
148
|
+
plugins.push(mathPlugin());
|
|
149
|
+
}
|
|
150
|
+
return satteri({
|
|
151
|
+
features: {
|
|
152
|
+
...FEATURES,
|
|
153
|
+
directive: true,
|
|
154
|
+
...(options.math ? { math: true } : {}),
|
|
155
|
+
},
|
|
156
|
+
hastPlugins: blumeHastPlugins(options),
|
|
157
|
+
mdastPlugins: plugins as unknown as MdastPlugin[],
|
|
158
|
+
});
|
|
159
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline syntax highlighting for `` `code{:lang}` ``. A Satteri hast plugin runs
|
|
3
|
+
* after Markdown is turned into hast and looks at each inline `<code>` for a
|
|
4
|
+
* trailing `{:lang}` marker. The marker sits *inside* the backticks (Shiki's
|
|
5
|
+
* "tailing-curly-colon" convention) so it survives MDX, where a `{…}` after a
|
|
6
|
+
* code span would be parsed as a JSX expression. When found, it strips the
|
|
7
|
+
* marker and replaces the code's text with Shiki tokens (dual github-light/dark
|
|
8
|
+
* via CSS variables, like fenced blocks).
|
|
9
|
+
*
|
|
10
|
+
* Shiki is imported lazily, so it loads only on pages that actually use inline
|
|
11
|
+
* highlighting — and never when `markdown.code.inline` is off (the plugin is not
|
|
12
|
+
* added to the pipeline at all).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** A minimal hast node (avoids a hast type dependency). */
|
|
16
|
+
interface HastNode {
|
|
17
|
+
children?: HastNode[];
|
|
18
|
+
properties?: Record<string, unknown>;
|
|
19
|
+
tagName?: string;
|
|
20
|
+
type: string;
|
|
21
|
+
value?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The slice of Satteri's hast visitor context this plugin reads. */
|
|
25
|
+
interface HastContext {
|
|
26
|
+
parent: (node: HastNode) => { tagName?: string } | undefined;
|
|
27
|
+
textContent: (node: HastNode) => string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** A Satteri hast plugin, typed structurally to avoid a Satteri dep. */
|
|
31
|
+
export interface InlineCodePlugin {
|
|
32
|
+
name: string;
|
|
33
|
+
element: {
|
|
34
|
+
filter: string[];
|
|
35
|
+
visit: (node: HastNode, ctx: HastContext) => Promise<HastNode | undefined>;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Parsed `{:lang}` marker and the code with it removed. */
|
|
40
|
+
export interface InlineLang {
|
|
41
|
+
code: string;
|
|
42
|
+
lang: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const INLINE_LANG = /\{:(?<lang>[\w+-]+)\}$/u;
|
|
46
|
+
|
|
47
|
+
/** Parse a trailing `{:lang}` marker from an inline `<code>`'s own text. */
|
|
48
|
+
export const parseInlineLang = (text: string): InlineLang | null => {
|
|
49
|
+
const match = text.match(INLINE_LANG);
|
|
50
|
+
if (!match?.groups?.lang || match.index === undefined) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const code = text.slice(0, match.index);
|
|
54
|
+
return code ? { code, lang: match.groups.lang } : null;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Loosely-typed Shiki entry: just the inline-highlight call this plugin makes. */
|
|
58
|
+
type InlineHighlighter = (
|
|
59
|
+
code: string,
|
|
60
|
+
options: {
|
|
61
|
+
defaultColor: false;
|
|
62
|
+
lang: string;
|
|
63
|
+
structure: "inline";
|
|
64
|
+
themes: { dark: string; light: string };
|
|
65
|
+
}
|
|
66
|
+
) => Promise<{ children: HastNode[] }>;
|
|
67
|
+
|
|
68
|
+
// `import()` caches the module, so this dedupes Shiki across calls on its own.
|
|
69
|
+
const loadHighlighter = async (): Promise<InlineHighlighter> => {
|
|
70
|
+
const mod = await import("shiki");
|
|
71
|
+
return mod.codeToHast as unknown as InlineHighlighter;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/** Build the plugin. Highlights inline `` `code{:lang}` `` snippets. */
|
|
75
|
+
export const inlineCodeHighlightPlugin = (): InlineCodePlugin => ({
|
|
76
|
+
element: {
|
|
77
|
+
filter: ["code"],
|
|
78
|
+
async visit(node, ctx) {
|
|
79
|
+
// Only inline code: a <code> inside <pre> is a fenced block — skip it.
|
|
80
|
+
if (ctx.parent(node)?.tagName === "pre") {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const parsed = parseInlineLang(ctx.textContent(node));
|
|
84
|
+
if (!parsed) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const codeToHast = await loadHighlighter();
|
|
89
|
+
const root = await codeToHast(parsed.code, {
|
|
90
|
+
defaultColor: false,
|
|
91
|
+
lang: parsed.lang,
|
|
92
|
+
structure: "inline",
|
|
93
|
+
themes: { dark: "github-dark", light: "github-light" },
|
|
94
|
+
});
|
|
95
|
+
return {
|
|
96
|
+
children: root.children,
|
|
97
|
+
properties: { className: ["blume-inline-code"] },
|
|
98
|
+
tagName: "code",
|
|
99
|
+
type: "element",
|
|
100
|
+
};
|
|
101
|
+
} catch {
|
|
102
|
+
// Unknown language or load failure: leave the code unhighlighted
|
|
103
|
+
// (falling through returns undefined, so the node is left as-is).
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
name: "blume:inline-code",
|
|
108
|
+
});
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Shiki transformer that prepends a brand icon to a code block's header,
|
|
3
|
+
* keyed off the fence language. Icons are sourced from `simple-icons` (raw SVG
|
|
4
|
+
* path data) at build time and emitted as an inline `<svg>` — no client JS and
|
|
5
|
+
* no React, so it works in the core theme. The icon renders in `currentColor`
|
|
6
|
+
* (the muted header color) so it stays legible in both light and dark; brand
|
|
7
|
+
* hex colors are skipped because dark-on-dark logos (Next.js, Rust…) vanish.
|
|
8
|
+
*
|
|
9
|
+
* The theme styles `.blume-lang-icon` and shifts the language label
|
|
10
|
+
* (`pre[data-icon]::before`) to make room.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
siAstro,
|
|
15
|
+
siC,
|
|
16
|
+
siCplusplus,
|
|
17
|
+
siCss3,
|
|
18
|
+
siDart,
|
|
19
|
+
siDocker,
|
|
20
|
+
siGnubash,
|
|
21
|
+
siGo,
|
|
22
|
+
siGraphql,
|
|
23
|
+
siHtml5,
|
|
24
|
+
siJavascript,
|
|
25
|
+
siJson,
|
|
26
|
+
siKotlin,
|
|
27
|
+
siLess,
|
|
28
|
+
siLua,
|
|
29
|
+
siMarkdown,
|
|
30
|
+
siMdx,
|
|
31
|
+
siMysql,
|
|
32
|
+
siNextdotjs,
|
|
33
|
+
siPhp,
|
|
34
|
+
siPrisma,
|
|
35
|
+
siPython,
|
|
36
|
+
siReact,
|
|
37
|
+
siRuby,
|
|
38
|
+
siRust,
|
|
39
|
+
siSass,
|
|
40
|
+
siScala,
|
|
41
|
+
siSvelte,
|
|
42
|
+
siSvg,
|
|
43
|
+
siSwift,
|
|
44
|
+
siToml,
|
|
45
|
+
siTypescript,
|
|
46
|
+
siVuedotjs,
|
|
47
|
+
siWebassembly,
|
|
48
|
+
siYaml,
|
|
49
|
+
} from "simple-icons";
|
|
50
|
+
|
|
51
|
+
/** The slice of a `simple-icons` icon Blume reads (the SVG path data). */
|
|
52
|
+
interface SimpleIcon {
|
|
53
|
+
path: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Fence language (and common aliases) → icon. Unmapped languages get none. */
|
|
57
|
+
const LANGUAGE_ICONS: Record<string, SimpleIcon> = {
|
|
58
|
+
astro: siAstro,
|
|
59
|
+
bash: siGnubash,
|
|
60
|
+
c: siC,
|
|
61
|
+
"c++": siCplusplus,
|
|
62
|
+
cjs: siJavascript,
|
|
63
|
+
cpp: siCplusplus,
|
|
64
|
+
css: siCss3,
|
|
65
|
+
cts: siTypescript,
|
|
66
|
+
dart: siDart,
|
|
67
|
+
docker: siDocker,
|
|
68
|
+
dockerfile: siDocker,
|
|
69
|
+
go: siGo,
|
|
70
|
+
gql: siGraphql,
|
|
71
|
+
graphql: siGraphql,
|
|
72
|
+
html: siHtml5,
|
|
73
|
+
javascript: siJavascript,
|
|
74
|
+
js: siJavascript,
|
|
75
|
+
json: siJson,
|
|
76
|
+
json5: siJson,
|
|
77
|
+
jsonc: siJson,
|
|
78
|
+
jsx: siReact,
|
|
79
|
+
kotlin: siKotlin,
|
|
80
|
+
kt: siKotlin,
|
|
81
|
+
less: siLess,
|
|
82
|
+
lua: siLua,
|
|
83
|
+
markdown: siMarkdown,
|
|
84
|
+
md: siMarkdown,
|
|
85
|
+
mdx: siMdx,
|
|
86
|
+
mjs: siJavascript,
|
|
87
|
+
mts: siTypescript,
|
|
88
|
+
nextjs: siNextdotjs,
|
|
89
|
+
php: siPhp,
|
|
90
|
+
prisma: siPrisma,
|
|
91
|
+
py: siPython,
|
|
92
|
+
python: siPython,
|
|
93
|
+
rb: siRuby,
|
|
94
|
+
react: siReact,
|
|
95
|
+
rs: siRust,
|
|
96
|
+
ruby: siRuby,
|
|
97
|
+
rust: siRust,
|
|
98
|
+
sass: siSass,
|
|
99
|
+
scala: siScala,
|
|
100
|
+
scss: siSass,
|
|
101
|
+
sh: siGnubash,
|
|
102
|
+
shell: siGnubash,
|
|
103
|
+
sql: siMysql,
|
|
104
|
+
svelte: siSvelte,
|
|
105
|
+
svg: siSvg,
|
|
106
|
+
swift: siSwift,
|
|
107
|
+
toml: siToml,
|
|
108
|
+
ts: siTypescript,
|
|
109
|
+
tsx: siReact,
|
|
110
|
+
typescript: siTypescript,
|
|
111
|
+
vue: siVuedotjs,
|
|
112
|
+
wasm: siWebassembly,
|
|
113
|
+
yaml: siYaml,
|
|
114
|
+
yml: siYaml,
|
|
115
|
+
zsh: siGnubash,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/** A minimal hast node (avoids a hast type dependency). */
|
|
119
|
+
interface HastNode {
|
|
120
|
+
children?: HastNode[];
|
|
121
|
+
properties?: Record<string, unknown>;
|
|
122
|
+
tagName?: string;
|
|
123
|
+
type: string;
|
|
124
|
+
value?: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** The slice of Shiki's transformer `this` context the icon hook reads. */
|
|
128
|
+
interface IconContext {
|
|
129
|
+
options: { lang?: string };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** The `<pre>` hast node a Shiki `pre` hook receives. */
|
|
133
|
+
interface IconPreNode {
|
|
134
|
+
children: HastNode[];
|
|
135
|
+
properties: Record<string, boolean | number | string | undefined>;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** A Shiki-compatible transformer, typed structurally to avoid a Shiki dep. */
|
|
139
|
+
export interface LanguageIconTransformer {
|
|
140
|
+
name: string;
|
|
141
|
+
pre: (this: IconContext, node: IconPreNode) => void;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Build an inline SVG hast node from a simple-icons path. */
|
|
145
|
+
const iconNode = (path: string): HastNode => ({
|
|
146
|
+
children: [
|
|
147
|
+
{ children: [], properties: { d: path }, tagName: "path", type: "element" },
|
|
148
|
+
],
|
|
149
|
+
properties: {
|
|
150
|
+
ariaHidden: "true",
|
|
151
|
+
className: ["blume-lang-icon"],
|
|
152
|
+
fill: "currentColor",
|
|
153
|
+
height: 14,
|
|
154
|
+
viewBox: "0 0 24 24",
|
|
155
|
+
width: 14,
|
|
156
|
+
},
|
|
157
|
+
tagName: "svg",
|
|
158
|
+
type: "element",
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
/** Build the transformer. Runs after Shiki's built-in `data-language` hook. */
|
|
162
|
+
export const languageIconTransformer = (): LanguageIconTransformer => ({
|
|
163
|
+
name: "blume:language-icon",
|
|
164
|
+
pre(node) {
|
|
165
|
+
const icon = LANGUAGE_ICONS[(this.options.lang ?? "").toLowerCase()];
|
|
166
|
+
if (!icon) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
node.children.unshift(iconNode(icon.path));
|
|
170
|
+
node.properties.dataIcon = "";
|
|
171
|
+
},
|
|
172
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsxAttribute, jsxFlowElement, jsxTextElement } from "./mdast.ts";
|
|
2
|
+
import type { MdastNode, MdastVisitorContext } from "./mdast.ts";
|
|
3
|
+
|
|
4
|
+
interface MathNode extends MdastNode {
|
|
5
|
+
value: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Satteri MDAST plugin that turns math nodes into Blume's `<Math>` component,
|
|
10
|
+
* which renders them with KaTeX at build time. Block math (`$$…$$`) becomes a
|
|
11
|
+
* block element; inline math (`$…$`) stays inline. Only active when math is
|
|
12
|
+
* enabled in config, so `$` is otherwise left as literal text.
|
|
13
|
+
*/
|
|
14
|
+
export const mathPlugin = () => ({
|
|
15
|
+
inlineMath(node: MathNode, ctx: MdastVisitorContext) {
|
|
16
|
+
ctx.replaceNode(
|
|
17
|
+
node,
|
|
18
|
+
jsxTextElement("Math", [jsxAttribute("code", node.value)])
|
|
19
|
+
);
|
|
20
|
+
},
|
|
21
|
+
math(node: MathNode, ctx: MdastVisitorContext) {
|
|
22
|
+
ctx.replaceNode(
|
|
23
|
+
node,
|
|
24
|
+
jsxFlowElement(
|
|
25
|
+
"Math",
|
|
26
|
+
[jsxAttribute("code", node.value), jsxAttribute("display")],
|
|
27
|
+
[]
|
|
28
|
+
)
|
|
29
|
+
);
|
|
30
|
+
},
|
|
31
|
+
name: "blume-math",
|
|
32
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal structural types and node builders for the (alpha) Satteri MDAST
|
|
3
|
+
* plugin API. We model only what Blume's plugins read and construct; the full
|
|
4
|
+
* types live in `satteri`, a transitive dependency. Plugins are bridged to
|
|
5
|
+
* Satteri's real `MdastPlugin` type at a single boundary in `index.ts`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** The visitor context Blume's plugins use to mutate the tree. */
|
|
9
|
+
export interface MdastVisitorContext {
|
|
10
|
+
replaceNode: (node: unknown, replacement: unknown) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Any MDAST node, keyed loosely since we build a small subset by hand. */
|
|
14
|
+
export interface MdastNode {
|
|
15
|
+
type: string;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Build an MDX JSX attribute. A `null` value renders as a boolean attribute. */
|
|
20
|
+
export const jsxAttribute = (name: string, value: string | null = null) => ({
|
|
21
|
+
name,
|
|
22
|
+
type: "mdxJsxAttribute",
|
|
23
|
+
value,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
type JsxAttribute = ReturnType<typeof jsxAttribute>;
|
|
27
|
+
|
|
28
|
+
/** Build a block-level MDX JSX element (`<Name>…</Name>`). */
|
|
29
|
+
export const jsxFlowElement = (
|
|
30
|
+
name: string,
|
|
31
|
+
attributes: JsxAttribute[],
|
|
32
|
+
children: unknown[]
|
|
33
|
+
) => ({ attributes, children, name, type: "mdxJsxFlowElement" });
|
|
34
|
+
|
|
35
|
+
/** Build an inline MDX JSX element (phrasing context). */
|
|
36
|
+
export const jsxTextElement = (
|
|
37
|
+
name: string,
|
|
38
|
+
attributes: JsxAttribute[],
|
|
39
|
+
children: unknown[] = []
|
|
40
|
+
) => ({ attributes, children, name, type: "mdxJsxTextElement" });
|
|
41
|
+
|
|
42
|
+
/** Build a fenced code block node. */
|
|
43
|
+
export const codeBlock = (lang: string, value: string) => ({
|
|
44
|
+
lang,
|
|
45
|
+
meta: null,
|
|
46
|
+
type: "code",
|
|
47
|
+
value,
|
|
48
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsxAttribute, jsxFlowElement } from "./mdast.ts";
|
|
2
|
+
import type { MdastNode, MdastVisitorContext } from "./mdast.ts";
|
|
3
|
+
|
|
4
|
+
interface CodeNode extends MdastNode {
|
|
5
|
+
lang?: string | null;
|
|
6
|
+
value: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Satteri MDAST plugin that turns a ` ```mermaid ` code block into a
|
|
11
|
+
* `<blume-mermaid>` custom element carrying the raw diagram source. There is no
|
|
12
|
+
* importable component — the fence is the whole interface. The element is
|
|
13
|
+
* rendered on the client (Mermaid needs a DOM), so the source rides on a string
|
|
14
|
+
* attribute rather than as child text (which MDX would try to parse).
|
|
15
|
+
*/
|
|
16
|
+
export const mermaidPlugin = () => ({
|
|
17
|
+
code(node: CodeNode, ctx: MdastVisitorContext) {
|
|
18
|
+
if (node.lang !== "mermaid") {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
ctx.replaceNode(
|
|
22
|
+
node,
|
|
23
|
+
jsxFlowElement(
|
|
24
|
+
"blume-mermaid",
|
|
25
|
+
[
|
|
26
|
+
jsxAttribute(
|
|
27
|
+
"class",
|
|
28
|
+
"not-prose my-6 flex justify-center overflow-x-auto"
|
|
29
|
+
),
|
|
30
|
+
jsxAttribute("data-source", node.value),
|
|
31
|
+
],
|
|
32
|
+
[]
|
|
33
|
+
)
|
|
34
|
+
);
|
|
35
|
+
},
|
|
36
|
+
name: "blume-mermaid",
|
|
37
|
+
});
|