@grimoire-rs/indexer 0.3.2 → 0.3.3
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.
|
@@ -120,12 +120,21 @@ function CopyButton({ command, variant = "default", name, }) {
|
|
|
120
120
|
// hydrates in the browser, so importing the build-time payload here would
|
|
121
121
|
// ship the whole catalog twice.
|
|
122
122
|
export default function Catalog({ packages, vscodeExtension, }) {
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
|
|
123
|
+
// Empty on the first render, ALWAYS — `?q=…` is applied a beat later, in
|
|
124
|
+
// the layout effect below. This is not a style preference, it is the one
|
|
125
|
+
// rule this island has to obey.
|
|
126
|
+
//
|
|
127
|
+
// Preact does not diff props while hydrating; its own source says so, and
|
|
128
|
+
// only re-applies props whose value is a function. Text children *are*
|
|
129
|
+
// diffed. So when the first client render disagrees with the server's, you
|
|
130
|
+
// get cards whose text is right and whose every attribute belongs to
|
|
131
|
+
// whichever package the server put at that position: one package's logo
|
|
132
|
+
// over another's name, and a link that opens the wrong page. Nothing
|
|
133
|
+
// throws. This used to seed from `location` here, and did exactly that.
|
|
134
|
+
const [query, setQuery] = useState("");
|
|
135
|
+
// Whether the URL's query has been applied. Gates the reveal below, so the
|
|
136
|
+
// catalog is never unhidden while it still shows the unfiltered list.
|
|
137
|
+
const [seeded, setSeeded] = useState(false);
|
|
129
138
|
const [kind, setKind] = useState(null);
|
|
130
139
|
const [sort, setSort] = useState("name");
|
|
131
140
|
// Deprecated packages are hidden until asked for: a retired package is
|
|
@@ -168,19 +177,29 @@ export default function Catalog({ packages, vscodeExtension, }) {
|
|
|
168
177
|
behavior: matchMedia("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth",
|
|
169
178
|
});
|
|
170
179
|
};
|
|
180
|
+
// Apply `?q=…`, now that hydration has matched the server's markup and
|
|
181
|
+
// Preact owns the tree. A layout effect rather than a plain one: the
|
|
182
|
+
// resulting render must land before the browser paints, or a `?q=` visitor
|
|
183
|
+
// sees the whole catalog flash past on the way to their results.
|
|
184
|
+
useLayoutEffect(() => {
|
|
185
|
+
setQuery(new URLSearchParams(location.search).get("q") ?? "");
|
|
186
|
+
setSeeded(true);
|
|
187
|
+
}, []);
|
|
171
188
|
// Base.astro hides the catalog before first paint when the URL carries a
|
|
172
|
-
// query. Reveal it once
|
|
173
|
-
//
|
|
189
|
+
// query. Reveal it only once the filtered render is in the DOM — keyed on
|
|
190
|
+
// `seeded`, so the unfiltered first render is never the one revealed.
|
|
174
191
|
useLayoutEffect(() => {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
//
|
|
178
|
-
//
|
|
192
|
+
if (!seeded)
|
|
193
|
+
return;
|
|
194
|
+
// The input's `value` prop was skipped during hydration for the same
|
|
195
|
+
// reason every other prop was, and the render that applied the query is
|
|
196
|
+
// a normal diff — but write it anyway: this effect is also what runs on
|
|
197
|
+
// a `?q=`-less load, where no second render is queued at all.
|
|
179
198
|
const input = searchRef.current;
|
|
180
199
|
if (input && input.value !== query)
|
|
181
200
|
input.value = query;
|
|
182
201
|
delete document.documentElement.dataset.query;
|
|
183
|
-
}, []);
|
|
202
|
+
}, [seeded]);
|
|
184
203
|
// `/` jumps to the search box, the convention every package registry
|
|
185
204
|
// shares. Bound on the document so it works wherever the reader is.
|
|
186
205
|
useEffect(() => {
|
|
@@ -187,14 +187,21 @@ export default function Catalog({
|
|
|
187
187
|
packages: CatalogPackage[];
|
|
188
188
|
vscodeExtension: string | null;
|
|
189
189
|
}) {
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
190
|
+
// Empty on the first render, ALWAYS — `?q=…` is applied a beat later, in
|
|
191
|
+
// the layout effect below. This is not a style preference, it is the one
|
|
192
|
+
// rule this island has to obey.
|
|
193
|
+
//
|
|
194
|
+
// Preact does not diff props while hydrating; its own source says so, and
|
|
195
|
+
// only re-applies props whose value is a function. Text children *are*
|
|
196
|
+
// diffed. So when the first client render disagrees with the server's, you
|
|
197
|
+
// get cards whose text is right and whose every attribute belongs to
|
|
198
|
+
// whichever package the server put at that position: one package's logo
|
|
199
|
+
// over another's name, and a link that opens the wrong page. Nothing
|
|
200
|
+
// throws. This used to seed from `location` here, and did exactly that.
|
|
201
|
+
const [query, setQuery] = useState("");
|
|
202
|
+
// Whether the URL's query has been applied. Gates the reveal below, so the
|
|
203
|
+
// catalog is never unhidden while it still shows the unfiltered list.
|
|
204
|
+
const [seeded, setSeeded] = useState(false);
|
|
198
205
|
const [kind, setKind] = useState<string | null>(null);
|
|
199
206
|
const [sort, setSort] = useState<Sort>("name");
|
|
200
207
|
// Deprecated packages are hidden until asked for: a retired package is
|
|
@@ -240,18 +247,28 @@ export default function Catalog({
|
|
|
240
247
|
});
|
|
241
248
|
};
|
|
242
249
|
|
|
250
|
+
// Apply `?q=…`, now that hydration has matched the server's markup and
|
|
251
|
+
// Preact owns the tree. A layout effect rather than a plain one: the
|
|
252
|
+
// resulting render must land before the browser paints, or a `?q=` visitor
|
|
253
|
+
// sees the whole catalog flash past on the way to their results.
|
|
254
|
+
useLayoutEffect(() => {
|
|
255
|
+
setQuery(new URLSearchParams(location.search).get("q") ?? "");
|
|
256
|
+
setSeeded(true);
|
|
257
|
+
}, []);
|
|
258
|
+
|
|
243
259
|
// Base.astro hides the catalog before first paint when the URL carries a
|
|
244
|
-
// query. Reveal it once
|
|
245
|
-
//
|
|
260
|
+
// query. Reveal it only once the filtered render is in the DOM — keyed on
|
|
261
|
+
// `seeded`, so the unfiltered first render is never the one revealed.
|
|
246
262
|
useLayoutEffect(() => {
|
|
247
|
-
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
263
|
+
if (!seeded) return;
|
|
264
|
+
// The input's `value` prop was skipped during hydration for the same
|
|
265
|
+
// reason every other prop was, and the render that applied the query is
|
|
266
|
+
// a normal diff — but write it anyway: this effect is also what runs on
|
|
267
|
+
// a `?q=`-less load, where no second render is queued at all.
|
|
251
268
|
const input = searchRef.current;
|
|
252
269
|
if (input && input.value !== query) input.value = query;
|
|
253
270
|
delete document.documentElement.dataset.query;
|
|
254
|
-
}, []);
|
|
271
|
+
}, [seeded]);
|
|
255
272
|
|
|
256
273
|
// `/` jumps to the search box, the convention every package registry
|
|
257
274
|
// shares. Bound on the document so it works wherever the reader is.
|
|
@@ -68,9 +68,10 @@ function repoLabel(url: string): string {
|
|
|
68
68
|
// package page) means the catalog the server rendered — every package,
|
|
69
69
|
// empty search box — is the wrong one. Flag it so CSS holds the
|
|
70
70
|
// catalog back rather than showing the full list and collapsing it a
|
|
71
|
-
// moment later. The island drops the flag
|
|
72
|
-
// is
|
|
73
|
-
//
|
|
71
|
+
// moment later. The island drops the flag once it has applied the
|
|
72
|
+
// query — which is the render after hydration, never the hydrating one
|
|
73
|
+
// itself; the timeout is the failsafe for an island that never
|
|
74
|
+
// hydrates, where a flash beats a blank page.
|
|
74
75
|
if (new URLSearchParams(location.search).get("q")) {
|
|
75
76
|
document.documentElement.dataset.query = "";
|
|
76
77
|
setTimeout(() => delete document.documentElement.dataset.query, 3000);
|
|
@@ -864,8 +865,10 @@ function repoLabel(url: string): string {
|
|
|
864
865
|
search box, so painting it and then collapsing to the match is a
|
|
865
866
|
flash of the wrong answer. `visibility` rather than `display` —
|
|
866
867
|
the space stays reserved, so the footer does not jump either. The
|
|
867
|
-
attribute is set before first paint and dropped
|
|
868
|
-
|
|
868
|
+
attribute is set before first paint and dropped once the island
|
|
869
|
+
has applied the query — the render after hydration, because
|
|
870
|
+
hydrating against markup that disagrees with the server silently
|
|
871
|
+
mismatches every attribute (see Catalog.tsx). */
|
|
869
872
|
:root[data-query] .catalog {
|
|
870
873
|
visibility: hidden;
|
|
871
874
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grimoire-rs/indexer",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "CLI + Astro integration for running your own grim package index",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"@vitest/coverage-v8": "^4.1.10",
|
|
39
39
|
"eslint": "^10.8.0",
|
|
40
40
|
"js-yaml": "^4.1.0",
|
|
41
|
+
"jsdom": "^29.1.1",
|
|
41
42
|
"typescript": "^6.0.3",
|
|
42
43
|
"typescript-eslint": "^8.65.0",
|
|
43
44
|
"vitest": "^4.1.10"
|