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,533 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { EN_UI } from "../../core/i18n-ui.ts";
|
|
3
|
+
import type { UIStrings } from "../../core/i18n-ui.ts";
|
|
4
|
+
import type {
|
|
5
|
+
Heading,
|
|
6
|
+
LocaleSwitchOption,
|
|
7
|
+
Navigation,
|
|
8
|
+
} from "../../core/types.ts";
|
|
9
|
+
import "blume:theme";
|
|
10
|
+
import type { ComponentOverride } from "../../core/define-components.ts";
|
|
11
|
+
import { buildStructuredData } from "../../seo/jsonld.ts";
|
|
12
|
+
import Analytics from "./Analytics.astro";
|
|
13
|
+
import Banner from "./Banner.astro";
|
|
14
|
+
import Breadcrumbs from "./Breadcrumbs.astro";
|
|
15
|
+
import Favicon from "./Favicon.astro";
|
|
16
|
+
import Fonts from "./Fonts.astro";
|
|
17
|
+
import Header from "./Header.astro";
|
|
18
|
+
import { findBreadcrumbs, flattenPages, getPagination } from "./nav-utils.ts";
|
|
19
|
+
import NavTree from "./NavTree.astro";
|
|
20
|
+
import { resolveSlot } from "./overrides.ts";
|
|
21
|
+
import PageActions from "./PageActions.astro";
|
|
22
|
+
import PageFeedback from "./PageFeedback.astro";
|
|
23
|
+
import Pagination from "./Pagination.astro";
|
|
24
|
+
import TableOfContents from "./TableOfContents.astro";
|
|
25
|
+
|
|
26
|
+
interface Props {
|
|
27
|
+
site: { title: string; description?: string };
|
|
28
|
+
logo?: {
|
|
29
|
+
svg?: string;
|
|
30
|
+
light?: string;
|
|
31
|
+
dark?: string;
|
|
32
|
+
alt: string;
|
|
33
|
+
href: string;
|
|
34
|
+
} | null;
|
|
35
|
+
favicon?: {
|
|
36
|
+
href: string;
|
|
37
|
+
type?: string;
|
|
38
|
+
} | null;
|
|
39
|
+
banner?: {
|
|
40
|
+
content: string;
|
|
41
|
+
link?: { text: string; href: string };
|
|
42
|
+
dismissible: boolean;
|
|
43
|
+
key: string;
|
|
44
|
+
} | null;
|
|
45
|
+
analytics?: {
|
|
46
|
+
posthog?: { host?: string; key: string };
|
|
47
|
+
scripts?: {
|
|
48
|
+
attributes?: Record<string, string>;
|
|
49
|
+
content?: string;
|
|
50
|
+
src?: string;
|
|
51
|
+
strategy?: "async" | "defer";
|
|
52
|
+
}[];
|
|
53
|
+
vercel?: boolean;
|
|
54
|
+
} | null;
|
|
55
|
+
navigation: Navigation;
|
|
56
|
+
mcp?: { name: string; route: string } | null;
|
|
57
|
+
page: { title: string; description?: string; route: string };
|
|
58
|
+
headings: Heading[];
|
|
59
|
+
imageZoom?: boolean;
|
|
60
|
+
codeWrap?: boolean;
|
|
61
|
+
themeMode: "system" | "light" | "dark";
|
|
62
|
+
fontCssVars?: string[];
|
|
63
|
+
searchEnabled: boolean;
|
|
64
|
+
indexable: boolean;
|
|
65
|
+
ogImage?: string | null;
|
|
66
|
+
canonical?: string | null;
|
|
67
|
+
editUrl?: string | null;
|
|
68
|
+
askEnabled?: boolean;
|
|
69
|
+
/** Show the "Was this page helpful?" rating below the content. */
|
|
70
|
+
feedback?: boolean;
|
|
71
|
+
exportPdf?: boolean;
|
|
72
|
+
exportEpub?: boolean;
|
|
73
|
+
feeds?: { title: string; href: string }[];
|
|
74
|
+
siteUrl?: string | null;
|
|
75
|
+
pageType?: string;
|
|
76
|
+
published?: string | Date | null;
|
|
77
|
+
lastModified?: string | null;
|
|
78
|
+
noindex?: boolean;
|
|
79
|
+
structuredDataEnabled?: boolean;
|
|
80
|
+
/** Active locale code for `<html lang>` (defaults to `en`). */
|
|
81
|
+
locale?: string;
|
|
82
|
+
/** Text direction for `<html dir>` (defaults to `ltr`). */
|
|
83
|
+
dir?: "ltr" | "rtl";
|
|
84
|
+
/**
|
|
85
|
+
* Direction of the page content's own language — differs from `dir` on a
|
|
86
|
+
* fallback page (e.g. English content under an RTL locale). Applied to the
|
|
87
|
+
* article so prose punctuation reads correctly; the chrome still mirrors.
|
|
88
|
+
*/
|
|
89
|
+
contentDir?: "ltr" | "rtl";
|
|
90
|
+
/** Resolved UI dictionary; English baseline when omitted. */
|
|
91
|
+
ui?: UIStrings;
|
|
92
|
+
/** `hreflang` alternates (absolute URLs) for this page's translations. */
|
|
93
|
+
localeAlternates?: { hreflang: string; href: string }[];
|
|
94
|
+
/** Absolute URL of the default-locale translation, for `hreflang=x-default`. */
|
|
95
|
+
xDefault?: string | null;
|
|
96
|
+
/** Language-switcher entries for the current page. */
|
|
97
|
+
localeSwitch?: LocaleSwitchOption[];
|
|
98
|
+
/**
|
|
99
|
+
* User layout-slot overrides from `components.ts` (`defineComponents`). Each
|
|
100
|
+
* key replaces the matching built-in; unknown keys are ignored. Wired slots:
|
|
101
|
+
* `Header`, `Sidebar`, `Breadcrumbs`, `TableOfContents`, `Pagination`.
|
|
102
|
+
*/
|
|
103
|
+
layout?: Record<string, ComponentOverride>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const {
|
|
107
|
+
site,
|
|
108
|
+
logo,
|
|
109
|
+
favicon,
|
|
110
|
+
banner,
|
|
111
|
+
analytics,
|
|
112
|
+
navigation,
|
|
113
|
+
mcp,
|
|
114
|
+
page,
|
|
115
|
+
headings,
|
|
116
|
+
imageZoom = true,
|
|
117
|
+
codeWrap = false,
|
|
118
|
+
themeMode,
|
|
119
|
+
fontCssVars,
|
|
120
|
+
searchEnabled,
|
|
121
|
+
indexable,
|
|
122
|
+
ogImage,
|
|
123
|
+
canonical,
|
|
124
|
+
editUrl,
|
|
125
|
+
askEnabled,
|
|
126
|
+
feedback = false,
|
|
127
|
+
exportPdf,
|
|
128
|
+
exportEpub,
|
|
129
|
+
feeds,
|
|
130
|
+
siteUrl,
|
|
131
|
+
pageType,
|
|
132
|
+
published,
|
|
133
|
+
lastModified,
|
|
134
|
+
noindex,
|
|
135
|
+
structuredDataEnabled,
|
|
136
|
+
locale = "en",
|
|
137
|
+
dir = "ltr",
|
|
138
|
+
contentDir,
|
|
139
|
+
ui,
|
|
140
|
+
localeAlternates,
|
|
141
|
+
xDefault,
|
|
142
|
+
localeSwitch,
|
|
143
|
+
layout = {},
|
|
144
|
+
} = Astro.props;
|
|
145
|
+
|
|
146
|
+
// Layout-slot overrides: render the user's component when configured, else the
|
|
147
|
+
// built-in. Each override receives the same props as the component it replaces.
|
|
148
|
+
const HeaderSlot = resolveSlot(layout.Header, Header);
|
|
149
|
+
const SidebarSlot = resolveSlot(layout.Sidebar, NavTree);
|
|
150
|
+
const BreadcrumbsSlot = resolveSlot(layout.Breadcrumbs, Breadcrumbs);
|
|
151
|
+
const TableOfContentsSlot = resolveSlot(layout.TableOfContents, TableOfContents);
|
|
152
|
+
const PaginationSlot = resolveSlot(layout.Pagination, Pagination);
|
|
153
|
+
|
|
154
|
+
const strings = ui ?? EN_UI;
|
|
155
|
+
// Filter search to the active language only when the site is multi-locale.
|
|
156
|
+
const searchLocale =
|
|
157
|
+
localeSwitch && localeSwitch.length > 1 ? locale : undefined;
|
|
158
|
+
const tocHeadings = headings.filter((h) => h.depth >= 2 && h.depth <= 3);
|
|
159
|
+
const pageTitle = page.title ? `${page.title} - ${site.title}` : site.title;
|
|
160
|
+
const description = page.description ?? site.description;
|
|
161
|
+
|
|
162
|
+
// "Last updated on <date>" — formatted in UTC to match the changelog timeline.
|
|
163
|
+
const lastModifiedDate = lastModified ? new Date(lastModified) : null;
|
|
164
|
+
const formattedLastModified =
|
|
165
|
+
lastModifiedDate && !Number.isNaN(lastModifiedDate.getTime())
|
|
166
|
+
? new Intl.DateTimeFormat(locale || "en", {
|
|
167
|
+
dateStyle: "long",
|
|
168
|
+
timeZone: "UTC",
|
|
169
|
+
}).format(lastModifiedDate)
|
|
170
|
+
: null;
|
|
171
|
+
|
|
172
|
+
// The hosted MCP server's absolute URL, used by the page-actions install menu.
|
|
173
|
+
// Needs a configured site to be useful, so the menu is hidden without one.
|
|
174
|
+
const mcpUrl = mcp && siteUrl ? new URL(mcp.route, siteUrl).href : null;
|
|
175
|
+
|
|
176
|
+
const crumbs = findBreadcrumbs(navigation.sidebar, page.route);
|
|
177
|
+
const { prev, next } = getPagination(
|
|
178
|
+
flattenPages(navigation.sidebar),
|
|
179
|
+
page.route
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// Structured data is skipped when disabled or for pages we ask crawlers not to
|
|
183
|
+
// index. Escape `<` so a title/description containing `</script>` can't break
|
|
184
|
+
// out of the inline JSON-LD tag.
|
|
185
|
+
const structuredData =
|
|
186
|
+
structuredDataEnabled === false || noindex
|
|
187
|
+
? null
|
|
188
|
+
: buildStructuredData({
|
|
189
|
+
breadcrumbs: crumbs,
|
|
190
|
+
description,
|
|
191
|
+
locale,
|
|
192
|
+
modified: lastModified,
|
|
193
|
+
pageType,
|
|
194
|
+
published,
|
|
195
|
+
route: page.route,
|
|
196
|
+
siteName: site.title,
|
|
197
|
+
siteUrl: siteUrl ?? null,
|
|
198
|
+
title: page.title,
|
|
199
|
+
});
|
|
200
|
+
const structuredDataJson = structuredData
|
|
201
|
+
? JSON.stringify(structuredData).replaceAll("<", "\\u003c")
|
|
202
|
+
: null;
|
|
203
|
+
|
|
204
|
+
const initialThemeScript = `(()=>{const m=${JSON.stringify(themeMode)};const s=localStorage.getItem("blume-theme");const sys=matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light";document.documentElement.dataset.theme=s??(m==="system"?sys:m);})();`;
|
|
205
|
+
|
|
206
|
+
// Pre-paint: hide a previously-dismissed banner before it flashes in.
|
|
207
|
+
const bannerScript = banner?.dismissible
|
|
208
|
+
? `(()=>{if(localStorage.getItem("blume-banner:"+${JSON.stringify(banner.key)}))document.documentElement.setAttribute("data-blume-banner-hidden","");})();`
|
|
209
|
+
: null;
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
<!doctype html>
|
|
213
|
+
<html dir={dir} lang={locale}>
|
|
214
|
+
<head>
|
|
215
|
+
<meta charset="utf-8" />
|
|
216
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
217
|
+
<title>{pageTitle}</title>
|
|
218
|
+
<Favicon favicon={favicon} />
|
|
219
|
+
<Fonts cssVars={fontCssVars ?? []} />
|
|
220
|
+
{canonical && <link rel="canonical" href={canonical} />}
|
|
221
|
+
{
|
|
222
|
+
localeAlternates?.map((alt) => (
|
|
223
|
+
<link href={alt.href} hreflang={alt.hreflang} rel="alternate" />
|
|
224
|
+
))
|
|
225
|
+
}
|
|
226
|
+
{xDefault && <link href={xDefault} hreflang="x-default" rel="alternate" />}
|
|
227
|
+
{noindex && <meta name="robots" content="noindex" />}
|
|
228
|
+
{description && <meta name="description" content={description} />}
|
|
229
|
+
<meta property="og:title" content={pageTitle} />
|
|
230
|
+
{description && <meta property="og:description" content={description} />}
|
|
231
|
+
{
|
|
232
|
+
ogImage && (
|
|
233
|
+
<>
|
|
234
|
+
<meta property="og:image" content={ogImage} />
|
|
235
|
+
<meta name="twitter:image" content={ogImage} />
|
|
236
|
+
<meta name="twitter:card" content="summary_large_image" />
|
|
237
|
+
</>
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
{
|
|
241
|
+
feeds?.map((feed) => (
|
|
242
|
+
<link
|
|
243
|
+
href={feed.href}
|
|
244
|
+
rel="alternate"
|
|
245
|
+
title={feed.title}
|
|
246
|
+
type="application/rss+xml"
|
|
247
|
+
/>
|
|
248
|
+
))
|
|
249
|
+
}
|
|
250
|
+
{
|
|
251
|
+
structuredDataJson && (
|
|
252
|
+
<script
|
|
253
|
+
is:inline
|
|
254
|
+
set:html={structuredDataJson}
|
|
255
|
+
type="application/ld+json"
|
|
256
|
+
/>
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
{bannerScript && <script is:inline set:html={bannerScript} />}
|
|
260
|
+
<script is:inline set:html={initialThemeScript} />
|
|
261
|
+
<Analytics analytics={analytics} />
|
|
262
|
+
</head>
|
|
263
|
+
<body
|
|
264
|
+
class="bg-background font-sans text-foreground antialiased"
|
|
265
|
+
data-blume-code-wrap={codeWrap ? "" : undefined}
|
|
266
|
+
data-blume-image-zoom={imageZoom ? "" : undefined}
|
|
267
|
+
>
|
|
268
|
+
<a
|
|
269
|
+
class="absolute start-[-999px] top-0 z-[100] bg-accent px-4 py-2 text-accent-foreground focus:start-0"
|
|
270
|
+
href="#blume-content">{strings.page.skipToContent}</a
|
|
271
|
+
>
|
|
272
|
+
<Banner banner={banner} />
|
|
273
|
+
<HeaderSlot
|
|
274
|
+
askEnabled={askEnabled}
|
|
275
|
+
localeSwitch={localeSwitch}
|
|
276
|
+
logo={logo}
|
|
277
|
+
navigation={navigation}
|
|
278
|
+
route={page.route}
|
|
279
|
+
searchEnabled={searchEnabled}
|
|
280
|
+
searchLocale={searchLocale}
|
|
281
|
+
searchStrings={strings.search}
|
|
282
|
+
site={site}
|
|
283
|
+
switcherStrings={strings.languageSwitcher}
|
|
284
|
+
>
|
|
285
|
+
<slot name="ask" slot="ask" />
|
|
286
|
+
</HeaderSlot>
|
|
287
|
+
<div class="mx-auto grid grid-cols-1 items-start lg:grid-cols-[17.5rem_minmax(0,1fr)] xl:grid-cols-[17.5rem_minmax(0,1fr)_17.5rem]">
|
|
288
|
+
<aside
|
|
289
|
+
aria-label="Primary"
|
|
290
|
+
class="fixed top-16 start-0 z-[35] h-[calc(100dvh-4rem)] w-64 max-w-[80vw] -translate-x-[105%] overflow-y-auto border-border border-e bg-background px-5 pt-4 pb-6 transition-transform rtl:translate-x-[105%] [:where([data-blume-nav-open])_&]:translate-x-0! lg:sticky lg:z-auto lg:w-auto lg:max-w-none lg:translate-x-0! lg:border-e-0 lg:bg-transparent lg:px-4"
|
|
291
|
+
>
|
|
292
|
+
<nav>
|
|
293
|
+
<SidebarSlot currentRoute={page.route} items={navigation.sidebar} />
|
|
294
|
+
</nav>
|
|
295
|
+
</aside>
|
|
296
|
+
<main class="min-w-0 px-6 pt-6 pb-10 lg:px-8 xl:px-10" id="blume-content">
|
|
297
|
+
<BreadcrumbsSlot crumbs={crumbs} />
|
|
298
|
+
<TableOfContentsSlot
|
|
299
|
+
headings={tocHeadings}
|
|
300
|
+
title={strings.toc.title}
|
|
301
|
+
variant="mobile"
|
|
302
|
+
/>
|
|
303
|
+
<article class="prose mx-auto max-w-[42rem]" dir={contentDir}>
|
|
304
|
+
<slot />
|
|
305
|
+
</article>
|
|
306
|
+
{
|
|
307
|
+
formattedLastModified && (
|
|
308
|
+
<p class="mx-auto mt-10 max-w-[42rem] text-muted-foreground text-sm">
|
|
309
|
+
{strings.page.lastUpdated} {formattedLastModified}
|
|
310
|
+
</p>
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
{feedback && <PageFeedback strings={strings.feedback} />}
|
|
314
|
+
<PaginationSlot next={next} prev={prev} strings={strings.page} />
|
|
315
|
+
</main>
|
|
316
|
+
<aside
|
|
317
|
+
aria-label={strings.toc.title}
|
|
318
|
+
class="sticky top-16 hidden h-[calc(100dvh-4rem)] overflow-y-auto px-4 pt-6 pb-10 text-sm xl:block"
|
|
319
|
+
>
|
|
320
|
+
<TableOfContentsSlot
|
|
321
|
+
headings={tocHeadings}
|
|
322
|
+
title={strings.toc.title}
|
|
323
|
+
variant="desktop"
|
|
324
|
+
/>
|
|
325
|
+
<PageActions
|
|
326
|
+
askEnabled={askEnabled}
|
|
327
|
+
editUrl={editUrl}
|
|
328
|
+
exportEpub={exportEpub}
|
|
329
|
+
exportPdf={exportPdf}
|
|
330
|
+
mcpName={mcp?.name}
|
|
331
|
+
mcpUrl={mcpUrl}
|
|
332
|
+
route={page.route}
|
|
333
|
+
strings={strings.actions}
|
|
334
|
+
/>
|
|
335
|
+
</aside>
|
|
336
|
+
</div>
|
|
337
|
+
<button
|
|
338
|
+
aria-label="Close navigation"
|
|
339
|
+
class="fixed inset-0 z-[30] hidden cursor-pointer border-0 bg-black/40 [:where([data-blume-nav-open])_&]:block lg:hidden"
|
|
340
|
+
data-blume-nav-toggle
|
|
341
|
+
type="button"
|
|
342
|
+
></button>
|
|
343
|
+
<script>
|
|
344
|
+
import { icons } from "../../theme/icons.ts";
|
|
345
|
+
// Registers the <blume-mermaid> custom element (emitted by ```mermaid
|
|
346
|
+
// fences). Mermaid itself is lazy-loaded only on pages that use a diagram.
|
|
347
|
+
import "../content/mermaid-element.ts";
|
|
348
|
+
// Registers the <blume-toc> custom element: scrollspy for the table of
|
|
349
|
+
// contents, highlighting the section currently in view as you scroll.
|
|
350
|
+
import "./toc-element.ts";
|
|
351
|
+
|
|
352
|
+
const svg = (name: string) =>
|
|
353
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${icons[name]}</svg>`;
|
|
354
|
+
|
|
355
|
+
const buttonClass =
|
|
356
|
+
"absolute right-3 inline-flex size-7 items-center justify-center rounded-full bg-transparent text-muted-foreground opacity-70 transition hover:bg-muted hover:text-foreground hover:opacity-100 focus-visible:opacity-100";
|
|
357
|
+
|
|
358
|
+
const languageLabels: Record<string, string> = {
|
|
359
|
+
astro: "Astro",
|
|
360
|
+
bash: "Bash",
|
|
361
|
+
css: "CSS",
|
|
362
|
+
html: "HTML",
|
|
363
|
+
js: "JavaScript",
|
|
364
|
+
json: "JSON",
|
|
365
|
+
jsx: "JSX",
|
|
366
|
+
md: "Markdown",
|
|
367
|
+
mdx: "MDX",
|
|
368
|
+
plaintext: "Text",
|
|
369
|
+
sh: "Shell",
|
|
370
|
+
shell: "Shell",
|
|
371
|
+
ts: "TypeScript",
|
|
372
|
+
tsx: "TSX",
|
|
373
|
+
txt: "Text",
|
|
374
|
+
yaml: "YAML",
|
|
375
|
+
yml: "YAML",
|
|
376
|
+
zsh: "Zsh",
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
for (const pre of document.querySelectorAll(".prose pre")) {
|
|
380
|
+
if (pre.querySelector("[data-blume-copy]")) {
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
// Skip nested <pre>: Twoslash renders each hover popup's type signature
|
|
384
|
+
// as a <pre> inside the code block, which shouldn't get its own button.
|
|
385
|
+
if (pre.parentElement?.closest("pre")) {
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
const language = pre.getAttribute("data-language");
|
|
389
|
+
if (language) {
|
|
390
|
+
pre.setAttribute(
|
|
391
|
+
"data-language",
|
|
392
|
+
languageLabels[language.toLowerCase()] ?? language
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
pre.classList.add("group", "relative");
|
|
396
|
+
const button = document.createElement("button");
|
|
397
|
+
button.type = "button";
|
|
398
|
+
// The language-label bar (prose) vs flush code (tabs) need a different
|
|
399
|
+
// offset; pick the Tailwind class by context instead of a CSS override.
|
|
400
|
+
const topClass = pre.closest("blume-tabs, .not-prose")
|
|
401
|
+
? "top-2.5"
|
|
402
|
+
: "top-2";
|
|
403
|
+
button.className = `${buttonClass} ${topClass}`;
|
|
404
|
+
button.setAttribute("data-blume-copy", "");
|
|
405
|
+
button.setAttribute("aria-label", "Copy code");
|
|
406
|
+
button.innerHTML = svg("copy");
|
|
407
|
+
button.addEventListener("click", async () => {
|
|
408
|
+
const text = pre.querySelector("code")?.textContent ?? "";
|
|
409
|
+
try {
|
|
410
|
+
await navigator.clipboard.writeText(text);
|
|
411
|
+
} catch {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
button.innerHTML = svg("check");
|
|
415
|
+
setTimeout(() => {
|
|
416
|
+
button.innerHTML = svg("copy");
|
|
417
|
+
}, 1500);
|
|
418
|
+
});
|
|
419
|
+
pre.appendChild(button);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Click-to-zoom for content images (gated by `markdown.imageZoom`).
|
|
423
|
+
// Enhances every `.prose img` so plain Markdown images open in a lightbox.
|
|
424
|
+
// Opt out per-image with `data-no-zoom`.
|
|
425
|
+
const zoomImages: Iterable<HTMLImageElement> =
|
|
426
|
+
document.body.hasAttribute("data-blume-image-zoom")
|
|
427
|
+
? document.querySelectorAll<HTMLImageElement>(
|
|
428
|
+
".prose img:not([data-no-zoom])"
|
|
429
|
+
)
|
|
430
|
+
: [];
|
|
431
|
+
const zoomTargets = Array.from(zoomImages);
|
|
432
|
+
if (zoomTargets.length > 0) {
|
|
433
|
+
const reduceMotion = window.matchMedia(
|
|
434
|
+
"(prefers-reduced-motion: reduce)"
|
|
435
|
+
).matches;
|
|
436
|
+
let active: {
|
|
437
|
+
clone: HTMLImageElement;
|
|
438
|
+
original: HTMLImageElement;
|
|
439
|
+
overlay: HTMLElement;
|
|
440
|
+
} | null = null;
|
|
441
|
+
|
|
442
|
+
const closeZoom = () => {
|
|
443
|
+
if (!active) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
const { clone, original, overlay } = active;
|
|
447
|
+
active = null;
|
|
448
|
+
overlay.style.opacity = "0";
|
|
449
|
+
clone.style.transform = "translate(0px, 0px) scale(1)";
|
|
450
|
+
const cleanup = () => {
|
|
451
|
+
overlay.remove();
|
|
452
|
+
original.style.visibility = "";
|
|
453
|
+
document.removeEventListener("keydown", onKey);
|
|
454
|
+
window.removeEventListener("scroll", closeZoom);
|
|
455
|
+
};
|
|
456
|
+
if (reduceMotion) {
|
|
457
|
+
cleanup();
|
|
458
|
+
} else {
|
|
459
|
+
clone.addEventListener("transitionend", cleanup, { once: true });
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
const onKey = (event: KeyboardEvent) => {
|
|
464
|
+
if (event.key === "Escape") {
|
|
465
|
+
closeZoom();
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const openZoom = (image: HTMLImageElement) => {
|
|
470
|
+
if (active) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const start = image.getBoundingClientRect();
|
|
474
|
+
if (start.width === 0 || start.height === 0) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const overlay = document.createElement("div");
|
|
479
|
+
overlay.className =
|
|
480
|
+
"fixed inset-0 z-[100] flex cursor-zoom-out items-center justify-center bg-background/80 opacity-0 backdrop-blur-sm transition-opacity duration-300";
|
|
481
|
+
|
|
482
|
+
const clone = image.cloneNode(true) as HTMLImageElement;
|
|
483
|
+
clone.className = "absolute m-0 max-w-none rounded-blume shadow-2xl";
|
|
484
|
+
clone.style.top = `${start.top}px`;
|
|
485
|
+
clone.style.left = `${start.left}px`;
|
|
486
|
+
clone.style.width = `${start.width}px`;
|
|
487
|
+
clone.style.height = `${start.height}px`;
|
|
488
|
+
clone.style.transformOrigin = "top left";
|
|
489
|
+
if (!reduceMotion) {
|
|
490
|
+
clone.style.transition =
|
|
491
|
+
"transform 300ms cubic-bezier(0.22, 1, 0.36, 1)";
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
overlay.appendChild(clone);
|
|
495
|
+
document.body.appendChild(overlay);
|
|
496
|
+
image.style.visibility = "hidden";
|
|
497
|
+
active = { clone, original: image, overlay };
|
|
498
|
+
|
|
499
|
+
const margin = 0.92;
|
|
500
|
+
const ratio = start.width / start.height;
|
|
501
|
+
let targetWidth = window.innerWidth * margin;
|
|
502
|
+
let targetHeight = targetWidth / ratio;
|
|
503
|
+
if (targetHeight > window.innerHeight * margin) {
|
|
504
|
+
targetHeight = window.innerHeight * margin;
|
|
505
|
+
targetWidth = targetHeight * ratio;
|
|
506
|
+
}
|
|
507
|
+
const naturalWidth = image.naturalWidth || targetWidth;
|
|
508
|
+
if (targetWidth > naturalWidth) {
|
|
509
|
+
targetWidth = naturalWidth;
|
|
510
|
+
targetHeight = targetWidth / ratio;
|
|
511
|
+
}
|
|
512
|
+
const scale = targetWidth / start.width;
|
|
513
|
+
const dx = (window.innerWidth - targetWidth) / 2 - start.left;
|
|
514
|
+
const dy = (window.innerHeight - targetHeight) / 2 - start.top;
|
|
515
|
+
|
|
516
|
+
requestAnimationFrame(() => {
|
|
517
|
+
overlay.style.opacity = "1";
|
|
518
|
+
clone.style.transform = `translate(${dx}px, ${dy}px) scale(${scale})`;
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
overlay.addEventListener("click", closeZoom);
|
|
522
|
+
document.addEventListener("keydown", onKey);
|
|
523
|
+
window.addEventListener("scroll", closeZoom, { passive: true });
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
for (const image of zoomTargets) {
|
|
527
|
+
image.classList.add("cursor-zoom-in");
|
|
528
|
+
image.addEventListener("click", () => openZoom(image));
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
</script>
|
|
532
|
+
</body>
|
|
533
|
+
</html>
|