gemstar 1.1.1 → 1.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.
@@ -8,6 +8,7 @@
8
8
  --line: #ddcfbf;
9
9
  --accent: #b44d25;
10
10
  --accent-soft: rgba(180, 77, 37, 0.14);
11
+ --focus: #2563c9;
11
12
  --green: #2f8f5b;
12
13
  --green-soft: rgba(47, 143, 91, 0.12);
13
14
  --red: #a9473c;
@@ -182,6 +183,63 @@
182
183
  opacity: 0.55;
183
184
  cursor: not-allowed;
184
185
  }
186
+ .command-dialog-backdrop {
187
+ position: fixed;
188
+ inset: 0;
189
+ z-index: 20;
190
+ display: grid;
191
+ place-items: center;
192
+ padding: 1rem;
193
+ background: rgba(31, 27, 23, 0.42);
194
+ }
195
+ .command-dialog {
196
+ width: min(48rem, 100%);
197
+ max-height: min(42rem, calc(100vh - 2rem));
198
+ display: grid;
199
+ grid-template-rows: auto minmax(10rem, 1fr) auto;
200
+ overflow: hidden;
201
+ border: 1px solid var(--line);
202
+ border-radius: 0.45rem;
203
+ background: #fff;
204
+ box-shadow: 0 1.25rem 3rem rgba(31, 27, 23, 0.24);
205
+ }
206
+ .command-dialog-header {
207
+ display: grid;
208
+ gap: 0.3rem;
209
+ padding: 0.85rem 1rem 0.7rem;
210
+ border-bottom: 1px solid #ece8df;
211
+ }
212
+ .command-dialog-header h2 {
213
+ margin: 0;
214
+ font-size: 1rem;
215
+ line-height: 1.15;
216
+ }
217
+ .command-dialog-command {
218
+ margin: 0;
219
+ color: var(--muted);
220
+ font-family: "SFMono-Regular", "Cascadia Code", monospace;
221
+ font-size: 0.78rem;
222
+ overflow-wrap: anywhere;
223
+ }
224
+ .command-dialog-output {
225
+ min-height: 0;
226
+ margin: 0;
227
+ padding: 0.85rem 1rem;
228
+ overflow: auto;
229
+ white-space: pre-wrap;
230
+ background: #181614;
231
+ color: #f8efe4;
232
+ font-size: 0.76rem;
233
+ line-height: 1.45;
234
+ }
235
+ .command-dialog-actions {
236
+ display: flex;
237
+ justify-content: flex-end;
238
+ gap: 0.35rem;
239
+ padding: 0.65rem 0.75rem;
240
+ border-top: 1px solid #ece8df;
241
+ background: #fff;
242
+ }
185
243
  .toolbar-meta {
186
244
  color: var(--muted);
187
245
  display: flex;
@@ -358,6 +416,11 @@
358
416
  min-height: 0;
359
417
  overflow: auto;
360
418
  }
419
+ .detail:focus {
420
+ outline: none;
421
+ border-color: var(--focus);
422
+ box-shadow: 0 0 0 1px var(--focus);
423
+ }
361
424
  .detail-hero,
362
425
  .panel-heading {
363
426
  display: flex;
@@ -366,6 +429,16 @@
366
429
  align-items: start;
367
430
  flex-wrap: wrap;
368
431
  }
432
+ .detail-hero {
433
+ position: sticky;
434
+ top: -0.62rem;
435
+ z-index: 2;
436
+ margin: -0.62rem -0.8rem 0 -0.5rem;
437
+ padding: 0.62rem 0.8rem 0.5rem 0.5rem;
438
+ border-bottom: 1px solid #f0ede6;
439
+ background: rgba(255, 255, 255, 0.96);
440
+ backdrop-filter: blur(0.45rem);
441
+ }
369
442
  .detail-hero-copy {
370
443
  display: grid;
371
444
  gap: 0.2rem;
@@ -384,6 +457,16 @@
384
457
  min-width: 0;
385
458
  flex: 1 1 auto;
386
459
  }
460
+ .detail-heading {
461
+ display: flex;
462
+ align-items: center;
463
+ flex-wrap: wrap;
464
+ gap: 0.5rem;
465
+ min-width: 0;
466
+ }
467
+ .detail-hero-copy .detail-heading h2 {
468
+ flex: 0 1 auto;
469
+ }
387
470
  .detail-hero-copy h2 {
388
471
  margin: 0;
389
472
  font-size: 2.35rem;
@@ -396,6 +479,32 @@
396
479
  font-weight: 600;
397
480
  color: var(--muted);
398
481
  }
482
+ .detail-package-type {
483
+ display: inline-flex;
484
+ align-items: center;
485
+ gap: 0.28rem;
486
+ flex: 0 0 auto;
487
+ border: 1px solid var(--line);
488
+ border-radius: 999px;
489
+ background: #faf8f3;
490
+ color: var(--muted);
491
+ padding: 0.16rem 0.42rem 0.16rem 0.3rem;
492
+ font-size: 0.68rem;
493
+ font-weight: 700;
494
+ line-height: 1;
495
+ letter-spacing: 0.01em;
496
+ }
497
+ .detail-package-type-icon {
498
+ display: inline-flex;
499
+ width: 0.9rem;
500
+ height: 0.9rem;
501
+ color: var(--ink);
502
+ }
503
+ .detail-package-type-icon svg {
504
+ display: block;
505
+ width: 100%;
506
+ height: 100%;
507
+ }
399
508
  .detail-title-row .link-strip {
400
509
  margin-left: auto;
401
510
  align-self: start;
@@ -455,6 +564,12 @@
455
564
  background: #fff;
456
565
  padding: 0.5rem;
457
566
  }
567
+ .detail-inline-actions {
568
+ display: flex;
569
+ flex-wrap: wrap;
570
+ gap: 0.35rem;
571
+ margin-top: 0.55rem;
572
+ }
458
573
  .detail-loading-shell {
459
574
  min-height: 14rem;
460
575
  display: grid;
@@ -522,9 +637,18 @@
522
637
  overflow: hidden;
523
638
  }
524
639
  .revision-future {
525
- border-style: dashed;
526
- border-color: rgba(124, 124, 133, 0.55);
527
- background: linear-gradient(180deg, rgba(124, 124, 133, 0.07), rgba(255,255,255,0.45));
640
+ position: relative;
641
+ border: 2px solid rgba(37, 99, 201, 0.3);
642
+ background:
643
+ repeating-linear-gradient(
644
+ 135deg,
645
+ rgba(37, 99, 201, 0.018) 0,
646
+ rgba(37, 99, 201, 0.018) 0.5rem,
647
+ rgba(255, 255, 255, 0) 0.5rem,
648
+ rgba(255, 255, 255, 0) 1.2rem
649
+ ),
650
+ #fbfdff;
651
+ box-shadow: inset 0.24rem 0 0 rgba(37, 99, 201, 0.42);
528
652
  }
529
653
  .revision-current {
530
654
  border-color: rgba(47, 143, 91, 0.25);
@@ -557,7 +681,8 @@
557
681
  background: rgba(124, 124, 133, 0.09);
558
682
  }
559
683
  .revision-future .revision-card-titlebar {
560
- background: rgba(124, 124, 133, 0.07);
684
+ background: rgba(37, 99, 201, 0.07);
685
+ border-bottom-color: rgba(37, 99, 201, 0.14);
561
686
  }
562
687
  .revision-card-actions {
563
688
  display: inline-flex;
@@ -637,4 +762,12 @@
637
762
  .detail {
638
763
  overflow: visible;
639
764
  }
765
+ .detail-hero {
766
+ position: static;
767
+ margin: 0;
768
+ padding: 0;
769
+ border-bottom: 0;
770
+ background: transparent;
771
+ backdrop-filter: none;
772
+ }
640
773
  }
@@ -2,6 +2,7 @@
2
2
  const projectSelect = document.querySelector("[data-project-select]");
3
3
  const fromSelect = document.querySelector("[data-from-select]");
4
4
  const toSelect = document.querySelector("[data-to-select]");
5
+ const projectActionButtons = Array.from(document.querySelectorAll("[data-project-action]"));
5
6
  const sidebarPanel = document.querySelector("[data-sidebar-panel]");
6
7
  const filterButtons = Array.from(document.querySelectorAll("[data-filter-button]"));
7
8
  const ecosystemButtons = Array.from(document.querySelectorAll("[data-ecosystem-button]"));
@@ -19,6 +20,12 @@
19
20
  const emptyDetailHtml = <%= empty_detail_html_json %>;
20
21
  const packageCollectionLabel = <%= (@selected_project&.package_collection_label || "Packages").downcase.dump %>;
21
22
  const detailDisclosureStorageKey = "gemstar.detailDisclosureOpen";
23
+ const pageWasReloaded = (() => {
24
+ const navigationEntry = performance.getEntriesByType && performance.getEntriesByType("navigation")[0];
25
+ if (navigationEntry) return navigationEntry.type === "reload";
26
+ return performance.navigation && performance.navigation.type === 1;
27
+ })();
28
+ let pendingManualDetailRefresh = pageWasReloaded;
22
29
 
23
30
  const visibleGemLinks = () => gemLinks.filter((link) => !link.hidden);
24
31
  const currentSelectedIndex = () => visibleGemLinks().findIndex((link) => link.classList.contains("is-selected"));
@@ -28,7 +35,9 @@
28
35
  });
29
36
  };
30
37
  const packageNameParam = (url) => url.searchParams.get("package") || url.searchParams.get("gem");
38
+ const packageIdParam = (url) => url.searchParams.get("package_id");
31
39
  const requestedGemName = () => packageNameParam(new URL(window.location.href));
40
+ const requestedPackageId = () => packageIdParam(new URL(window.location.href));
32
41
  const currentDetailGemName = () => {
33
42
  if (!detailPanel || !detailPanel.dataset.detailUrl) return null;
34
43
 
@@ -38,6 +47,15 @@
38
47
  return null;
39
48
  }
40
49
  };
50
+ const currentDetailPackageId = () => {
51
+ if (!detailPanel || !detailPanel.dataset.detailUrl) return null;
52
+
53
+ try {
54
+ return packageIdParam(new URL(detailPanel.dataset.detailUrl, window.location.origin));
55
+ } catch (_error) {
56
+ return null;
57
+ }
58
+ };
41
59
  const detailNeedsInitialFetch = () => detailPanel && detailPanel.dataset.detailDeferred === "true" && !!detailPanel.dataset.detailUrl;
42
60
  const isSidebarFocused = () => document.activeElement === sidebarPanel;
43
61
  const isDetailFocused = () => detailPanel && document.activeElement === detailPanel;
@@ -47,6 +65,73 @@
47
65
  const focusDetail = () => {
48
66
  if (detailPanel) detailPanel.focus({ preventScroll: true });
49
67
  };
68
+ const closeCommandErrorDialog = () => {
69
+ const overlay = document.querySelector("[data-command-error-dialog]");
70
+ if (overlay) overlay.remove();
71
+ };
72
+ const showCommandErrorDialog = (command, output) => {
73
+ closeCommandErrorDialog();
74
+
75
+ const overlay = document.createElement("div");
76
+ overlay.className = "command-dialog-backdrop";
77
+ overlay.dataset.commandErrorDialog = "true";
78
+
79
+ const dialog = document.createElement("section");
80
+ dialog.className = "command-dialog";
81
+ dialog.setAttribute("role", "alertdialog");
82
+ dialog.setAttribute("aria-modal", "true");
83
+ dialog.setAttribute("aria-labelledby", "command-dialog-title");
84
+
85
+ const header = document.createElement("header");
86
+ header.className = "command-dialog-header";
87
+
88
+ const title = document.createElement("h2");
89
+ title.id = "command-dialog-title";
90
+ title.textContent = "Command failed";
91
+
92
+ const commandLine = document.createElement("p");
93
+ commandLine.className = "command-dialog-command";
94
+ commandLine.textContent = command;
95
+
96
+ header.append(title, commandLine);
97
+
98
+ const outputField = document.createElement("pre");
99
+ outputField.className = "command-dialog-output";
100
+ outputField.tabIndex = 0;
101
+ outputField.textContent = output || "No output captured.";
102
+
103
+ const actions = document.createElement("div");
104
+ actions.className = "command-dialog-actions";
105
+
106
+ const copyButton = document.createElement("button");
107
+ copyButton.type = "button";
108
+ copyButton.className = "action";
109
+ copyButton.textContent = "Copy";
110
+ copyButton.addEventListener("click", () => {
111
+ navigator.clipboard.writeText(`Command failed: ${command}\n\n${output || ""}`)
112
+ .then(() => {
113
+ copyButton.textContent = "Copied";
114
+ setTimeout(() => {
115
+ copyButton.textContent = "Copy";
116
+ }, 1400);
117
+ })
118
+ .catch(() => {
119
+ outputField.focus();
120
+ });
121
+ });
122
+
123
+ const closeButton = document.createElement("button");
124
+ closeButton.type = "button";
125
+ closeButton.className = "action action-primary";
126
+ closeButton.textContent = "Close";
127
+ closeButton.addEventListener("click", closeCommandErrorDialog);
128
+
129
+ actions.append(copyButton, closeButton);
130
+ dialog.append(header, outputField, actions);
131
+ overlay.append(dialog);
132
+ document.body.append(overlay);
133
+ closeButton.focus({ preventScroll: true });
134
+ };
50
135
  const storedDetailDisclosureOpen = () => {
51
136
  try {
52
137
  return window.localStorage.getItem(detailDisclosureStorageKey) === "true";
@@ -76,6 +161,7 @@
76
161
  const applyGemFilter = (filter) => {
77
162
  currentFilter = filter;
78
163
  const pinnedGemName = requestedGemName();
164
+ const pinnedPackageId = requestedPackageId();
79
165
  const searchTerm = currentSearch.trim().toLowerCase();
80
166
 
81
167
  filterButtons.forEach((button) => {
@@ -88,7 +174,7 @@
88
174
 
89
175
  gemLinks.forEach((link) => {
90
176
  const updated = link.dataset.gemUpdated === "true";
91
- const pinned = pinnedGemName && link.dataset.gemName === pinnedGemName;
177
+ const pinned = pinnedPackageId ? link.dataset.packageId === pinnedPackageId : pinnedGemName && link.dataset.gemName === pinnedGemName;
92
178
  const matchesScope = currentPackageScope === "all" || link.dataset.packageScope === currentPackageScope;
93
179
  const matchesSearch = searchTerm === "" || link.dataset.gemName.toLowerCase().includes(searchTerm);
94
180
  link.hidden = (!matchesScope || (filter === "updated" && !updated && !pinned) || !matchesSearch);
@@ -109,16 +195,34 @@
109
195
  }
110
196
  };
111
197
 
112
- const syncSidebarSelection = (gemName = null, keepVisible = false) => {
198
+ const keepSidebarLinkVisible = (link) => {
199
+ if (!sidebarPanel || !link) return;
200
+
201
+ const panelBounds = sidebarPanel.getBoundingClientRect();
202
+ const stickyHeader = sidebarPanel.querySelector(".sidebar-header");
203
+ const headerBounds = stickyHeader && stickyHeader.getBoundingClientRect();
204
+ const linkBounds = link.getBoundingClientRect();
205
+ const visibleTop = headerBounds ? Math.max(panelBounds.top, headerBounds.bottom) : panelBounds.top;
206
+
207
+ if (linkBounds.top < visibleTop) {
208
+ sidebarPanel.scrollTop -= visibleTop - linkBounds.top;
209
+ } else if (linkBounds.bottom > panelBounds.bottom) {
210
+ sidebarPanel.scrollTop += linkBounds.bottom - panelBounds.bottom;
211
+ }
212
+ };
213
+
214
+ const syncSidebarSelection = (packageId = null, gemName = null, keepVisible = false) => {
215
+ const effectivePackageId = packageId || requestedPackageId();
113
216
  const effectiveGemName = gemName || requestedGemName();
114
- if (!effectiveGemName) {
217
+ if (!effectivePackageId && !effectiveGemName) {
115
218
  clearSidebarSelection();
116
219
  return;
117
220
  }
118
221
 
119
222
  let selectedLink = null;
120
223
  gemLinks.forEach((link) => {
121
- const matches = link.dataset.gemName === effectiveGemName;
224
+ const identityMatches = effectivePackageId ? link.dataset.packageId === effectivePackageId : link.dataset.gemName === effectiveGemName;
225
+ const matches = !link.hidden && identityMatches;
122
226
  link.classList.toggle("is-selected", matches);
123
227
  if (matches) {
124
228
  selectedLink = link;
@@ -126,7 +230,7 @@
126
230
  });
127
231
 
128
232
  if (keepVisible && sidebarPanel && selectedLink) {
129
- selectedLink.scrollIntoView({ block: "nearest" });
233
+ keepSidebarLinkVisible(selectedLink);
130
234
  }
131
235
  };
132
236
 
@@ -170,7 +274,15 @@
170
274
  </section>
171
275
  `;
172
276
 
173
- const fetchDetail = (url, historyMode = "push") => {
277
+ const fetchDetail = (url, historyMode = "push", options = {}) => {
278
+ const requestUrl = new URL(url, window.location.origin);
279
+ if (options.refresh) {
280
+ requestUrl.searchParams.set("refresh", "1");
281
+ }
282
+ if (options.useGh) {
283
+ requestUrl.searchParams.set("use_gh", "1");
284
+ requestUrl.searchParams.set("refresh", "1");
285
+ }
174
286
  const normalizedUrl = new URL(url, window.location.origin).toString();
175
287
  const requestToken = ++detailRequestToken;
176
288
  activeDetailUrl = normalizedUrl;
@@ -180,7 +292,7 @@
180
292
  replaceDetail(loadingDetailHtml(normalizedUrl));
181
293
  }, 1000);
182
294
 
183
- fetch(url, { headers: { "X-Requested-With": "gemstar-detail" } })
295
+ fetch(requestUrl.toString(), { headers: { "X-Requested-With": "gemstar-detail" } })
184
296
  .then((response) => response.text())
185
297
  .then((html) => {
186
298
  stopDetailLoading();
@@ -197,7 +309,7 @@
197
309
  window.history[historyMethod]({}, "", pageUrl);
198
310
  }
199
311
  const detailUrl = new URL(url, window.location.origin);
200
- syncSidebarSelection(packageNameParam(detailUrl));
312
+ syncSidebarSelection(packageIdParam(detailUrl), packageNameParam(detailUrl));
201
313
  })
202
314
  .catch(() => {
203
315
  stopDetailLoading();
@@ -209,7 +321,7 @@
209
321
 
210
322
  stopDetailPoll();
211
323
  stopDetailLoading();
212
- syncSidebarSelection(link.dataset.gemName, keepVisible);
324
+ syncSidebarSelection(link.dataset.packageId, link.dataset.gemName, keepVisible);
213
325
  fetchDetail(link.dataset.detailUrl || link.href, historyMode);
214
326
 
215
327
  focusSidebar();
@@ -217,13 +329,17 @@
217
329
 
218
330
  const ensureInitialGemSelection = () => {
219
331
  const requested = requestedGemName();
332
+ const requestedId = requestedPackageId();
220
333
 
221
334
  const detailGemName = currentDetailGemName();
222
- const detailLink = detailGemName ? visibleGemLinks().find((link) => link.dataset.gemName === detailGemName) : null;
335
+ const detailPackageId = currentDetailPackageId();
336
+ const detailLink = detailPackageId ?
337
+ visibleGemLinks().find((link) => link.dataset.packageId === detailPackageId) :
338
+ (detailGemName ? visibleGemLinks().find((link) => link.dataset.gemName === detailGemName) : null);
223
339
 
224
340
  if (detailLink) {
225
- syncSidebarSelection(detailGemName, true);
226
- if (!requested) {
341
+ syncSidebarSelection(detailLink.dataset.packageId, detailLink.dataset.gemName, true);
342
+ if (!requested && !requestedId) {
227
343
  const pageUrl = new URL(window.location.href);
228
344
  const detailUrl = new URL(detailLink.dataset.detailUrl || detailLink.href, window.location.origin);
229
345
  pageUrl.search = detailUrl.search;
@@ -232,16 +348,21 @@
232
348
  window.history.replaceState({}, "", pageUrl);
233
349
  }
234
350
  if (detailNeedsInitialFetch()) {
235
- fetchDetail(detailLink.dataset.detailUrl || detailLink.href, requested ? "none" : "replace");
351
+ fetchDetail(detailLink.dataset.detailUrl || detailLink.href, requested || requestedId ? "none" : "replace", { refresh: pendingManualDetailRefresh });
352
+ pendingManualDetailRefresh = false;
236
353
  }
237
354
  return;
238
355
  }
239
356
 
240
- if (requested) return;
357
+ if (requested || requestedId) return;
241
358
 
242
359
  const firstVisibleLink = visibleGemLinks()[0];
243
360
  if (firstVisibleLink) {
244
- activateGemLink(firstVisibleLink, "replace", true);
361
+ const refresh = pendingManualDetailRefresh;
362
+ pendingManualDetailRefresh = false;
363
+ syncSidebarSelection(firstVisibleLink.dataset.packageId, firstVisibleLink.dataset.gemName, true);
364
+ fetchDetail(firstVisibleLink.dataset.detailUrl || firstVisibleLink.href, "replace", { refresh });
365
+ focusSidebar();
245
366
  }
246
367
  };
247
368
 
@@ -260,7 +381,7 @@
260
381
  scheduleDetailPoll();
261
382
  }
262
383
 
263
- syncSidebarSelection(null, true);
384
+ syncSidebarSelection(null, null, true);
264
385
  applyGemFilter(currentFilter);
265
386
  ensureInitialGemSelection();
266
387
 
@@ -280,7 +401,7 @@
280
401
  const url = new URL(window.location.href);
281
402
  url.searchParams.set("filter", currentFilter);
282
403
  window.history.replaceState({}, "", url);
283
- syncSidebarSelection(null, true);
404
+ syncSidebarSelection(null, null, true);
284
405
  });
285
406
  });
286
407
 
@@ -294,7 +415,7 @@
294
415
  const url = new URL(window.location.href);
295
416
  url.searchParams.set("scope", currentPackageScope);
296
417
  window.history.replaceState({}, "", url);
297
- syncSidebarSelection(null, true);
418
+ syncSidebarSelection(null, null, true);
298
419
  });
299
420
  });
300
421
 
@@ -305,10 +426,69 @@
305
426
  if (sidebarPanel) {
306
427
  sidebarPanel.scrollTop = 0;
307
428
  }
308
- syncSidebarSelection(null, true);
429
+ syncSidebarSelection(null, null, true);
309
430
  });
310
431
  }
311
432
 
433
+ document.addEventListener("click", (event) => {
434
+ const button = event.target.closest("[data-detail-use-gh]");
435
+ if (!button || !detailPanel || !detailPanel.dataset.detailUrl) return;
436
+
437
+ event.preventDefault();
438
+ button.disabled = true;
439
+ button.textContent = "Checking...";
440
+ fetchDetail(detailPanel.dataset.detailUrl, "none", { refresh: true, useGh: true });
441
+ });
442
+
443
+ projectActionButtons.forEach((button) => {
444
+ button.addEventListener("click", () => {
445
+ const actionId = button.dataset.projectAction;
446
+ const command = button.dataset.projectActionCommand || actionId;
447
+ if (!actionId) return;
448
+ if (!window.confirm(`Run ${command}?`)) return;
449
+
450
+ const originalText = button.textContent;
451
+ projectActionButtons.forEach((actionButton) => {
452
+ actionButton.disabled = true;
453
+ });
454
+ button.textContent = "Running...";
455
+
456
+ const params = new URLSearchParams();
457
+ params.set("project", "<%= selected_project_index %>");
458
+
459
+ fetch(`/actions/${encodeURIComponent(actionId)}`, {
460
+ method: "POST",
461
+ headers: {
462
+ "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
463
+ "X-Requested-With": "gemstar-action"
464
+ },
465
+ body: params.toString()
466
+ })
467
+ .then((response) => response.json().then((payload) => ({ response, payload })))
468
+ .then(({ response, payload }) => {
469
+ if (!response.ok || !payload.ok) {
470
+ const output = payload && payload.output ? payload.output : "";
471
+ const failedCommand = payload && payload.command ? payload.command : command;
472
+ showCommandErrorDialog(failedCommand, output);
473
+ button.textContent = originalText;
474
+ projectActionButtons.forEach((actionButton) => {
475
+ actionButton.disabled = false;
476
+ });
477
+ return;
478
+ }
479
+
480
+ window.location.reload();
481
+ })
482
+ .catch((error) => {
483
+ showCommandErrorDialog(command, error.message);
484
+ button.textContent = originalText;
485
+ projectActionButtons.forEach((actionButton) => {
486
+ actionButton.disabled = false;
487
+ });
488
+ });
489
+ });
490
+ });
491
+
312
492
  const navigate = (params) => {
313
493
  const url = new URL(window.location.href);
314
494
  Object.entries(params).forEach(([key, value]) => {
@@ -328,23 +508,29 @@
328
508
  event.target.value = "<%= selected_project_index %>";
329
509
  return;
330
510
  }
331
- navigate({ project: event.target.value, from: null, to: "worktree", filter: currentFilter, scope: currentPackageScope, package: null, gem: null });
511
+ navigate({ project: event.target.value, from: null, to: "worktree", filter: currentFilter, scope: currentPackageScope, package: null, package_id: null, gem: null });
332
512
  });
333
513
  }
334
514
 
335
515
  if (fromSelect) {
336
516
  fromSelect.addEventListener("change", (event) => {
337
- navigate({ from: event.target.value, filter: currentFilter, scope: currentPackageScope, package: null, gem: null });
517
+ navigate({ from: event.target.value, filter: currentFilter, scope: currentPackageScope, package: null, package_id: null, gem: null });
338
518
  });
339
519
  }
340
520
 
341
521
  if (toSelect) {
342
522
  toSelect.addEventListener("change", (event) => {
343
- navigate({ to: event.target.value, filter: currentFilter, scope: currentPackageScope, package: null, gem: null });
523
+ navigate({ to: event.target.value, filter: currentFilter, scope: currentPackageScope, package: null, package_id: null, gem: null });
344
524
  });
345
525
  }
346
526
 
347
527
  document.addEventListener("keydown", (event) => {
528
+ if (event.key === "Escape" && document.querySelector("[data-command-error-dialog]")) {
529
+ event.preventDefault();
530
+ closeCommandErrorDialog();
531
+ return;
532
+ }
533
+
348
534
  const tagName = document.activeElement && document.activeElement.tagName;
349
535
  if (tagName === "INPUT" || tagName === "TEXTAREA" || tagName === "SELECT") return;
350
536
 
@@ -392,7 +578,7 @@
392
578
  if (!links.length) return;
393
579
 
394
580
  const selectedGem = currentSelectedIndex();
395
- const currentIndex = selectedGem >= 0 ? selectedGem : 0;
581
+ const currentIndex = selectedGem >= 0 ? selectedGem : (event.key === "ArrowUp" ? links.length : -1);
396
582
  let nextIndex = null;
397
583
 
398
584
  if (event.key === "ArrowDown") nextIndex = Math.min(currentIndex + 1, links.length - 1);
data/lib/gemstar.rb CHANGED
@@ -9,6 +9,8 @@ require "gemstar/commands/cache"
9
9
  require "gemstar/commands/diff"
10
10
  require "gemstar/commands/server"
11
11
  require "gemstar/config"
12
+ require "gemstar/cargo_lock_file"
13
+ require "gemstar/crates_io_metadata"
12
14
  require "gemstar/outputs/basic"
13
15
  require "gemstar/outputs/html"
14
16
  require "gemstar/outputs/markdown"
@@ -19,8 +21,10 @@ require "gemstar/importmap_file"
19
21
  require "gemstar/lock_file"
20
22
  require "gemstar/npm_metadata"
21
23
  require "gemstar/package_lock_file"
24
+ require "gemstar/pypi_metadata"
22
25
  require "gemstar/project"
23
26
  require "gemstar/remote_repository"
24
27
  require "gemstar/utils"
25
28
  require "gemstar/ruby_gems_metadata"
29
+ require "gemstar/uv_lock_file"
26
30
  require "gemstar/git_repo"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gemstar
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: '1.3'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Dejako
@@ -219,8 +219,8 @@ dependencies:
219
219
  - - "~>"
220
220
  - !ruby/object:Gem::Version
221
221
  version: '1.9'
222
- description: Gem changelog viewer and more, but starting as a changelog viewer for
223
- bundled gems.
222
+ description: Browse and compare changelogs for Ruby gems, JavaScript packages, Python
223
+ packages, and Rust crates in your projects.
224
224
  email:
225
225
  - fdejako@gmail.com
226
226
  executables:
@@ -236,6 +236,7 @@ files:
236
236
  - lib/gemstar/cache.rb
237
237
  - lib/gemstar/cache_cli.rb
238
238
  - lib/gemstar/cache_warmer.rb
239
+ - lib/gemstar/cargo_lock_file.rb
239
240
  - lib/gemstar/change_log.rb
240
241
  - lib/gemstar/cli.rb
241
242
  - lib/gemstar/commands/cache.rb
@@ -243,6 +244,7 @@ files:
243
244
  - lib/gemstar/commands/diff.rb
244
245
  - lib/gemstar/commands/server.rb
245
246
  - lib/gemstar/config.rb
247
+ - lib/gemstar/crates_io_metadata.rb
246
248
  - lib/gemstar/data/importmap_package_metadata.json
247
249
  - lib/gemstar/data/ruby_gems_metadata.json
248
250
  - lib/gemstar/git_hub.rb
@@ -255,10 +257,12 @@ files:
255
257
  - lib/gemstar/outputs/markdown.rb
256
258
  - lib/gemstar/package_lock_file.rb
257
259
  - lib/gemstar/project.rb
260
+ - lib/gemstar/pypi_metadata.rb
258
261
  - lib/gemstar/remote_repository.rb
259
262
  - lib/gemstar/request_logger.rb
260
263
  - lib/gemstar/ruby_gems_metadata.rb
261
264
  - lib/gemstar/utils.rb
265
+ - lib/gemstar/uv_lock_file.rb
262
266
  - lib/gemstar/version.rb
263
267
  - lib/gemstar/web/app.rb
264
268
  - lib/gemstar/web/templates/app.css
@@ -288,5 +292,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
288
292
  requirements: []
289
293
  rubygems_version: 3.6.9
290
294
  specification_version: 4
291
- summary: Making sense of gems.
295
+ summary: Changelog viewer for Ruby, JavaScript, Python, and Rust dependencies.
292
296
  test_files: []