@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
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 The Grimoire Authors
|
|
3
|
+
|
|
4
|
+
export interface KeywordChip {
|
|
5
|
+
keyword: string;
|
|
6
|
+
count: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The rail's chips, picked by SPLITTING POWER rather than raw frequency.
|
|
11
|
+
*
|
|
12
|
+
* A chip is worth a rail slot when clicking it meaningfully partitions what
|
|
13
|
+
* is on screen, so:
|
|
14
|
+
*
|
|
15
|
+
* - a near-ubiquitous keyword scores ~0 (clicking it barely narrows),
|
|
16
|
+
* - a tiny keyword scores low (it barely selects anything),
|
|
17
|
+
* - a keyword redundant with one already picked scores low (its packages are
|
|
18
|
+
* already reachable through that one).
|
|
19
|
+
*
|
|
20
|
+
* Greedy. Each round scores every unpicked keyword as
|
|
21
|
+
* `min(uncoveredCount, total - count)` — a tent over coverage, peaking near
|
|
22
|
+
* half the set, intersected with the marginal gain over what is already
|
|
23
|
+
* covered — and takes the best. Ties go to the higher total count, then
|
|
24
|
+
* alphabetically. When nothing scores above zero (a small or homogeneous
|
|
25
|
+
* catalog) the remaining slots are padded by plain frequency, so the rail is
|
|
26
|
+
* never emptier than it has to be.
|
|
27
|
+
*
|
|
28
|
+
* The input is deliberately narrow — only `keywords` is read — so the tests
|
|
29
|
+
* need no full `CatalogPackage` fixtures.
|
|
30
|
+
*
|
|
31
|
+
* Ported from `@ocx-sh/catalog`'s `src/theme/utils/keywordRail.ts`, which
|
|
32
|
+
* arrived at this after a frequency-ranked rail kept offering the tags every
|
|
33
|
+
* package already carries.
|
|
34
|
+
*/
|
|
35
|
+
export function selectRailKeywords(
|
|
36
|
+
items: readonly { keywords?: readonly string[] }[],
|
|
37
|
+
limit: number,
|
|
38
|
+
): KeywordChip[] {
|
|
39
|
+
const total = items.length;
|
|
40
|
+
const members = new Map<string, Set<number>>();
|
|
41
|
+
items.forEach((item, i) => {
|
|
42
|
+
for (const kw of item.keywords ?? []) {
|
|
43
|
+
let set = members.get(kw);
|
|
44
|
+
if (!set) members.set(kw, (set = new Set()));
|
|
45
|
+
set.add(i);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const picked: KeywordChip[] = [];
|
|
50
|
+
const pickedSet = new Set<string>();
|
|
51
|
+
const covered = new Set<number>();
|
|
52
|
+
|
|
53
|
+
while (picked.length < limit) {
|
|
54
|
+
let best: string | null = null;
|
|
55
|
+
let bestScore = 0;
|
|
56
|
+
let bestCount = 0;
|
|
57
|
+
for (const [kw, set] of members) {
|
|
58
|
+
if (pickedSet.has(kw)) continue;
|
|
59
|
+
let uncovered = 0;
|
|
60
|
+
for (const i of set) if (!covered.has(i)) uncovered++;
|
|
61
|
+
const score = Math.min(uncovered, total - set.size);
|
|
62
|
+
const wins =
|
|
63
|
+
score > bestScore ||
|
|
64
|
+
(score === bestScore &&
|
|
65
|
+
score > 0 &&
|
|
66
|
+
(set.size > bestCount ||
|
|
67
|
+
(set.size === bestCount && best !== null && kw < best)));
|
|
68
|
+
if (wins) {
|
|
69
|
+
best = kw;
|
|
70
|
+
bestScore = score;
|
|
71
|
+
bestCount = set.size;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (best === null) break;
|
|
75
|
+
picked.push({ keyword: best, count: members.get(best)!.size });
|
|
76
|
+
pickedSet.add(best);
|
|
77
|
+
for (const i of members.get(best)!) covered.add(i);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Frequency padding for the slots the greedy pass could not justify.
|
|
81
|
+
if (picked.length < limit) {
|
|
82
|
+
const rest = [...members]
|
|
83
|
+
.filter(([kw]) => !pickedSet.has(kw))
|
|
84
|
+
.map(([keyword, set]) => ({ keyword, count: set.size }))
|
|
85
|
+
.sort((a, b) => b.count - a.count || a.keyword.localeCompare(b.keyword));
|
|
86
|
+
picked.push(...rest.slice(0, limit - picked.length));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return picked;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Every keyword in `items`, most common first. Feeds the overflow menu, which
|
|
94
|
+
* lists what the rail had no room for.
|
|
95
|
+
*
|
|
96
|
+
* Scored over the same set the rail is, so a menu row is never a click to an
|
|
97
|
+
* empty catalog: a keyword no surviving package carries is simply not there.
|
|
98
|
+
*/
|
|
99
|
+
export function keywordFrequency(
|
|
100
|
+
items: readonly { keywords?: readonly string[] }[],
|
|
101
|
+
): KeywordChip[] {
|
|
102
|
+
const counts = new Map<string, number>();
|
|
103
|
+
for (const item of items) {
|
|
104
|
+
for (const kw of item.keywords ?? [])
|
|
105
|
+
counts.set(kw, (counts.get(kw) ?? 0) + 1);
|
|
106
|
+
}
|
|
107
|
+
return [...counts]
|
|
108
|
+
.map(([keyword, count]) => ({ keyword, count }))
|
|
109
|
+
.sort((a, b) => b.count - a.count || a.keyword.localeCompare(b.keyword));
|
|
110
|
+
}
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
import Base from "../layouts/Base.astro";
|
|
3
3
|
import Catalog from "../components/Catalog.tsx";
|
|
4
|
-
import
|
|
5
|
-
import PickerMenu from "../components/PickerMenu.astro";
|
|
4
|
+
import CommandBar from "../components/CommandBar.astro";
|
|
6
5
|
import { BrandMark } from "../components/BrandMark.tsx";
|
|
7
|
-
import { FolderRoot, Globe } from "lucide-preact";
|
|
8
6
|
import { addRegistryUrl } from "../lib/catalog";
|
|
7
|
+
import { installChoices, registryAddCommand, registryScopeChoices } from "../lib/commands";
|
|
9
8
|
import { data } from "../lib/data";
|
|
10
|
-
import {
|
|
11
|
-
mdiApple,
|
|
12
|
-
mdiConsole,
|
|
13
|
-
mdiMicrosoftVisualStudioCode,
|
|
14
|
-
mdiMicrosoftWindows,
|
|
15
|
-
mdiPenguin,
|
|
16
|
-
} from "@mdi/js";
|
|
9
|
+
import { mdiMicrosoftVisualStudioCode } from "@mdi/js";
|
|
17
10
|
|
|
18
11
|
const { config, packages } = data;
|
|
19
12
|
|
|
@@ -28,50 +21,11 @@ const marketplace = config.vscodeExtension
|
|
|
28
21
|
? `https://marketplace.visualstudio.com/items?itemName=${config.vscodeExtension}`
|
|
29
22
|
: null;
|
|
30
23
|
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
? `grim config registry add ${config.registry.alias} --index ${config.registry.index}`
|
|
37
|
-
: null;
|
|
38
|
-
|
|
39
|
-
// One installer row usually covers several platforms ("Linux / macOS"), and
|
|
40
|
-
// the toggle reads better as the platforms themselves than as the config's
|
|
41
|
-
// row labels — so a row expands into one button per platform it names, all
|
|
42
|
-
// pointing at the same command. A row naming none (a custom label) keeps a
|
|
43
|
-
// single generic button, so an unrecognized platform is never dropped.
|
|
44
|
-
//
|
|
45
|
-
// `mdiPenguin`, not `mdiLinux`: Tux's own silhouette is a solid mass that
|
|
46
|
-
// reads as an indistinct blob at 16px, where the plainer penguin keeps its
|
|
47
|
-
// outline. Still a brand mark from the same set, so it sits beside Apple
|
|
48
|
-
// and Windows without a weight change.
|
|
49
|
-
const PLATFORMS = [
|
|
50
|
-
{ match: /linux/i, name: "Linux", path: mdiPenguin },
|
|
51
|
-
{ match: /mac|darwin|osx|apple/i, name: "macOS", path: mdiApple },
|
|
52
|
-
{ match: /win/i, name: "Windows", path: mdiMicrosoftWindows },
|
|
53
|
-
];
|
|
54
|
-
const installChoices = config.install.flatMap((row) => {
|
|
55
|
-
const hits = PLATFORMS.filter((p) => p.match.test(row.os));
|
|
56
|
-
return hits.length > 0
|
|
57
|
-
? hits.map((p) => ({ name: p.name, path: p.path, command: row.command }))
|
|
58
|
-
: [{ name: row.os, path: mdiConsole, command: row.command }];
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// Registry scope, the same two-way choice the package cards offer and drawn
|
|
62
|
-
// with the same two Lucide glyphs. Global leads: someone arriving from an
|
|
63
|
-
// index website wants this index available everywhere, not wired into
|
|
64
|
-
// whichever directory happens to be open — and it is also the scope that
|
|
65
|
-
// works with no project at all.
|
|
66
|
-
// `--global` leads rather than trails: it is a top-level flag, and appended
|
|
67
|
-
// after a long `--index <url>` it fell off the end of the line, so switching
|
|
68
|
-
// scope looked like it changed nothing at all.
|
|
69
|
-
const scopeChoices = addCommand
|
|
70
|
-
? [
|
|
71
|
-
{ name: "Global", command: `grim --global ${addCommand.slice("grim ".length)}`, Icon: Globe },
|
|
72
|
-
{ name: "Project", command: addCommand, Icon: FolderRoot },
|
|
73
|
-
]
|
|
74
|
-
: [];
|
|
24
|
+
// All three derivations live in `lib/commands` rather than here, so a page an
|
|
25
|
+
// index adds under `theme/pages/` can draw these same bars — see CommandBar.
|
|
26
|
+
const install = installChoices(config);
|
|
27
|
+
const addCommand = registryAddCommand(config);
|
|
28
|
+
const scopeChoices = registryScopeChoices(config);
|
|
75
29
|
---
|
|
76
30
|
|
|
77
31
|
<Base title={config.brand} description={config.description}>
|
|
@@ -93,26 +47,22 @@ const scopeChoices = addCommand
|
|
|
93
47
|
reading what this index is. Two labelled rows of one shape — the
|
|
94
48
|
command, then the ways to act on it, inside a single border. */}
|
|
95
49
|
{
|
|
96
|
-
(
|
|
50
|
+
(install.length > 0 || addCommand || addRegistry) && (
|
|
97
51
|
<div class="hero-side">
|
|
98
|
-
{
|
|
52
|
+
{install.length > 0 && (
|
|
99
53
|
<Fragment>
|
|
100
54
|
<span class="side-label">install grim</span>
|
|
101
|
-
<
|
|
102
|
-
{
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
<CommandField
|
|
110
|
-
value={installChoices[0].command}
|
|
111
|
-
name={`${installChoices[0].name} install command`}
|
|
112
|
-
label="Copy the install command"
|
|
113
|
-
/>
|
|
55
|
+
<CommandBar
|
|
56
|
+
choices={install}
|
|
57
|
+
noun="install command"
|
|
58
|
+
copyLabel="Copy the install command"
|
|
59
|
+
pickerTitle="Platform"
|
|
60
|
+
pickerLabel="Choose your platform"
|
|
61
|
+
detect
|
|
62
|
+
>
|
|
114
63
|
{marketplace && (
|
|
115
64
|
<a
|
|
65
|
+
slot="action"
|
|
116
66
|
class="seg brand-link"
|
|
117
67
|
href={marketplace}
|
|
118
68
|
target="_blank"
|
|
@@ -123,25 +73,22 @@ const scopeChoices = addCommand
|
|
|
123
73
|
<BrandMark path={mdiMicrosoftVisualStudioCode} />
|
|
124
74
|
</a>
|
|
125
75
|
)}
|
|
126
|
-
</
|
|
76
|
+
</CommandBar>
|
|
127
77
|
</Fragment>
|
|
128
78
|
)}
|
|
129
79
|
{(addCommand || addRegistry) && (
|
|
130
80
|
<Fragment>
|
|
131
81
|
<span class="side-label">add registry</span>
|
|
132
|
-
<
|
|
133
|
-
{scopeChoices
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
name={`${scopeChoices[0].name} registry add command`}
|
|
140
|
-
label="Copy the registry add command"
|
|
141
|
-
/>
|
|
142
|
-
)}
|
|
82
|
+
<CommandBar
|
|
83
|
+
choices={scopeChoices}
|
|
84
|
+
noun="registry add command"
|
|
85
|
+
copyLabel="Copy the registry add command"
|
|
86
|
+
pickerTitle="Scope"
|
|
87
|
+
pickerLabel="Choose the scope"
|
|
88
|
+
>
|
|
143
89
|
{addRegistry && (
|
|
144
90
|
<a
|
|
91
|
+
slot="action"
|
|
145
92
|
class="seg brand-link"
|
|
146
93
|
href={addRegistry}
|
|
147
94
|
title="Add this index via VS Code"
|
|
@@ -150,7 +97,7 @@ const scopeChoices = addCommand
|
|
|
150
97
|
<BrandMark path={mdiMicrosoftVisualStudioCode} />
|
|
151
98
|
</a>
|
|
152
99
|
)}
|
|
153
|
-
</
|
|
100
|
+
</CommandBar>
|
|
154
101
|
</Fragment>
|
|
155
102
|
)}
|
|
156
103
|
</div>
|
|
@@ -158,11 +105,17 @@ const scopeChoices = addCommand
|
|
|
158
105
|
}
|
|
159
106
|
</div>
|
|
160
107
|
|
|
161
|
-
{/*
|
|
162
|
-
end of the document, past the whole
|
|
163
|
-
before it runs — long enough to
|
|
164
|
-
|
|
165
|
-
|
|
108
|
+
{/* A deliberate duplicate of the layout's own preselect loop, kept for
|
|
109
|
+
one reason: that one sits at the end of the document, past the whole
|
|
110
|
+
catalog, and the browser can paint before it runs — long enough to
|
|
111
|
+
show a Linux command to someone on Windows. This lands in the same
|
|
112
|
+
parse as the hero.
|
|
113
|
+
|
|
114
|
+
The layout's copy is what makes `detect` work on a page under
|
|
115
|
+
`theme/pages/`, which has no script of its own; both runs pick the same
|
|
116
|
+
choice and `__grimPick` is idempotent, so running twice here costs a
|
|
117
|
+
few microseconds and nothing else. A platform the bar does not offer
|
|
118
|
+
leaves the configured first choice alone. */}
|
|
166
119
|
<script is:inline>
|
|
167
120
|
for (const bar of document.querySelectorAll("[data-os-detect]")) {
|
|
168
121
|
const host = window.__grimHostOs();
|