blume 0.0.0 → 0.1.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/bin/blume.mjs +18 -0
- package/dist/cli/index.js +12696 -0
- package/dist/cli/index.js.map +145 -0
- package/dist/types/core/bridge.d.ts +24 -0
- package/dist/types/core/config.d.ts +35 -0
- package/dist/types/core/data.d.ts +129 -0
- package/dist/types/core/define-components.d.ts +27 -0
- package/dist/types/core/define-meta.d.ts +16 -0
- package/dist/types/core/deployment-env.d.ts +9 -0
- package/dist/types/core/diagnostics.d.ts +17 -0
- package/dist/types/core/i18n-ui.d.ts +500 -0
- package/dist/types/core/load-module.d.ts +7 -0
- package/dist/types/core/package-root.d.ts +17 -0
- package/dist/types/core/project.d.ts +9 -0
- package/dist/types/core/schema.d.ts +3451 -0
- package/dist/types/core/sources/types.d.ts +107 -0
- package/dist/types/core/types.d.ts +245 -0
- package/dist/types/core/ui-packs/ar.d.ts +3 -0
- package/dist/types/core/ui-packs/bg.d.ts +3 -0
- package/dist/types/core/ui-packs/bn.d.ts +3 -0
- package/dist/types/core/ui-packs/ca.d.ts +3 -0
- package/dist/types/core/ui-packs/cs.d.ts +3 -0
- package/dist/types/core/ui-packs/da.d.ts +3 -0
- package/dist/types/core/ui-packs/de.d.ts +3 -0
- package/dist/types/core/ui-packs/el.d.ts +3 -0
- package/dist/types/core/ui-packs/es.d.ts +3 -0
- package/dist/types/core/ui-packs/fa.d.ts +3 -0
- package/dist/types/core/ui-packs/fi.d.ts +3 -0
- package/dist/types/core/ui-packs/fr.d.ts +3 -0
- package/dist/types/core/ui-packs/he.d.ts +3 -0
- package/dist/types/core/ui-packs/hi.d.ts +3 -0
- package/dist/types/core/ui-packs/hr.d.ts +3 -0
- package/dist/types/core/ui-packs/hu.d.ts +3 -0
- package/dist/types/core/ui-packs/id.d.ts +3 -0
- package/dist/types/core/ui-packs/index.d.ts +13 -0
- package/dist/types/core/ui-packs/it.d.ts +3 -0
- package/dist/types/core/ui-packs/ja.d.ts +3 -0
- package/dist/types/core/ui-packs/ko.d.ts +3 -0
- package/dist/types/core/ui-packs/nl.d.ts +3 -0
- package/dist/types/core/ui-packs/no.d.ts +3 -0
- package/dist/types/core/ui-packs/pl.d.ts +3 -0
- package/dist/types/core/ui-packs/pt-br.d.ts +3 -0
- package/dist/types/core/ui-packs/pt.d.ts +3 -0
- package/dist/types/core/ui-packs/ro.d.ts +3 -0
- package/dist/types/core/ui-packs/ru.d.ts +3 -0
- package/dist/types/core/ui-packs/sk.d.ts +3 -0
- package/dist/types/core/ui-packs/sr.d.ts +3 -0
- package/dist/types/core/ui-packs/sv.d.ts +3 -0
- package/dist/types/core/ui-packs/th.d.ts +3 -0
- package/dist/types/core/ui-packs/tr.d.ts +3 -0
- package/dist/types/core/ui-packs/uk.d.ts +3 -0
- package/dist/types/core/ui-packs/vi.d.ts +3 -0
- package/dist/types/core/ui-packs/zh-tw.d.ts +3 -0
- package/dist/types/core/ui-packs/zh.d.ts +3 -0
- package/dist/types/core/version.d.ts +8 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/migrate/mintlify/config.d.ts +2 -0
- package/dist/types/migrate/mintlify/i18n.d.ts +7 -0
- package/dist/types/theme/fonts.d.ts +163 -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 +268 -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 +290 -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 +651 -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 +150 -6
- package/src/ai/ask.ts +93 -0
- package/src/ai/llms.ts +64 -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/examples.ts +104 -0
- package/src/astro/generate.ts +1006 -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 +87 -0
- package/src/astro/templates.ts +1326 -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 +61 -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/CodeBlock.astro +28 -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/Component.astro +65 -0
- package/src/components/content/Diff.astro +44 -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 +46 -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/diff.ts +95 -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 +22 -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/PageLayout.astro +173 -0
- package/src/components/layout/Pagination.astro +56 -0
- package/src/components/layout/ReferenceLayout.astro +107 -0
- package/src/components/layout/RootLayout.astro +537 -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/head-scripts.ts +19 -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/data.ts +128 -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/frontmatter.ts +43 -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 +352 -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 +215 -0
- package/src/core/sources/mintlify.ts +161 -0
- package/src/core/sources/normalize.ts +243 -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/tsconfig-aliases.ts +201 -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 +39 -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 +228 -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 +155 -0
- package/src/migrate/fumadocs/content.ts +365 -0
- package/src/migrate/fumadocs/frontmatter.ts +18 -0
- package/src/migrate/fumadocs/groups.ts +230 -0
- package/src/migrate/fumadocs/index.ts +337 -0
- package/src/migrate/fumadocs/meta.ts +244 -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 +306 -0
- package/src/migrate/mintlify/transform.ts +80 -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 +720 -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 +310 -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 +164 -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 +637 -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,83 @@
|
|
|
1
|
+
import { jsxAttribute, jsxFlowElement } from "./mdast.ts";
|
|
2
|
+
import type { MdastNode, MdastVisitorContext } from "./mdast.ts";
|
|
3
|
+
|
|
4
|
+
interface DirectiveNode extends MdastNode {
|
|
5
|
+
attributes?: Record<string, string | null | undefined> | null;
|
|
6
|
+
children: MdastNode[];
|
|
7
|
+
name: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Directive names that map directly onto a Callout type. */
|
|
11
|
+
const CALLOUT_TYPES = new Set([
|
|
12
|
+
"danger",
|
|
13
|
+
"info",
|
|
14
|
+
"note",
|
|
15
|
+
"success",
|
|
16
|
+
"tip",
|
|
17
|
+
"warning",
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
/** Friendly aliases for the canonical Callout types. */
|
|
21
|
+
const ALIASES: Record<string, string> = {
|
|
22
|
+
caution: "warning",
|
|
23
|
+
error: "danger",
|
|
24
|
+
important: "note",
|
|
25
|
+
warn: "warning",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Resolve a directive name to a Callout type, or `null` if it is not one. */
|
|
29
|
+
export const calloutTypeFor = (name: string): string | null => {
|
|
30
|
+
const lower = name.toLowerCase();
|
|
31
|
+
if (CALLOUT_TYPES.has(lower)) {
|
|
32
|
+
return lower;
|
|
33
|
+
}
|
|
34
|
+
return ALIASES[lower] ?? null;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
interface TextNode extends MdastNode {
|
|
38
|
+
value?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Concatenate the plain text of a node's immediate phrasing children. */
|
|
42
|
+
const textOf = (node: MdastNode): string =>
|
|
43
|
+
((node.children as TextNode[] | undefined) ?? [])
|
|
44
|
+
.map((child) => child.value ?? "")
|
|
45
|
+
.join("");
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Satteri MDAST plugin mapping container directives (`:::note`, `:::warning`,
|
|
49
|
+
* `:::tip`, …) onto Blume's `<Callout>` component. The title comes from a
|
|
50
|
+
* `[label]` or a `{title="…"}` attribute; the body becomes the callout content.
|
|
51
|
+
* Directive names that are not callouts are left untouched.
|
|
52
|
+
*/
|
|
53
|
+
export const directiveToCalloutPlugin = () => ({
|
|
54
|
+
containerDirective(node: DirectiveNode, ctx: MdastVisitorContext) {
|
|
55
|
+
const type = calloutTypeFor(node.name);
|
|
56
|
+
if (type === null) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const children = [...node.children];
|
|
61
|
+
let title = node.attributes?.title ?? undefined;
|
|
62
|
+
|
|
63
|
+
// A leading `:::name[Label]` parses to a paragraph flagged `directiveLabel`.
|
|
64
|
+
const labelIndex = children.findIndex(
|
|
65
|
+
(child) =>
|
|
66
|
+
child.type === "paragraph" &&
|
|
67
|
+
(child.data as { directiveLabel?: boolean } | undefined)?.directiveLabel
|
|
68
|
+
);
|
|
69
|
+
if (labelIndex !== -1) {
|
|
70
|
+
const [label] = children.splice(labelIndex, 1);
|
|
71
|
+
if (label) {
|
|
72
|
+
title ??= textOf(label) || undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const attributes = [jsxAttribute("type", type)];
|
|
77
|
+
if (title) {
|
|
78
|
+
attributes.push(jsxAttribute("title", title));
|
|
79
|
+
}
|
|
80
|
+
ctx.replaceNode(node, jsxFlowElement("Callout", attributes, children));
|
|
81
|
+
},
|
|
82
|
+
name: "blume-directive-callout",
|
|
83
|
+
});
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn every section heading into its own permalink. A Satteri hast plugin runs
|
|
3
|
+
* after Markdown is turned into hast and wraps each `<h2>`–`<h6>`'s content in an
|
|
4
|
+
* `<a href="#slug">`, so a reader can click the heading to copy, bookmark, or
|
|
5
|
+
* share a link straight to that section. `<h1>` (the page title) is slugged for
|
|
6
|
+
* parity but left unwrapped.
|
|
7
|
+
*
|
|
8
|
+
* Satteri's own `heading-ids` plugin (which assigns the `id` used by the table
|
|
9
|
+
* of contents) runs *after* every user hast plugin, and it reuses an `id` that
|
|
10
|
+
* is already present rather than re-slugging. So this plugin is the authoritative
|
|
11
|
+
* id setter: it slugs each heading with the same algorithm (a per-document
|
|
12
|
+
* `github-slugger`, the library Satteri and rehype-slug both use) and writes the
|
|
13
|
+
* `id`, which `heading-ids` then adopts — keeping the in-page anchor, the
|
|
14
|
+
* heading's `id`, and the TOC entry in lockstep. To match Satteri's duplicate
|
|
15
|
+
* disambiguation (`setup`, `setup-1`, …) exactly, it advances the slugger over
|
|
16
|
+
* `<h1>`–`<h6>` in document order even though only `<h2>`–`<h6>` get wrapped.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { satteriCollectHastText } from "@astrojs/markdown-satteri";
|
|
20
|
+
import GithubSlugger from "github-slugger";
|
|
21
|
+
|
|
22
|
+
/** A minimal hast node (avoids a hast type dependency). */
|
|
23
|
+
interface HastNode {
|
|
24
|
+
children?: HastNode[];
|
|
25
|
+
name?: string;
|
|
26
|
+
properties?: Record<string, unknown>;
|
|
27
|
+
tagName?: string;
|
|
28
|
+
type: string;
|
|
29
|
+
value?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The slice of Satteri's hast visitor context this plugin reads. */
|
|
33
|
+
interface HastContext {
|
|
34
|
+
data?: { astro?: { frontmatter?: Record<string, unknown> } };
|
|
35
|
+
setProperty: (node: HastNode, key: string, value: unknown) => void;
|
|
36
|
+
textContent: (node: HastNode) => string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A Satteri hast plugin, typed structurally to avoid a Satteri dep. */
|
|
40
|
+
export interface HeadingAnchorPlugin {
|
|
41
|
+
name: string;
|
|
42
|
+
element: {
|
|
43
|
+
filter: string[];
|
|
44
|
+
visit: (node: HastNode, ctx: HastContext) => HastNode | undefined;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Headings slugged for id parity with Satteri; only a subset gets wrapped. */
|
|
49
|
+
const HEADINGS = ["h1", "h2", "h3", "h4", "h5", "h6"];
|
|
50
|
+
const WRAPPED = new Set(["h2", "h3", "h4", "h5", "h6"]);
|
|
51
|
+
|
|
52
|
+
/** True if the subtree already contains an `<a>`, so wrapping would nest links. */
|
|
53
|
+
const containsAnchor = (node: HastNode): boolean => {
|
|
54
|
+
for (const child of node.children ?? []) {
|
|
55
|
+
if ((child.tagName ?? child.name) === "a" || containsAnchor(child)) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// One slugger per document render. The plugin instance is shared across every
|
|
63
|
+
// page, but slug disambiguation must reset per document; the render-scoped
|
|
64
|
+
// `astro` data object is a stable, unique key for one render (entries are
|
|
65
|
+
// dropped once the render is collected, so this never leaks).
|
|
66
|
+
const FALLBACK_SCOPE: object = {};
|
|
67
|
+
const sluggers = new WeakMap<object, GithubSlugger>();
|
|
68
|
+
|
|
69
|
+
const sluggerFor = (ctx: HastContext): GithubSlugger => {
|
|
70
|
+
const scope = ctx.data?.astro ?? ctx.data ?? FALLBACK_SCOPE;
|
|
71
|
+
const existing = sluggers.get(scope);
|
|
72
|
+
if (existing) {
|
|
73
|
+
return existing;
|
|
74
|
+
}
|
|
75
|
+
const slugger = new GithubSlugger();
|
|
76
|
+
sluggers.set(scope, slugger);
|
|
77
|
+
return slugger;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** The slug for a heading, mirroring Satteri's `heading-ids` exactly. */
|
|
81
|
+
const slugFor = (
|
|
82
|
+
node: HastNode,
|
|
83
|
+
ctx: HastContext,
|
|
84
|
+
slugger: GithubSlugger
|
|
85
|
+
): string => {
|
|
86
|
+
const rawText = ctx.textContent(node);
|
|
87
|
+
// `frontmatter`-interpolated MDX headings (`## {frontmatter.title}`) need the
|
|
88
|
+
// resolved value; the helper is the same one `heading-ids` defers to.
|
|
89
|
+
const text = rawText.includes("frontmatter")
|
|
90
|
+
? satteriCollectHastText(
|
|
91
|
+
node as Parameters<typeof satteriCollectHastText>[0],
|
|
92
|
+
ctx.data?.astro?.frontmatter ?? {}
|
|
93
|
+
)
|
|
94
|
+
: rawText;
|
|
95
|
+
const existingId = node.properties?.id;
|
|
96
|
+
return typeof existingId === "string" ? existingId : slugger.slug(text);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/** Build the plugin. Wraps `<h2>`–`<h6>` in self-linking anchors. */
|
|
100
|
+
export const headingAnchorPlugin = (): HeadingAnchorPlugin => ({
|
|
101
|
+
element: {
|
|
102
|
+
filter: HEADINGS,
|
|
103
|
+
visit(node, ctx) {
|
|
104
|
+
const slug = slugFor(node, ctx, sluggerFor(ctx));
|
|
105
|
+
const wrap = node.tagName
|
|
106
|
+
? WRAPPED.has(node.tagName) && slug !== "" && !containsAnchor(node)
|
|
107
|
+
: false;
|
|
108
|
+
if (!wrap) {
|
|
109
|
+
// Unwrapped headings (h1, an empty slug, or one that already links) still
|
|
110
|
+
// need the id so `heading-ids` adopts it instead of re-slugging.
|
|
111
|
+
if (typeof node.properties?.id !== "string") {
|
|
112
|
+
ctx.setProperty(node, "id", slug);
|
|
113
|
+
}
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// Replacing the heading re-emits its original children as refs inside the
|
|
117
|
+
// new anchor (Satteri passes reused nodes through untouched).
|
|
118
|
+
return {
|
|
119
|
+
children: [
|
|
120
|
+
{
|
|
121
|
+
children: node.children ?? [],
|
|
122
|
+
properties: {
|
|
123
|
+
className: ["blume-heading-anchor"],
|
|
124
|
+
href: `#${slug}`,
|
|
125
|
+
},
|
|
126
|
+
tagName: "a",
|
|
127
|
+
type: "element",
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
properties: { ...node.properties, id: slug },
|
|
131
|
+
tagName: node.tagName,
|
|
132
|
+
type: "element",
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
name: "blume:heading-anchors",
|
|
137
|
+
});
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { satteri } from "@astrojs/markdown-satteri";
|
|
2
|
+
import {
|
|
3
|
+
transformerMetaHighlight,
|
|
4
|
+
transformerNotationDiff,
|
|
5
|
+
transformerNotationFocus,
|
|
6
|
+
transformerNotationHighlight,
|
|
7
|
+
transformerNotationWordHighlight,
|
|
8
|
+
} from "@shikijs/transformers";
|
|
9
|
+
import { codeToHtml } from "shiki";
|
|
10
|
+
|
|
11
|
+
import { codeTitleTransformer } from "./code-title.ts";
|
|
12
|
+
import { directiveToCalloutPlugin } from "./directives.ts";
|
|
13
|
+
import { headingAnchorPlugin } from "./heading-anchors.ts";
|
|
14
|
+
import { inlineCodeHighlightPlugin } from "./inline-code.ts";
|
|
15
|
+
import { languageIconTransformer } from "./language-icon.ts";
|
|
16
|
+
import { mathPlugin } from "./math.ts";
|
|
17
|
+
import { mermaidPlugin } from "./mermaid.ts";
|
|
18
|
+
import { packageInstallPlugin } from "./package-install.ts";
|
|
19
|
+
|
|
20
|
+
/** A Shiki transformer, derived from the upstream factories' return type. */
|
|
21
|
+
type ShikiTransformer = ReturnType<typeof transformerNotationDiff>;
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
PACKAGE_MANAGERS,
|
|
25
|
+
type PackageManager,
|
|
26
|
+
toPackageCommands,
|
|
27
|
+
} from "./package-commands.ts";
|
|
28
|
+
export {
|
|
29
|
+
type CodeTitleTransformer,
|
|
30
|
+
codeTitleTransformer,
|
|
31
|
+
} from "./code-title.ts";
|
|
32
|
+
export { calloutTypeFor } from "./directives.ts";
|
|
33
|
+
export { headingAnchorPlugin } from "./heading-anchors.ts";
|
|
34
|
+
export { mermaidPlugin } from "./mermaid.ts";
|
|
35
|
+
export { packageInstallPlugin } from "./package-install.ts";
|
|
36
|
+
|
|
37
|
+
/** Element type of Satteri's `mdastPlugins`, sourced from the (alpha) core. */
|
|
38
|
+
type MdastPlugin = NonNullable<
|
|
39
|
+
NonNullable<Parameters<typeof satteri>[0]>["mdastPlugins"]
|
|
40
|
+
>[number];
|
|
41
|
+
|
|
42
|
+
/** Element type of Satteri's `hastPlugins`. */
|
|
43
|
+
type HastPlugin = NonNullable<
|
|
44
|
+
NonNullable<Parameters<typeof satteri>[0]>["hastPlugins"]
|
|
45
|
+
>[number];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Hast plugins enabled by config. Inline `` `code`{:lang} `` highlighting is
|
|
49
|
+
* opt-in; self-linking heading anchors (`<h2>`–`<h6>` wrapped in an `<a>` to
|
|
50
|
+
* their own id) are on unless `markdown.headingAnchors` is `false`. Inline code
|
|
51
|
+
* runs first so the anchor wrap re-refs already-highlighted code.
|
|
52
|
+
*/
|
|
53
|
+
const blumeHastPlugins = (options: BlumeMarkdownOptions): HastPlugin[] => {
|
|
54
|
+
const plugins: HastPlugin[] = [];
|
|
55
|
+
if (options.inline) {
|
|
56
|
+
plugins.push(inlineCodeHighlightPlugin() as unknown as HastPlugin);
|
|
57
|
+
}
|
|
58
|
+
if (options.headingAnchors !== false) {
|
|
59
|
+
plugins.push(headingAnchorPlugin() as unknown as HastPlugin);
|
|
60
|
+
}
|
|
61
|
+
return plugins;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Shiki transformers enabled by default for every code block. The four upstream
|
|
66
|
+
* notation transformers read GitHub-style comments and strip them from the
|
|
67
|
+
* output: `// [!code highlight]`, `// [!code ++]` / `// [!code --]`,
|
|
68
|
+
* `// [!code word:x]`, and `// [!code focus]`. The v3 match algorithm scopes a
|
|
69
|
+
* notation to the line it sits on (or the next, for a trailing comment).
|
|
70
|
+
* `transformerMetaHighlight` adds numeric range highlighting from the fence meta
|
|
71
|
+
* (` ```ts {1,3-5} `), reusing the same `highlighted` class. Blume's own
|
|
72
|
+
* {@link languageIconTransformer} prepends a brand icon, and
|
|
73
|
+
* {@link codeTitleTransformer} runs last to promote fence-meta (title / line
|
|
74
|
+
* numbers) to `<pre>` attributes. The theme styles the classes these emit
|
|
75
|
+
* (`highlighted`, `diff add/remove`, `highlighted-word`, `focused`,
|
|
76
|
+
* `blume-lang-icon`).
|
|
77
|
+
*/
|
|
78
|
+
export interface BlumeShikiOptions {
|
|
79
|
+
/** Prepend a brand language icon to the header (`markdown.code.icons`). */
|
|
80
|
+
icons?: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const blumeShikiTransformers = (
|
|
84
|
+
options: BlumeShikiOptions = {}
|
|
85
|
+
): ShikiTransformer[] => {
|
|
86
|
+
const transformers: ShikiTransformer[] = [
|
|
87
|
+
transformerNotationHighlight({ matchAlgorithm: "v3" }),
|
|
88
|
+
transformerNotationDiff({ matchAlgorithm: "v3" }),
|
|
89
|
+
transformerNotationWordHighlight({ matchAlgorithm: "v3" }),
|
|
90
|
+
transformerNotationFocus({ matchAlgorithm: "v3" }),
|
|
91
|
+
transformerMetaHighlight(),
|
|
92
|
+
];
|
|
93
|
+
if (options.icons !== false) {
|
|
94
|
+
transformers.push(languageIconTransformer() as unknown as ShikiTransformer);
|
|
95
|
+
}
|
|
96
|
+
// The fence-meta reader (title / line numbers) always runs last.
|
|
97
|
+
transformers.push(codeTitleTransformer() as unknown as ShikiTransformer);
|
|
98
|
+
return transformers;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The light/dark Shiki themes Blume highlights with. Kept in lockstep with the
|
|
103
|
+
* generated Astro config's `shikiConfig.themes` so code highlighted outside the
|
|
104
|
+
* Markdown pipeline (via {@link highlightCode}) matches fenced code exactly.
|
|
105
|
+
*/
|
|
106
|
+
const CODE_THEMES = { dark: "github-dark", light: "github-light" } as const;
|
|
107
|
+
|
|
108
|
+
const escapeHtml = (value: string): string =>
|
|
109
|
+
value
|
|
110
|
+
.replaceAll("&", "&")
|
|
111
|
+
.replaceAll("<", "<")
|
|
112
|
+
.replaceAll(">", ">");
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Tag the highlighted `<pre>` with `astro-code` (plus any extra classes) so the
|
|
116
|
+
* theme's code-block styles apply — `codeToHtml`'s bare output is `pre.shiki`,
|
|
117
|
+
* which the theme doesn't style.
|
|
118
|
+
*/
|
|
119
|
+
const astroCodeClassTransformer = (extra?: string): ShikiTransformer =>
|
|
120
|
+
({
|
|
121
|
+
name: "blume:astro-code-class",
|
|
122
|
+
pre(node: { properties: Record<string, unknown> }) {
|
|
123
|
+
const existing =
|
|
124
|
+
typeof node.properties.class === "string" ? node.properties.class : "";
|
|
125
|
+
node.properties.class = `astro-code ${extra ?? ""} ${existing}`
|
|
126
|
+
.replaceAll(/\s+/gu, " ")
|
|
127
|
+
.trim();
|
|
128
|
+
},
|
|
129
|
+
}) as unknown as ShikiTransformer;
|
|
130
|
+
|
|
131
|
+
export interface HighlightCodeOptions extends BlumeShikiOptions {
|
|
132
|
+
/** Extra `<pre>` class names, e.g. `blume-source` for a height-capped pane. */
|
|
133
|
+
className?: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Highlight a code string with the same Shiki themes and transformers as Blume's
|
|
138
|
+
* Markdown code fences, returning ready-to-render HTML. Use it to show themed
|
|
139
|
+
* code *outside* the Markdown pipeline (custom pages, components): the output
|
|
140
|
+
* carries the `astro-code` class and dual (light/dark) color variables, so the
|
|
141
|
+
* theme styles it — including the light/dark swap — with no extra CSS. The
|
|
142
|
+
* theme's code-block rules are scoped to `.prose`, so render the result inside a
|
|
143
|
+
* `.prose` container (the shipped `<CodeBlock>` does this). An unknown language
|
|
144
|
+
* falls back to an escaped plain block.
|
|
145
|
+
*/
|
|
146
|
+
export const highlightCode = async (
|
|
147
|
+
code: string,
|
|
148
|
+
lang: string,
|
|
149
|
+
options: HighlightCodeOptions = {}
|
|
150
|
+
): Promise<string> => {
|
|
151
|
+
try {
|
|
152
|
+
return await codeToHtml(code, {
|
|
153
|
+
defaultColor: false,
|
|
154
|
+
lang,
|
|
155
|
+
themes: CODE_THEMES,
|
|
156
|
+
transformers: [
|
|
157
|
+
...blumeShikiTransformers({ icons: options.icons }),
|
|
158
|
+
astroCodeClassTransformer(options.className),
|
|
159
|
+
],
|
|
160
|
+
});
|
|
161
|
+
} catch {
|
|
162
|
+
const className = `astro-code ${options.className ?? ""}`
|
|
163
|
+
.replaceAll(/\s+/gu, " ")
|
|
164
|
+
.trim();
|
|
165
|
+
return `<pre class="${className}"><code>${escapeHtml(code)}</code></pre>`;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Sätteri Markdown features Blume enables beyond Astro's defaults. GFM,
|
|
171
|
+
* frontmatter, and smart punctuation are already on; this adds superscript
|
|
172
|
+
* (`^text^`) and subscript (`~text~`), which render to native `<sup>`/`<sub>`.
|
|
173
|
+
*/
|
|
174
|
+
const FEATURES = { subscript: true, superscript: true };
|
|
175
|
+
|
|
176
|
+
/** Options shared by both processors. */
|
|
177
|
+
export interface BlumeMarkdownOptions {
|
|
178
|
+
/**
|
|
179
|
+
* Wrap `<h2>`–`<h6>` in self-linking anchors (`markdown.headingAnchors`).
|
|
180
|
+
* On unless explicitly `false`.
|
|
181
|
+
*/
|
|
182
|
+
headingAnchors?: boolean;
|
|
183
|
+
/** Highlight inline `` `code`{:lang} `` snippets (`markdown.code.inline`). */
|
|
184
|
+
inline?: boolean;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Sätteri processor for plain `.md`, with Blume's curated feature set. */
|
|
188
|
+
export const blumeMarkdownProcessor = (options: BlumeMarkdownOptions = {}) =>
|
|
189
|
+
satteri({
|
|
190
|
+
features: { ...FEATURES },
|
|
191
|
+
hastPlugins: blumeHastPlugins(options),
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
export interface BlumeMdxOptions extends BlumeMarkdownOptions {
|
|
195
|
+
/** Enable KaTeX math parsing and rendering. */
|
|
196
|
+
math?: boolean;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Sätteri MDX processor: Blume's feature set plus the MDAST plugins that target
|
|
201
|
+
* components — `package-install` → package-manager tabs, `:::note` →
|
|
202
|
+
* `<Callout>`, and ` ```mermaid ` → a `<blume-mermaid>` element. Used as the
|
|
203
|
+
* `processor` for `@astrojs/mdx` so these apply to
|
|
204
|
+
* `.mdx` only (plain `.md` uses {@link blumeMarkdownProcessor}). Math is opt-in
|
|
205
|
+
* via config since `$` is common in prose and code.
|
|
206
|
+
*
|
|
207
|
+
* The plugins are modeled with minimal structural types; bridge them to
|
|
208
|
+
* Satteri's full `MdastPlugin` type at this single boundary.
|
|
209
|
+
*/
|
|
210
|
+
export const blumeMdxProcessor = (options: BlumeMdxOptions = {}) => {
|
|
211
|
+
const plugins: unknown[] = [
|
|
212
|
+
packageInstallPlugin(),
|
|
213
|
+
directiveToCalloutPlugin(),
|
|
214
|
+
mermaidPlugin(),
|
|
215
|
+
];
|
|
216
|
+
if (options.math) {
|
|
217
|
+
plugins.push(mathPlugin());
|
|
218
|
+
}
|
|
219
|
+
return satteri({
|
|
220
|
+
features: {
|
|
221
|
+
...FEATURES,
|
|
222
|
+
directive: true,
|
|
223
|
+
...(options.math ? { math: true } : {}),
|
|
224
|
+
},
|
|
225
|
+
hastPlugins: blumeHastPlugins(options),
|
|
226
|
+
mdastPlugins: plugins as unknown as MdastPlugin[],
|
|
227
|
+
});
|
|
228
|
+
};
|
|
@@ -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
|
+
});
|