blume 2.2.1 → 2.2.2
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
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# blume
|
|
2
2
|
|
|
3
|
+
## 2.2.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 6143000: A page actions menu (Export, Open in chat, Connect to MCP) now wraps a label too long for the right rail, as in many of the built-in translations (French, Portuguese, Russian, Swedish, and others) or with a long custom MCP client label. Before, the menu could reach past the rail's edge, and the rail, which scrolls, cut it off. A wrapped label keeps its icons beside its first line, and in a short window a menu opens upward only when it fits below the header, where the rail can show it, instead of tucking its top rows under the header.
|
|
8
|
+
- ffbb7c0: The API reference playground's proxy (`playground: { proxy: true }`) now works on Cloudflare Workers. Before, every Try it request it forwarded to one of the spec's servers failed with a 502 "Illegal invocation" error, because the proxy called the runtime's `fetch` in a way Workers rejects and Node allows.
|
|
9
|
+
|
|
3
10
|
## 2.2.1
|
|
4
11
|
|
|
5
12
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -115,11 +115,17 @@ const rowClass =
|
|
|
115
115
|
"flex w-full items-center gap-2.5 py-1.5 text-start text-muted-foreground text-sm transition-colors hover:text-foreground";
|
|
116
116
|
|
|
117
117
|
// Items inside the floating popovers keep menu affordances (padding + hover
|
|
118
|
-
// background).
|
|
118
|
+
// background). Their icons sit in a box one line tall, so a label that wraps
|
|
119
|
+
// keeps them beside its first line.
|
|
119
120
|
const menuRowClass =
|
|
120
|
-
"flex w-full items-
|
|
121
|
+
"flex w-full items-start gap-2.5 rounded-md px-2 py-1.5 text-start text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground";
|
|
122
|
+
const menuIconClass = "flex h-lh shrink-0 items-center";
|
|
123
|
+
// At most out to the rail's edge, past its padding (`--blume-rail-inset`, set
|
|
124
|
+
// by the layout), less 2px so the border never lands on the edge: the rail
|
|
125
|
+
// scrolls, so it clips anything wider. Longer labels wrap instead. The minimum
|
|
126
|
+
// width gives way to that cap in a narrower rail.
|
|
121
127
|
const panelClass =
|
|
122
|
-
"absolute top-full
|
|
128
|
+
"absolute top-full end-0 z-50 mt-1 w-max [--blume-menu-room:calc(100%+var(--blume-rail-inset,1rem)-2px)] min-w-[min(14rem,var(--blume-menu-room))] max-w-(--blume-menu-room) wrap-anywhere rounded-blume border border-border bg-background p-1 shadow-xl";
|
|
123
129
|
---
|
|
124
130
|
|
|
125
131
|
<div
|
|
@@ -172,13 +178,17 @@ const panelClass =
|
|
|
172
178
|
<div class={panelClass} data-blume-menu>
|
|
173
179
|
{exportPdf && (
|
|
174
180
|
<button class={menuRowClass} data-blume-export-pdf type="button">
|
|
175
|
-
<
|
|
181
|
+
<span class={menuIconClass}>
|
|
182
|
+
<Icon name="file" size={16} />
|
|
183
|
+
</span>
|
|
176
184
|
<span class="flex-1">{a.exportPdf}</span>
|
|
177
185
|
</button>
|
|
178
186
|
)}
|
|
179
187
|
{exportEpub && (
|
|
180
188
|
<button class={menuRowClass} data-blume-export-epub type="button">
|
|
181
|
-
<
|
|
189
|
+
<span class={menuIconClass}>
|
|
190
|
+
<Icon name="book-open" size={16} />
|
|
191
|
+
</span>
|
|
182
192
|
<span class="flex-1" data-blume-epub-label>
|
|
183
193
|
{a.exportEpub}
|
|
184
194
|
</span>
|
|
@@ -214,9 +224,11 @@ const panelClass =
|
|
|
214
224
|
data-open-in={provider.key}
|
|
215
225
|
{...NEW_TAB_ATTRS}
|
|
216
226
|
>
|
|
217
|
-
<span set:html={LOGOS[provider.key]} />
|
|
227
|
+
<span class={menuIconClass} set:html={LOGOS[provider.key]} />
|
|
218
228
|
<span class="flex-1">{provider.label}</span>
|
|
219
|
-
<
|
|
229
|
+
<span class={menuIconClass}>
|
|
230
|
+
<Icon name="external-link" size={13} />
|
|
231
|
+
</span>
|
|
220
232
|
</a>
|
|
221
233
|
</>
|
|
222
234
|
))}
|
|
@@ -241,7 +253,9 @@ const panelClass =
|
|
|
241
253
|
</summary>
|
|
242
254
|
<div class={panelClass} data-blume-menu>
|
|
243
255
|
<button class={menuRowClass} data-mcp-copy-url type="button">
|
|
244
|
-
<
|
|
256
|
+
<span class={menuIconClass}>
|
|
257
|
+
<Icon name="link" size={16} />
|
|
258
|
+
</span>
|
|
245
259
|
<span class="flex-1" data-mcp-url-label>
|
|
246
260
|
{a.copyServerUrl}
|
|
247
261
|
</span>
|
|
@@ -255,7 +269,7 @@ const panelClass =
|
|
|
255
269
|
data-mcp-copy-claude
|
|
256
270
|
type="button"
|
|
257
271
|
>
|
|
258
|
-
<span set:html={LOGOS.claude} />
|
|
272
|
+
<span class={menuIconClass} set:html={LOGOS.claude} />
|
|
259
273
|
<span class="flex-1" data-mcp-claude-label>
|
|
260
274
|
{a.copyClaudeCode}
|
|
261
275
|
</span>
|
|
@@ -263,7 +277,7 @@ const panelClass =
|
|
|
263
277
|
)}
|
|
264
278
|
{client === "codex" && (
|
|
265
279
|
<button class={menuRowClass} data-mcp-copy-codex type="button">
|
|
266
|
-
<span set:html={LOGOS.codex} />
|
|
280
|
+
<span class={menuIconClass} set:html={LOGOS.codex} />
|
|
267
281
|
<span class="flex-1" data-mcp-codex-label>
|
|
268
282
|
{a.copyCodex}
|
|
269
283
|
</span>
|
|
@@ -271,16 +285,22 @@ const panelClass =
|
|
|
271
285
|
)}
|
|
272
286
|
{client === "cursor" && (
|
|
273
287
|
<a class={menuRowClass} data-mcp-cursor rel="noreferrer">
|
|
274
|
-
<span set:html={LOGOS.cursor} />
|
|
288
|
+
<span class={menuIconClass} set:html={LOGOS.cursor} />
|
|
275
289
|
<span class="flex-1">{a.addToCursor}</span>
|
|
276
|
-
<
|
|
290
|
+
<span class={menuIconClass}>
|
|
291
|
+
<Icon name="external-link" size={13} />
|
|
292
|
+
</span>
|
|
277
293
|
</a>
|
|
278
294
|
)}
|
|
279
295
|
{client === "vscode" && (
|
|
280
296
|
<a class={menuRowClass} data-mcp-vscode rel="noreferrer">
|
|
281
|
-
<
|
|
297
|
+
<span class={menuIconClass}>
|
|
298
|
+
<Icon name="code" size={16} />
|
|
299
|
+
</span>
|
|
282
300
|
<span class="flex-1">{a.addToVscode}</span>
|
|
283
|
-
<
|
|
301
|
+
<span class={menuIconClass}>
|
|
302
|
+
<Icon name="external-link" size={13} />
|
|
303
|
+
</span>
|
|
284
304
|
</a>
|
|
285
305
|
)}
|
|
286
306
|
{!isBuiltInMcpClient(client) && (
|
|
@@ -289,7 +309,9 @@ const panelClass =
|
|
|
289
309
|
data-mcp-copy-command={client.command}
|
|
290
310
|
type="button"
|
|
291
311
|
>
|
|
292
|
-
<
|
|
312
|
+
<span class={menuIconClass}>
|
|
313
|
+
<Icon name={client.icon ?? "terminal"} size={16} />
|
|
314
|
+
</span>
|
|
293
315
|
<span class="flex-1" data-mcp-command-label>
|
|
294
316
|
{client.label}
|
|
295
317
|
</span>
|
|
@@ -442,10 +464,15 @@ hr { border: 0; border-top: 1px solid #ddd; margin: 2em 0; }`;
|
|
|
442
464
|
return;
|
|
443
465
|
}
|
|
444
466
|
const rect = summary.getBoundingClientRect();
|
|
467
|
+
// The rail scrolls, so it clips the menu at its own edges: its top sits
|
|
468
|
+
// under the sticky header, below the viewport's.
|
|
469
|
+
const room = details
|
|
470
|
+
.closest("[data-blume-toc]")
|
|
471
|
+
?.getBoundingClientRect() ?? { bottom: window.innerHeight, top: 0 };
|
|
445
472
|
const margin = 8;
|
|
446
473
|
const flipUp =
|
|
447
|
-
rect.bottom + menu.offsetHeight + margin >
|
|
448
|
-
rect.top - menu.offsetHeight - margin >
|
|
474
|
+
rect.bottom + menu.offsetHeight + margin > room.bottom &&
|
|
475
|
+
rect.top - menu.offsetHeight - margin > room.top;
|
|
449
476
|
menu.classList.toggle("top-full", !flipUp);
|
|
450
477
|
menu.classList.toggle("mt-1", !flipUp);
|
|
451
478
|
menu.classList.toggle("bottom-full", flipUp);
|
|
@@ -513,7 +540,7 @@ hr { border: 0; border-top: 1px solid #ddd; margin: 2em 0; }`;
|
|
|
513
540
|
}
|
|
514
541
|
// The action dropdowns are native <details>. Make them behave like a proper
|
|
515
542
|
// menu: only one open at a time, and flip above the trigger when opening
|
|
516
|
-
// downward would run past the
|
|
543
|
+
// downward would run past the rail's bottom and there's room above.
|
|
517
544
|
const dropdowns = [...root.querySelectorAll<HTMLDetailsElement>("details")];
|
|
518
545
|
for (const details of dropdowns) {
|
|
519
546
|
details.addEventListener("toggle", () => {
|
|
@@ -968,7 +968,7 @@ const localeRedirect = localeTargets
|
|
|
968
968
|
showToc && (
|
|
969
969
|
<aside
|
|
970
970
|
aria-label={strings.toc.title}
|
|
971
|
-
class="sticky top-16 hidden h-[calc(100dvh-4rem)] scrollbar-thin scrollbar-thumb-border scrollbar-track-transparent overflow-y-auto px-
|
|
971
|
+
class="sticky top-16 hidden h-[calc(100dvh-4rem)] scrollbar-thin scrollbar-thumb-border scrollbar-track-transparent overflow-y-auto [--blume-rail-inset:1rem] px-(--blume-rail-inset) pt-6 pb-10 text-sm xl:block"
|
|
972
972
|
data-blume-toc
|
|
973
973
|
>
|
|
974
974
|
{hasToc && (
|
package/src/openapi/proxy.ts
CHANGED
|
@@ -196,7 +196,11 @@ const followUpstream = async (args: {
|
|
|
196
196
|
signal: AbortSignal;
|
|
197
197
|
url: URL;
|
|
198
198
|
}): Promise<Response> => {
|
|
199
|
-
|
|
199
|
+
// Called bare, never as `args.fetchImpl(...)`: that would run the global
|
|
200
|
+
// `fetch` with `this` set to `args`, which Node allows and workerd rejects
|
|
201
|
+
// as an "Illegal invocation".
|
|
202
|
+
const { fetchImpl } = args;
|
|
203
|
+
const response = await fetchImpl(args.url, {
|
|
200
204
|
body: args.body,
|
|
201
205
|
headers: args.headers,
|
|
202
206
|
method: args.method,
|