blume 1.6.1 → 1.6.3
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 +12 -0
- package/dist/cli/index.js +907 -152
- package/dist/cli/index.js.map +20 -16
- package/dist/types/core/config-input.d.ts +9 -0
- package/dist/types/core/data.d.ts +2 -0
- package/dist/types/core/i18n-ui.d.ts +2 -0
- package/dist/types/core/schema.d.ts +2 -0
- package/docs/advanced/custom-pages.mdx +1 -1
- package/docs/configuration/ai.mdx +72 -7
- package/docs/content/components.mdx +1 -1
- package/docs/index.mdx +2 -2
- package/package.json +1 -1
- package/skills/blume/SKILL.md +2 -2
- package/src/ai/agent-readability.ts +60 -17
- package/src/ai/api/handlers.ts +273 -0
- package/src/ai/api/paths.ts +14 -0
- package/src/ai/api/problem.ts +63 -0
- package/src/ai/api/spec.ts +681 -0
- package/src/ai/api-catalog.ts +11 -1
- package/src/ai/link-headers.ts +12 -3
- package/src/ai/llms.ts +9 -2
- package/src/ai/mcp/query.ts +390 -0
- package/src/ai/mcp/server.ts +32 -352
- package/src/astro/generate.ts +166 -12
- package/src/astro/templates.ts +157 -0
- package/src/cli/commands/build.ts +8 -6
- package/src/components/content/Component.astro +5 -9
- package/src/components/content/Tabs.astro +24 -9
- package/src/components/layout/RootLayout.astro +105 -36
- package/src/core/config-input.ts +9 -0
- package/src/core/data.ts +7 -1
- package/src/core/i18n-ui.ts +2 -0
- package/src/core/schema.ts +7 -0
- package/src/deploy/vercel-negotiation.ts +56 -8
- package/src/theme/code-block-padding.ts +0 -8
- package/src/theme/entry.ts +14 -25
|
@@ -798,9 +798,55 @@ const bannerKey = banner?.dismissible ? banner.key : null;
|
|
|
798
798
|
// Opaque icon chip over the code (in the style of Lina's code block),
|
|
799
799
|
// with the check icon scale-swapping in over the copy icon after a copy.
|
|
800
800
|
const buttonClass =
|
|
801
|
-
"
|
|
801
|
+
"inline-flex size-[1.875rem] select-none items-center justify-center rounded-md text-muted-foreground bg-background transition-colors [&_svg]:pointer-events-none [&_svg]:shrink-0";
|
|
802
|
+
// Pinned over the block's top-right corner when the button lives in
|
|
803
|
+
// the pre; a strip-hosted button (see codeGroupActions) is in flow.
|
|
804
|
+
const overlayClass = "absolute right-3 z-[2]";
|
|
802
805
|
const idleClasses = ["hover:bg-muted", "hover:text-foreground"];
|
|
803
806
|
|
|
807
|
+
// A tab panel that is nothing but code: a CodeGroup/ts2js block (the
|
|
808
|
+
// pre is the panel) or a <Tab> holding only fences and the icon
|
|
809
|
+
// template — the same predicate Tab.astro's flush-padding selector uses.
|
|
810
|
+
const isCodePanel = (panel: Element) =>
|
|
811
|
+
panel.matches("pre, figure") ||
|
|
812
|
+
(panel.matches("[data-blume-tab-panel]") &&
|
|
813
|
+
panel.querySelector(":scope > pre") !== null &&
|
|
814
|
+
Array.from(panel.children).every((child) =>
|
|
815
|
+
child.matches("pre, template")
|
|
816
|
+
));
|
|
817
|
+
|
|
818
|
+
// The tab group's actions slot, when the group is a code switcher —
|
|
819
|
+
// every panel a bare block — so one copy button in the strip serves
|
|
820
|
+
// whichever panel is showing. A group with prose panels (or none) gets
|
|
821
|
+
// null and keeps a button in each pre.
|
|
822
|
+
const codeGroupActions = (pre: Element) => {
|
|
823
|
+
const tabs = pre.closest("blume-tabs");
|
|
824
|
+
const content = tabs?.querySelector(":scope > [data-blume-tab-content]");
|
|
825
|
+
const panels = content ? Array.from(content.children) : [];
|
|
826
|
+
if (!tabs || panels.length === 0 || !panels.every(isCodePanel)) {
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
return tabs.querySelector<HTMLElement>(
|
|
830
|
+
":scope > * > [data-blume-tab-actions]"
|
|
831
|
+
);
|
|
832
|
+
};
|
|
833
|
+
|
|
834
|
+
// The block the strip's copy button should read: the visible panel's
|
|
835
|
+
// pre. Panels toggle a `hidden` class once the tabs element upgrades;
|
|
836
|
+
// before that only the first is painted, and it is also the first
|
|
837
|
+
// match here.
|
|
838
|
+
const activeCodePre = (tabs: Element) => {
|
|
839
|
+
const content = tabs.querySelector(":scope > [data-blume-tab-content]");
|
|
840
|
+
const panel =
|
|
841
|
+
Array.from(content?.children ?? []).find(
|
|
842
|
+
(child) => !child.classList.contains("hidden")
|
|
843
|
+
) ?? content?.firstElementChild;
|
|
844
|
+
if (!panel) {
|
|
845
|
+
return null;
|
|
846
|
+
}
|
|
847
|
+
return panel.matches("pre") ? panel : panel.querySelector("pre");
|
|
848
|
+
};
|
|
849
|
+
|
|
804
850
|
const languageLabels: Record<string, string> = {
|
|
805
851
|
astro: "Astro",
|
|
806
852
|
bash: "Bash",
|
|
@@ -836,41 +882,16 @@ const bannerKey = banner?.dismissible ? banner.key : null;
|
|
|
836
882
|
const copiedLabel =
|
|
837
883
|
document.body.getAttribute("data-i18n-copied") || "Copied!";
|
|
838
884
|
|
|
839
|
-
for
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
continue;
|
|
847
|
-
}
|
|
848
|
-
const language = pre.getAttribute("data-language");
|
|
849
|
-
if (language) {
|
|
850
|
-
pre.setAttribute(
|
|
851
|
-
"data-language",
|
|
852
|
-
languageLabels[language.toLowerCase()] ?? language
|
|
853
|
-
);
|
|
854
|
-
}
|
|
855
|
-
pre.classList.add("group", "relative");
|
|
856
|
-
// The code element is the scroll container (see the theme entry), but
|
|
857
|
-
// Shiki's tab stop lands on the pre, which no longer scrolls. Move the
|
|
858
|
-
// stop to the code so keyboard users can actually scroll the block
|
|
859
|
-
// (WCAG 2.1.1 — the same rule the table wrapper handles). Twoslash and
|
|
860
|
-
// API-panel blocks keep the pre as their scroller, so theirs stays.
|
|
861
|
-
const scroller = pre.querySelector("code");
|
|
862
|
-
if (scroller && !pre.matches(".twoslash, blume-panel-tabs *")) {
|
|
863
|
-
scroller.setAttribute("tabindex", "0");
|
|
864
|
-
pre.removeAttribute("tabindex");
|
|
865
|
-
}
|
|
885
|
+
// `placement` is the extra class set for an overlay button (empty for
|
|
886
|
+
// one hosted by a tab strip); `readPre` resolves the block to copy at
|
|
887
|
+
// click time, since a strip button follows the active panel.
|
|
888
|
+
const createCopyButton = (
|
|
889
|
+
placement: string,
|
|
890
|
+
readPre: () => Element | null
|
|
891
|
+
) => {
|
|
866
892
|
const button = document.createElement("button");
|
|
867
893
|
button.type = "button";
|
|
868
|
-
|
|
869
|
-
// offset; pick the Tailwind class by context instead of a CSS override.
|
|
870
|
-
const topClass = pre.closest("blume-tabs, .not-prose")
|
|
871
|
-
? "top-2.5"
|
|
872
|
-
: "top-2";
|
|
873
|
-
button.className = `${buttonClass} ${idleClasses.join(" ")} ${topClass}`;
|
|
894
|
+
button.className = `${buttonClass} ${idleClasses.join(" ")} ${placement}`;
|
|
874
895
|
button.setAttribute("data-blume-copy", "");
|
|
875
896
|
button.setAttribute("aria-label", copyCodeLabel);
|
|
876
897
|
button.innerHTML =
|
|
@@ -896,7 +917,7 @@ const bannerKey = banner?.dismissible ? banner.key : null;
|
|
|
896
917
|
};
|
|
897
918
|
const flash = createCopyFlash(setChecked, copiedLabel);
|
|
898
919
|
button.addEventListener("click", async () => {
|
|
899
|
-
const code =
|
|
920
|
+
const code = readPre()?.querySelector("code");
|
|
900
921
|
let text = code?.textContent ?? "";
|
|
901
922
|
// Twoslash nests each hover popup's type signature and docs inside
|
|
902
923
|
// the <code>; copying textContent verbatim would interleave them
|
|
@@ -914,7 +935,55 @@ const bannerKey = banner?.dismissible ? banner.key : null;
|
|
|
914
935
|
flash();
|
|
915
936
|
}
|
|
916
937
|
});
|
|
917
|
-
|
|
938
|
+
return button;
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
for (const pre of document.querySelectorAll(".prose pre")) {
|
|
942
|
+
if (pre.querySelector("[data-blume-copy]")) {
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
// Skip nested <pre>: Twoslash renders each hover popup's type signature
|
|
946
|
+
// as a <pre> inside the code block, which shouldn't get its own button.
|
|
947
|
+
if (pre.parentElement?.closest("pre")) {
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
const language = pre.getAttribute("data-language");
|
|
951
|
+
if (language) {
|
|
952
|
+
pre.setAttribute(
|
|
953
|
+
"data-language",
|
|
954
|
+
languageLabels[language.toLowerCase()] ?? language
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
pre.classList.add("group", "relative");
|
|
958
|
+
// The code element is the scroll container (see the theme entry), but
|
|
959
|
+
// Shiki's tab stop lands on the pre, which no longer scrolls. Move the
|
|
960
|
+
// stop to the code so keyboard users can actually scroll the block
|
|
961
|
+
// (WCAG 2.1.1 — the same rule the table wrapper handles). Twoslash and
|
|
962
|
+
// API-panel blocks keep the pre as their scroller, so theirs stays.
|
|
963
|
+
const scroller = pre.querySelector("code");
|
|
964
|
+
if (scroller && !pre.matches(".twoslash, blume-panel-tabs *")) {
|
|
965
|
+
scroller.setAttribute("tabindex", "0");
|
|
966
|
+
pre.removeAttribute("tabindex");
|
|
967
|
+
}
|
|
968
|
+
const actions = codeGroupActions(pre);
|
|
969
|
+
if (actions) {
|
|
970
|
+
// One button per code switcher, reading whichever panel shows.
|
|
971
|
+
if (!actions.querySelector("[data-blume-copy]")) {
|
|
972
|
+
const tabs = actions.closest("blume-tabs");
|
|
973
|
+
actions.appendChild(
|
|
974
|
+
createCopyButton("", () => (tabs ? activeCodePre(tabs) : null))
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
// The language-label bar (prose) vs flush code (tabs) need a different
|
|
980
|
+
// offset; pick the Tailwind class by context instead of a CSS override.
|
|
981
|
+
const topClass = pre.closest("blume-tabs, .not-prose")
|
|
982
|
+
? "top-2.5"
|
|
983
|
+
: "top-2";
|
|
984
|
+
pre.appendChild(
|
|
985
|
+
createCopyButton(`${overlayClass} ${topClass}`, () => pre)
|
|
986
|
+
);
|
|
918
987
|
}
|
|
919
988
|
|
|
920
989
|
// Click-to-zoom for content images (gated by `markdown.imageZoom`),
|
package/src/core/config-input.ts
CHANGED
|
@@ -788,6 +788,15 @@ export interface McpConfig {
|
|
|
788
788
|
* hosted MCP server.
|
|
789
789
|
*/
|
|
790
790
|
export interface AiConfig {
|
|
791
|
+
/**
|
|
792
|
+
* The JSON docs API — the REST twin of the MCP tools. Serves the page index
|
|
793
|
+
* (`/api/docs/pages.json`), each page as JSON (`/api/docs/pages/{route}.json`),
|
|
794
|
+
* and the navigation tree (`/api/docs/navigation.json`) as prerendered
|
|
795
|
+
* files, plus full-text search (`/api/docs/search?q=`) on server output,
|
|
796
|
+
* all described by an OpenAPI document at `/openapi.json`. Errors are RFC
|
|
797
|
+
* 9457 problem details. Defaults to `true`; set `false` to publish none of it.
|
|
798
|
+
*/
|
|
799
|
+
api?: boolean;
|
|
791
800
|
/** The Ask AI chat assistant. */
|
|
792
801
|
ask?: AskConfig;
|
|
793
802
|
/**
|
package/src/core/data.ts
CHANGED
|
@@ -138,7 +138,13 @@ export interface BlumeDataConfig {
|
|
|
138
138
|
* `deployment.site`, the condition under which one is emitted) feeds the
|
|
139
139
|
* 404 page's recovery links rather than the head.
|
|
140
140
|
*/
|
|
141
|
-
discovery: {
|
|
141
|
+
discovery: {
|
|
142
|
+
agentReadability: boolean;
|
|
143
|
+
/** Whether the JSON docs API and its `/openapi.json` are published. */
|
|
144
|
+
api: boolean;
|
|
145
|
+
llmsTxt: boolean;
|
|
146
|
+
sitemap: boolean;
|
|
147
|
+
};
|
|
142
148
|
favicon: BlumeFavicon;
|
|
143
149
|
feedback: boolean;
|
|
144
150
|
/**
|
package/src/core/i18n-ui.ts
CHANGED
|
@@ -109,6 +109,8 @@ const uiStringsObject = z.object({
|
|
|
109
109
|
.prefault({}),
|
|
110
110
|
notFound: z
|
|
111
111
|
.object({
|
|
112
|
+
/** Label of the OpenAPI description link on the Markdown/JSON 404. */
|
|
113
|
+
api: z.string().default("JSON API description (openapi.json)"),
|
|
112
114
|
description: z
|
|
113
115
|
.string()
|
|
114
116
|
.default("We couldn't find the page you're looking for."),
|
package/src/core/schema.ts
CHANGED
|
@@ -838,6 +838,13 @@ const llmsTxtObjectSchema = z.strictObject({
|
|
|
838
838
|
type LlmsTxtResolved = z.output<typeof llmsTxtObjectSchema>;
|
|
839
839
|
|
|
840
840
|
const aiConfigSchema = z.strictObject({
|
|
841
|
+
/**
|
|
842
|
+
* The JSON docs API: the page index, per-page JSON, and navigation under
|
|
843
|
+
* `/api/docs/` (prerendered, so a static site serves them from files), the
|
|
844
|
+
* live search endpoint on server output, and the OpenAPI description of
|
|
845
|
+
* the whole machine-readable surface at `/openapi.json`. On by default.
|
|
846
|
+
*/
|
|
847
|
+
api: z.boolean().default(true),
|
|
841
848
|
ask: z
|
|
842
849
|
.strictObject({
|
|
843
850
|
// Name of the env var holding the provider's API key; each provider has
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
* counterpart of the dev-server rewrite in `astro/markdown-negotiation.ts`.
|
|
12
12
|
* The same routing config also answers a *missing* page: a request that
|
|
13
13
|
* prefers Markdown (or asks for a `.md` URL no page backs) gets the
|
|
14
|
-
* prerendered Markdown 404 body with the 404 status, instead of the HTML
|
|
14
|
+
* prerendered Markdown 404 body with the 404 status, instead of the HTML
|
|
15
|
+
* shell, and one that prefers JSON (or asks for a `.json` URL) gets the
|
|
16
|
+
* prerendered problem-details 404.
|
|
15
17
|
*/
|
|
16
18
|
|
|
17
19
|
/**
|
|
@@ -27,6 +29,15 @@
|
|
|
27
29
|
export const ACCEPT_MARKDOWN_HEADER_VALUE =
|
|
28
30
|
"(.*,)?\\s*text/(x-)?markdown(\\s*[;,].*)?$";
|
|
29
31
|
|
|
32
|
+
/**
|
|
33
|
+
* The JSON counterpart, for the problem-details 404: `application/json` or
|
|
34
|
+
* `application/problem+json`. Browsers never send either on a navigation
|
|
35
|
+
* (the catch-all wildcard does not match), so ordinary page requests are
|
|
36
|
+
* unaffected.
|
|
37
|
+
*/
|
|
38
|
+
export const ACCEPT_JSON_HEADER_VALUE =
|
|
39
|
+
"(.*,)?\\s*application/(problem\\+)?json(\\s*[;,].*)?$";
|
|
40
|
+
|
|
30
41
|
/**
|
|
31
42
|
* A Build Output API route — the subset these helpers read and write. Parsed
|
|
32
43
|
* routes keep whatever other fields they carry at runtime; only these are
|
|
@@ -50,11 +61,18 @@ const ACCEPT_MARKDOWN_CONDITION: VercelRoute["has"] = [
|
|
|
50
61
|
{ key: "accept", type: "header", value: ACCEPT_MARKDOWN_HEADER_VALUE },
|
|
51
62
|
];
|
|
52
63
|
|
|
64
|
+
const ACCEPT_JSON_CONDITION: VercelRoute["has"] = [
|
|
65
|
+
{ key: "accept", type: "header", value: ACCEPT_JSON_HEADER_VALUE },
|
|
66
|
+
];
|
|
67
|
+
|
|
53
68
|
const VARY_ACCEPT = { vary: "Accept" };
|
|
54
69
|
|
|
55
70
|
/** Where the prerendered Markdown 404 (`pages/404.md.ts`) lands. */
|
|
56
71
|
const NOT_FOUND_MARKDOWN_DEST = "/404.md";
|
|
57
72
|
|
|
73
|
+
/** Where the prerendered JSON 404 (`pages/404.json.ts`) lands. */
|
|
74
|
+
const NOT_FOUND_JSON_DEST = "/404.json";
|
|
75
|
+
|
|
58
76
|
/** The adapter's own not-found fallback — the anchor the Markdown 404 precedes. */
|
|
59
77
|
const NOT_FOUND_HTML_DEST = "/404.html";
|
|
60
78
|
|
|
@@ -78,6 +96,30 @@ const NOT_FOUND_MARKDOWN_ROUTES: readonly VercelRoute[] = [
|
|
|
78
96
|
{ dest: NOT_FOUND_MARKDOWN_DEST, src: "^/.*\\.mdx?$", status: 404 },
|
|
79
97
|
];
|
|
80
98
|
|
|
99
|
+
/**
|
|
100
|
+
* The JSON 404's miss-phase routes, the problem-details twin of the Markdown
|
|
101
|
+
* ones: any path when the client prefers JSON, and any `.json` URL no file
|
|
102
|
+
* backs. Spliced at the same anchor, after every server route — so the
|
|
103
|
+
* `/api/` catch-all (which answers its own namespace with a problem document)
|
|
104
|
+
* has already had its turn.
|
|
105
|
+
*/
|
|
106
|
+
const NOT_FOUND_JSON_ROUTES: readonly VercelRoute[] = [
|
|
107
|
+
{
|
|
108
|
+
dest: NOT_FOUND_JSON_DEST,
|
|
109
|
+
has: ACCEPT_JSON_CONDITION,
|
|
110
|
+
headers: VARY_ACCEPT,
|
|
111
|
+
src: "^/.*$",
|
|
112
|
+
status: 404,
|
|
113
|
+
},
|
|
114
|
+
{ dest: NOT_FOUND_JSON_DEST, src: "^/.*\\.json$", status: 404 },
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
/** Which prerendered 404 twins the build emitted, so their routes get wired. */
|
|
118
|
+
export interface NotFoundVariants {
|
|
119
|
+
json?: boolean;
|
|
120
|
+
markdown?: boolean;
|
|
121
|
+
}
|
|
122
|
+
|
|
81
123
|
/**
|
|
82
124
|
* Vercel rejects route `src` patterns longer than 4096 characters, so route
|
|
83
125
|
* alternations are split across as many route entries as needed. The budget
|
|
@@ -223,6 +265,7 @@ const isNegotiationRoute = (route: VercelRoute): boolean =>
|
|
|
223
265
|
(condition) => condition.value === ACCEPT_MARKDOWN_HEADER_VALUE
|
|
224
266
|
) === true ||
|
|
225
267
|
(route.dest === NOT_FOUND_MARKDOWN_DEST && route.status === 404) ||
|
|
268
|
+
(route.dest === NOT_FOUND_JSON_DEST && route.status === 404) ||
|
|
226
269
|
(route.continue === true &&
|
|
227
270
|
route.headers?.vary === "Accept" &&
|
|
228
271
|
isString(route.src) &&
|
|
@@ -244,10 +287,11 @@ const isNegotiationRoute = (route: VercelRoute): boolean =>
|
|
|
244
287
|
* platform's mechanism for extensionless static files (e.g. the Web Bot Auth
|
|
245
288
|
* signature directory). The trailing-slash 308 redirect is always spliced in
|
|
246
289
|
* alongside, so slashed duplicates of every page collapse onto the canonical
|
|
247
|
-
* slashless URL.
|
|
248
|
-
*
|
|
249
|
-
* `/404.html` fallback — and nowhere when
|
|
250
|
-
* `dest` with no file behind it would serve
|
|
290
|
+
* slashless URL. For each 404 twin the build emitted (`notFound.markdown` for
|
|
291
|
+
* `404.md`, `notFound.json` for `404.json`), its routes go into the miss
|
|
292
|
+
* phase right before the adapter's `/404.html` fallback — and nowhere when
|
|
293
|
+
* that fallback is absent, since a `dest` with no file behind it would serve
|
|
294
|
+
* nothing. Returns the updated JSON
|
|
251
295
|
* text (tab-indented, like the adapter's own output), or `null` when there is
|
|
252
296
|
* nowhere safe to splice: an unparsable config, no `routes` array, or no
|
|
253
297
|
* `handle: "filesystem"` marker to anchor the splice.
|
|
@@ -258,7 +302,7 @@ export const injectNegotiationRoutes = (
|
|
|
258
302
|
homeLinkHeader?: string | null,
|
|
259
303
|
contentTypeOverrides?: Record<string, string>,
|
|
260
304
|
homeTokens?: number,
|
|
261
|
-
|
|
305
|
+
notFound: NotFoundVariants = {}
|
|
262
306
|
): string | null => {
|
|
263
307
|
const overrideEntries = Object.entries(contentTypeOverrides ?? {});
|
|
264
308
|
let config: {
|
|
@@ -307,12 +351,16 @@ export const injectNegotiationRoutes = (
|
|
|
307
351
|
...rewriteRoutes,
|
|
308
352
|
TRAILING_SLASH_REDIRECT
|
|
309
353
|
);
|
|
310
|
-
|
|
354
|
+
const notFoundRoutes = [
|
|
355
|
+
...(notFound.markdown ? NOT_FOUND_MARKDOWN_ROUTES : []),
|
|
356
|
+
...(notFound.json ? NOT_FOUND_JSON_ROUTES : []),
|
|
357
|
+
];
|
|
358
|
+
if (notFoundRoutes.length > 0) {
|
|
311
359
|
const fallbackIndex = routes.findIndex(
|
|
312
360
|
(route) => route.status === 404 && route.dest === NOT_FOUND_HTML_DEST
|
|
313
361
|
);
|
|
314
362
|
if (fallbackIndex !== -1) {
|
|
315
|
-
routes.splice(fallbackIndex, 0, ...
|
|
363
|
+
routes.splice(fallbackIndex, 0, ...notFoundRoutes);
|
|
316
364
|
}
|
|
317
365
|
}
|
|
318
366
|
config.routes = routes;
|
|
@@ -6,11 +6,3 @@
|
|
|
6
6
|
|
|
7
7
|
/** Top and bottom inset of a plain prose block with no chrome, in rem. */
|
|
8
8
|
export const CODE_PADDING_BLOCK_REM = 1;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Top inset of a flush block — inside tabs or a `not-prose` component, or an
|
|
12
|
-
* untitled block with no language bar — where the layout's copy button is
|
|
13
|
-
* absolutely positioned over the first line: `top-2.5` plus a 1.875rem button
|
|
14
|
-
* lands at 2.5rem, so the first line starts there.
|
|
15
|
-
*/
|
|
16
|
-
export const FLUSH_CODE_PADDING_TOP_REM = 2.5;
|
package/src/theme/entry.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CODE_PADDING_BLOCK_REM,
|
|
3
|
-
FLUSH_CODE_PADDING_TOP_REM,
|
|
4
|
-
} from "./code-block-padding.ts";
|
|
1
|
+
import { CODE_PADDING_BLOCK_REM } from "./code-block-padding.ts";
|
|
5
2
|
|
|
6
3
|
interface TailwindEntryOptions {
|
|
7
4
|
/**
|
|
@@ -614,27 +611,19 @@ blume-tabs pre[data-language],
|
|
|
614
611
|
padding-top: ${CODE_PADDING_BLOCK_REM}rem;
|
|
615
612
|
}
|
|
616
613
|
|
|
617
|
-
/*
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
\`.astro-code\`, no language bar, but the injector gives it a button all
|
|
631
|
-
the same — keyed on that injected button, which is exactly what separates
|
|
632
|
-
it from the playground's pre. API panel blocks match, but their own
|
|
633
|
-
!important padding wins and they hide the injected button. */
|
|
634
|
-
[data-blume-code-copy] :is(blume-tabs, .not-prose) pre.astro-code,
|
|
635
|
-
[data-blume-code-copy] .prose pre.astro-code:not([data-language]),
|
|
636
|
-
[data-blume-code-copy] .prose pre:not([data-language]):has(> [data-blume-copy]) {
|
|
637
|
-
padding-top: ${FLUSH_CODE_PADDING_TOP_REM}rem;
|
|
614
|
+
/* Where no language bar holds the copy button — a flush block in tabs or a
|
|
615
|
+
not-prose component, a bar-less block in prose — the docs layout still pins
|
|
616
|
+
one over the block's top-right corner, so a first line long enough to reach
|
|
617
|
+
it would end underneath. A code switcher (CodeGroup, ts2js) hosts the button
|
|
618
|
+
in its tab strip instead and needs nothing here; for every block that keeps
|
|
619
|
+
an overlay button, give the scrolling code element enough end padding that
|
|
620
|
+
the line's tail clears the button at the end of its scroll. Keyed on the
|
|
621
|
+
injected button itself (which the playground's client-created response pre
|
|
622
|
+
never gets), and \`.prose\`-scoped to outrank the base \`:where(pre code)\`
|
|
623
|
+
inset. Not a vertical inset — that reserved a strip above every one-line
|
|
624
|
+
command and read as a rendering bug. */
|
|
625
|
+
.prose :is(blume-tabs pre, .not-prose pre, pre:not([data-language])):not(.twoslash, .twoslash pre, blume-panel-tabs *):has(> [data-blume-copy]) > code {
|
|
626
|
+
padding-inline-end: 3.5rem;
|
|
638
627
|
}
|
|
639
628
|
|
|
640
629
|
blume-tabs pre[data-language]::before,
|