blume 1.5.2 → 1.6.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/CHANGELOG.md +85 -0
- package/dist/cli/index.js +3639 -1377
- package/dist/cli/index.js.map +103 -91
- package/dist/types/ai/component-markdown.d.ts +79 -0
- package/dist/types/components/layout/nav-utils.d.ts +60 -0
- package/dist/types/core/base-path.d.ts +9 -0
- package/dist/types/core/config-input.d.ts +206 -4
- package/dist/types/core/config.d.ts +6 -4
- package/dist/types/core/data.d.ts +23 -1
- package/dist/types/core/github.d.ts +35 -0
- package/dist/types/core/i18n-ui.d.ts +8 -0
- package/dist/types/core/navigation.d.ts +69 -0
- package/dist/types/core/schema.d.ts +117 -1
- package/dist/types/core/sources/types.d.ts +31 -6
- package/dist/types/core/types.d.ts +23 -2
- package/dist/types/markdown/features.d.ts +21 -0
- package/dist/types/openapi/references.d.ts +21 -1
- package/dist/types/seo/jsonld.d.ts +105 -0
- package/dist/types/theme/fonts.d.ts +34 -4
- package/docs/_snippets/include-demo.mdx +7 -0
- package/docs/advanced/api-reference.mdx +3 -3
- package/docs/advanced/custom-pages.mdx +1 -1
- package/docs/advanced/graphql.mdx +84 -0
- package/docs/advanced/meta.ts +8 -1
- package/docs/configuration/ai.mdx +21 -3
- package/docs/configuration/index.mdx +24 -0
- package/docs/configuration/search.mdx +13 -1
- package/docs/configuration/seo.mdx +27 -0
- package/docs/configuration/theming.mdx +17 -0
- package/docs/content/components.mdx +7 -0
- package/docs/content/includes.mdx +68 -0
- package/docs/content/meta.ts +1 -0
- package/docs/content/navigation.mdx +25 -0
- package/docs/content/sources.mdx +42 -1
- package/docs/content/syntax.mdx +69 -1
- package/docs/content/versioning.mdx +15 -9
- package/docs/reference/cli.mdx +2 -1
- package/package.json +23 -14
- package/skills/blume-migrate/SKILL.md +16 -7
- package/skills/blume-migrate/references/docusaurus.md +5 -3
- package/skills/blume-migrate/references/fumadocs.md +10 -2
- package/skills/blume-migrate/references/mintlify.md +3 -2
- package/skills/blume-migrate/references/nextra.md +2 -2
- package/skills/blume-migrate/references/starlight.md +1 -1
- package/src/ai/agent-readability.ts +2 -1
- package/src/ai/ask-data.ts +2 -1
- package/src/ai/component-markdown.ts +199 -36
- package/src/ai/llms.ts +93 -6
- package/src/ai/markdown.ts +2 -2
- package/src/ai/mcp/discovery.ts +10 -2
- package/src/ai/mcp/server.ts +74 -2
- package/src/astro/generate.ts +183 -116
- package/src/astro/include-hmr.ts +81 -0
- package/src/astro/include-refresh.ts +0 -0
- package/src/astro/index.ts +3 -5
- package/src/astro/templates.ts +125 -76
- package/src/cli/commands/build.ts +84 -15
- package/src/cli/init/questions.ts +1 -0
- package/src/cli/init/scaffold.ts +27 -4
- package/src/components/colors.ts +142 -0
- package/src/components/content/Badge.astro +5 -12
- package/src/components/content/Callout.astro +19 -36
- package/src/components/content/Card.astro +15 -21
- package/src/components/content/Component.astro +10 -1
- package/src/components/content/GithubInfo.astro +28 -9
- package/src/components/content/Tabs.astro +27 -5
- package/src/components/content/github-info.ts +20 -5
- package/src/components/dropdown-dismiss.ts +122 -0
- package/src/components/layout/Fonts.astro +15 -8
- package/src/components/layout/Header.astro +44 -0
- package/src/components/layout/LanguageSwitcher.astro +9 -1
- package/src/components/layout/NavSelector.astro +12 -3
- package/src/components/layout/NavTree.astro +6 -18
- package/src/components/layout/PageActions.astro +29 -8
- package/src/components/layout/PageLayout.astro +10 -1
- package/src/components/layout/ReferenceLayout.astro +6 -1
- package/src/components/layout/RootLayout.astro +46 -15
- package/src/components/layout/Search.astro +36 -4
- package/src/components/layout/TableOfContents.astro +8 -2
- package/src/components/layout/head-scripts.ts +53 -1
- package/src/components/openapi/ApiOverview.astro +13 -3
- package/src/components/openapi/AsyncApiOperation.astro +7 -14
- package/src/components/openapi/GraphqlChip.astro +33 -0
- package/src/components/openapi/GraphqlFieldsTable.astro +111 -0
- package/src/components/openapi/GraphqlOperation.astro +186 -0
- package/src/components/openapi/GraphqlType.astro +154 -0
- package/src/components/openapi/MethodBadge.astro +3 -14
- package/src/components/openapi/Operation.astro +12 -5
- package/src/components/openapi/OperationPanel.astro +43 -0
- package/src/components/openapi/RequestPanel.astro +5 -10
- package/src/components/openapi/Responses.astro +1 -16
- package/src/components/openapi/graphql-helpers.ts +466 -0
- package/src/components/openapi/playground-client.ts +15 -0
- package/src/components/openapi/sample-panels.ts +45 -0
- package/src/components/openapi/snippets.ts +13 -35
- package/src/core/base-path.ts +11 -0
- package/src/core/config-input.ts +209 -2
- package/src/core/config.ts +6 -4
- package/src/core/content-assets.ts +15 -4
- package/src/core/data.ts +18 -2
- package/src/core/diagnostics.ts +8 -0
- package/src/core/frontmatter.ts +20 -8
- package/src/core/github.ts +71 -0
- package/src/core/graph.ts +22 -8
- package/src/core/heading-markers.ts +96 -0
- package/src/core/i18n-ui.ts +11 -0
- package/src/core/includes.ts +632 -0
- package/src/core/last-modified.ts +36 -11
- package/src/core/links.ts +79 -13
- package/src/core/meta.ts +2 -1
- package/src/core/nav-diagnostics.ts +11 -2
- package/src/core/navigation.ts +27 -6
- package/src/core/project-graph.ts +61 -9
- package/src/core/schema.ts +226 -35
- package/src/core/server-features.ts +5 -9
- package/src/core/sources/github-releases.ts +2 -2
- package/src/core/sources/normalize.ts +502 -115
- package/src/core/sources/notion.ts +43 -8
- package/src/core/sources/obsidian.ts +1038 -0
- package/src/core/sources/read.ts +36 -1
- package/src/core/sources/resolve.ts +34 -1
- package/src/core/sources/types.ts +28 -6
- package/src/core/sources/watch.ts +12 -8
- package/src/core/tsconfig-aliases.ts +48 -35
- package/src/core/types.ts +25 -2
- package/src/core/ui-packs/ar.ts +1 -0
- package/src/core/ui-packs/bg.ts +2 -0
- package/src/core/ui-packs/bn.ts +1 -0
- package/src/core/ui-packs/ca.ts +2 -0
- package/src/core/ui-packs/cs.ts +1 -0
- package/src/core/ui-packs/da.ts +1 -0
- package/src/core/ui-packs/de.ts +2 -0
- package/src/core/ui-packs/el.ts +2 -0
- package/src/core/ui-packs/es.ts +2 -0
- package/src/core/ui-packs/fa.ts +1 -0
- package/src/core/ui-packs/fi.ts +1 -0
- package/src/core/ui-packs/fr.ts +2 -0
- package/src/core/ui-packs/he.ts +1 -0
- package/src/core/ui-packs/hi.ts +1 -0
- package/src/core/ui-packs/hr.ts +2 -0
- package/src/core/ui-packs/hu.ts +2 -0
- package/src/core/ui-packs/id.ts +2 -0
- package/src/core/ui-packs/it.ts +1 -0
- package/src/core/ui-packs/ja.ts +2 -0
- package/src/core/ui-packs/ko.ts +2 -0
- package/src/core/ui-packs/nl.ts +2 -0
- package/src/core/ui-packs/no.ts +2 -0
- package/src/core/ui-packs/pl.ts +2 -0
- package/src/core/ui-packs/pt-br.ts +2 -0
- package/src/core/ui-packs/pt.ts +2 -0
- package/src/core/ui-packs/ro.ts +2 -0
- package/src/core/ui-packs/ru.ts +2 -0
- package/src/core/ui-packs/sk.ts +1 -0
- package/src/core/ui-packs/sr.ts +1 -0
- package/src/core/ui-packs/sv.ts +2 -0
- package/src/core/ui-packs/th.ts +1 -0
- package/src/core/ui-packs/tr.ts +2 -0
- package/src/core/ui-packs/uk.ts +2 -0
- package/src/core/ui-packs/vi.ts +1 -0
- package/src/core/ui-packs/zh-tw.ts +1 -0
- package/src/core/ui-packs/zh.ts +1 -0
- package/src/core/version-cut.ts +21 -3
- package/src/core/yaml.ts +26 -0
- package/src/deploy/function-bundle.ts +251 -0
- package/src/eval/schema.ts +3 -1
- package/src/markdown/code-title.ts +22 -16
- package/src/markdown/features.ts +21 -0
- package/src/markdown/fence-meta.ts +50 -0
- package/src/markdown/heading-anchors.ts +198 -37
- package/src/markdown/include.ts +247 -0
- package/src/markdown/index.ts +43 -34
- package/src/markdown/language-icon.ts +2 -2
- package/src/markdown/mdast.ts +7 -3
- package/src/markdown/ts2js.ts +264 -0
- package/src/openapi/asyncapi.ts +4 -1
- package/src/openapi/graphql-build.ts +293 -0
- package/src/openapi/graphql.ts +212 -0
- package/src/openapi/model.ts +38 -5
- package/src/openapi/parse.ts +34 -0
- package/src/openapi/proxy.ts +30 -5
- package/src/openapi/references.ts +89 -13
- package/src/openapi/render-mdx.ts +48 -8
- package/src/openapi/scalar.ts +5 -12
- package/src/openapi/source.ts +91 -23
- package/src/registry/eject.ts +11 -0
- package/src/search/documents.ts +229 -37
- package/src/search/orama-index.ts +9 -5
- package/src/seo/jsonld.ts +293 -51
- package/src/theme/code-block-padding.ts +16 -0
- package/src/theme/entry.ts +65 -11
- package/src/theme/fonts.ts +189 -16
- package/src/translate/prompts.ts +2 -0
- package/src/translate/run.ts +7 -0
- package/src/translate/work-list.ts +0 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color classes shared by every component that draws a label over a tint of
|
|
3
|
+
* its own hue — method badges, response status chips, `<Badge>`, the sidebar's
|
|
4
|
+
* method badges — and by the typed callout/card icons. One table so the pairs
|
|
5
|
+
* can't drift between components.
|
|
6
|
+
*
|
|
7
|
+
* Contrast rule (light mode): text over a 15% tint of its hue must clear 4.5:1,
|
|
8
|
+
* the WCAG AA bar for text this size. `-700` does for most hues; green and
|
|
9
|
+
* orange need `-800`, the way yellow already does over its 20% tint. Dark mode
|
|
10
|
+
* clears the bar at `-300` throughout. Check any hue added here.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type Hue =
|
|
14
|
+
| "blue"
|
|
15
|
+
| "green"
|
|
16
|
+
| "orange"
|
|
17
|
+
| "purple"
|
|
18
|
+
| "red"
|
|
19
|
+
| "teal"
|
|
20
|
+
| "violet"
|
|
21
|
+
| "yellow";
|
|
22
|
+
|
|
23
|
+
/** Label over a translucent tint of the same hue. */
|
|
24
|
+
export const TINT = {
|
|
25
|
+
blue: "bg-blue-500/15 text-blue-700 dark:text-blue-300",
|
|
26
|
+
green: "bg-green-500/15 text-green-800 dark:text-green-300",
|
|
27
|
+
orange: "bg-orange-500/15 text-orange-800 dark:text-orange-300",
|
|
28
|
+
purple: "bg-purple-500/15 text-purple-700 dark:text-purple-300",
|
|
29
|
+
red: "bg-red-500/15 text-red-700 dark:text-red-300",
|
|
30
|
+
teal: "bg-teal-500/15 text-teal-700 dark:text-teal-300",
|
|
31
|
+
violet: "bg-violet-500/15 text-violet-700 dark:text-violet-300",
|
|
32
|
+
yellow: "bg-yellow-500/20 text-yellow-800 dark:text-yellow-300",
|
|
33
|
+
} satisfies Record<Hue, string>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Label with a translucent border of the same hue and no fill. The label
|
|
37
|
+
* inherits whatever surface it sits on (a callout, a card, the muted panel), so
|
|
38
|
+
* it is held to the same values as `TINT`: green at `-700` is 4.47:1 on the
|
|
39
|
+
* default muted surface.
|
|
40
|
+
*/
|
|
41
|
+
export const STROKE = {
|
|
42
|
+
blue: "border-blue-500/40 text-blue-700 dark:text-blue-300",
|
|
43
|
+
green: "border-green-500/40 text-green-800 dark:text-green-300",
|
|
44
|
+
orange: "border-orange-500/40 text-orange-800 dark:text-orange-300",
|
|
45
|
+
purple: "border-purple-500/40 text-purple-700 dark:text-purple-300",
|
|
46
|
+
red: "border-red-500/40 text-red-700 dark:text-red-300",
|
|
47
|
+
teal: "border-teal-500/40 text-teal-700 dark:text-teal-300",
|
|
48
|
+
violet: "border-violet-500/40 text-violet-700 dark:text-violet-300",
|
|
49
|
+
yellow: "border-yellow-500/40 text-yellow-800 dark:text-yellow-300",
|
|
50
|
+
} satisfies Record<Hue, string>;
|
|
51
|
+
|
|
52
|
+
/** The neutral fallback every table falls through to. */
|
|
53
|
+
export const MUTED = "bg-muted text-muted-foreground";
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* HTTP methods, AsyncAPI actions, and GraphQL root-field kinds, as shown on an
|
|
57
|
+
* operation's badge and in a reference's sidebar. Type-page kinds fall through
|
|
58
|
+
* to the muted default so operation badges stay the loud ones.
|
|
59
|
+
*/
|
|
60
|
+
export const METHOD_COLORS = {
|
|
61
|
+
DELETE: TINT.red,
|
|
62
|
+
GET: TINT.green,
|
|
63
|
+
HEAD: MUTED,
|
|
64
|
+
MUTATION: TINT.blue,
|
|
65
|
+
OPTIONS: MUTED,
|
|
66
|
+
PATCH: TINT.yellow,
|
|
67
|
+
POST: TINT.blue,
|
|
68
|
+
PUT: TINT.orange,
|
|
69
|
+
QUERY: TINT.green,
|
|
70
|
+
RECEIVE: TINT.teal,
|
|
71
|
+
SEND: TINT.violet,
|
|
72
|
+
SUBSCRIPTION: TINT.violet,
|
|
73
|
+
} satisfies Record<string, string>;
|
|
74
|
+
|
|
75
|
+
const isMethod = (key: string): key is keyof typeof METHOD_COLORS =>
|
|
76
|
+
Object.hasOwn(METHOD_COLORS, key);
|
|
77
|
+
|
|
78
|
+
export const methodColor = (method: string): string => {
|
|
79
|
+
const key = method.toUpperCase();
|
|
80
|
+
return isMethod(key) ? METHOD_COLORS[key] : MUTED;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/** Response status chips, by the status code's class. */
|
|
84
|
+
export const statusColor = (status: string): string => {
|
|
85
|
+
if (status.startsWith("2")) {
|
|
86
|
+
return TINT.green;
|
|
87
|
+
}
|
|
88
|
+
if (status.startsWith("3")) {
|
|
89
|
+
return TINT.blue;
|
|
90
|
+
}
|
|
91
|
+
if (status.startsWith("4")) {
|
|
92
|
+
return TINT.orange;
|
|
93
|
+
}
|
|
94
|
+
if (status.startsWith("5")) {
|
|
95
|
+
return TINT.red;
|
|
96
|
+
}
|
|
97
|
+
return MUTED;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The small `deprecated` label beside an operation's path or field name. It
|
|
102
|
+
* draws on the page background, where orange needs `-700` for 4.5:1.
|
|
103
|
+
*/
|
|
104
|
+
export const DEPRECATED_LABEL_CLASS =
|
|
105
|
+
"font-medium text-[0.625rem] text-orange-700 uppercase tracking-wide dark:text-orange-400";
|
|
106
|
+
|
|
107
|
+
/** The typed admonitions `<Callout>` and `<Card>` render; `check` is an alias of `success`. */
|
|
108
|
+
export type AdmonitionType =
|
|
109
|
+
| "info"
|
|
110
|
+
| "note"
|
|
111
|
+
| "tip"
|
|
112
|
+
| "success"
|
|
113
|
+
| "warning"
|
|
114
|
+
| "danger";
|
|
115
|
+
|
|
116
|
+
export const admonitionType = (
|
|
117
|
+
type: AdmonitionType | "check"
|
|
118
|
+
): AdmonitionType => (type === "check" ? "success" : type);
|
|
119
|
+
|
|
120
|
+
export const ADMONITION_ICON = {
|
|
121
|
+
danger: "circle-x",
|
|
122
|
+
info: "info",
|
|
123
|
+
note: "info",
|
|
124
|
+
success: "circle-check",
|
|
125
|
+
tip: "lightbulb",
|
|
126
|
+
warning: "triangle-alert",
|
|
127
|
+
} satisfies Record<AdmonitionType, string>;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* An admonition's icon names its type, so it is meaningful UI held to the 3:1
|
|
131
|
+
* non-text bar against the tint behind it: green and amber need `-700` over
|
|
132
|
+
* their 10% tint, and the note icon is full-strength muted-foreground (at 70%
|
|
133
|
+
* it fell under the bar on the muted surface).
|
|
134
|
+
*/
|
|
135
|
+
export const ADMONITION_ICON_CLASS = {
|
|
136
|
+
danger: "text-red-600 dark:text-red-400",
|
|
137
|
+
info: "text-blue-600 dark:text-blue-400",
|
|
138
|
+
note: "text-muted-foreground",
|
|
139
|
+
success: "text-green-700 dark:text-green-400",
|
|
140
|
+
tip: "text-accent",
|
|
141
|
+
warning: "text-amber-700 dark:text-amber-400",
|
|
142
|
+
} satisfies Record<AdmonitionType, string>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { STROKE, TINT } from "../colors.ts";
|
|
2
3
|
import Icon from "../Icon.astro";
|
|
3
4
|
|
|
4
5
|
type BadgeVariant = "default" | "accent" | "success" | "warning" | "danger";
|
|
@@ -44,34 +45,26 @@ const colorName =
|
|
|
44
45
|
}[variant];
|
|
45
46
|
const customColor = colorName.startsWith("#") ? colorName : null;
|
|
46
47
|
|
|
48
|
+
// The hue rows come from the shared table (see colors.ts for the contrast
|
|
49
|
+
// rule); the neutral and surface rows are Badge's own.
|
|
47
50
|
const filledColorClass: Record<string, string> = {
|
|
48
|
-
|
|
51
|
+
...TINT,
|
|
49
52
|
gray: "bg-muted text-muted-foreground",
|
|
50
|
-
green: "bg-green-500/15 text-green-700 dark:text-green-300",
|
|
51
|
-
orange: "bg-orange-500/15 text-orange-700 dark:text-orange-300",
|
|
52
|
-
purple: "bg-purple-500/15 text-purple-700 dark:text-purple-300",
|
|
53
|
-
red: "bg-red-500/15 text-red-700 dark:text-red-300",
|
|
54
53
|
surface: "bg-background text-muted-foreground ring-1 ring-border",
|
|
55
54
|
"surface-destructive":
|
|
56
55
|
"bg-background text-red-700 ring-1 ring-red-500/30 dark:text-red-300",
|
|
57
56
|
white: "bg-white text-muted-foreground ring-1 ring-border dark:bg-white/10",
|
|
58
57
|
"white-destructive":
|
|
59
58
|
"bg-white text-red-700 ring-1 ring-red-500/30 dark:bg-white/10 dark:text-red-300",
|
|
60
|
-
yellow: "bg-yellow-500/20 text-yellow-800 dark:text-yellow-300",
|
|
61
59
|
};
|
|
62
60
|
|
|
63
61
|
const strokeColorClass: Record<string, string> = {
|
|
64
|
-
|
|
62
|
+
...STROKE,
|
|
65
63
|
gray: "border-border text-muted-foreground",
|
|
66
|
-
green: "border-green-500/40 text-green-700 dark:text-green-300",
|
|
67
|
-
orange: "border-orange-500/40 text-orange-700 dark:text-orange-300",
|
|
68
|
-
purple: "border-purple-500/40 text-purple-700 dark:text-purple-300",
|
|
69
|
-
red: "border-red-500/40 text-red-700 dark:text-red-300",
|
|
70
64
|
surface: "border-border text-muted-foreground",
|
|
71
65
|
"surface-destructive": "border-red-500/40 text-red-700 dark:text-red-300",
|
|
72
66
|
white: "border-border text-muted-foreground",
|
|
73
67
|
"white-destructive": "border-red-500/40 text-red-700 dark:text-red-300",
|
|
74
|
-
yellow: "border-yellow-500/40 text-yellow-800 dark:text-yellow-300",
|
|
75
68
|
};
|
|
76
69
|
|
|
77
70
|
const sizeClass: Record<BadgeSize, string> = {
|
|
@@ -1,64 +1,47 @@
|
|
|
1
1
|
---
|
|
2
|
+
import {
|
|
3
|
+
ADMONITION_ICON,
|
|
4
|
+
ADMONITION_ICON_CLASS,
|
|
5
|
+
type AdmonitionType,
|
|
6
|
+
admonitionType,
|
|
7
|
+
} from "../colors.ts";
|
|
2
8
|
import Icon from "../Icon.astro";
|
|
3
9
|
|
|
4
|
-
type CalloutType =
|
|
5
|
-
| "info"
|
|
6
|
-
| "note"
|
|
7
|
-
| "tip"
|
|
8
|
-
| "success"
|
|
9
|
-
| "check"
|
|
10
|
-
| "warning"
|
|
11
|
-
| "danger";
|
|
12
|
-
|
|
13
10
|
interface Props {
|
|
14
|
-
type?:
|
|
11
|
+
type?: AdmonitionType | "check";
|
|
15
12
|
title?: string;
|
|
16
13
|
icon?: unknown;
|
|
17
14
|
color?: string;
|
|
18
15
|
}
|
|
19
16
|
|
|
20
17
|
const { color, icon, type = "info", title } = Astro.props;
|
|
18
|
+
// `check` is an alias of `success`; fold it once so the tables need one row.
|
|
19
|
+
const kind = admonitionType(type);
|
|
21
20
|
|
|
22
|
-
const
|
|
23
|
-
check: "circle-check",
|
|
24
|
-
danger: "circle-x",
|
|
25
|
-
info: "info",
|
|
26
|
-
note: "info",
|
|
27
|
-
success: "circle-check",
|
|
28
|
-
tip: "lightbulb",
|
|
29
|
-
warning: "triangle-alert",
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const variantClass: Record<CalloutType, string> = {
|
|
33
|
-
check: "border-green-500/25 bg-green-500/10",
|
|
21
|
+
const variantClass: Record<AdmonitionType, string> = {
|
|
34
22
|
danger: "border-red-500/25 bg-red-500/10",
|
|
35
23
|
info: "border-blue-500/25 bg-blue-500/10",
|
|
36
24
|
note: "border-border bg-muted",
|
|
37
25
|
success: "border-green-500/25 bg-green-500/10",
|
|
38
|
-
|
|
26
|
+
// 6% rather than the 10% the other variants use: this tint comes from the
|
|
27
|
+
// user-configurable accent, whose default is near black, and muted body
|
|
28
|
+
// text on a 10% black tint sits at 4.25:1. 6% clears WCAG AA (4.5:1)
|
|
29
|
+
// against any accent — 4.62:1 worst case on pure black.
|
|
30
|
+
tip: "border-accent/25 bg-accent/6",
|
|
39
31
|
warning: "border-amber-500/25 bg-amber-500/10",
|
|
40
32
|
};
|
|
41
|
-
|
|
42
|
-
const iconClass: Record<CalloutType, string> = {
|
|
43
|
-
check: "text-green-600 dark:text-green-400",
|
|
44
|
-
danger: "text-red-600 dark:text-red-400",
|
|
45
|
-
info: "text-blue-600 dark:text-blue-400",
|
|
46
|
-
note: "text-muted-foreground/70",
|
|
47
|
-
success: "text-green-600 dark:text-green-400",
|
|
48
|
-
tip: "text-accent",
|
|
49
|
-
warning: "text-amber-600 dark:text-amber-400",
|
|
50
|
-
};
|
|
51
33
|
---
|
|
52
34
|
|
|
53
35
|
<aside
|
|
54
36
|
class:list={[
|
|
55
37
|
"not-prose my-5 flex flex-row items-start gap-2 rounded-blume border px-4 py-3 font-sans text-muted-foreground text-sm leading-6 [&_a]:underline [&_a]:decoration-current/50 [&_code]:font-mono",
|
|
56
|
-
color ? "bg-background" : variantClass[
|
|
38
|
+
color ? "bg-background" : variantClass[kind],
|
|
57
39
|
]}
|
|
40
|
+
data-blume-callout={kind}
|
|
58
41
|
style={color ? `border:1px solid ${color};color:${color}` : undefined}
|
|
59
42
|
>
|
|
60
|
-
<span class:list={["mt-0.5 shrink-0", color ? "" :
|
|
61
|
-
<Icon color={color} icon={icon ??
|
|
43
|
+
<span class:list={["mt-0.5 shrink-0", color ? "" : ADMONITION_ICON_CLASS[kind]]}>
|
|
44
|
+
<Icon color={color} icon={icon ?? ADMONITION_ICON[kind]} size={16} />
|
|
62
45
|
</span>
|
|
63
46
|
{/* The global prose rule leaks a 1rem margin onto these paragraphs/lists even
|
|
64
47
|
though the callout is not-prose; with a title the body isn't the first
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
+
import {
|
|
3
|
+
ADMONITION_ICON,
|
|
4
|
+
ADMONITION_ICON_CLASS,
|
|
5
|
+
admonitionType,
|
|
6
|
+
} from "../colors.ts";
|
|
2
7
|
import Icon from "../Icon.astro";
|
|
3
8
|
import { withBase } from "../islands/base-path.ts";
|
|
4
9
|
import { contentHref } from "./base-href.ts";
|
|
@@ -22,31 +27,20 @@ const external = href?.startsWith("http");
|
|
|
22
27
|
const isHorizontal = horizontal === true || horizontal === "true";
|
|
23
28
|
const showArrow =
|
|
24
29
|
href && arrow === undefined ? external : arrow === true || arrow === "true";
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
note: "info",
|
|
30
|
-
tip: "lightbulb",
|
|
31
|
-
warning: "triangle-alert",
|
|
32
|
-
};
|
|
33
|
-
const iconClass = {
|
|
34
|
-
check: "text-green-600 dark:text-green-400",
|
|
35
|
-
danger: "text-red-600 dark:text-red-400",
|
|
36
|
-
info: "text-blue-600 dark:text-blue-400",
|
|
37
|
-
note: "text-muted-foreground/70",
|
|
38
|
-
tip: "text-accent",
|
|
39
|
-
warning: "text-amber-600 dark:text-amber-400",
|
|
40
|
-
};
|
|
41
|
-
const iconName = icon ?? (type ? iconByType[type] : undefined);
|
|
30
|
+
// The typed icon shares Callout's table (and its contrast rule); the surface
|
|
31
|
+
// tints are Card's own, a shade lighter than a callout's.
|
|
32
|
+
const kind = type ? admonitionType(type) : undefined;
|
|
33
|
+
const iconName = icon ?? (kind ? ADMONITION_ICON[kind] : undefined);
|
|
42
34
|
const variantClass = {
|
|
43
|
-
check: "border-green-500/30 bg-green-500/10",
|
|
44
35
|
danger: "border-red-500/30 bg-red-500/10",
|
|
45
36
|
info: "border-blue-500/30 bg-blue-500/10",
|
|
46
37
|
note: "border-border bg-muted/40",
|
|
47
|
-
|
|
38
|
+
success: "border-green-500/30 bg-green-500/10",
|
|
39
|
+
// 6% for the same reason as Callout: the accent defaults to near black, and
|
|
40
|
+
// muted body text on a 10% black tint misses WCAG AA.
|
|
41
|
+
tip: "border-accent/30 bg-accent/6",
|
|
48
42
|
warning: "border-amber-500/30 bg-amber-500/10",
|
|
49
|
-
}[
|
|
43
|
+
}[kind ?? "note"];
|
|
50
44
|
---
|
|
51
45
|
|
|
52
46
|
<Tag
|
|
@@ -75,7 +69,7 @@ const variantClass = {
|
|
|
75
69
|
<div class="p-5">
|
|
76
70
|
{
|
|
77
71
|
iconName && (
|
|
78
|
-
<div class:list={["mb-2.5", color ? "" :
|
|
72
|
+
<div class:list={["mb-2.5", color ? "" : kind ? ADMONITION_ICON_CLASS[kind] : "text-accent"]}>
|
|
79
73
|
<Icon color={color} name={iconName} size={20} />
|
|
80
74
|
</div>
|
|
81
75
|
)
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
// examples are all supported. The source is highlighted with the same Shiki
|
|
11
11
|
// setup as ordinary code fences.
|
|
12
12
|
import data from "blume:data";
|
|
13
|
+
import {
|
|
14
|
+
CODE_PADDING_BLOCK_REM,
|
|
15
|
+
FLUSH_CODE_PADDING_TOP_REM,
|
|
16
|
+
} from "../../theme/code-block-padding.ts";
|
|
13
17
|
|
|
14
18
|
import { highlightCode } from "../../markdown/index.ts";
|
|
15
19
|
import { withBase } from "../islands/base-path.ts";
|
|
@@ -58,7 +62,12 @@ const codeHtml = entry
|
|
|
58
62
|
// whose collapse to the measured height no transition could hide. No-JS
|
|
59
63
|
// readers aren't hurt by the cap, since the source scrolls at any height.
|
|
60
64
|
const LINE_PX = 21;
|
|
61
|
-
const
|
|
65
|
+
const REM_PX = 16;
|
|
66
|
+
// The pre's vertical padding, from the same constants the theme emits: the
|
|
67
|
+
// copy-button strip on top (the pane is a flush block inside tabs) and the
|
|
68
|
+
// plain inset below. The tab panel and the pre carry no border of their own.
|
|
69
|
+
const PADDING_PX =
|
|
70
|
+
(FLUSH_CODE_PADDING_TOP_REM + CODE_PADDING_BLOCK_REM) * REM_PX;
|
|
62
71
|
const ESTIMATE_MAX_PX = 400;
|
|
63
72
|
// The floor also clamps the measured height client-side; it rides along on the
|
|
64
73
|
// iframe as `data-blume-min-pane` so the script and this estimate can't drift.
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// at build time (no client JS). Pass `owner`/`repo`, or omit them to use the
|
|
4
4
|
// repo from blume.config. A `GITHUB_TOKEN` env var lifts the API rate limit.
|
|
5
5
|
// If the API is unreachable the card still renders, just without counts.
|
|
6
|
+
import { apiUrl, PUBLIC_HOST_URL } from "../../core/github.ts";
|
|
6
7
|
import { resolveIcon } from "../../theme/icons.ts";
|
|
7
8
|
import data from "blume:data";
|
|
8
9
|
import { GITHUB_MARK } from "../github-mark.ts";
|
|
@@ -15,22 +16,40 @@ const starIcon = resolveIcon("star")?.body ?? "";
|
|
|
15
16
|
const forkIcon = resolveIcon("git-fork")?.body ?? "";
|
|
16
17
|
|
|
17
18
|
interface Props {
|
|
19
|
+
/**
|
|
20
|
+
* Origin of the GitHub instance the card's repo lives on. Defaults to the
|
|
21
|
+
* configured `github.host`, so a card on an Enterprise site reads that
|
|
22
|
+
* instance; set it to point one card elsewhere — `https://github.com` for a
|
|
23
|
+
* public project from an Enterprise-hosted docs site, say. The REST base is
|
|
24
|
+
* derived from it the same way `github.api` is derived from `github.host`.
|
|
25
|
+
*/
|
|
26
|
+
host?: string;
|
|
18
27
|
owner?: string;
|
|
19
28
|
repo?: string;
|
|
20
29
|
token?: string;
|
|
21
30
|
}
|
|
22
31
|
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
32
|
+
// `data.config.github` carries the configured repo's coordinates, so the card
|
|
33
|
+
// defaults to it and addresses the right instance — an Enterprise host serves
|
|
34
|
+
// both the link and the API from somewhere other than github.com. Explicit
|
|
35
|
+
// `owner`/`repo` props are read against that same instance unless `host`
|
|
36
|
+
// points them somewhere else; with no `github` configured at all, they fall
|
|
37
|
+
// back to the public one.
|
|
38
|
+
const configured = data.config.github;
|
|
39
|
+
const owner = Astro.props.owner ?? configured?.owner;
|
|
40
|
+
const repo = Astro.props.repo ?? configured?.repo;
|
|
30
41
|
const token = Astro.props.token ?? process.env.GITHUB_TOKEN;
|
|
42
|
+
// A `host` prop is reduced to its origin, as the config field is, so a
|
|
43
|
+
// trailing slash or a path never lands mid-link.
|
|
44
|
+
const host = Astro.props.host
|
|
45
|
+
? new URL(Astro.props.host).origin
|
|
46
|
+
: (configured?.host ?? PUBLIC_HOST_URL);
|
|
47
|
+
const baseUrl = Astro.props.host ? apiUrl({ host }) : configured?.api;
|
|
31
48
|
|
|
32
49
|
const info =
|
|
33
|
-
owner && repo
|
|
50
|
+
owner && repo
|
|
51
|
+
? await fetchRepositoryInfo({ baseUrl, owner, repo, token })
|
|
52
|
+
: null;
|
|
34
53
|
|
|
35
54
|
// Compact notation matches GitHub's own counts (e.g. 1.2k, 34.5k).
|
|
36
55
|
const numbers = new Intl.NumberFormat("en", {
|
|
@@ -46,7 +65,7 @@ const statIcon =
|
|
|
46
65
|
owner && repo && (
|
|
47
66
|
<a
|
|
48
67
|
class="not-prose my-6 flex flex-col gap-2 rounded-blume border border-border p-4 no-underline! transition-colors hover:border-foreground/30 hover:bg-muted/40"
|
|
49
|
-
href={
|
|
68
|
+
href={`${host}/${owner}/${repo}`}
|
|
50
69
|
rel="noreferrer"
|
|
51
70
|
target="_blank"
|
|
52
71
|
>
|
|
@@ -19,6 +19,13 @@ interface Props {
|
|
|
19
19
|
*/
|
|
20
20
|
param?: string;
|
|
21
21
|
sync?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Sync tab selection only with other groups sharing the same key. Groups
|
|
24
|
+
* without a key form one page-wide pool (the default); keyed groups — like
|
|
25
|
+
* the generated ts2js dialect pairs — sync among themselves, so picking a
|
|
26
|
+
* tab there can't drag along an authored group with a same-titled tab.
|
|
27
|
+
*/
|
|
28
|
+
syncKey?: string;
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
const {
|
|
@@ -29,6 +36,7 @@ const {
|
|
|
29
36
|
inline = false,
|
|
30
37
|
param,
|
|
31
38
|
sync = true,
|
|
39
|
+
syncKey,
|
|
32
40
|
} = Astro.props;
|
|
33
41
|
|
|
34
42
|
// MDX string attributes (`hash="false"` from generated markup) must read as
|
|
@@ -52,6 +60,7 @@ const useDropdown = dropdown && !inline;
|
|
|
52
60
|
data-hash={hashEnabled ? "true" : "false"}
|
|
53
61
|
data-param={param}
|
|
54
62
|
data-sync={sync ? "true" : "false"}
|
|
63
|
+
data-sync-key={syncKey}
|
|
55
64
|
>
|
|
56
65
|
<div
|
|
57
66
|
class:list={[
|
|
@@ -116,9 +125,12 @@ const useDropdown = dropdown && !inline;
|
|
|
116
125
|
|
|
117
126
|
connectedCallback() {
|
|
118
127
|
const list = this.querySelector<HTMLElement>("[data-blume-tablist]");
|
|
128
|
+
// Nested groups (a generated ts2js pair inside a CodeGroup, say) own
|
|
129
|
+
// their panels; adopting a descendant group's panels here would leave
|
|
130
|
+
// both groups toggling the same elements.
|
|
119
131
|
let panels = Array.from(
|
|
120
132
|
this.querySelectorAll<HTMLElement>("[data-blume-tab-panel]")
|
|
121
|
-
);
|
|
133
|
+
).filter((panel) => panel.closest("blume-tabs") === this);
|
|
122
134
|
// CodeGroup passes raw code fences instead of <Tab> elements, so adopt
|
|
123
135
|
// the content wrapper's direct code blocks as panels. The tab label comes
|
|
124
136
|
// from each block's title (the text after the language in the fence).
|
|
@@ -251,7 +263,10 @@ const useDropdown = dropdown && !inline;
|
|
|
251
263
|
if (sync && this.#syncEnabled()) {
|
|
252
264
|
document.dispatchEvent(
|
|
253
265
|
new CustomEvent(SYNC_EVENT, {
|
|
254
|
-
detail: {
|
|
266
|
+
detail: {
|
|
267
|
+
key: this.#syncKey(),
|
|
268
|
+
title: panelTitle(activePanel, index),
|
|
269
|
+
},
|
|
255
270
|
})
|
|
256
271
|
);
|
|
257
272
|
}
|
|
@@ -339,6 +354,10 @@ const useDropdown = dropdown && !inline;
|
|
|
339
354
|
return this.dataset.sync !== "false";
|
|
340
355
|
}
|
|
341
356
|
|
|
357
|
+
#syncKey() {
|
|
358
|
+
return this.dataset.syncKey ?? "";
|
|
359
|
+
}
|
|
360
|
+
|
|
342
361
|
#hashEnabled() {
|
|
343
362
|
return this.dataset.hash !== "false";
|
|
344
363
|
}
|
|
@@ -358,9 +377,12 @@ const useDropdown = dropdown && !inline;
|
|
|
358
377
|
}
|
|
359
378
|
|
|
360
379
|
#sync = (event: Event) => {
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
380
|
+
const detail = (event as CustomEvent<{ key?: string; title?: string }>)
|
|
381
|
+
.detail;
|
|
382
|
+
// Only same-key groups sync: keyless groups form one page-wide pool,
|
|
383
|
+
// keyed groups (generated dialect pairs) their own.
|
|
384
|
+
if (detail?.title && (detail.key ?? "") === this.#syncKey()) {
|
|
385
|
+
this.activateByTitle(detail.title, false);
|
|
364
386
|
}
|
|
365
387
|
};
|
|
366
388
|
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* than throwing, so a card never breaks the build when the API is unreachable.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { PUBLIC_API_URL } from "../../core/github.ts";
|
|
11
|
+
|
|
10
12
|
export interface RepositoryInfo {
|
|
11
13
|
description: string | null;
|
|
12
14
|
forks: number;
|
|
@@ -20,18 +22,31 @@ export interface FetchRepositoryOptions {
|
|
|
20
22
|
token?: string;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
const DEFAULT_BASE_URL = "https://api.github.com";
|
|
24
|
-
|
|
25
25
|
/** In-process dedupe so the same repo is fetched once per build. */
|
|
26
26
|
const cache = new Map<string, Promise<RepositoryInfo | null>>();
|
|
27
27
|
|
|
28
|
+
/** Cleartext bases already warned about, so a site of cards logs once. */
|
|
29
|
+
const warnedCleartext = new Set<string>();
|
|
30
|
+
|
|
28
31
|
const load = async (
|
|
29
32
|
options: FetchRepositoryOptions
|
|
30
33
|
): Promise<RepositoryInfo | null> => {
|
|
31
|
-
const { baseUrl =
|
|
34
|
+
const { baseUrl = PUBLIC_API_URL, owner, repo, token } = options;
|
|
32
35
|
const headers = new Headers({ Accept: "application/vnd.github+json" });
|
|
36
|
+
// An Enterprise instance can be configured on plain HTTP; a bearer token is
|
|
37
|
+
// never worth putting on the wire in cleartext, so it is dropped rather than
|
|
38
|
+
// sent. The request still goes out — a public repo's counts render either way
|
|
39
|
+
// — but a private repo's card comes back bare, which looks exactly like a
|
|
40
|
+
// network failure, so say why once per base.
|
|
33
41
|
if (token) {
|
|
34
|
-
|
|
42
|
+
if (new URL(baseUrl).protocol === "https:") {
|
|
43
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
44
|
+
} else if (!warnedCleartext.has(baseUrl)) {
|
|
45
|
+
warnedCleartext.add(baseUrl);
|
|
46
|
+
console.warn(
|
|
47
|
+
`[blume] <GithubInfo> is not sending its token to ${baseUrl}: the API base is plain HTTP, so the bearer token would travel in cleartext. Counts for a private repository will be missing; serve the API over https to authenticate.`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
35
50
|
}
|
|
36
51
|
|
|
37
52
|
const response = await fetch(`${baseUrl}/repos/${owner}/${repo}`, {
|
|
@@ -69,7 +84,7 @@ const loadSafe = async (
|
|
|
69
84
|
export const fetchRepositoryInfo = (
|
|
70
85
|
options: FetchRepositoryOptions
|
|
71
86
|
): Promise<RepositoryInfo | null> => {
|
|
72
|
-
const key = `${options.baseUrl ??
|
|
87
|
+
const key = `${options.baseUrl ?? PUBLIC_API_URL}/${options.owner}/${options.repo}`;
|
|
73
88
|
const existing = cache.get(key);
|
|
74
89
|
if (existing) {
|
|
75
90
|
return existing;
|