@grimoire-rs/indexer 0.1.8 → 0.2.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/README.md +38 -0
- package/dist/cli/init.js +1 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/config.d.ts +21 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +19 -2
- package/dist/config.js.map +1 -1
- package/dist/enrich/index.d.ts.map +1 -1
- package/dist/enrich/index.js +62 -0
- package/dist/enrich/index.js.map +1 -1
- package/dist/renderer/astro/components/BrandMark.d.ts +14 -0
- package/dist/renderer/astro/components/BrandMark.d.ts.map +1 -0
- package/dist/renderer/astro/components/BrandMark.js +16 -0
- package/dist/renderer/astro/components/BrandMark.js.map +1 -0
- package/dist/renderer/astro/components/BrandMark.tsx +19 -0
- package/dist/renderer/astro/components/Catalog.d.ts.map +1 -1
- package/dist/renderer/astro/components/Catalog.js +310 -18
- package/dist/renderer/astro/components/Catalog.js.map +1 -1
- package/dist/renderer/astro/components/Catalog.tsx +445 -63
- package/dist/renderer/astro/components/CommandField.astro +43 -0
- package/dist/renderer/astro/components/PickerMenu.astro +68 -0
- package/dist/renderer/astro/components/VersionMenu.astro +6 -2
- package/dist/renderer/astro/content.config.ts +23 -5
- package/dist/renderer/astro/layouts/Base.astro +794 -66
- package/dist/renderer/astro/lib/catalog.d.ts +24 -0
- package/dist/renderer/astro/lib/catalog.d.ts.map +1 -1
- package/dist/renderer/astro/lib/catalog.js +62 -0
- package/dist/renderer/astro/lib/catalog.js.map +1 -1
- package/dist/renderer/astro/lib/catalog.ts +62 -0
- package/dist/renderer/astro/lib/code.d.ts +22 -0
- package/dist/renderer/astro/lib/code.d.ts.map +1 -0
- package/dist/renderer/astro/lib/code.js +21 -0
- package/dist/renderer/astro/lib/code.js.map +1 -0
- package/dist/renderer/astro/lib/code.ts +21 -0
- package/dist/renderer/astro/pages/index.astro +144 -36
- package/dist/renderer/astro/pages/p/[...slug].astro +403 -55
- package/dist/renderer/index.d.ts +23 -0
- package/dist/renderer/index.d.ts.map +1 -1
- package/dist/renderer/index.js +117 -30
- package/dist/renderer/index.js.map +1 -1
- package/package.json +5 -1
- package/dist/renderer/astro/components/CopyButton.astro +0 -24
|
@@ -2,13 +2,31 @@
|
|
|
2
2
|
// Frozen public URL: /p/<namespace>/<name>/. grim resolves against it, the
|
|
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
|
+
import { ChevronRight, FolderRoot, Globe, Image, ImageOff, Scale, User } from "lucide-preact";
|
|
5
6
|
import Base from "../../layouts/Base.astro";
|
|
6
7
|
import VersionMenu from "../../components/VersionMenu.astro";
|
|
7
|
-
import
|
|
8
|
+
import CommandField from "../../components/CommandField.astro";
|
|
9
|
+
import PickerMenu from "../../components/PickerMenu.astro";
|
|
10
|
+
import { BrandMark } from "../../components/BrandMark.tsx";
|
|
11
|
+
import {
|
|
12
|
+
mdiBitbucket,
|
|
13
|
+
mdiGit,
|
|
14
|
+
mdiGithub,
|
|
15
|
+
mdiGitlab,
|
|
16
|
+
mdiMicrosoftVisualStudioCode,
|
|
17
|
+
} from "@mdi/js";
|
|
8
18
|
import { withBase } from "../../lib/base";
|
|
9
|
-
import {
|
|
19
|
+
import { SHIKI_THEMES } from "../../lib/code";
|
|
20
|
+
import {
|
|
21
|
+
compareVersions,
|
|
22
|
+
resolveMemberRef,
|
|
23
|
+
versionCascade,
|
|
24
|
+
vscodeUrl,
|
|
25
|
+
type CatalogPackage,
|
|
26
|
+
} from "../../lib/catalog";
|
|
10
27
|
import { data } from "../../lib/data";
|
|
11
28
|
import { getEntry, render } from "astro:content";
|
|
29
|
+
import { Code } from "astro:components";
|
|
12
30
|
|
|
13
31
|
export function getStaticPaths() {
|
|
14
32
|
return data.packages.map((pkg) => ({
|
|
@@ -17,7 +35,7 @@ export function getStaticPaths() {
|
|
|
17
35
|
}));
|
|
18
36
|
}
|
|
19
37
|
|
|
20
|
-
const { config } = data;
|
|
38
|
+
const { config, packages } = data;
|
|
21
39
|
const { pkg } = Astro.props as { pkg: CatalogPackage };
|
|
22
40
|
const id = `${pkg.namespace}/${pkg.name}`;
|
|
23
41
|
|
|
@@ -26,14 +44,72 @@ const changelogEntry = await getEntry("changelogs", id);
|
|
|
26
44
|
const Readme = readmeEntry ? (await render(readmeEntry)).Content : null;
|
|
27
45
|
const Changelog = changelogEntry ? (await render(changelogEntry)).Content : null;
|
|
28
46
|
|
|
47
|
+
// What the artifact actually is, as opposed to what its README says about
|
|
48
|
+
// itself. Three shapes, one per kind family: a skill, rule or agent carries
|
|
49
|
+
// its entry-point markdown; a bundle carries the list it installs; an MCP
|
|
50
|
+
// server carries its descriptor. `enrich` writes whichever applies.
|
|
51
|
+
const contentsEntry = await getEntry("contents", id);
|
|
52
|
+
const Contents = contentsEntry ? (await render(contentsEntry)).Content : null;
|
|
53
|
+
const contentsData = (await getEntry("contentData", id))?.data as
|
|
54
|
+
| { members?: { kind?: string; name?: string; id?: string }[] }
|
|
55
|
+
| undefined;
|
|
56
|
+
|
|
57
|
+
// Members resolved against this index: a member published elsewhere has no
|
|
58
|
+
// page here, so it is named without a link rather than linked into a 404.
|
|
59
|
+
const memberList = contentsData?.members ?? [];
|
|
60
|
+
const members = memberList.map((member) => {
|
|
61
|
+
const ref = member.id ? resolveMemberRef(pkg.ref, member.id) : null;
|
|
62
|
+
const target = ref ? packages.find((p) => p.ref === ref) : undefined;
|
|
63
|
+
return {
|
|
64
|
+
kind: member.kind ?? "",
|
|
65
|
+
name: member.name ?? ref ?? "",
|
|
66
|
+
ref,
|
|
67
|
+
href: target ? withBase(`/p/${target.namespace}/${target.name}/`) : null,
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
|
|
29
71
|
const tags = [...(pkg.tags ?? [])].sort(compareVersions);
|
|
30
72
|
const cascade = versionCascade(pkg.version, tags);
|
|
31
73
|
const older = tags.filter((t) => !cascade.includes(t));
|
|
74
|
+
// Highlight what an untagged `grim add` actually installs. That is `latest`
|
|
75
|
+
// wherever it was published — the exact release is one of the things latest
|
|
76
|
+
// currently points at, not the default.
|
|
77
|
+
const defaultTag = cascade.includes("latest") ? "latest" : (pkg.version ?? cascade[0]);
|
|
78
|
+
|
|
79
|
+
// The forge the repository link points at, drawn as its own mark. `mdiGit`
|
|
80
|
+
// is the honest fallback for a host with no brand in the set — it still says
|
|
81
|
+
// "source repository", where a generic branch glyph said nothing.
|
|
82
|
+
const FORGES = [
|
|
83
|
+
{ match: /(^|\.)github\.com$/i, path: mdiGithub },
|
|
84
|
+
{ match: /(^|\.)gitlab\.[a-z.]+$/i, path: mdiGitlab },
|
|
85
|
+
{ match: /(^|\.)bitbucket\.org$/i, path: mdiBitbucket },
|
|
86
|
+
];
|
|
87
|
+
const repoHost = URL.canParse(pkg.repository ?? "") ? new URL(pkg.repository!).hostname : "";
|
|
88
|
+
const repoMark = FORGES.find((f) => f.match.test(repoHost))?.path ?? mdiGit;
|
|
89
|
+
|
|
90
|
+
// Which tabs have anything behind them. An empty one is offered but not
|
|
91
|
+
// selectable — greyed in the ribbon rather than clickable through to a
|
|
92
|
+
// sentence apologising for itself. The first tab that does have content is
|
|
93
|
+
// the one that opens, so a package with no README does not land on a blank.
|
|
94
|
+
const filled = {
|
|
95
|
+
readme: Readme !== null,
|
|
96
|
+
contents: Contents !== null || members.length > 0 || contentsData !== undefined,
|
|
97
|
+
changelog: Changelog !== null,
|
|
98
|
+
};
|
|
99
|
+
const openTab = (["readme", "contents", "changelog"] as const).find((tab) => filled[tab]) ?? null;
|
|
32
100
|
|
|
33
101
|
const title = pkg.title ?? pkg.name;
|
|
34
102
|
const description = pkg.summary ?? pkg.description ?? `${pkg.kind} package: ${pkg.ref}`;
|
|
35
103
|
const vscode = vscodeUrl(config.vscodeExtension, pkg.ref);
|
|
36
104
|
const addCommand = `grim add ${pkg.ref}`;
|
|
105
|
+
|
|
106
|
+
// The same two-way scope choice the package cards offer and the hero's
|
|
107
|
+
// registry bar draws, with the same two glyphs and the same order — global
|
|
108
|
+
// first, because it needs no project open.
|
|
109
|
+
const scopeChoices = [
|
|
110
|
+
{ name: "Global", command: `grim add --global ${pkg.ref}`, Icon: Globe },
|
|
111
|
+
{ name: "Project", command: addCommand, Icon: FolderRoot },
|
|
112
|
+
];
|
|
37
113
|
---
|
|
38
114
|
|
|
39
115
|
<Base title={`${title} — ${config.brand}`} description={description} image={pkg.logo}>
|
|
@@ -41,7 +117,18 @@ const addCommand = `grim add ${pkg.ref}`;
|
|
|
41
117
|
<header class="detail-head">
|
|
42
118
|
{
|
|
43
119
|
pkg.logo ? (
|
|
44
|
-
|
|
120
|
+
// The placeholder ships in the markup and the image fades in over
|
|
121
|
+
// it, so a slow or failing load never flashes a broken glyph. The
|
|
122
|
+
// slot starts `loading`; the handler in `Base.astro` moves it to
|
|
123
|
+
// `ready` or `broken`. Icon mirrors `LogoMark` in `Catalog.tsx`.
|
|
124
|
+
<span class="tile logo-slot" data-static data-state="loading">
|
|
125
|
+
{/* Both marks ship; CSS shows one per state. The card grid picks
|
|
126
|
+
between them in Preact instead, but this page has to settle
|
|
127
|
+
its placeholder before any script runs. */}
|
|
128
|
+
<Image class="logo-mark mark-loading" aria-hidden="true" />
|
|
129
|
+
<ImageOff class="logo-mark mark-broken" aria-hidden="true" />
|
|
130
|
+
<img src={withBase(pkg.logo)} alt="" />
|
|
131
|
+
</span>
|
|
45
132
|
) : (
|
|
46
133
|
<div class="tile letter" style={`background: var(--kind-${pkg.kind}, var(--muted))`}>
|
|
47
134
|
{pkg.name[0]?.toUpperCase()}
|
|
@@ -64,22 +151,120 @@ const addCommand = `grim add ${pkg.ref}`;
|
|
|
64
151
|
)
|
|
65
152
|
}
|
|
66
153
|
|
|
67
|
-
<div class="
|
|
68
|
-
<
|
|
69
|
-
<
|
|
154
|
+
<div class="cmd-bar" data-os-switch data-os-noun={`add command for ${pkg.name}`}>
|
|
155
|
+
<PickerMenu choices={scopeChoices} title="Scope" label="Choose the scope" />
|
|
156
|
+
<CommandField
|
|
157
|
+
value={scopeChoices[0].command}
|
|
158
|
+
name={`${scopeChoices[0].name} add command for ${pkg.name}`}
|
|
159
|
+
label="Copy the add command"
|
|
160
|
+
/>
|
|
161
|
+
{
|
|
162
|
+
vscode && (
|
|
163
|
+
<a
|
|
164
|
+
class="seg brand-link"
|
|
165
|
+
href={vscode}
|
|
166
|
+
title="Open in VS Code"
|
|
167
|
+
aria-label={`Open ${pkg.name} in VS Code`}
|
|
168
|
+
>
|
|
169
|
+
<BrandMark path={mdiMicrosoftVisualStudioCode} />
|
|
170
|
+
</a>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
70
173
|
</div>
|
|
71
174
|
|
|
72
175
|
<div class="detail-body">
|
|
73
176
|
<main class="detail-main">
|
|
74
|
-
{
|
|
177
|
+
{/* Radio tabs, not a scripted tablist: the ribbon works on a page
|
|
178
|
+
whose JS never ran, and a radio group already has the keyboard
|
|
179
|
+
behaviour tabs want (arrows move, only one selected). The inputs
|
|
180
|
+
are visually hidden rather than `hidden`, which would take them
|
|
181
|
+
out of the focus order along with the pixels. */}
|
|
182
|
+
{/* All three tabs are always offered — a ribbon that grows a tab per
|
|
183
|
+
package makes the panel a different shape on every page. An empty
|
|
184
|
+
one is disabled instead: greyed, not clickable through to a
|
|
185
|
+
sentence apologising for itself. */}
|
|
186
|
+
<input
|
|
187
|
+
type="radio"
|
|
188
|
+
name="panel"
|
|
189
|
+
id="panel-readme"
|
|
190
|
+
class="sr-only"
|
|
191
|
+
checked={openTab === "readme"}
|
|
192
|
+
disabled={!filled.readme}
|
|
193
|
+
/>
|
|
194
|
+
<input
|
|
195
|
+
type="radio"
|
|
196
|
+
name="panel"
|
|
197
|
+
id="panel-contents"
|
|
198
|
+
class="sr-only"
|
|
199
|
+
checked={openTab === "contents"}
|
|
200
|
+
disabled={!filled.contents}
|
|
201
|
+
/>
|
|
202
|
+
<input
|
|
203
|
+
type="radio"
|
|
204
|
+
name="panel"
|
|
205
|
+
id="panel-changelog"
|
|
206
|
+
class="sr-only"
|
|
207
|
+
checked={openTab === "changelog"}
|
|
208
|
+
disabled={!filled.changelog}
|
|
209
|
+
/>
|
|
210
|
+
{/* The greyed tab says it is not for clicking; the tooltip says why,
|
|
211
|
+
so the answer is one hover away instead of a panel of its own. */}
|
|
212
|
+
<div class="ribbon">
|
|
213
|
+
<label for="panel-readme" title={filled.readme ? undefined : "no readme available"}>
|
|
214
|
+
Readme
|
|
215
|
+
</label>
|
|
216
|
+
<label for="panel-contents" title={filled.contents ? undefined : "no contents available"}>
|
|
217
|
+
Contents
|
|
218
|
+
</label>
|
|
219
|
+
<label
|
|
220
|
+
for="panel-changelog"
|
|
221
|
+
title={filled.changelog ? undefined : "no changelog available"}
|
|
222
|
+
>
|
|
223
|
+
Changelog
|
|
224
|
+
</label>
|
|
225
|
+
</div>
|
|
226
|
+
{filled.readme && (
|
|
227
|
+
<div class="prose tab-panel" id="tab-readme">
|
|
228
|
+
<Readme />
|
|
229
|
+
</div>
|
|
230
|
+
)}
|
|
75
231
|
{
|
|
76
|
-
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
232
|
+
filled.contents && (
|
|
233
|
+
<div class="prose tab-panel" id="tab-contents">
|
|
234
|
+
{Contents ? (
|
|
235
|
+
<Contents />
|
|
236
|
+
) : members.length > 0 ? (
|
|
237
|
+
<ul class="members">
|
|
238
|
+
{members.map((member) => (
|
|
239
|
+
<li>
|
|
240
|
+
{member.href ? (
|
|
241
|
+
<a href={member.href}>{member.name}</a>
|
|
242
|
+
) : (
|
|
243
|
+
<span>{member.name}</span>
|
|
244
|
+
)}
|
|
245
|
+
<span class={`badge kind-${member.kind}`}>{member.kind}</span>
|
|
246
|
+
<code>{member.ref}</code>
|
|
247
|
+
</li>
|
|
248
|
+
))}
|
|
249
|
+
</ul>
|
|
250
|
+
) : (
|
|
251
|
+
<Code
|
|
252
|
+
code={JSON.stringify(contentsData, null, 2)}
|
|
253
|
+
lang="json"
|
|
254
|
+
themes={SHIKI_THEMES}
|
|
255
|
+
/>
|
|
256
|
+
)}
|
|
257
|
+
</div>
|
|
81
258
|
)
|
|
82
259
|
}
|
|
260
|
+
{filled.changelog && (
|
|
261
|
+
<div class="prose tab-panel" id="tab-changelog">
|
|
262
|
+
<Changelog />
|
|
263
|
+
</div>
|
|
264
|
+
)}
|
|
265
|
+
{/* Nothing anywhere: one statement in the middle of the panel, rather
|
|
266
|
+
than three tabs each denying it separately. */}
|
|
267
|
+
{openTab === null && <p class="panel-empty">No content available.</p>}
|
|
83
268
|
</main>
|
|
84
269
|
|
|
85
270
|
<aside class="detail-rail">
|
|
@@ -88,12 +273,15 @@ const addCommand = `grim add ${pkg.ref}`;
|
|
|
88
273
|
<h2>Versions</h2>
|
|
89
274
|
<div class="cascade">
|
|
90
275
|
{cascade.map((tag) => (
|
|
91
|
-
<VersionMenu pkgRef={pkg.ref} tag={tag} current={tag ===
|
|
276
|
+
<VersionMenu pkgRef={pkg.ref} tag={tag} current={tag === defaultTag} />
|
|
92
277
|
))}
|
|
93
278
|
</div>
|
|
94
279
|
{older.length > 0 && (
|
|
95
280
|
<details class="older">
|
|
96
|
-
<summary>
|
|
281
|
+
<summary>
|
|
282
|
+
<ChevronRight size={14} aria-hidden="true" />
|
|
283
|
+
older versions
|
|
284
|
+
</summary>
|
|
97
285
|
<div class="cascade">
|
|
98
286
|
{older.map((tag) => (
|
|
99
287
|
<VersionMenu pkgRef={pkg.ref} tag={tag} />
|
|
@@ -109,13 +297,19 @@ const addCommand = `grim add ${pkg.ref}`;
|
|
|
109
297
|
<dl>
|
|
110
298
|
{pkg.license && (
|
|
111
299
|
<>
|
|
112
|
-
<dt>
|
|
300
|
+
<dt>
|
|
301
|
+
<Scale size={15} aria-hidden="true" />
|
|
302
|
+
License
|
|
303
|
+
</dt>
|
|
113
304
|
<dd>{pkg.license}</dd>
|
|
114
305
|
</>
|
|
115
306
|
)}
|
|
116
307
|
{pkg.repository && (
|
|
117
308
|
<>
|
|
118
|
-
<dt>
|
|
309
|
+
<dt>
|
|
310
|
+
<BrandMark path={repoMark} size={15} />
|
|
311
|
+
Repository
|
|
312
|
+
</dt>
|
|
119
313
|
<dd>
|
|
120
314
|
<a href={pkg.repository} target="_blank" rel="noopener noreferrer">
|
|
121
315
|
source
|
|
@@ -125,7 +319,10 @@ const addCommand = `grim add ${pkg.ref}`;
|
|
|
125
319
|
)}
|
|
126
320
|
{pkg.owner?.github && (
|
|
127
321
|
<>
|
|
128
|
-
<dt>
|
|
322
|
+
<dt>
|
|
323
|
+
<User size={15} aria-hidden="true" />
|
|
324
|
+
Owner
|
|
325
|
+
</dt>
|
|
129
326
|
<dd>
|
|
130
327
|
<a
|
|
131
328
|
href={`https://github.com/${pkg.owner.github}`}
|
|
@@ -141,15 +338,19 @@ const addCommand = `grim add ${pkg.ref}`;
|
|
|
141
338
|
{pkg.keywords && pkg.keywords.length > 0 && (
|
|
142
339
|
<ul class="keywords">
|
|
143
340
|
{pkg.keywords.map((k) => (
|
|
144
|
-
<li>
|
|
341
|
+
<li>
|
|
342
|
+
{/* Back to the catalog with the term already in the search
|
|
343
|
+
box — a keyword is only useful if it leads somewhere. */}
|
|
344
|
+
<a
|
|
345
|
+
href={withBase(`/?q=${encodeURIComponent(k)}`)}
|
|
346
|
+
title={`Find packages tagged ${k}`}
|
|
347
|
+
>
|
|
348
|
+
{k}
|
|
349
|
+
</a>
|
|
350
|
+
</li>
|
|
145
351
|
))}
|
|
146
352
|
</ul>
|
|
147
353
|
)}
|
|
148
|
-
{vscode && (
|
|
149
|
-
<a class="vscode-link" href={vscode}>
|
|
150
|
-
Open in VS Code
|
|
151
|
-
</a>
|
|
152
|
-
)}
|
|
153
354
|
</section>
|
|
154
355
|
</aside>
|
|
155
356
|
</div>
|
|
@@ -247,20 +448,6 @@ const addCommand = `grim add ${pkg.ref}`;
|
|
|
247
448
|
padding: 0.6rem 0.9rem;
|
|
248
449
|
font-weight: 600;
|
|
249
450
|
}
|
|
250
|
-
.install-row {
|
|
251
|
-
display: flex;
|
|
252
|
-
align-items: center;
|
|
253
|
-
justify-content: space-between;
|
|
254
|
-
gap: 0.6rem;
|
|
255
|
-
background: var(--card);
|
|
256
|
-
border: 1px solid var(--border);
|
|
257
|
-
border-radius: 8px;
|
|
258
|
-
padding: 0.6rem 0.9rem;
|
|
259
|
-
}
|
|
260
|
-
.install-row code {
|
|
261
|
-
font-size: 0.9rem;
|
|
262
|
-
overflow-x: auto;
|
|
263
|
-
}
|
|
264
451
|
.detail-body {
|
|
265
452
|
display: grid;
|
|
266
453
|
grid-template-columns: 1fr 18rem;
|
|
@@ -279,20 +466,122 @@ const addCommand = `grim add ${pkg.ref}`;
|
|
|
279
466
|
border-radius: 10px;
|
|
280
467
|
padding: 1.25rem 1.5rem;
|
|
281
468
|
}
|
|
469
|
+
/* Markdown opens with its own heading, whose margin lands on top of the
|
|
470
|
+
panel padding and reads as a hole. Every panel sets its own top edge. */
|
|
471
|
+
.prose > :global(:first-child) {
|
|
472
|
+
margin-block-start: 0;
|
|
473
|
+
}
|
|
282
474
|
.detail-main :global(img) {
|
|
283
475
|
max-width: 100%;
|
|
284
476
|
}
|
|
285
477
|
.detail-main :global(pre) {
|
|
286
478
|
overflow-x: auto;
|
|
287
479
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
480
|
+
/* Full-bleed inside the panel's padding, so the rule under it reads as the
|
|
481
|
+
panel's own top edge rather than a line floating in the content. */
|
|
482
|
+
.ribbon {
|
|
483
|
+
display: flex;
|
|
484
|
+
gap: 1.25rem;
|
|
485
|
+
margin: -1.25rem -1.5rem 1.25rem;
|
|
486
|
+
padding: 0 1.5rem;
|
|
487
|
+
border-bottom: 1px solid var(--border);
|
|
292
488
|
}
|
|
293
|
-
.
|
|
489
|
+
.ribbon label {
|
|
490
|
+
padding: 0.7rem 0;
|
|
491
|
+
margin-bottom: -1px; /* the active underline sits on the panel rule */
|
|
492
|
+
border-bottom: 2px solid transparent;
|
|
493
|
+
font-size: 0.85rem;
|
|
494
|
+
text-transform: uppercase;
|
|
495
|
+
letter-spacing: 0.04em;
|
|
496
|
+
color: var(--muted);
|
|
294
497
|
cursor: pointer;
|
|
295
|
-
|
|
498
|
+
}
|
|
499
|
+
.ribbon label:hover {
|
|
500
|
+
color: var(--fg);
|
|
501
|
+
}
|
|
502
|
+
/* Nothing behind it. Still shown, so the ribbon is the same shape on every
|
|
503
|
+
package page, but visibly not a thing to click. */
|
|
504
|
+
#panel-readme:disabled ~ .ribbon label[for="panel-readme"],
|
|
505
|
+
#panel-contents:disabled ~ .ribbon label[for="panel-contents"],
|
|
506
|
+
#panel-changelog:disabled ~ .ribbon label[for="panel-changelog"] {
|
|
507
|
+
opacity: 0.4;
|
|
508
|
+
cursor: default;
|
|
509
|
+
}
|
|
510
|
+
#panel-readme:disabled ~ .ribbon label[for="panel-readme"]:hover,
|
|
511
|
+
#panel-contents:disabled ~ .ribbon label[for="panel-contents"]:hover,
|
|
512
|
+
#panel-changelog:disabled ~ .ribbon label[for="panel-changelog"]:hover {
|
|
513
|
+
color: var(--muted);
|
|
514
|
+
}
|
|
515
|
+
/* Every tab empty: say it once, in the middle. */
|
|
516
|
+
.panel-empty {
|
|
517
|
+
display: grid;
|
|
518
|
+
place-items: center;
|
|
519
|
+
min-height: 11rem;
|
|
520
|
+
margin: 0;
|
|
521
|
+
color: var(--muted);
|
|
522
|
+
}
|
|
523
|
+
.tab-panel {
|
|
524
|
+
display: none;
|
|
525
|
+
}
|
|
526
|
+
#panel-readme:checked ~ #tab-readme,
|
|
527
|
+
#panel-contents:checked ~ #tab-contents,
|
|
528
|
+
#panel-changelog:checked ~ #tab-changelog {
|
|
529
|
+
display: block;
|
|
530
|
+
}
|
|
531
|
+
#panel-readme:checked ~ .ribbon label[for="panel-readme"],
|
|
532
|
+
#panel-contents:checked ~ .ribbon label[for="panel-contents"],
|
|
533
|
+
#panel-changelog:checked ~ .ribbon label[for="panel-changelog"] {
|
|
534
|
+
color: var(--fg);
|
|
535
|
+
border-bottom-color: var(--accent);
|
|
536
|
+
}
|
|
537
|
+
/* The input carries the focus, the label shows it. */
|
|
538
|
+
#panel-readme:focus-visible ~ .ribbon label[for="panel-readme"],
|
|
539
|
+
#panel-contents:focus-visible ~ .ribbon label[for="panel-contents"],
|
|
540
|
+
#panel-changelog:focus-visible ~ .ribbon label[for="panel-changelog"] {
|
|
541
|
+
outline: 2px solid var(--accent);
|
|
542
|
+
outline-offset: 2px;
|
|
543
|
+
border-radius: 3px;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/* A bundle's member list: what it installs, one row each. A grid, not a
|
|
547
|
+
row of flex items — names and badges of different widths have to line up
|
|
548
|
+
down the column, which is the whole reason to list them. `subgrid` puts
|
|
549
|
+
each <li>'s three cells on the parent's tracks. */
|
|
550
|
+
.members {
|
|
551
|
+
list-style: none;
|
|
552
|
+
margin: 0;
|
|
553
|
+
padding: 0;
|
|
554
|
+
display: grid;
|
|
555
|
+
grid-template-columns: auto auto 1fr;
|
|
556
|
+
gap: 0.45rem 0.75rem;
|
|
557
|
+
align-items: center;
|
|
558
|
+
}
|
|
559
|
+
.members li {
|
|
560
|
+
display: grid;
|
|
561
|
+
grid-column: 1 / -1;
|
|
562
|
+
grid-template-columns: subgrid;
|
|
563
|
+
align-items: center;
|
|
564
|
+
}
|
|
565
|
+
/* A grid item stretches by default, so a short badge grew to the width of
|
|
566
|
+
the longest one in the column and its pill outran its own text. */
|
|
567
|
+
.members .badge {
|
|
568
|
+
justify-self: start;
|
|
569
|
+
}
|
|
570
|
+
.members code {
|
|
571
|
+
color: var(--muted);
|
|
572
|
+
font-size: 0.82rem;
|
|
573
|
+
overflow-wrap: anywhere;
|
|
574
|
+
}
|
|
575
|
+
/* Narrow: three columns of a long ref do not fit, so each member becomes
|
|
576
|
+
its own block instead of a squeezed row. */
|
|
577
|
+
@media (max-width: 34rem) {
|
|
578
|
+
.members,
|
|
579
|
+
.members li {
|
|
580
|
+
grid-template-columns: auto 1fr;
|
|
581
|
+
}
|
|
582
|
+
.members li > code {
|
|
583
|
+
grid-column: 1 / -1;
|
|
584
|
+
}
|
|
296
585
|
}
|
|
297
586
|
.detail-rail {
|
|
298
587
|
display: flex;
|
|
@@ -305,6 +594,8 @@ const addCommand = `grim add ${pkg.ref}`;
|
|
|
305
594
|
border-radius: 10px;
|
|
306
595
|
padding: 1rem;
|
|
307
596
|
}
|
|
597
|
+
/* Panel label. The main panel's ribbon labels match this by hand — they
|
|
598
|
+
are <label>s, not headings, so they cannot join the selector. */
|
|
308
599
|
.detail-rail h2 {
|
|
309
600
|
margin: 0 0 0.6rem;
|
|
310
601
|
font-size: 0.85rem;
|
|
@@ -320,31 +611,69 @@ const addCommand = `grim add ${pkg.ref}`;
|
|
|
320
611
|
gap: 0.3rem;
|
|
321
612
|
}
|
|
322
613
|
.older {
|
|
323
|
-
margin-top: 0.
|
|
324
|
-
}
|
|
325
|
-
.older[open] > summary {
|
|
326
|
-
margin-bottom: 0.5rem;
|
|
614
|
+
margin-top: 0.7rem;
|
|
327
615
|
}
|
|
616
|
+
/* The UA marker is a glyph that flips between two characters with no
|
|
617
|
+
in-between; a real chevron can turn. `list-style` kills it everywhere
|
|
618
|
+
but WebKit, which needs its own pseudo-element. Closed points right at
|
|
619
|
+
the row it would open, open points down at it — a quarter turn, the way
|
|
620
|
+
a file tree behaves. */
|
|
328
621
|
.older > summary {
|
|
622
|
+
display: flex;
|
|
623
|
+
align-items: center;
|
|
624
|
+
gap: 0.35rem;
|
|
625
|
+
list-style: none;
|
|
626
|
+
padding-block: 0.15rem;
|
|
329
627
|
cursor: pointer;
|
|
330
628
|
font-size: 0.8rem;
|
|
331
629
|
color: var(--muted);
|
|
332
630
|
}
|
|
631
|
+
.older > summary::-webkit-details-marker {
|
|
632
|
+
display: none;
|
|
633
|
+
}
|
|
333
634
|
.older > summary:hover {
|
|
334
635
|
color: var(--fg);
|
|
335
636
|
}
|
|
637
|
+
.older > summary :global(svg) {
|
|
638
|
+
flex-shrink: 0;
|
|
639
|
+
transition: transform 160ms ease;
|
|
640
|
+
}
|
|
641
|
+
.older[open] > summary :global(svg) {
|
|
642
|
+
transform: rotate(90deg);
|
|
643
|
+
}
|
|
644
|
+
@media (prefers-reduced-motion: reduce) {
|
|
645
|
+
.older > summary :global(svg) {
|
|
646
|
+
transition: none;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
.older[open] > summary {
|
|
650
|
+
margin-bottom: 0.5rem;
|
|
651
|
+
}
|
|
336
652
|
dl {
|
|
337
653
|
margin: 0;
|
|
338
654
|
display: grid;
|
|
339
655
|
grid-template-columns: auto 1fr;
|
|
340
|
-
|
|
656
|
+
/* A row pairs a small muted label with a larger value. Grid's default
|
|
657
|
+
`stretch` sits each at the top of its own box, so the size difference
|
|
658
|
+
reads as two lines that never quite line up. */
|
|
659
|
+
align-items: center;
|
|
660
|
+
gap: 0.45rem 0.7rem;
|
|
341
661
|
}
|
|
342
662
|
dt {
|
|
663
|
+
display: flex;
|
|
664
|
+
align-items: center;
|
|
665
|
+
gap: 0.4rem;
|
|
343
666
|
color: var(--muted);
|
|
344
667
|
font-size: 0.85rem;
|
|
345
668
|
}
|
|
669
|
+
/* Every icon is the same box, so the label text lands on one x without an
|
|
670
|
+
icon column of its own. */
|
|
671
|
+
dt :global(svg) {
|
|
672
|
+
flex-shrink: 0;
|
|
673
|
+
}
|
|
346
674
|
dd {
|
|
347
675
|
margin: 0;
|
|
676
|
+
font-size: 0.9rem;
|
|
348
677
|
}
|
|
349
678
|
.keywords {
|
|
350
679
|
list-style: none;
|
|
@@ -354,15 +683,34 @@ const addCommand = `grim add ${pkg.ref}`;
|
|
|
354
683
|
flex-wrap: wrap;
|
|
355
684
|
gap: 0.35rem;
|
|
356
685
|
}
|
|
357
|
-
|
|
358
|
-
|
|
686
|
+
/* The chip is the link, not a box around it — otherwise the padding is
|
|
687
|
+
dead space the pointer falls through. Bordered and hovering to accent
|
|
688
|
+
like the version pills beside it and the kind chips on the catalog:
|
|
689
|
+
every other pill on the site answers a pointer the same way, and one
|
|
690
|
+
that answers differently reads as a different kind of thing. */
|
|
691
|
+
.keywords a {
|
|
692
|
+
display: inline-block;
|
|
693
|
+
border: 1px solid var(--border);
|
|
359
694
|
border-radius: 999px;
|
|
695
|
+
background: var(--chip-bg);
|
|
360
696
|
padding: 0.15rem 0.6rem;
|
|
361
697
|
font-size: 0.78rem;
|
|
698
|
+
color: var(--muted);
|
|
699
|
+
text-decoration: none;
|
|
700
|
+
transition:
|
|
701
|
+
color 120ms ease,
|
|
702
|
+
border-color 120ms ease,
|
|
703
|
+
background-color 120ms ease;
|
|
362
704
|
}
|
|
363
|
-
.
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
705
|
+
.keywords a:hover,
|
|
706
|
+
.keywords a:focus-visible {
|
|
707
|
+
color: var(--accent);
|
|
708
|
+
border-color: var(--accent);
|
|
709
|
+
background: var(--accent-soft);
|
|
710
|
+
}
|
|
711
|
+
@media (prefers-reduced-motion: reduce) {
|
|
712
|
+
.keywords a {
|
|
713
|
+
transition: none;
|
|
714
|
+
}
|
|
367
715
|
}
|
|
368
716
|
</style>
|
package/dist/renderer/index.d.ts
CHANGED
|
@@ -6,6 +6,20 @@ export interface BuildSiteOptions {
|
|
|
6
6
|
outDir: string;
|
|
7
7
|
config: SiteConfig;
|
|
8
8
|
}
|
|
9
|
+
export interface DevSiteOptions extends BuildSiteOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Astro `srcDir`. Point it at the working tree's `src/renderer/astro` to
|
|
12
|
+
* get HMR on the templates being edited; omitted, dev stages a copy and
|
|
13
|
+
* behaves like a build that never finishes.
|
|
14
|
+
*/
|
|
15
|
+
srcDir?: string;
|
|
16
|
+
port?: number;
|
|
17
|
+
}
|
|
18
|
+
/** A running dev server. `stop()` also removes the staged directory. */
|
|
19
|
+
export interface DevServer {
|
|
20
|
+
url: string;
|
|
21
|
+
stop(): Promise<void>;
|
|
22
|
+
}
|
|
9
23
|
/** Thrown when `<outDir>/all.json` is missing or malformed. */
|
|
10
24
|
export declare class RenderInputError extends Error {
|
|
11
25
|
constructor(message: string);
|
|
@@ -16,5 +30,14 @@ export declare const ASTRO_SRC_DIR: string;
|
|
|
16
30
|
export declare const DEFAULT_PUBLIC_DIR: string;
|
|
17
31
|
/** Render the catalog site from `<outDir>/all.json` into `outDir`. */
|
|
18
32
|
export declare function buildSite(opts: BuildSiteOptions): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Serve the same site `buildSite` would emit, with HMR — the review loop for
|
|
35
|
+
* a rendering change, so the look can be judged without cutting a release.
|
|
36
|
+
*
|
|
37
|
+
* The staged dir and the chdir both outlive the call, because the server
|
|
38
|
+
* does: Vite resolves the staged `node_modules` on every request. `stop()`
|
|
39
|
+
* undoes both.
|
|
40
|
+
*/
|
|
41
|
+
export declare function devSite(opts: DevSiteOptions): Promise<DevServer>;
|
|
19
42
|
export type { CatalogPackage, GrimoireData } from "./types.js";
|
|
20
43
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/renderer/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/renderer/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAI9D,MAAM,WAAW,gBAAgB;IAC/B,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,MAAM,WAAW,cAAe,SAAQ,gBAAgB;IACtD;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wEAAwE;AACxE,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,+DAA+D;AAC/D,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI5B;AA4BD,mEAAmE;AACnE,eAAO,MAAM,aAAa,QAA2B,CAAC;AACtD,2DAA2D;AAC3D,eAAO,MAAM,kBAAkB,QAA4B,CAAC;AAuM5D,sEAAsE;AACtE,wBAAsB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBrE;AAED;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,SAAS,CAAC,CA2BtE;AAED,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
|