gemstar 1.0.4 → 1.1

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.
@@ -210,6 +210,18 @@
210
210
  margin: 0.3rem 0 0;
211
211
  color: var(--muted);
212
212
  }
213
+ .detail-subtitle > :first-child {
214
+ margin-top: 0;
215
+ }
216
+ .detail-subtitle > :last-child {
217
+ margin-bottom: 0;
218
+ }
219
+ .detail-subtitle a[href] {
220
+ color: #2563c9;
221
+ }
222
+ .detail-subtitle a[href]:hover {
223
+ text-decoration: underline;
224
+ }
213
225
  .detail-origin {
214
226
  margin: 0;
215
227
  color: var(--ink);
@@ -236,6 +248,9 @@
236
248
  gap: 0.28rem;
237
249
  flex-wrap: wrap;
238
250
  }
251
+ .list-filters-secondary {
252
+ padding-top: 0.05rem;
253
+ }
239
254
  .list-filter-button {
240
255
  border: 1px solid var(--line);
241
256
  border-radius: 999px;
@@ -296,6 +311,12 @@
296
311
  justify-content: space-between;
297
312
  gap: 0.5rem;
298
313
  }
314
+ .gem-name-lockup {
315
+ display: inline-flex;
316
+ align-items: center;
317
+ gap: 0.38rem;
318
+ min-width: 0;
319
+ }
299
320
  .gem-updated-dot {
300
321
  width: 0.45rem;
301
322
  height: 0.45rem;
@@ -311,6 +332,20 @@
311
332
  color: var(--muted);
312
333
  font-size: 0.76rem;
313
334
  }
335
+ .package-type-tag {
336
+ display: inline-flex;
337
+ align-items: center;
338
+ border: 1px solid #ded6c8;
339
+ border-radius: 999px;
340
+ padding: 0.02rem 0.33rem;
341
+ background: #fbf7ef;
342
+ color: #786d61;
343
+ font-size: 0.62rem;
344
+ font-weight: 600;
345
+ letter-spacing: 0.01em;
346
+ line-height: 1.25;
347
+ flex: 0 0 auto;
348
+ }
314
349
  .gem-row.status-added .gem-version {
315
350
  color: var(--green);
316
351
  font-weight: 600;
@@ -4,6 +4,7 @@
4
4
  const toSelect = document.querySelector("[data-to-select]");
5
5
  const sidebarPanel = document.querySelector("[data-sidebar-panel]");
6
6
  const filterButtons = Array.from(document.querySelectorAll("[data-filter-button]"));
7
+ const ecosystemButtons = Array.from(document.querySelectorAll("[data-ecosystem-button]"));
7
8
  const gemSearch = document.querySelector("[data-gem-search]");
8
9
  const emptyGemList = document.querySelector("[data-gem-list-empty]");
9
10
  let detailPanel = document.querySelector("[data-detail-panel]");
@@ -13,8 +14,10 @@
13
14
  let detailRequestToken = 0;
14
15
  let activeDetailUrl = detailPanel ? detailPanel.dataset.detailUrl : null;
15
16
  let currentFilter = <%= selected_filter_json %>;
17
+ let currentPackageScope = <%= selected_package_scope_json %>;
16
18
  let currentSearch = "";
17
19
  const emptyDetailHtml = <%= empty_detail_html_json %>;
20
+ const packageCollectionLabel = <%= (@selected_project&.package_collection_label || "Packages").downcase.dump %>;
18
21
  const detailDisclosureStorageKey = "gemstar.detailDisclosureOpen";
19
22
 
20
23
  const visibleGemLinks = () => gemLinks.filter((link) => !link.hidden);
@@ -24,16 +27,18 @@
24
27
  link.classList.remove("is-selected");
25
28
  });
26
29
  };
27
- const requestedGemName = () => new URL(window.location.href).searchParams.get("gem");
30
+ const packageNameParam = (url) => url.searchParams.get("package") || url.searchParams.get("gem");
31
+ const requestedGemName = () => packageNameParam(new URL(window.location.href));
28
32
  const currentDetailGemName = () => {
29
33
  if (!detailPanel || !detailPanel.dataset.detailUrl) return null;
30
34
 
31
35
  try {
32
- return new URL(detailPanel.dataset.detailUrl, window.location.origin).searchParams.get("gem");
36
+ return packageNameParam(new URL(detailPanel.dataset.detailUrl, window.location.origin));
33
37
  } catch (_error) {
34
38
  return null;
35
39
  }
36
40
  };
41
+ const detailNeedsInitialFetch = () => detailPanel && detailPanel.dataset.detailDeferred === "true" && !!detailPanel.dataset.detailUrl;
37
42
  const isSidebarFocused = () => document.activeElement === sidebarPanel;
38
43
  const isDetailFocused = () => detailPanel && document.activeElement === detailPanel;
39
44
  const focusSidebar = () => {
@@ -77,15 +82,20 @@
77
82
  button.classList.toggle("is-active", button.dataset.filterButton === filter);
78
83
  });
79
84
 
85
+ ecosystemButtons.forEach((button) => {
86
+ button.classList.toggle("is-active", button.dataset.ecosystemButton === currentPackageScope);
87
+ });
88
+
80
89
  gemLinks.forEach((link) => {
81
90
  const updated = link.dataset.gemUpdated === "true";
82
91
  const pinned = pinnedGemName && link.dataset.gemName === pinnedGemName;
92
+ const matchesScope = currentPackageScope === "all" || link.dataset.packageScope === currentPackageScope;
83
93
  const matchesSearch = searchTerm === "" || link.dataset.gemName.toLowerCase().includes(searchTerm);
84
- link.hidden = ((filter === "updated" && !updated && !pinned) || !matchesSearch);
94
+ link.hidden = (!matchesScope || (filter === "updated" && !updated && !pinned) || !matchesSearch);
85
95
  });
86
96
 
87
97
  if (emptyGemList) {
88
- const emptyMessage = searchTerm === "" ? "No updated gems in this revision range." : "No gems match this filter.";
98
+ const emptyMessage = searchTerm === "" ? `No updated ${packageCollectionLabel} in this revision range.` : `No ${packageCollectionLabel} match this filter.`;
89
99
  emptyGemList.hidden = visibleGemLinks().length !== 0;
90
100
  const text = emptyGemList.querySelector("p");
91
101
  if (text) text.textContent = emptyMessage;
@@ -93,12 +103,14 @@
93
103
 
94
104
  if (currentSelectedIndex() === -1) {
95
105
  clearSidebarSelection();
96
- replaceDetail(emptyDetailHtml);
106
+ if (visibleGemLinks().length === 0) {
107
+ replaceDetail(emptyDetailHtml);
108
+ }
97
109
  }
98
110
  };
99
111
 
100
112
  const syncSidebarSelection = (gemName = null, keepVisible = false) => {
101
- const effectiveGemName = gemName || new URL(window.location.href).searchParams.get("gem");
113
+ const effectiveGemName = gemName || requestedGemName();
102
114
  if (!effectiveGemName) {
103
115
  clearSidebarSelection();
104
116
  return;
@@ -180,11 +192,12 @@
180
192
  const detailUrl = new URL(url, window.location.origin);
181
193
  pageUrl.search = detailUrl.search;
182
194
  pageUrl.searchParams.set("filter", currentFilter);
195
+ pageUrl.searchParams.set("scope", currentPackageScope);
183
196
  const historyMethod = historyMode === "replace" ? "replaceState" : "pushState";
184
197
  window.history[historyMethod]({}, "", pageUrl);
185
198
  }
186
199
  const detailUrl = new URL(url, window.location.origin);
187
- syncSidebarSelection(detailUrl.searchParams.get("gem"));
200
+ syncSidebarSelection(packageNameParam(detailUrl));
188
201
  })
189
202
  .catch(() => {
190
203
  stopDetailLoading();
@@ -203,21 +216,29 @@
203
216
  };
204
217
 
205
218
  const ensureInitialGemSelection = () => {
206
- if (requestedGemName()) return;
219
+ const requested = requestedGemName();
207
220
 
208
221
  const detailGemName = currentDetailGemName();
209
222
  const detailLink = detailGemName ? visibleGemLinks().find((link) => link.dataset.gemName === detailGemName) : null;
210
223
 
211
224
  if (detailLink) {
212
225
  syncSidebarSelection(detailGemName, true);
213
- const pageUrl = new URL(window.location.href);
214
- const detailUrl = new URL(detailLink.dataset.detailUrl || detailLink.href, window.location.origin);
215
- pageUrl.search = detailUrl.search;
216
- pageUrl.searchParams.set("filter", currentFilter);
217
- window.history.replaceState({}, "", pageUrl);
226
+ if (!requested) {
227
+ const pageUrl = new URL(window.location.href);
228
+ const detailUrl = new URL(detailLink.dataset.detailUrl || detailLink.href, window.location.origin);
229
+ pageUrl.search = detailUrl.search;
230
+ pageUrl.searchParams.set("filter", currentFilter);
231
+ pageUrl.searchParams.set("scope", currentPackageScope);
232
+ window.history.replaceState({}, "", pageUrl);
233
+ }
234
+ if (detailNeedsInitialFetch()) {
235
+ fetchDetail(detailLink.dataset.detailUrl || detailLink.href, requested ? "none" : "replace");
236
+ }
218
237
  return;
219
238
  }
220
239
 
240
+ if (requested) return;
241
+
221
242
  const firstVisibleLink = visibleGemLinks()[0];
222
243
  if (firstVisibleLink) {
223
244
  activateGemLink(firstVisibleLink, "replace", true);
@@ -263,6 +284,20 @@
263
284
  });
264
285
  });
265
286
 
287
+ ecosystemButtons.forEach((button) => {
288
+ button.addEventListener("click", () => {
289
+ currentPackageScope = button.dataset.ecosystemButton;
290
+ applyGemFilter(currentFilter);
291
+ if (sidebarPanel) {
292
+ sidebarPanel.scrollTop = 0;
293
+ }
294
+ const url = new URL(window.location.href);
295
+ url.searchParams.set("scope", currentPackageScope);
296
+ window.history.replaceState({}, "", url);
297
+ syncSidebarSelection(null, true);
298
+ });
299
+ });
300
+
266
301
  if (gemSearch) {
267
302
  gemSearch.addEventListener("input", (event) => {
268
303
  currentSearch = event.target.value || "";
@@ -293,19 +328,19 @@
293
328
  event.target.value = "<%= selected_project_index %>";
294
329
  return;
295
330
  }
296
- navigate({ project: event.target.value, from: null, to: "worktree", filter: currentFilter, gem: null });
331
+ navigate({ project: event.target.value, from: null, to: "worktree", filter: currentFilter, scope: currentPackageScope, package: null, gem: null });
297
332
  });
298
333
  }
299
334
 
300
335
  if (fromSelect) {
301
336
  fromSelect.addEventListener("change", (event) => {
302
- navigate({ from: event.target.value, filter: currentFilter, gem: null });
337
+ navigate({ from: event.target.value, filter: currentFilter, scope: currentPackageScope, package: null, gem: null });
303
338
  });
304
339
  }
305
340
 
306
341
  if (toSelect) {
307
342
  toSelect.addEventListener("change", (event) => {
308
- navigate({ to: event.target.value, filter: currentFilter, gem: null });
343
+ navigate({ to: event.target.value, filter: currentFilter, scope: currentPackageScope, package: null, gem: null });
309
344
  });
310
345
  }
311
346
 
@@ -4,7 +4,8 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title><%= title %></title>
7
- <link rel="icon" href="<%= favicon_data_uri %>">
7
+ <link rel="icon" href="/favicon.svg" type="image/svg+xml">
8
+ <link rel="shortcut icon" href="/favicon.ico">
8
9
  <style>
9
10
  <%= styles_css %>
10
11
  </style>
data/lib/gemstar.rb CHANGED
@@ -15,7 +15,10 @@ require "gemstar/outputs/markdown"
15
15
  require "gemstar/cache"
16
16
  require "gemstar/change_log"
17
17
  require "gemstar/git_hub"
18
+ require "gemstar/importmap_file"
18
19
  require "gemstar/lock_file"
20
+ require "gemstar/npm_metadata"
21
+ require "gemstar/package_lock_file"
19
22
  require "gemstar/project"
20
23
  require "gemstar/remote_repository"
21
24
  require "gemstar/utils"
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.0.4
4
+ version: '1.1'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Dejako
@@ -243,12 +243,17 @@ files:
243
243
  - lib/gemstar/commands/diff.rb
244
244
  - lib/gemstar/commands/server.rb
245
245
  - lib/gemstar/config.rb
246
+ - lib/gemstar/data/importmap_package_metadata.json
247
+ - lib/gemstar/data/ruby_gems_metadata.json
246
248
  - lib/gemstar/git_hub.rb
247
249
  - lib/gemstar/git_repo.rb
250
+ - lib/gemstar/importmap_file.rb
248
251
  - lib/gemstar/lock_file.rb
252
+ - lib/gemstar/npm_metadata.rb
249
253
  - lib/gemstar/outputs/basic.rb
250
254
  - lib/gemstar/outputs/html.rb
251
255
  - lib/gemstar/outputs/markdown.rb
256
+ - lib/gemstar/package_lock_file.rb
252
257
  - lib/gemstar/project.rb
253
258
  - lib/gemstar/remote_repository.rb
254
259
  - lib/gemstar/request_logger.rb