@grimoire-rs/indexer 0.1.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/LICENSE +201 -0
- package/README.md +48 -0
- package/dist/cli/build.d.ts +6 -0
- package/dist/cli/build.d.ts.map +1 -0
- package/dist/cli/build.js +29 -0
- package/dist/cli/build.js.map +1 -0
- package/dist/cli/exit.d.ts +28 -0
- package/dist/cli/exit.d.ts.map +1 -0
- package/dist/cli/exit.js +33 -0
- package/dist/cli/exit.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +8 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +40 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +317 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +112 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/validate.d.ts +18 -0
- package/dist/cli/validate.d.ts.map +1 -0
- package/dist/cli/validate.js +139 -0
- package/dist/cli/validate.js.map +1 -0
- package/dist/config.d.ts +76 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +134 -0
- package/dist/config.js.map +1 -0
- package/dist/data/index.d.ts +40 -0
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/index.js +103 -0
- package/dist/data/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/integration.d.ts +2 -0
- package/dist/integration.d.ts.map +1 -0
- package/dist/integration.js +4 -0
- package/dist/integration.js.map +1 -0
- package/dist/renderer/astro/components/Catalog.d.ts +6 -0
- package/dist/renderer/astro/components/Catalog.d.ts.map +1 -0
- package/dist/renderer/astro/components/Catalog.js +73 -0
- package/dist/renderer/astro/components/Catalog.js.map +1 -0
- package/dist/renderer/astro/components/Catalog.tsx +256 -0
- package/dist/renderer/astro/components/CopyButton.astro +24 -0
- package/dist/renderer/astro/components/VersionMenu.astro +105 -0
- package/dist/renderer/astro/content.config.ts +30 -0
- package/dist/renderer/astro/layouts/Base.astro +623 -0
- package/dist/renderer/astro/lib/catalog.d.ts +7 -0
- package/dist/renderer/astro/lib/catalog.d.ts.map +1 -0
- package/dist/renderer/astro/lib/catalog.js +61 -0
- package/dist/renderer/astro/lib/catalog.js.map +1 -0
- package/dist/renderer/astro/lib/catalog.ts +69 -0
- package/dist/renderer/astro/lib/data.d.ts +3 -0
- package/dist/renderer/astro/lib/data.d.ts.map +1 -0
- package/dist/renderer/astro/lib/data.js +4 -0
- package/dist/renderer/astro/lib/data.js.map +1 -0
- package/dist/renderer/astro/lib/data.ts +12 -0
- package/dist/renderer/astro/pages/index.astro +69 -0
- package/dist/renderer/astro/pages/p/[...slug].astro +367 -0
- package/dist/renderer/index.d.ts +20 -0
- package/dist/renderer/index.d.ts.map +1 -0
- package/dist/renderer/index.js +192 -0
- package/dist/renderer/index.js.map +1 -0
- package/dist/renderer/public/favicon.svg +4 -0
- package/dist/renderer/types.d.ts +34 -0
- package/dist/renderer/types.d.ts.map +1 -0
- package/dist/renderer/types.js +4 -0
- package/dist/renderer/types.js.map +1 -0
- package/dist/validate/adapters/files.d.ts +17 -0
- package/dist/validate/adapters/files.d.ts.map +1 -0
- package/dist/validate/adapters/files.js +50 -0
- package/dist/validate/adapters/files.js.map +1 -0
- package/dist/validate/adapters/forge.d.ts +25 -0
- package/dist/validate/adapters/forge.d.ts.map +1 -0
- package/dist/validate/adapters/forge.js +100 -0
- package/dist/validate/adapters/forge.js.map +1 -0
- package/dist/validate/adapters/http.d.ts +24 -0
- package/dist/validate/adapters/http.d.ts.map +1 -0
- package/dist/validate/adapters/http.js +75 -0
- package/dist/validate/adapters/http.js.map +1 -0
- package/dist/validate/adapters/registry.d.ts +16 -0
- package/dist/validate/adapters/registry.d.ts.map +1 -0
- package/dist/validate/adapters/registry.js +91 -0
- package/dist/validate/adapters/registry.js.map +1 -0
- package/dist/validate/core/metadata.d.ts +67 -0
- package/dist/validate/core/metadata.d.ts.map +1 -0
- package/dist/validate/core/metadata.js +149 -0
- package/dist/validate/core/metadata.js.map +1 -0
- package/dist/validate/core/ownership.d.ts +43 -0
- package/dist/validate/core/ownership.d.ts.map +1 -0
- package/dist/validate/core/ownership.js +33 -0
- package/dist/validate/core/ownership.js.map +1 -0
- package/dist/validate/core/paths.d.ts +21 -0
- package/dist/validate/core/paths.d.ts.map +1 -0
- package/dist/validate/core/paths.js +34 -0
- package/dist/validate/core/paths.js.map +1 -0
- package/dist/validate/index.d.ts +40 -0
- package/dist/validate/index.d.ts.map +1 -0
- package/dist/validate/index.js +206 -0
- package/dist/validate/index.js.map +1 -0
- package/package.json +50 -0
- package/templates/README.md +48 -0
- package/templates/github-pages.yml +24 -0
- package/templates/github-validate.yml +36 -0
- package/templates/gitignore +9 -0
- package/templates/gitlab-ci.yml +13 -0
- package/templates/publish.toml +17 -0
- package/templates/reusable/gitlab-index-ci.yml +61 -0
- package/templates/reusable/index-pages.yml +80 -0
- package/templates/reusable/index-validate.yml +88 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 The Grimoire Authors
|
|
3
|
+
|
|
4
|
+
// Presentational helpers only — pure functions, safe to pull into the
|
|
5
|
+
// client bundle. The build-time payload lives in `./data.ts`; the package
|
|
6
|
+
// data itself is compiled by `src/data/` into `<outDir>/all.json`, so
|
|
7
|
+
// nothing here walks the index tree.
|
|
8
|
+
export type { CatalogPackage } from "../../types.js";
|
|
9
|
+
|
|
10
|
+
// Publishing 0.10.0 also moves the rolling tags 0.10, 0 and latest, so the
|
|
11
|
+
// full tag list is mostly history. Return just the current release's chain
|
|
12
|
+
// (latest | 0 | 0.10 | 0.10.0). `tags` must already be sorted newest-first.
|
|
13
|
+
export function versionCascade(version: string | undefined, tags: string[]): string[] {
|
|
14
|
+
const pinned = version ?? tags.find((t) => /^v?\d/.test(t));
|
|
15
|
+
// Split the raw string so a "v" prefix rides along into the rolling tags
|
|
16
|
+
// it was published under ("v1.2.3" -> "v1", "v1.2"), same as unprefixed.
|
|
17
|
+
const [major, minor] = (pinned ?? "").split(".");
|
|
18
|
+
const chain = [...new Set(["latest", major, `${major}.${minor}`, pinned])].filter(
|
|
19
|
+
(t): t is string => !!t && tags.includes(t),
|
|
20
|
+
);
|
|
21
|
+
// Tags nothing recognises as a version (["stable", "beta"]) yield no chain
|
|
22
|
+
// — front the newest few rather than an empty strip.
|
|
23
|
+
return chain.length > 0 ? chain : tags.slice(0, 4);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Tags are published version strings (e.g. "v1.2.0"); sort latest first,
|
|
27
|
+
// numeric-aware so "1.10.0" outranks "1.9.0".
|
|
28
|
+
export function compareVersions(a: string, b: string): number {
|
|
29
|
+
const parts = (s: string) => s.replace(/^v/i, "").split(/[.-]/);
|
|
30
|
+
const pa = parts(a);
|
|
31
|
+
const pb = parts(b);
|
|
32
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
33
|
+
const [na, nb] = [Number(pa[i]), Number(pb[i])];
|
|
34
|
+
if (!Number.isNaN(na) && !Number.isNaN(nb)) {
|
|
35
|
+
if (na !== nb) return nb - na;
|
|
36
|
+
} else if (pa[i] !== pb[i]) {
|
|
37
|
+
return (pb[i] ?? "").localeCompare(pa[i] ?? "");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// MDN-standard Intl.RelativeTimeFormat rollup (no date library).
|
|
44
|
+
const RTF = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
|
45
|
+
const TIME_DIVISIONS: [number, Intl.RelativeTimeFormatUnit][] = [
|
|
46
|
+
[60, "seconds"],
|
|
47
|
+
[60, "minutes"],
|
|
48
|
+
[24, "hours"],
|
|
49
|
+
[7, "days"],
|
|
50
|
+
[4.34524, "weeks"],
|
|
51
|
+
[12, "months"],
|
|
52
|
+
[Infinity, "years"],
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
export function timeAgo(iso: string): string {
|
|
56
|
+
const ms = new Date(iso).getTime();
|
|
57
|
+
if (!Number.isFinite(ms)) return ""; // unparseable: caller hides the element
|
|
58
|
+
let duration = (ms - Date.now()) / 1000;
|
|
59
|
+
for (const [amount, unit] of TIME_DIVISIONS) {
|
|
60
|
+
if (Math.abs(duration) < amount) return RTF.format(Math.round(duration), unit);
|
|
61
|
+
duration /= amount;
|
|
62
|
+
}
|
|
63
|
+
return RTF.format(Math.round(duration), "years");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** `vscode://<publisher.extension>/open?repo=<ref>`, or null when disabled. */
|
|
67
|
+
export function vscodeUrl(extension: string | null, ref: string): string | null {
|
|
68
|
+
return extension ? `vscode://${extension}/open?repo=${encodeURIComponent(ref)}` : null;
|
|
69
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../../../src/renderer/astro/lib/data.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAInD,eAAO,MAAM,IAAI,EAAE,YAAgC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data.js","sourceRoot":"","sources":["../../../../src/renderer/astro/lib/data.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,sCAAsC;AAUtC,MAAM,CAAC,MAAM,IAAI,GAAiB,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 The Grimoire Authors
|
|
3
|
+
|
|
4
|
+
// The build-time bridge. `buildSite` replaces `__GRIMOIRE_DATA__` with a
|
|
5
|
+
// JSON literal through Vite `define`, so this module is server-only by
|
|
6
|
+
// construction — importing it from a hydrated island would inline the
|
|
7
|
+
// whole catalog into the client bundle. Islands take what they need as props.
|
|
8
|
+
import type { GrimoireData } from "../../types.js";
|
|
9
|
+
|
|
10
|
+
declare const __GRIMOIRE_DATA__: GrimoireData;
|
|
11
|
+
|
|
12
|
+
export const data: GrimoireData = __GRIMOIRE_DATA__;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Base from "../layouts/Base.astro";
|
|
3
|
+
import Catalog from "../components/Catalog.tsx";
|
|
4
|
+
import CopyButton from "../components/CopyButton.astro";
|
|
5
|
+
import { data } from "../lib/data";
|
|
6
|
+
|
|
7
|
+
const { config, packages } = data;
|
|
8
|
+
|
|
9
|
+
// The "add this index" ergonomic (`helm repo add`): the command a visitor
|
|
10
|
+
// needs to point their own grim at *this* index, plus the equivalent
|
|
11
|
+
// hand-edit. Absent `registry` config means the index has no public URL to
|
|
12
|
+
// hand out, so the block is omitted rather than guessed.
|
|
13
|
+
const registryRows = config.registry
|
|
14
|
+
? [
|
|
15
|
+
{
|
|
16
|
+
os: "add index",
|
|
17
|
+
command: `grim config registry add ${config.registry.alias} --index ${config.registry.index}`,
|
|
18
|
+
block: false,
|
|
19
|
+
label: "Copy the registry add command",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
os: "grimoire.toml",
|
|
23
|
+
command: `[[registries]]\nalias = "${config.registry.alias}"\nindex = "${config.registry.index}"`,
|
|
24
|
+
block: true,
|
|
25
|
+
label: "Copy the registries TOML entry",
|
|
26
|
+
},
|
|
27
|
+
]
|
|
28
|
+
: [];
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
<Base title={config.brand} description={config.description}>
|
|
32
|
+
<div class="hero">
|
|
33
|
+
<h1>{config.brand}</h1>
|
|
34
|
+
<p>
|
|
35
|
+
{config.tagline}
|
|
36
|
+
{
|
|
37
|
+
config.installDocsUrl && (
|
|
38
|
+
<Fragment>
|
|
39
|
+
{" "}
|
|
40
|
+
Copy an <code>add</code> command below — or{" "}
|
|
41
|
+
<a href={config.installDocsUrl}>install grim</a> first.
|
|
42
|
+
</Fragment>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
</p>
|
|
46
|
+
{
|
|
47
|
+
(config.install.length > 0 || registryRows.length > 0) && (
|
|
48
|
+
<div class="install">
|
|
49
|
+
{config.install.map((row) => (
|
|
50
|
+
<div class="install-row">
|
|
51
|
+
<span class="os">{row.os}</span>
|
|
52
|
+
<code>{row.command}</code>
|
|
53
|
+
<CopyButton value={row.command} label={`Copy the ${row.os} install command`} />
|
|
54
|
+
</div>
|
|
55
|
+
))}
|
|
56
|
+
{registryRows.map((row) => (
|
|
57
|
+
<div class="install-row">
|
|
58
|
+
<span class="os">{row.os}</span>
|
|
59
|
+
<code class:list={[{ block: row.block }]}>{row.command}</code>
|
|
60
|
+
<CopyButton value={row.command} label={row.label} />
|
|
61
|
+
</div>
|
|
62
|
+
))}
|
|
63
|
+
</div>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<Catalog client:load packages={packages} vscodeExtension={config.vscodeExtension} />
|
|
69
|
+
</Base>
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Frozen public URL: /p/<namespace>/<name>/. grim resolves against it, the
|
|
3
|
+
// VS Code deep link points at it, and search engines index it — the route
|
|
4
|
+
// shape is a contract, not an implementation detail.
|
|
5
|
+
import Base from "../../layouts/Base.astro";
|
|
6
|
+
import VersionMenu from "../../components/VersionMenu.astro";
|
|
7
|
+
import CopyButton from "../../components/CopyButton.astro";
|
|
8
|
+
import { compareVersions, versionCascade, vscodeUrl, type CatalogPackage } from "../../lib/catalog";
|
|
9
|
+
import { data } from "../../lib/data";
|
|
10
|
+
import { getEntry, render } from "astro:content";
|
|
11
|
+
|
|
12
|
+
export function getStaticPaths() {
|
|
13
|
+
return data.packages.map((pkg) => ({
|
|
14
|
+
params: { slug: `${pkg.namespace}/${pkg.name}` },
|
|
15
|
+
props: { pkg },
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { config } = data;
|
|
20
|
+
const { pkg } = Astro.props as { pkg: CatalogPackage };
|
|
21
|
+
const id = `${pkg.namespace}/${pkg.name}`;
|
|
22
|
+
|
|
23
|
+
const readmeEntry = await getEntry("readmes", id);
|
|
24
|
+
const changelogEntry = await getEntry("changelogs", id);
|
|
25
|
+
const Readme = readmeEntry ? (await render(readmeEntry)).Content : null;
|
|
26
|
+
const Changelog = changelogEntry ? (await render(changelogEntry)).Content : null;
|
|
27
|
+
|
|
28
|
+
const tags = [...(pkg.tags ?? [])].sort(compareVersions);
|
|
29
|
+
const cascade = versionCascade(pkg.version, tags);
|
|
30
|
+
const older = tags.filter((t) => !cascade.includes(t));
|
|
31
|
+
|
|
32
|
+
const title = pkg.title ?? pkg.name;
|
|
33
|
+
const description = pkg.summary ?? pkg.description ?? `${pkg.kind} package: ${pkg.ref}`;
|
|
34
|
+
const vscode = vscodeUrl(config.vscodeExtension, pkg.ref);
|
|
35
|
+
const addCommand = `grim add ${pkg.ref}`;
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
<Base title={`${title} — ${config.brand}`} description={description} image={pkg.logo}>
|
|
39
|
+
<div class="detail">
|
|
40
|
+
<header class="detail-head">
|
|
41
|
+
{
|
|
42
|
+
pkg.logo ? (
|
|
43
|
+
<img class="tile logo" src={pkg.logo} alt="" />
|
|
44
|
+
) : (
|
|
45
|
+
<div class="tile letter" style={`background: var(--kind-${pkg.kind}, var(--muted))`}>
|
|
46
|
+
{pkg.name[0]?.toUpperCase()}
|
|
47
|
+
</div>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
<div class="detail-head-text">
|
|
51
|
+
<h1>
|
|
52
|
+
{pkg.name} <span class={`badge kind-${pkg.kind}`}>{pkg.kind}</span>
|
|
53
|
+
</h1>
|
|
54
|
+
{description && <p class="summary">{description}</p>}
|
|
55
|
+
</div>
|
|
56
|
+
</header>
|
|
57
|
+
|
|
58
|
+
{
|
|
59
|
+
pkg.deprecated && (
|
|
60
|
+
<p class="deprecated-banner">
|
|
61
|
+
⚠️ deprecated{pkg.replacedBy ? ` — replaced by ${pkg.replacedBy}` : ""}
|
|
62
|
+
</p>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
<div class="install-row">
|
|
67
|
+
<code>{addCommand}</code>
|
|
68
|
+
<CopyButton value={addCommand} label="Copy the install command" />
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div class="detail-body">
|
|
72
|
+
<main class="detail-main">
|
|
73
|
+
{Readme ? <Readme /> : <p class="empty">No README available.</p>}
|
|
74
|
+
{
|
|
75
|
+
Changelog && (
|
|
76
|
+
<details class="changelog">
|
|
77
|
+
<summary>Changelog</summary>
|
|
78
|
+
<Changelog />
|
|
79
|
+
</details>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
</main>
|
|
83
|
+
|
|
84
|
+
<aside class="detail-rail">
|
|
85
|
+
{tags.length > 0 && (
|
|
86
|
+
<section>
|
|
87
|
+
<h2>Versions</h2>
|
|
88
|
+
<div class="cascade">
|
|
89
|
+
{cascade.map((tag) => (
|
|
90
|
+
<VersionMenu pkgRef={pkg.ref} tag={tag} current={tag === pkg.version} />
|
|
91
|
+
))}
|
|
92
|
+
</div>
|
|
93
|
+
{older.length > 0 && (
|
|
94
|
+
<details class="older">
|
|
95
|
+
<summary>{older.length} older</summary>
|
|
96
|
+
<div class="cascade">
|
|
97
|
+
{older.map((tag) => (
|
|
98
|
+
<VersionMenu pkgRef={pkg.ref} tag={tag} />
|
|
99
|
+
))}
|
|
100
|
+
</div>
|
|
101
|
+
</details>
|
|
102
|
+
)}
|
|
103
|
+
</section>
|
|
104
|
+
)}
|
|
105
|
+
|
|
106
|
+
<section>
|
|
107
|
+
<h2>Details</h2>
|
|
108
|
+
<dl>
|
|
109
|
+
{pkg.license && (
|
|
110
|
+
<>
|
|
111
|
+
<dt>License</dt>
|
|
112
|
+
<dd>{pkg.license}</dd>
|
|
113
|
+
</>
|
|
114
|
+
)}
|
|
115
|
+
{pkg.repository && (
|
|
116
|
+
<>
|
|
117
|
+
<dt>Repository</dt>
|
|
118
|
+
<dd>
|
|
119
|
+
<a href={pkg.repository} target="_blank" rel="noopener noreferrer">
|
|
120
|
+
source
|
|
121
|
+
</a>
|
|
122
|
+
</dd>
|
|
123
|
+
</>
|
|
124
|
+
)}
|
|
125
|
+
{pkg.owner?.github && (
|
|
126
|
+
<>
|
|
127
|
+
<dt>Owner</dt>
|
|
128
|
+
<dd>
|
|
129
|
+
<a
|
|
130
|
+
href={`https://github.com/${pkg.owner.github}`}
|
|
131
|
+
target="_blank"
|
|
132
|
+
rel="noopener noreferrer"
|
|
133
|
+
>
|
|
134
|
+
{pkg.owner.github}
|
|
135
|
+
</a>
|
|
136
|
+
</dd>
|
|
137
|
+
</>
|
|
138
|
+
)}
|
|
139
|
+
</dl>
|
|
140
|
+
{pkg.keywords && pkg.keywords.length > 0 && (
|
|
141
|
+
<ul class="keywords">
|
|
142
|
+
{pkg.keywords.map((k) => (
|
|
143
|
+
<li>{k}</li>
|
|
144
|
+
))}
|
|
145
|
+
</ul>
|
|
146
|
+
)}
|
|
147
|
+
{vscode && (
|
|
148
|
+
<a class="vscode-link" href={vscode}>
|
|
149
|
+
Open in VS Code
|
|
150
|
+
</a>
|
|
151
|
+
)}
|
|
152
|
+
</section>
|
|
153
|
+
</aside>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
</Base>
|
|
157
|
+
|
|
158
|
+
<script>
|
|
159
|
+
// Right-click a version: open its command menu at the pointer. The menu is
|
|
160
|
+
// a `manual` popover (see VersionMenu.astro), so dismissal is ours.
|
|
161
|
+
// Copying itself rides the shared [data-copy] listener in Base.astro.
|
|
162
|
+
let openMenu: HTMLElement | null = null;
|
|
163
|
+
const closeMenu = () => {
|
|
164
|
+
openMenu?.hidePopover();
|
|
165
|
+
openMenu = null;
|
|
166
|
+
};
|
|
167
|
+
// A right-click fires no `click`, so this never eats the gesture that just
|
|
168
|
+
// opened the menu — but it does close on a command item, which is wanted.
|
|
169
|
+
document.addEventListener("click", closeMenu);
|
|
170
|
+
document.addEventListener("scroll", closeMenu, true);
|
|
171
|
+
document.addEventListener("keydown", (e) => {
|
|
172
|
+
if (e.key === "Escape") closeMenu();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
for (const seg of document.querySelectorAll<HTMLElement>("[data-menu]")) {
|
|
176
|
+
const menu = document.getElementById(seg.dataset.menu ?? "");
|
|
177
|
+
if (!menu) continue;
|
|
178
|
+
seg.addEventListener("contextmenu", (e) => {
|
|
179
|
+
e.preventDefault();
|
|
180
|
+
closeMenu();
|
|
181
|
+
// The keyboard Menu key fires contextmenu at 0,0 — fall back to the
|
|
182
|
+
// segment itself so it doesn't land in the page corner.
|
|
183
|
+
const seat = seg.getBoundingClientRect();
|
|
184
|
+
menu.style.left = `${e.clientX || seat.left}px`;
|
|
185
|
+
menu.style.top = `${e.clientY || seat.bottom}px`;
|
|
186
|
+
menu.showPopover();
|
|
187
|
+
openMenu = menu;
|
|
188
|
+
// Nudge back inside once measured — the pointer can be within a menu's
|
|
189
|
+
// width of the right or bottom edge.
|
|
190
|
+
const box = menu.getBoundingClientRect();
|
|
191
|
+
if (box.right > innerWidth) menu.style.left = `${innerWidth - box.width - 8}px`;
|
|
192
|
+
if (box.bottom > innerHeight) menu.style.top = `${innerHeight - box.height - 8}px`;
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
</script>
|
|
196
|
+
|
|
197
|
+
<style>
|
|
198
|
+
.detail {
|
|
199
|
+
display: flex;
|
|
200
|
+
flex-direction: column;
|
|
201
|
+
gap: 1.25rem;
|
|
202
|
+
}
|
|
203
|
+
.detail-head {
|
|
204
|
+
display: flex;
|
|
205
|
+
align-items: center;
|
|
206
|
+
gap: 1rem;
|
|
207
|
+
background: var(--card);
|
|
208
|
+
border: 1px solid var(--border);
|
|
209
|
+
border-radius: 10px;
|
|
210
|
+
padding: 1rem 1.25rem;
|
|
211
|
+
}
|
|
212
|
+
.tile {
|
|
213
|
+
flex-shrink: 0;
|
|
214
|
+
width: 3.5rem;
|
|
215
|
+
height: 3.5rem;
|
|
216
|
+
border-radius: 10px;
|
|
217
|
+
object-fit: cover;
|
|
218
|
+
}
|
|
219
|
+
.tile.letter {
|
|
220
|
+
display: flex;
|
|
221
|
+
align-items: center;
|
|
222
|
+
justify-content: center;
|
|
223
|
+
color: var(--on-accent);
|
|
224
|
+
font-size: 1.5rem;
|
|
225
|
+
font-weight: 700;
|
|
226
|
+
font-family: ui-monospace, "SFMono-Regular", monospace;
|
|
227
|
+
}
|
|
228
|
+
.detail-head-text h1 {
|
|
229
|
+
margin: 0;
|
|
230
|
+
display: flex;
|
|
231
|
+
align-items: center;
|
|
232
|
+
gap: 0.6rem;
|
|
233
|
+
font-size: 1.5rem;
|
|
234
|
+
font-family: ui-monospace, "SFMono-Regular", monospace;
|
|
235
|
+
}
|
|
236
|
+
.summary {
|
|
237
|
+
margin: 0.3rem 0 0;
|
|
238
|
+
color: var(--muted);
|
|
239
|
+
}
|
|
240
|
+
.deprecated-banner {
|
|
241
|
+
margin: 0;
|
|
242
|
+
background: var(--banner-bg);
|
|
243
|
+
color: var(--banner-fg);
|
|
244
|
+
border: 1px solid var(--banner-border);
|
|
245
|
+
border-radius: 8px;
|
|
246
|
+
padding: 0.6rem 0.9rem;
|
|
247
|
+
font-weight: 600;
|
|
248
|
+
}
|
|
249
|
+
.install-row {
|
|
250
|
+
display: flex;
|
|
251
|
+
align-items: center;
|
|
252
|
+
justify-content: space-between;
|
|
253
|
+
gap: 0.6rem;
|
|
254
|
+
background: var(--card);
|
|
255
|
+
border: 1px solid var(--border);
|
|
256
|
+
border-radius: 8px;
|
|
257
|
+
padding: 0.6rem 0.9rem;
|
|
258
|
+
}
|
|
259
|
+
.install-row code {
|
|
260
|
+
font-size: 0.9rem;
|
|
261
|
+
overflow-x: auto;
|
|
262
|
+
}
|
|
263
|
+
.detail-body {
|
|
264
|
+
display: grid;
|
|
265
|
+
grid-template-columns: 1fr 18rem;
|
|
266
|
+
gap: 2rem;
|
|
267
|
+
align-items: start;
|
|
268
|
+
}
|
|
269
|
+
@media (max-width: 55rem) {
|
|
270
|
+
.detail-body {
|
|
271
|
+
grid-template-columns: 1fr;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
.detail-main {
|
|
275
|
+
min-width: 0;
|
|
276
|
+
background: var(--card);
|
|
277
|
+
border: 1px solid var(--border);
|
|
278
|
+
border-radius: 10px;
|
|
279
|
+
padding: 1.25rem 1.5rem;
|
|
280
|
+
}
|
|
281
|
+
.detail-main :global(img) {
|
|
282
|
+
max-width: 100%;
|
|
283
|
+
}
|
|
284
|
+
.detail-main :global(pre) {
|
|
285
|
+
overflow-x: auto;
|
|
286
|
+
}
|
|
287
|
+
.changelog {
|
|
288
|
+
margin-top: 1.5rem;
|
|
289
|
+
border-top: 1px solid var(--border);
|
|
290
|
+
padding-top: 1rem;
|
|
291
|
+
}
|
|
292
|
+
.changelog summary {
|
|
293
|
+
cursor: pointer;
|
|
294
|
+
font-weight: 600;
|
|
295
|
+
}
|
|
296
|
+
.detail-rail {
|
|
297
|
+
display: flex;
|
|
298
|
+
flex-direction: column;
|
|
299
|
+
gap: 1.25rem;
|
|
300
|
+
}
|
|
301
|
+
.detail-rail section {
|
|
302
|
+
background: var(--card);
|
|
303
|
+
border: 1px solid var(--border);
|
|
304
|
+
border-radius: 10px;
|
|
305
|
+
padding: 1rem;
|
|
306
|
+
}
|
|
307
|
+
.detail-rail h2 {
|
|
308
|
+
margin: 0 0 0.6rem;
|
|
309
|
+
font-size: 0.85rem;
|
|
310
|
+
text-transform: uppercase;
|
|
311
|
+
letter-spacing: 0.04em;
|
|
312
|
+
color: var(--muted);
|
|
313
|
+
}
|
|
314
|
+
/* Loose badges, both for the cascade and the older tags — VersionMenu owns
|
|
315
|
+
the pill itself, this only lays them out. */
|
|
316
|
+
.cascade {
|
|
317
|
+
display: flex;
|
|
318
|
+
flex-wrap: wrap;
|
|
319
|
+
gap: 0.3rem;
|
|
320
|
+
}
|
|
321
|
+
.older {
|
|
322
|
+
margin-top: 0.5rem;
|
|
323
|
+
}
|
|
324
|
+
.older[open] > summary {
|
|
325
|
+
margin-bottom: 0.5rem;
|
|
326
|
+
}
|
|
327
|
+
.older > summary {
|
|
328
|
+
cursor: pointer;
|
|
329
|
+
font-size: 0.8rem;
|
|
330
|
+
color: var(--muted);
|
|
331
|
+
}
|
|
332
|
+
.older > summary:hover {
|
|
333
|
+
color: var(--fg);
|
|
334
|
+
}
|
|
335
|
+
dl {
|
|
336
|
+
margin: 0;
|
|
337
|
+
display: grid;
|
|
338
|
+
grid-template-columns: auto 1fr;
|
|
339
|
+
gap: 0.3rem 0.6rem;
|
|
340
|
+
}
|
|
341
|
+
dt {
|
|
342
|
+
color: var(--muted);
|
|
343
|
+
font-size: 0.85rem;
|
|
344
|
+
}
|
|
345
|
+
dd {
|
|
346
|
+
margin: 0;
|
|
347
|
+
}
|
|
348
|
+
.keywords {
|
|
349
|
+
list-style: none;
|
|
350
|
+
margin: 0.75rem 0 0;
|
|
351
|
+
padding: 0;
|
|
352
|
+
display: flex;
|
|
353
|
+
flex-wrap: wrap;
|
|
354
|
+
gap: 0.35rem;
|
|
355
|
+
}
|
|
356
|
+
.keywords li {
|
|
357
|
+
background: var(--chip-bg);
|
|
358
|
+
border-radius: 999px;
|
|
359
|
+
padding: 0.15rem 0.6rem;
|
|
360
|
+
font-size: 0.78rem;
|
|
361
|
+
}
|
|
362
|
+
.vscode-link {
|
|
363
|
+
display: inline-block;
|
|
364
|
+
margin-top: 1rem;
|
|
365
|
+
font-size: 0.9rem;
|
|
366
|
+
}
|
|
367
|
+
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type SiteConfig } from "../config.js";
|
|
2
|
+
export interface BuildSiteOptions {
|
|
3
|
+
/** Index repo root. Astro's root; `enrich/` and `public/` resolve here. */
|
|
4
|
+
root: string;
|
|
5
|
+
/** Holds `all.json` on entry; holds the rendered site on return. */
|
|
6
|
+
outDir: string;
|
|
7
|
+
config: SiteConfig;
|
|
8
|
+
}
|
|
9
|
+
/** Thrown when `<outDir>/all.json` is missing or malformed. */
|
|
10
|
+
export declare class RenderInputError extends Error {
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
/** Astro `srcDir` — pages, layouts, components, content config. */
|
|
14
|
+
export declare const ASTRO_SRC_DIR: string;
|
|
15
|
+
/** Default `public/` layer; holds the fallback favicon. */
|
|
16
|
+
export declare const DEFAULT_PUBLIC_DIR: string;
|
|
17
|
+
/** Render the catalog site from `<outDir>/all.json` into `outDir`. */
|
|
18
|
+
export declare function buildSite(opts: BuildSiteOptions): Promise<void>;
|
|
19
|
+
export type { CatalogPackage, GrimoireData } from "./types.js";
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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;AAG9D,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,+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;AAkH5D,sEAAsE;AACtE,wBAAsB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkCrE;AAED,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
|