@grimoire-rs/indexer 0.4.3 → 0.5.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 +190 -0
- package/NOTICE +30 -0
- package/README.md +76 -331
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +35 -4
- package/dist/cli/init.js.map +1 -1
- package/dist/config.d.ts +107 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +181 -36
- package/dist/config.js.map +1 -1
- package/dist/renderer/astro/components/CardLogo.d.ts +5 -0
- package/dist/renderer/astro/components/CardLogo.js +58 -0
- package/dist/renderer/astro/components/CardLogo.tsx +96 -0
- package/dist/renderer/astro/components/Catalog.d.ts +14 -1
- package/dist/renderer/astro/components/Catalog.js +467 -108
- package/dist/renderer/astro/components/Catalog.tsx +756 -349
- package/dist/renderer/astro/components/CommandBar.astro +66 -0
- package/dist/renderer/astro/components/CopyButton.d.ts +7 -0
- package/dist/renderer/astro/components/CopyButton.js +28 -0
- package/dist/renderer/astro/components/CopyButton.tsx +56 -0
- package/dist/renderer/astro/components/KindMark.d.ts +69 -0
- package/dist/renderer/astro/components/KindMark.js +66 -0
- package/dist/renderer/astro/components/KindMark.tsx +141 -0
- package/dist/renderer/astro/components/PackageCard.d.ts +18 -0
- package/dist/renderer/astro/components/PackageCard.js +50 -0
- package/dist/renderer/astro/components/PackageCard.tsx +273 -0
- package/dist/renderer/astro/components/PackageRow.d.ts +10 -0
- package/dist/renderer/astro/components/PackageRow.js +32 -0
- package/dist/renderer/astro/components/PackageRow.tsx +126 -0
- package/dist/renderer/astro/components/PickerMenu.astro +5 -14
- package/dist/renderer/astro/components/SiteFooter.astro +64 -0
- package/dist/renderer/astro/components/SiteHeader.astro +74 -0
- package/dist/renderer/astro/components/VersionMenu.astro +2 -2
- package/dist/renderer/astro/layouts/Base.astro +860 -206
- package/dist/renderer/astro/lib/base.d.ts +25 -0
- package/dist/renderer/astro/lib/base.js +23 -0
- package/dist/renderer/astro/lib/base.ts +27 -0
- package/dist/renderer/astro/lib/catalog.d.ts +24 -0
- package/dist/renderer/astro/lib/catalog.js +36 -0
- package/dist/renderer/astro/lib/catalog.ts +37 -0
- package/dist/renderer/astro/lib/commands.d.ts +58 -0
- package/dist/renderer/astro/lib/commands.js +86 -0
- package/dist/renderer/astro/lib/commands.ts +117 -0
- package/dist/renderer/astro/lib/keywordRail.d.ts +44 -0
- package/dist/renderer/astro/lib/keywordRail.js +99 -0
- package/dist/renderer/astro/lib/keywordRail.ts +110 -0
- package/dist/renderer/astro/pages/index.astro +40 -87
- package/dist/renderer/astro/pages/p/[...slug].astro +340 -195
- package/dist/renderer/astro/styles/tokens.css +40 -5
- package/dist/renderer/index.d.ts +58 -0
- package/dist/renderer/index.d.ts.map +1 -1
- package/dist/renderer/index.js +547 -5
- package/dist/renderer/index.js.map +1 -1
- package/dist/renderer/types.d.ts +9 -0
- package/dist/renderer/types.d.ts.map +1 -1
- package/package.json +9 -4
- package/templates/README.md +6 -0
- package/templates/ci/github-ratings.yml +5 -0
- package/templates/gitignore +4 -1
- package/templates/theme/README.md +38 -0
- package/templates/tsconfig.json +47 -0
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
// VS Code deep link points at it, and search engines index it — the route
|
|
4
4
|
// shape is a contract, not an implementation detail.
|
|
5
5
|
import {
|
|
6
|
+
ArrowBigUp,
|
|
6
7
|
BookOpen,
|
|
7
8
|
Bug,
|
|
8
9
|
ChevronRight,
|
|
9
|
-
FolderRoot,
|
|
10
|
-
Globe,
|
|
11
10
|
History,
|
|
12
11
|
House,
|
|
13
12
|
Image,
|
|
@@ -16,13 +15,14 @@ import {
|
|
|
16
15
|
MessageCircle,
|
|
17
16
|
Scale,
|
|
18
17
|
ShieldAlert,
|
|
18
|
+
TriangleAlert,
|
|
19
19
|
User,
|
|
20
20
|
} from "lucide-preact";
|
|
21
21
|
import Base from "../../layouts/Base.astro";
|
|
22
22
|
import VersionMenu from "../../components/VersionMenu.astro";
|
|
23
|
-
import
|
|
24
|
-
import PickerMenu from "../../components/PickerMenu.astro";
|
|
23
|
+
import CommandBar from "../../components/CommandBar.astro";
|
|
25
24
|
import { BrandMark } from "../../components/BrandMark.tsx";
|
|
25
|
+
import { DEPRECATED_MARK, KIND_MARKS, KindMark } from "../../components/KindMark.tsx";
|
|
26
26
|
import {
|
|
27
27
|
mdiBitbucket,
|
|
28
28
|
mdiGit,
|
|
@@ -34,14 +34,17 @@ import { withBase } from "../../lib/base";
|
|
|
34
34
|
import { SHIKI_THEMES } from "../../lib/code";
|
|
35
35
|
import {
|
|
36
36
|
compareVersions,
|
|
37
|
+
deprecationNote,
|
|
37
38
|
externalUrl,
|
|
38
39
|
lastUpdated,
|
|
39
40
|
resolveMemberRef,
|
|
40
41
|
timeAgo,
|
|
41
42
|
versionCascade,
|
|
42
43
|
vscodeUrl,
|
|
44
|
+
vscodeVoteUrl,
|
|
43
45
|
type CatalogPackage,
|
|
44
46
|
} from "../../lib/catalog";
|
|
47
|
+
import { addArtifactChoices } from "../../lib/commands";
|
|
45
48
|
import { data } from "../../lib/data";
|
|
46
49
|
import { getEntry, render } from "astro:content";
|
|
47
50
|
import { Code } from "astro:components";
|
|
@@ -122,12 +125,8 @@ const vscode = vscodeUrl(config.vscodeExtension, pkg.ref);
|
|
|
122
125
|
const addCommand = `grim add ${pkg.ref}`;
|
|
123
126
|
|
|
124
127
|
// The same two-way scope choice the package cards offer and the hero's
|
|
125
|
-
// registry bar draws,
|
|
126
|
-
|
|
127
|
-
const scopeChoices = [
|
|
128
|
-
{ name: "Global", command: `grim add --global ${pkg.ref}`, Icon: Globe },
|
|
129
|
-
{ name: "Project", command: addCommand, Icon: FolderRoot },
|
|
130
|
-
];
|
|
128
|
+
// registry bar draws, from the one helper all three read.
|
|
129
|
+
const scopeChoices = addArtifactChoices(pkg.ref);
|
|
131
130
|
|
|
132
131
|
// Every outbound string on this page was handed to the index by a registry
|
|
133
132
|
// it does not control, so each one goes through the scheme allowlist before
|
|
@@ -162,167 +161,246 @@ const supportChannels = [
|
|
|
162
161
|
// taking a row of its own and putting 40 hex characters in the rail.
|
|
163
162
|
const updated = lastUpdated(pkg);
|
|
164
163
|
const updatedTitle = pkg.revision ? `${updated} · revision ${pkg.revision}` : updated;
|
|
164
|
+
|
|
165
|
+
// The card's vote badge, on the page the card links to. Same two halves and
|
|
166
|
+
// the same rule: the count opens the forge thread, because no forge offers a
|
|
167
|
+
// URL that casts a vote, and the extension's `/vote?repo=` route is the only
|
|
168
|
+
// one that does — behind its own disclosure modal, never on this link alone.
|
|
169
|
+
const votes = pkg.rating ? `${pkg.rating.up} upvote${pkg.rating.up === 1 ? "" : "s"}` : "";
|
|
170
|
+
const thread = externalUrl(pkg.rating?.url);
|
|
171
|
+
const voteUrl = vscodeVoteUrl(config.vscodeExtension, pkg.ref);
|
|
172
|
+
|
|
173
|
+
// The kind as texture, the way a card wears it. A retired package spends it
|
|
174
|
+
// on the warning instead — the kind is written in the badge either way.
|
|
175
|
+
const headMark = pkg.deprecated ? DEPRECATED_MARK : KIND_MARKS[pkg.kind];
|
|
176
|
+
const headMarkColor = pkg.deprecated
|
|
177
|
+
? "var(--grim-color-deprecated)"
|
|
178
|
+
: `var(--grim-color-kind-${pkg.kind}, var(--grim-color-muted))`;
|
|
165
179
|
---
|
|
166
180
|
|
|
167
181
|
<Base title={`${title} — ${config.brand}`} description={description} image={pkg.logo}>
|
|
168
182
|
<div class="detail">
|
|
169
|
-
<header class="detail-head" data-slot="detail-header">
|
|
170
|
-
{
|
|
171
|
-
pkg.logo ? (
|
|
172
|
-
// The placeholder ships in the markup and the image fades in over
|
|
173
|
-
// it, so a slow or failing load never flashes a broken glyph. The
|
|
174
|
-
// slot starts `loading`; the handler in `Base.astro` moves it to
|
|
175
|
-
// `ready` or `broken`. Icon mirrors `LogoMark` in `Catalog.tsx`.
|
|
176
|
-
<span class="tile logo-slot" data-static data-state="loading">
|
|
177
|
-
{/* Both marks ship; CSS shows one per state. The card grid picks
|
|
178
|
-
between them in Preact instead, but this page has to settle
|
|
179
|
-
its placeholder before any script runs. */}
|
|
180
|
-
<Image class="logo-mark mark-loading" aria-hidden="true" />
|
|
181
|
-
<ImageOff class="logo-mark mark-broken" aria-hidden="true" />
|
|
182
|
-
<img src={withBase(pkg.logo)} alt="" />
|
|
183
|
-
</span>
|
|
184
|
-
) : (
|
|
185
|
-
<div class="tile letter" style={`background: var(--grim-color-kind-${pkg.kind}, var(--grim-color-muted))`}>
|
|
186
|
-
{pkg.name[0]?.toUpperCase()}
|
|
187
|
-
</div>
|
|
188
|
-
)
|
|
189
|
-
}
|
|
190
|
-
<div class="detail-head-text">
|
|
191
|
-
<h1>
|
|
192
|
-
{pkg.name} <span class={`badge kind-${pkg.kind}`}>{pkg.kind}</span>
|
|
193
|
-
{/* Skills only — every other kind reports null, and grim's own
|
|
194
|
-
surfaces show it beside the kind for the same reason: it
|
|
195
|
-
qualifies what the thing is, not what it says about itself. */}
|
|
196
|
-
{pkg.compatibility && <span class="badge compat">{pkg.compatibility}</span>}
|
|
197
|
-
</h1>
|
|
198
|
-
{description && <p class="summary">{description}</p>}
|
|
199
|
-
{byline && <p class="byline">by {byline}</p>}
|
|
200
|
-
</div>
|
|
201
|
-
</header>
|
|
202
|
-
|
|
203
|
-
{
|
|
204
|
-
pkg.deprecated && (
|
|
205
|
-
<p class="deprecated-banner" data-slot="deprecated-banner">
|
|
206
|
-
⚠️ deprecated{pkg.replacedBy ? ` — replaced by ${pkg.replacedBy}` : ""}
|
|
207
|
-
</p>
|
|
208
|
-
)
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
<div class="cmd-bar" data-os-switch data-os-noun={`add command for ${pkg.name}`}>
|
|
212
|
-
<PickerMenu choices={scopeChoices} title="Scope" label="Choose the scope" />
|
|
213
|
-
<CommandField
|
|
214
|
-
value={scopeChoices[0].command}
|
|
215
|
-
name={`${scopeChoices[0].name} add command for ${pkg.name}`}
|
|
216
|
-
label="Copy the add command"
|
|
217
|
-
/>
|
|
218
|
-
{
|
|
219
|
-
vscode && (
|
|
220
|
-
<a
|
|
221
|
-
class="seg brand-link"
|
|
222
|
-
href={vscode}
|
|
223
|
-
title="Open in VS Code"
|
|
224
|
-
aria-label={`Open ${pkg.name} in VS Code`}
|
|
225
|
-
>
|
|
226
|
-
<BrandMark path={mdiMicrosoftVisualStudioCode} />
|
|
227
|
-
</a>
|
|
228
|
-
)
|
|
229
|
-
}
|
|
230
|
-
</div>
|
|
231
|
-
|
|
232
183
|
<div class="detail-body" data-slot="detail-body">
|
|
233
|
-
<
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
class="
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
<div class="ribbon">
|
|
270
|
-
<label for="panel-readme" title={filled.readme ? undefined : "no readme available"}>
|
|
271
|
-
Readme
|
|
272
|
-
</label>
|
|
273
|
-
<label for="panel-contents" title={filled.contents ? undefined : "no contents available"}>
|
|
274
|
-
Contents
|
|
275
|
-
</label>
|
|
276
|
-
<label
|
|
277
|
-
for="panel-changelog"
|
|
278
|
-
title={filled.changelog ? undefined : "no changelog available"}
|
|
279
|
-
>
|
|
280
|
-
Changelog
|
|
281
|
-
</label>
|
|
282
|
-
</div>
|
|
283
|
-
{filled.readme && (
|
|
284
|
-
<div class="prose tab-panel" id="tab-readme">
|
|
285
|
-
<Readme />
|
|
184
|
+
<div class="detail-col">
|
|
185
|
+
<header class="detail-head" data-slot="detail-header">
|
|
186
|
+
{
|
|
187
|
+
pkg.logo ? (
|
|
188
|
+
// The placeholder ships in the markup and the image fades in over
|
|
189
|
+
// it, so a slow or failing load never flashes a broken glyph. The
|
|
190
|
+
// slot starts `loading`; the handler in `Base.astro` moves it to
|
|
191
|
+
// `ready` or `broken`. Icon mirrors `LogoMark` in `Catalog.tsx`.
|
|
192
|
+
<span class="tile logo-slot" data-static data-state="loading">
|
|
193
|
+
{/* Both marks ship; CSS shows one per state. The card grid picks
|
|
194
|
+
between them in Preact instead, but this page has to settle
|
|
195
|
+
its placeholder before any script runs. */}
|
|
196
|
+
<Image class="logo-mark mark-loading" aria-hidden="true" />
|
|
197
|
+
<ImageOff class="logo-mark mark-broken" aria-hidden="true" />
|
|
198
|
+
<img src={withBase(pkg.logo)} alt="" />
|
|
199
|
+
</span>
|
|
200
|
+
) : (
|
|
201
|
+
<div class="tile letter" style={`background: var(--grim-color-kind-${pkg.kind}, var(--grim-color-muted))`}>
|
|
202
|
+
{pkg.name[0]?.toUpperCase()}
|
|
203
|
+
</div>
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
<div class="detail-head-text">
|
|
207
|
+
<h1>
|
|
208
|
+
{pkg.name} <span class={`badge kind-${pkg.kind}`}>{pkg.kind}</span>
|
|
209
|
+
{/* Skills only — every other kind reports null, and grim's own
|
|
210
|
+
surfaces show it beside the kind for the same reason: it
|
|
211
|
+
qualifies what the thing is, not what it says about itself. */}
|
|
212
|
+
{pkg.compatibility && (
|
|
213
|
+
<span class="badge compat" title={`Built for ${pkg.compatibility}`}>
|
|
214
|
+
{pkg.compatibility}
|
|
215
|
+
</span>
|
|
216
|
+
)}
|
|
217
|
+
</h1>
|
|
218
|
+
{description && <p class="summary">{description}</p>}
|
|
219
|
+
{byline && <p class="byline">by {byline}</p>}
|
|
286
220
|
</div>
|
|
287
|
-
|
|
221
|
+
</header>
|
|
222
|
+
|
|
288
223
|
{
|
|
289
|
-
|
|
290
|
-
<
|
|
291
|
-
{
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
<a href={member.href}>{member.name}</a>
|
|
299
|
-
) : (
|
|
300
|
-
<span>{member.name}</span>
|
|
301
|
-
)}
|
|
302
|
-
<span class={`badge kind-${member.kind}`}>{member.kind}</span>
|
|
303
|
-
<code>{member.ref}</code>
|
|
304
|
-
</li>
|
|
305
|
-
))}
|
|
306
|
-
</ul>
|
|
307
|
-
) : (
|
|
308
|
-
<Code
|
|
309
|
-
code={JSON.stringify(contentsData, null, 2)}
|
|
310
|
-
lang="json"
|
|
311
|
-
themes={SHIKI_THEMES}
|
|
312
|
-
/>
|
|
313
|
-
)}
|
|
314
|
-
</div>
|
|
224
|
+
pkg.deprecated && (
|
|
225
|
+
<p class="deprecated-banner" data-slot="deprecated-banner">
|
|
226
|
+
{/* Drawn, not an emoji: ⚠️ renders as a different glyph on every
|
|
227
|
+
platform, takes the system's colour rather than the token's,
|
|
228
|
+
and sits off the text baseline. Every other icon on this site
|
|
229
|
+
comes from one stroked set; this one had no reason not to. */}
|
|
230
|
+
<TriangleAlert size={16} aria-hidden="true" />
|
|
231
|
+
{deprecationNote(pkg)}
|
|
232
|
+
</p>
|
|
315
233
|
)
|
|
316
234
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
235
|
+
|
|
236
|
+
<CommandBar
|
|
237
|
+
choices={scopeChoices}
|
|
238
|
+
noun={`add command for ${pkg.name}`}
|
|
239
|
+
copyLabel="Copy the add command"
|
|
240
|
+
pickerTitle="Scope"
|
|
241
|
+
pickerLabel="Choose the scope"
|
|
242
|
+
>
|
|
243
|
+
{
|
|
244
|
+
vscode && (
|
|
245
|
+
<a
|
|
246
|
+
slot="action"
|
|
247
|
+
class="seg brand-link"
|
|
248
|
+
href={vscode}
|
|
249
|
+
title="Open in VS Code"
|
|
250
|
+
aria-label={`Open ${pkg.name} in VS Code`}
|
|
251
|
+
>
|
|
252
|
+
<BrandMark path={mdiMicrosoftVisualStudioCode} />
|
|
253
|
+
</a>
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
</CommandBar>
|
|
257
|
+
|
|
258
|
+
<main class="detail-main">
|
|
259
|
+
{/* The kind as texture behind the panel, the way a card wears it:
|
|
260
|
+
oversized and hung off the panel's bottom-right corner, so
|
|
261
|
+
`overflow: hidden` crops it to the rounded edge. Decorative — the
|
|
262
|
+
kind is a badge beside the name — so it is aria-hidden unless it
|
|
263
|
+
is the warning a retired package wears instead. */}
|
|
264
|
+
{
|
|
265
|
+
headMark && (
|
|
266
|
+
<KindMark
|
|
267
|
+
glyph={headMark}
|
|
268
|
+
class="panel-watermark"
|
|
269
|
+
size={220}
|
|
270
|
+
role={pkg.deprecated ? "img" : undefined}
|
|
271
|
+
aria-label={pkg.deprecated ? "deprecated" : undefined}
|
|
272
|
+
aria-hidden={pkg.deprecated ? undefined : "true"}
|
|
273
|
+
style={`color: ${headMarkColor}`}
|
|
274
|
+
/>
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
{/* Radio tabs, not a scripted tablist: the ribbon works on a page
|
|
278
|
+
whose JS never ran, and a radio group already has the keyboard
|
|
279
|
+
behaviour tabs want (arrows move, only one selected). The inputs
|
|
280
|
+
are visually hidden rather than `hidden`, which would take them
|
|
281
|
+
out of the focus order along with the pixels. */}
|
|
282
|
+
{/* All three tabs are always offered — a ribbon that grows a tab per
|
|
283
|
+
package makes the panel a different shape on every page. An empty
|
|
284
|
+
one is disabled instead: greyed, not clickable through to a
|
|
285
|
+
sentence apologising for itself. */}
|
|
286
|
+
<input
|
|
287
|
+
type="radio"
|
|
288
|
+
name="panel"
|
|
289
|
+
id="panel-readme"
|
|
290
|
+
class="sr-only"
|
|
291
|
+
checked={openTab === "readme"}
|
|
292
|
+
disabled={!filled.readme}
|
|
293
|
+
/>
|
|
294
|
+
<input
|
|
295
|
+
type="radio"
|
|
296
|
+
name="panel"
|
|
297
|
+
id="panel-contents"
|
|
298
|
+
class="sr-only"
|
|
299
|
+
checked={openTab === "contents"}
|
|
300
|
+
disabled={!filled.contents}
|
|
301
|
+
/>
|
|
302
|
+
<input
|
|
303
|
+
type="radio"
|
|
304
|
+
name="panel"
|
|
305
|
+
id="panel-changelog"
|
|
306
|
+
class="sr-only"
|
|
307
|
+
checked={openTab === "changelog"}
|
|
308
|
+
disabled={!filled.changelog}
|
|
309
|
+
/>
|
|
310
|
+
{/* The greyed tab says it is not for clicking; the tooltip says why,
|
|
311
|
+
so the answer is one hover away instead of a panel of its own. */}
|
|
312
|
+
<div class="ribbon">
|
|
313
|
+
<label for="panel-readme" title={filled.readme ? undefined : "no readme available"}>
|
|
314
|
+
Readme
|
|
315
|
+
</label>
|
|
316
|
+
<label for="panel-contents" title={filled.contents ? undefined : "no contents available"}>
|
|
317
|
+
Contents
|
|
318
|
+
</label>
|
|
319
|
+
<label
|
|
320
|
+
for="panel-changelog"
|
|
321
|
+
title={filled.changelog ? undefined : "no changelog available"}
|
|
322
|
+
>
|
|
323
|
+
Changelog
|
|
324
|
+
</label>
|
|
325
|
+
{
|
|
326
|
+
pkg.rating && (
|
|
327
|
+
<span class="rating-group">
|
|
328
|
+
{thread ? (
|
|
329
|
+
<a
|
|
330
|
+
class="rating-count"
|
|
331
|
+
href={thread}
|
|
332
|
+
target="_blank"
|
|
333
|
+
rel="noopener noreferrer"
|
|
334
|
+
title={`${votes} — open the thread to vote`}
|
|
335
|
+
aria-label={`${votes}. Open the voting thread`}
|
|
336
|
+
>
|
|
337
|
+
<ArrowBigUp size={13} aria-hidden="true" />
|
|
338
|
+
{pkg.rating.up}
|
|
339
|
+
</a>
|
|
340
|
+
) : (
|
|
341
|
+
<span class="rating-count" title={votes}>
|
|
342
|
+
<ArrowBigUp size={13} aria-hidden="true" />
|
|
343
|
+
{pkg.rating.up}
|
|
344
|
+
</span>
|
|
345
|
+
)}
|
|
346
|
+
{voteUrl && (
|
|
347
|
+
<a
|
|
348
|
+
class="rating-vote"
|
|
349
|
+
href={voteUrl}
|
|
350
|
+
title="Upvote in VS Code"
|
|
351
|
+
aria-label={`Upvote ${pkg.name} in VS Code`}
|
|
352
|
+
>
|
|
353
|
+
<BrandMark path={mdiMicrosoftVisualStudioCode} size={12} />
|
|
354
|
+
</a>
|
|
355
|
+
)}
|
|
356
|
+
</span>
|
|
357
|
+
)
|
|
358
|
+
}
|
|
320
359
|
</div>
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
360
|
+
{filled.readme && (
|
|
361
|
+
<div class="prose tab-panel" id="tab-readme">
|
|
362
|
+
<Readme />
|
|
363
|
+
</div>
|
|
364
|
+
)}
|
|
365
|
+
{
|
|
366
|
+
filled.contents && (
|
|
367
|
+
<div class="prose tab-panel" id="tab-contents">
|
|
368
|
+
{Contents ? (
|
|
369
|
+
<Contents />
|
|
370
|
+
) : members.length > 0 ? (
|
|
371
|
+
<ul class="members">
|
|
372
|
+
{members.map((member) => (
|
|
373
|
+
<li>
|
|
374
|
+
{member.href ? (
|
|
375
|
+
<a href={member.href}>{member.name}</a>
|
|
376
|
+
) : (
|
|
377
|
+
<span>{member.name}</span>
|
|
378
|
+
)}
|
|
379
|
+
<span class={`badge kind-${member.kind}`}>{member.kind}</span>
|
|
380
|
+
<code>{member.ref}</code>
|
|
381
|
+
</li>
|
|
382
|
+
))}
|
|
383
|
+
</ul>
|
|
384
|
+
) : (
|
|
385
|
+
<Code
|
|
386
|
+
code={JSON.stringify(contentsData, null, 2)}
|
|
387
|
+
lang="json"
|
|
388
|
+
themes={SHIKI_THEMES}
|
|
389
|
+
/>
|
|
390
|
+
)}
|
|
391
|
+
</div>
|
|
392
|
+
)
|
|
393
|
+
}
|
|
394
|
+
{filled.changelog && (
|
|
395
|
+
<div class="prose tab-panel" id="tab-changelog">
|
|
396
|
+
<Changelog />
|
|
397
|
+
</div>
|
|
398
|
+
)}
|
|
399
|
+
{/* Nothing anywhere: one statement in the middle of the panel, rather
|
|
400
|
+
than three tabs each denying it separately. */}
|
|
401
|
+
{openTab === null && <p class="panel-empty">No content available.</p>}
|
|
402
|
+
</main>
|
|
403
|
+
</div>
|
|
326
404
|
|
|
327
405
|
<aside class="detail-rail" data-slot="detail-rail">
|
|
328
406
|
{tags.length > 0 && (
|
|
@@ -349,6 +427,22 @@ const updatedTitle = pkg.revision ? `${updated} · revision ${pkg.revision}` : u
|
|
|
349
427
|
</section>
|
|
350
428
|
)}
|
|
351
429
|
|
|
430
|
+
{supportChannels.length > 0 && (
|
|
431
|
+
<section class="support">
|
|
432
|
+
<h2>Get help</h2>
|
|
433
|
+
<ul>
|
|
434
|
+
{supportChannels.map(({ label, href, Icon }) => (
|
|
435
|
+
<li>
|
|
436
|
+
<Icon size={15} aria-hidden="true" />
|
|
437
|
+
<a href={href} target="_blank" rel="noopener noreferrer">
|
|
438
|
+
{label}
|
|
439
|
+
</a>
|
|
440
|
+
</li>
|
|
441
|
+
))}
|
|
442
|
+
</ul>
|
|
443
|
+
</section>
|
|
444
|
+
)}
|
|
445
|
+
|
|
352
446
|
<section>
|
|
353
447
|
<h2>Details</h2>
|
|
354
448
|
<dl>
|
|
@@ -435,10 +529,13 @@ const updatedTitle = pkg.revision ? `${updated} · revision ${pkg.revision}` : u
|
|
|
435
529
|
<ul class="keywords">
|
|
436
530
|
{pkg.keywords.map((k) => (
|
|
437
531
|
<li>
|
|
438
|
-
{/* Back to the catalog with the
|
|
439
|
-
|
|
532
|
+
{/* Back to the catalog with the keyword FACET applied, not
|
|
533
|
+
a text search. `?q=` also matched every description with
|
|
534
|
+
the word in it, so a link labelled `cli` returned
|
|
535
|
+
packages nobody tagged `cli`. The catalog still reads
|
|
536
|
+
`?q=`, so links shared before this change still work. */}
|
|
440
537
|
<a
|
|
441
|
-
href={withBase(`/?
|
|
538
|
+
href={withBase(`/?kw=${encodeURIComponent(k)}`)}
|
|
442
539
|
title={`Find packages tagged ${k}`}
|
|
443
540
|
>
|
|
444
541
|
{k}
|
|
@@ -449,21 +546,6 @@ const updatedTitle = pkg.revision ? `${updated} · revision ${pkg.revision}` : u
|
|
|
449
546
|
)}
|
|
450
547
|
</section>
|
|
451
548
|
|
|
452
|
-
{supportChannels.length > 0 && (
|
|
453
|
-
<section class="support">
|
|
454
|
-
<h2>Get help</h2>
|
|
455
|
-
<ul>
|
|
456
|
-
{supportChannels.map(({ label, href, Icon }) => (
|
|
457
|
-
<li>
|
|
458
|
-
<Icon size={15} aria-hidden="true" />
|
|
459
|
-
<a href={href} target="_blank" rel="noopener noreferrer">
|
|
460
|
-
{label}
|
|
461
|
-
</a>
|
|
462
|
-
</li>
|
|
463
|
-
))}
|
|
464
|
-
</ul>
|
|
465
|
-
</section>
|
|
466
|
-
)}
|
|
467
549
|
</aside>
|
|
468
550
|
</div>
|
|
469
551
|
</div>
|
|
@@ -520,20 +602,42 @@ const updatedTitle = pkg.revision ? `${updated} · revision ${pkg.revision}` : u
|
|
|
520
602
|
flex-direction: column;
|
|
521
603
|
gap: var(--grim-space-7);
|
|
522
604
|
}
|
|
605
|
+
/* The left column: header, install bar and panel, all to the width of the
|
|
606
|
+
README rather than to the width of the page. The rail then runs the
|
|
607
|
+
full height beside them instead of starting a screenful down, which is
|
|
608
|
+
what left the top-right corner of the page empty. */
|
|
609
|
+
.detail-col {
|
|
610
|
+
display: flex;
|
|
611
|
+
flex-direction: column;
|
|
612
|
+
gap: var(--grim-space-7);
|
|
613
|
+
min-width: 0;
|
|
614
|
+
}
|
|
523
615
|
.detail-head {
|
|
524
616
|
display: flex;
|
|
525
|
-
|
|
617
|
+
/* Stretch, so the logo can take the height of the whole text block
|
|
618
|
+
beside it rather than sitting centred against a third of it. */
|
|
619
|
+
align-items: stretch;
|
|
526
620
|
gap: var(--grim-space-6);
|
|
527
621
|
background: var(--grim-color-card);
|
|
528
622
|
border: var(--grim-border-width) solid var(--grim-color-border);
|
|
529
|
-
border-radius: var(--grim-radius-
|
|
623
|
+
border-radius: var(--grim-radius-surface);
|
|
530
624
|
padding: var(--grim-space-6) var(--grim-space-7);
|
|
531
625
|
}
|
|
626
|
+
.detail-head-text {
|
|
627
|
+
min-width: 0;
|
|
628
|
+
}
|
|
532
629
|
.tile {
|
|
533
|
-
flex
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
630
|
+
flex: 0 0 auto;
|
|
631
|
+
align-self: stretch;
|
|
632
|
+
width: auto;
|
|
633
|
+
height: auto;
|
|
634
|
+
/* Square, sized by the text block it stands beside. Bounded at both
|
|
635
|
+
ends: a one-line header must not shrink it to a favicon, and a long
|
|
636
|
+
summary must not grow it into the page. */
|
|
637
|
+
aspect-ratio: 1;
|
|
638
|
+
min-height: 3.5rem;
|
|
639
|
+
max-height: 7rem;
|
|
640
|
+
border-radius: var(--grim-radius-surface);
|
|
537
641
|
object-fit: cover;
|
|
538
642
|
}
|
|
539
643
|
.tile.letter {
|
|
@@ -554,11 +658,14 @@ const updatedTitle = pkg.revision ? `${updated} · revision ${pkg.revision}` : u
|
|
|
554
658
|
font-family: ui-monospace, "SFMono-Regular", monospace;
|
|
555
659
|
}
|
|
556
660
|
.summary {
|
|
557
|
-
margin: var(--grim-space-
|
|
661
|
+
margin: var(--grim-space-4) 0 0;
|
|
558
662
|
color: var(--grim-color-muted);
|
|
559
663
|
}
|
|
664
|
+
/* Tight to the summary above it. Evenly spaced between the name and the
|
|
665
|
+
summary it read as a third heading; who publishes this is a footnote on
|
|
666
|
+
the sentence, so it sits with the sentence. */
|
|
560
667
|
.byline {
|
|
561
|
-
margin: var(--grim-space-
|
|
668
|
+
margin: var(--grim-space-1) 0 0;
|
|
562
669
|
color: var(--grim-color-muted);
|
|
563
670
|
font-size: var(--grim-text-sm);
|
|
564
671
|
}
|
|
@@ -590,16 +697,20 @@ const updatedTitle = pkg.revision ? `${updated} · revision ${pkg.revision}` : u
|
|
|
590
697
|
}
|
|
591
698
|
.deprecated-banner {
|
|
592
699
|
margin: 0;
|
|
700
|
+
/* The glyph sits on the sentence's line rather than above it. */
|
|
701
|
+
display: flex;
|
|
702
|
+
align-items: center;
|
|
703
|
+
gap: var(--grim-space-3);
|
|
593
704
|
background: var(--grim-color-banner-bg);
|
|
594
705
|
color: var(--grim-color-banner-fg);
|
|
595
706
|
border: var(--grim-border-width) solid var(--grim-color-banner-border);
|
|
596
|
-
border-radius: var(--grim-radius-
|
|
707
|
+
border-radius: var(--grim-radius-control);
|
|
597
708
|
padding: var(--grim-space-4) var(--grim-space-6);
|
|
598
709
|
font-weight: 600;
|
|
599
710
|
}
|
|
600
711
|
.detail-body {
|
|
601
712
|
display: grid;
|
|
602
|
-
grid-template-columns: 1fr 18rem;
|
|
713
|
+
grid-template-columns: minmax(0, 1fr) 18rem;
|
|
603
714
|
gap: var(--grim-space-8);
|
|
604
715
|
align-items: start;
|
|
605
716
|
}
|
|
@@ -612,8 +723,36 @@ const updatedTitle = pkg.revision ? `${updated} · revision ${pkg.revision}` : u
|
|
|
612
723
|
min-width: 0;
|
|
613
724
|
background: var(--grim-color-card);
|
|
614
725
|
border: var(--grim-border-width) solid var(--grim-color-border);
|
|
615
|
-
border-radius: var(--grim-radius-
|
|
726
|
+
border-radius: var(--grim-radius-surface);
|
|
616
727
|
padding: var(--grim-space-7) var(--grim-space-7);
|
|
728
|
+
/* Crops the oversized kind mark to the rounded edge and gives it a
|
|
729
|
+
stacking context to sit behind in — without `isolation` a
|
|
730
|
+
`z-index: -1` child escapes to the root and vanishes under the
|
|
731
|
+
panel's own background. */
|
|
732
|
+
position: relative;
|
|
733
|
+
overflow: hidden;
|
|
734
|
+
isolation: isolate;
|
|
735
|
+
}
|
|
736
|
+
/* Hung off the panel's bottom-right corner, cropped by the panel's own
|
|
737
|
+
rounded edge. Opacity on the element rather than in the colour: the
|
|
738
|
+
glyph's paths overlap, and a translucent fill composites every crossing
|
|
739
|
+
twice and comes out blotchy along its own joins. */
|
|
740
|
+
.detail-main :global(.panel-watermark) {
|
|
741
|
+
position: absolute;
|
|
742
|
+
z-index: -1;
|
|
743
|
+
opacity: 0.14;
|
|
744
|
+
bottom: 0;
|
|
745
|
+
right: 0;
|
|
746
|
+
/* Same 26% of its own box as the card's mark — see `.card-watermark`
|
|
747
|
+
in `Base.astro` for why this is a translate and not an inset. */
|
|
748
|
+
transform: translate(26%, 26%);
|
|
749
|
+
pointer-events: none;
|
|
750
|
+
}
|
|
751
|
+
/* The vote rides the ribbon's right end — the panel's own row of
|
|
752
|
+
controls, and the one line on this page that was already a toolbar. */
|
|
753
|
+
.ribbon .rating-group {
|
|
754
|
+
align-self: center;
|
|
755
|
+
margin-left: auto;
|
|
617
756
|
}
|
|
618
757
|
/* Markdown opens with its own heading, whose margin lands on top of the
|
|
619
758
|
panel padding and reads as a hole. Every panel sets its own top edge. */
|
|
@@ -692,7 +831,7 @@ const updatedTitle = pkg.revision ? `${updated} · revision ${pkg.revision}` : u
|
|
|
692
831
|
#panel-changelog:focus-visible ~ .ribbon label[for="panel-changelog"] {
|
|
693
832
|
outline: 2px solid var(--grim-color-accent);
|
|
694
833
|
outline-offset: 2px;
|
|
695
|
-
border-radius: var(--grim-radius-
|
|
834
|
+
border-radius: var(--grim-radius-code);
|
|
696
835
|
}
|
|
697
836
|
|
|
698
837
|
/* A bundle's member list: what it installs, one row each. A grid, not a
|
|
@@ -735,17 +874,23 @@ const updatedTitle = pkg.revision ? `${updated} · revision ${pkg.revision}` : u
|
|
|
735
874
|
grid-column: 1 / -1;
|
|
736
875
|
}
|
|
737
876
|
}
|
|
877
|
+
/* One panel, not three cards stacked. Beside a single tall README panel a
|
|
878
|
+
column of separately bordered boxes read as debris; the sections are
|
|
879
|
+
still separated, by a rule inside the panel rather than by a gap and a
|
|
880
|
+
second border each. */
|
|
738
881
|
.detail-rail {
|
|
739
882
|
display: flex;
|
|
740
883
|
flex-direction: column;
|
|
741
|
-
gap: var(--grim-space-7);
|
|
742
|
-
}
|
|
743
|
-
.detail-rail section {
|
|
744
884
|
background: var(--grim-color-card);
|
|
745
885
|
border: var(--grim-border-width) solid var(--grim-color-border);
|
|
746
|
-
border-radius: var(--grim-radius-
|
|
886
|
+
border-radius: var(--grim-radius-surface);
|
|
887
|
+
}
|
|
888
|
+
.detail-rail section {
|
|
747
889
|
padding: var(--grim-space-6);
|
|
748
890
|
}
|
|
891
|
+
.detail-rail section + section {
|
|
892
|
+
border-top: var(--grim-border-width) solid var(--grim-color-border);
|
|
893
|
+
}
|
|
749
894
|
/* Panel label. The main panel's ribbon labels match this by hand — they
|
|
750
895
|
are <label>s, not headings, so they cannot join the selector. */
|
|
751
896
|
.detail-rail h2 {
|