github-search-cli 9.9.0 → 9.10.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/dist/cli.js CHANGED
@@ -2153,7 +2153,7 @@ var cached = null;
2153
2153
  function getVersion() {
2154
2154
  if (cached) return cached;
2155
2155
  if (true) {
2156
- cached = "9.9.0";
2156
+ cached = "9.10.0";
2157
2157
  return cached;
2158
2158
  }
2159
2159
  try {
@@ -3874,6 +3874,135 @@ if (import.meta.main && false) {
3874
3874
  launchTrending();
3875
3875
  }
3876
3876
 
3877
+ // src/tui/mouse.ts
3878
+ var DOUBLE_CLICK_MS = 400;
3879
+ function hitIndexForY(geometry, screenY) {
3880
+ const pitch = geometry.linesPerItem;
3881
+ if (pitch <= 0) return -1;
3882
+ if (geometry.optionCount <= 0) return -1;
3883
+ const localY = screenY - geometry.screenY;
3884
+ if (localY < 0 || localY >= geometry.height) return -1;
3885
+ const row = Math.floor(localY / pitch);
3886
+ const index = geometry.scrollOffset + row;
3887
+ const last = geometry.optionCount - 1;
3888
+ if (index < 0) return 0;
3889
+ return index > last ? last : index;
3890
+ }
3891
+ function selectRowGeometry(select) {
3892
+ const internal = select;
3893
+ return {
3894
+ screenY: select.screenY,
3895
+ height: select.height,
3896
+ optionCount: select.options.length,
3897
+ scrollOffset: internal.scrollOffset ?? 0,
3898
+ linesPerItem: internal.linesPerItem ?? 0
3899
+ };
3900
+ }
3901
+ function attachClickToSelect(renderer, select, options = {}) {
3902
+ const wheelStep = options.wheelStep ?? 3;
3903
+ let lastClickIndex = -1;
3904
+ let lastClickAt = 0;
3905
+ let lastHovered = -1;
3906
+ select.onMouseDown = (event) => {
3907
+ event.stopPropagation();
3908
+ if (options.keepFocus) event.preventDefault();
3909
+ const index = hitIndexForY(selectRowGeometry(select), event.y);
3910
+ if (index < 0) {
3911
+ lastClickIndex = -1;
3912
+ options.onClickMiss?.();
3913
+ return;
3914
+ }
3915
+ const now = Date.now();
3916
+ const alreadySelected = select.getSelectedIndex() === index;
3917
+ const isDouble = alreadySelected && index === lastClickIndex && now - lastClickAt < DOUBLE_CLICK_MS;
3918
+ lastClickIndex = index;
3919
+ lastClickAt = now;
3920
+ if (isDouble) {
3921
+ lastClickIndex = -1;
3922
+ options.onOpen?.();
3923
+ return;
3924
+ }
3925
+ select.setSelectedIndex(index);
3926
+ };
3927
+ select.onMouseScroll = (event) => {
3928
+ event.stopPropagation();
3929
+ const direction = event.scroll?.direction;
3930
+ if (direction === "up") select.moveUp(wheelStep);
3931
+ else if (direction === "down") select.moveDown(wheelStep);
3932
+ else return;
3933
+ renderer.requestRender();
3934
+ };
3935
+ attachHoverHighlight(renderer, select, {
3936
+ onHover: (index) => {
3937
+ lastHovered = index;
3938
+ options.onHoverRow?.(index);
3939
+ },
3940
+ // A stale row must never outlive the options it pointed at.
3941
+ onLeave: () => {
3942
+ lastHovered = -1;
3943
+ }
3944
+ });
3945
+ return {
3946
+ reset() {
3947
+ lastClickIndex = -1;
3948
+ lastClickAt = 0;
3949
+ },
3950
+ get lastHovered() {
3951
+ return lastHovered;
3952
+ }
3953
+ };
3954
+ }
3955
+ function attachHoverHighlight(renderer, select, options = {}) {
3956
+ const idleBackground = select.backgroundColor;
3957
+ const hoverBackground = "#ffffff0f";
3958
+ let hovered = -1;
3959
+ select.onMouseOver = () => {
3960
+ renderer.setMousePointer("pointer");
3961
+ select.backgroundColor = hoverBackground;
3962
+ renderer.requestRender();
3963
+ };
3964
+ select.onMouseOut = () => {
3965
+ renderer.setMousePointer("default");
3966
+ select.backgroundColor = idleBackground;
3967
+ if (hovered !== -1) {
3968
+ hovered = -1;
3969
+ options.onLeave?.();
3970
+ }
3971
+ renderer.requestRender();
3972
+ };
3973
+ select.onMouseMove = (event) => {
3974
+ const index = hitIndexForY(selectRowGeometry(select), event.y);
3975
+ if (index === hovered) return;
3976
+ hovered = index;
3977
+ options.onHover?.(index);
3978
+ };
3979
+ }
3980
+ function attachClickOutside(target, onDismiss) {
3981
+ target.onMouseDown = (event) => {
3982
+ event.stopPropagation();
3983
+ onDismiss();
3984
+ };
3985
+ }
3986
+ function attachClickableBox(renderer, box, handlers) {
3987
+ box.onMouseDown = (event) => {
3988
+ event.stopPropagation();
3989
+ handlers.onActivate();
3990
+ };
3991
+ box.onMouseOver = () => {
3992
+ renderer.setMousePointer("pointer");
3993
+ box.borderColor = handlers.activeBorder;
3994
+ handlers.onStateChange?.();
3995
+ renderer.requestRender();
3996
+ };
3997
+ box.onMouseOut = () => {
3998
+ renderer.setMousePointer("default");
3999
+ box.borderColor = handlers.idleBorder;
4000
+ handlers.onStateChange?.();
4001
+ renderer.requestRender();
4002
+ };
4003
+ }
4004
+ var NON_INTERACTIVE_TEXT = { selectable: false };
4005
+
3877
4006
  // src/help.ts
3878
4007
  function buildHelpSections() {
3879
4008
  return [
@@ -3895,7 +4024,7 @@ function buildHelpSections() {
3895
4024
  { keys: "\u2191 \u2193 / j k", action: "Move selection" },
3896
4025
  { keys: "Enter", action: "Open repo in browser" },
3897
4026
  { keys: "/", action: "Focus search input" },
3898
- { keys: "Space", action: "Open command menu" },
4027
+ { keys: "Esc", action: "Open command menu" },
3899
4028
  { keys: "? / Ctrl+H", action: "Toggle this help screen" },
3900
4029
  { keys: "q", action: "Quit" }
3901
4030
  ]
@@ -3929,7 +4058,8 @@ function buildHelpSections() {
3929
4058
  rows: [
3930
4059
  { keys: "1-5", action: "Switch tab: Today \u2192 Week \u2192 Month \u2192 \u2026" },
3931
4060
  { keys: "\u2190 \u2192 / h l", action: "Navigate tabs" }
3932
- ]
4061
+ ],
4062
+ note: "Tabs are also clickable."
3933
4063
  },
3934
4064
  {
3935
4065
  title: " Packages Mode ",
@@ -3938,12 +4068,12 @@ function buildHelpSections() {
3938
4068
  { keys: "/", action: "Search npm packages" },
3939
4069
  { keys: "\u2191 \u2193 / j k", action: "Move selection" },
3940
4070
  { keys: "Enter", action: "View package details" },
3941
- { keys: "Space", action: "Open command menu" }
4071
+ { keys: "Esc", action: "Open command menu" }
3942
4072
  ],
3943
4073
  note: "Search by name, description, or tags. Sort by downloads or score."
3944
4074
  },
3945
4075
  {
3946
- title: " Command Menu (Space)",
4076
+ title: " Command Menu (Esc)",
3947
4077
  titleColor: "purple",
3948
4078
  rows: [
3949
4079
  { keys: "Sort", action: "Cycle sort: best-match \u2192 stars \u2192 \u2026" },
@@ -3968,7 +4098,7 @@ function buildHelpSections() {
3968
4098
  { keys: "History", action: "View search history" },
3969
4099
  { keys: "Help", action: "Show this help screen" }
3970
4100
  ],
3971
- note: "Navigate menu with \u2191 \u2193, select with Enter, back with \u2190 / Esc"
4101
+ note: "Navigate menu with \u2191 \u2193, select with Enter, back with \u2190 / Esc. Clicking a row runs it directly. Esc (or the \u2630 menu button) opens this menu; Space types a space in the query."
3972
4102
  },
3973
4103
  {
3974
4104
  title: " Bookmarks Page ",
@@ -3981,6 +4111,22 @@ function buildHelpSections() {
3981
4111
  ],
3982
4112
  note: "A full page, not an overlay \u2014 Esc returns to the mode menu."
3983
4113
  },
4114
+ {
4115
+ title: " Mouse ",
4116
+ titleColor: "green",
4117
+ rows: [
4118
+ { keys: "Click", action: "Select a row / card / tab" },
4119
+ { keys: "Double-click", action: "Open the repo or run the action" },
4120
+ { keys: "Scroll wheel", action: "Move the selection / scroll a pane" },
4121
+ { keys: "Click outside", action: "Dismiss an open overlay" },
4122
+ { keys: "Click query box", action: "Focus the search input" },
4123
+ {
4124
+ keys: "\u2630 menu button",
4125
+ action: "Open the command menu (bottom-right)"
4126
+ }
4127
+ ],
4128
+ note: "Every list is fully clickable; keyboard and mouse are interchangeable."
4129
+ },
3984
4130
  {
3985
4131
  title: " Overlay Keys ",
3986
4132
  titleColor: "teal",
@@ -5197,7 +5343,7 @@ async function launchBrowser(theme_override) {
5197
5343
  const compareList = [];
5198
5344
  const _quitArmed = false;
5199
5345
  const header = new TextRenderable5(renderer, {
5200
- content: " ghfind \u2014 GitHub repo browser [/]search [Space]menu [\u2192]open [?]help [q]uit",
5346
+ content: " ghfind \u2014 GitHub repo browser [/]search [Esc]menu [\u2192]open [?]help [q]uit \xB7 click select \xB7 dbl-click open",
5201
5347
  bg: colors.bg,
5202
5348
  fg: colors.muted,
5203
5349
  height: 1
@@ -5218,15 +5364,25 @@ async function launchBrowser(theme_override) {
5218
5364
  const isActive = name === trendingTab;
5219
5365
  const label = ` ${name} `;
5220
5366
  const tt = new TextRenderable5(renderer, {
5367
+ ...NON_INTERACTIVE_TEXT,
5221
5368
  content: label,
5222
5369
  fg: isActive ? colors.bg : colors.muted,
5223
5370
  bg: isActive ? colors.blue : colors.bg,
5224
5371
  height: 1
5225
5372
  });
5373
+ tt.onMouseDown = (event) => {
5374
+ event.stopPropagation();
5375
+ if (name === trendingTab) return;
5376
+ trendingTab = name;
5377
+ loadTrending();
5378
+ };
5379
+ tt.onMouseOver = () => renderer.setMousePointer("pointer");
5380
+ tt.onMouseOut = () => renderer.setMousePointer("default");
5226
5381
  trendingTabTexts.push(tt);
5227
5382
  trendingTabBox.add(tt);
5228
5383
  if (i < TAB_NAMES.length - 1) {
5229
5384
  const sp = new TextRenderable5(renderer, {
5385
+ ...NON_INTERACTIVE_TEXT,
5230
5386
  content: " ",
5231
5387
  fg: colors.muted,
5232
5388
  bg: colors.bg,
@@ -5314,7 +5470,10 @@ async function launchBrowser(theme_override) {
5314
5470
  const detailText = new TextRenderable5(renderer, {
5315
5471
  content: "",
5316
5472
  fg: colors.text,
5317
- wrapMode: "none"
5473
+ wrapMode: "none",
5474
+ // Non-interactive: a left-press here would otherwise be swallowed as a
5475
+ // text-selection drag instead of reaching the click handlers.
5476
+ selectable: false
5318
5477
  });
5319
5478
  detailBox.add(detailText);
5320
5479
  body.add(resultsBox);
@@ -5329,6 +5488,7 @@ async function launchBrowser(theme_override) {
5329
5488
  });
5330
5489
  root.add(landingBox);
5331
5490
  const landingBanner = new TextRenderable5(renderer, {
5491
+ ...NON_INTERACTIVE_TEXT,
5332
5492
  content: [
5333
5493
  " \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
5334
5494
  "\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
@@ -5406,7 +5566,8 @@ async function launchBrowser(theme_override) {
5406
5566
  card.add(spacerBot);
5407
5567
  }
5408
5568
  const landingHint = new TextRenderable5(renderer, {
5409
- content: " \u2191\u2193 Navigate \u21E9 Select [b]ookmarks [?]help [q]uit",
5569
+ ...NON_INTERACTIVE_TEXT,
5570
+ content: " \u2191\u2193 Navigate \u21E9 Select [b]ookmarks [?]help [q]uit \xB7 click to pick a mode",
5410
5571
  fg: colors.muted,
5411
5572
  bg: colors.bg,
5412
5573
  height: 1,
@@ -5434,6 +5595,28 @@ async function launchBrowser(theme_override) {
5434
5595
  }
5435
5596
  }
5436
5597
  }
5598
+ landingCards.forEach((card, i) => {
5599
+ let lastClickAt = 0;
5600
+ card.onMouseDown = (event) => {
5601
+ event.stopPropagation();
5602
+ const now = Date.now();
5603
+ const isDouble = landingSelected === i && now - lastClickAt < DOUBLE_CLICK_MS;
5604
+ lastClickAt = now;
5605
+ if (isDouble) {
5606
+ landingOptions[i].action();
5607
+ return;
5608
+ }
5609
+ landingSelected = i;
5610
+ updateLandingCards();
5611
+ renderer.requestRender();
5612
+ };
5613
+ card.onMouseOver = () => {
5614
+ renderer.setMousePointer("pointer");
5615
+ };
5616
+ card.onMouseOut = () => {
5617
+ renderer.setMousePointer("default");
5618
+ };
5619
+ });
5437
5620
  renderer.keyInput.on("keypress", (key) => {
5438
5621
  if (!landingKeysActive(currentMode, currentOverlay)) return;
5439
5622
  if (key.name === "up" || key.name === "k") {
@@ -5498,6 +5681,7 @@ async function launchBrowser(theme_override) {
5498
5681
  updateDim.visible = false;
5499
5682
  if (type === "none") {
5500
5683
  currentOverlay = "none";
5684
+ menuButton.visible = true;
5501
5685
  if (currentMode === "landing") {
5502
5686
  landingBox.visible = true;
5503
5687
  body.visible = false;
@@ -5511,6 +5695,7 @@ async function launchBrowser(theme_override) {
5511
5695
  return;
5512
5696
  }
5513
5697
  currentOverlay = type;
5698
+ menuButton.visible = false;
5514
5699
  landingBox.visible = false;
5515
5700
  if (type !== "leader") {
5516
5701
  hideMainContent();
@@ -5772,7 +5957,8 @@ async function launchBrowser(theme_override) {
5772
5957
  const compareText = new TextRenderable5(renderer, {
5773
5958
  content: "",
5774
5959
  fg: colors.text,
5775
- bg: colors.bg
5960
+ bg: colors.bg,
5961
+ selectable: false
5776
5962
  });
5777
5963
  compareBox.add(compareText);
5778
5964
  root.add(compareBox);
@@ -5901,7 +6087,8 @@ async function launchBrowser(theme_override) {
5901
6087
  itemSpacing: 0
5902
6088
  });
5903
6089
  const leaderFooter = new TextRenderable5(renderer, {
5904
- content: " \u2191\u2193 select Enter run Esc/q close ",
6090
+ ...NON_INTERACTIVE_TEXT,
6091
+ content: " \u2191\u2193 select Enter run Esc/q close \xB7 click to run click outside to close ",
5905
6092
  fg: colors.muted,
5906
6093
  bg: colors.surfaceDim,
5907
6094
  height: 1,
@@ -6033,10 +6220,59 @@ async function launchBrowser(theme_override) {
6033
6220
  neverText.fg = updateSelectedOption === 2 ? colors.yellow : colors.text;
6034
6221
  renderer.requestRender();
6035
6222
  }
6223
+ function runUpdateOption(index) {
6224
+ updateSelectedOption = index;
6225
+ renderUpdateOptions();
6226
+ const action = ["now", "later", "never"][index];
6227
+ if (action === "now") {
6228
+ showOverlay("none");
6229
+ recordPreUpdateState(cachedVersion ?? "");
6230
+ performUpdate().then((ok) => {
6231
+ if (ok) {
6232
+ setStatus("\u2713 Update complete \u2014 restart ghfind");
6233
+ } else {
6234
+ setStatus("\u2717 Update failed \u2014 check terminal output");
6235
+ }
6236
+ }).catch(() => {
6237
+ setStatus("\u2717 Update failed \u2014 check terminal output");
6238
+ });
6239
+ } else if (action === "never") {
6240
+ suppressUpdateNotices();
6241
+ showOverlay("none");
6242
+ setStatus("Update notices suppressed");
6243
+ } else {
6244
+ snoozeUpdateNotices(3);
6245
+ showOverlay("none");
6246
+ setStatus("Update reminder snoozed for 3 days");
6247
+ }
6248
+ renderer.requestRender();
6249
+ }
6250
+ const updateCards = [
6251
+ [updateNowBox, 0],
6252
+ [laterBox, 1],
6253
+ [neverBox, 2]
6254
+ ];
6255
+ for (const [box, index] of updateCards) {
6256
+ attachClickableBox(renderer, box, {
6257
+ onActivate: () => runUpdateOption(index),
6258
+ idleBorder: updateSelectedOption === index ? colors.yellow : colors.border,
6259
+ activeBorder: colors.yellow,
6260
+ // Hovering must not fight the selection highlight, so it only moves the
6261
+ // pointer cursor; the border stays owned by renderUpdateOptions().
6262
+ onStateChange: () => {
6263
+ box.borderColor = updateSelectedOption === index ? colors.yellow : colors.border;
6264
+ }
6265
+ });
6266
+ }
6267
+ attachClickOutside(updateDim, () => {
6268
+ showOverlay("none");
6269
+ renderer.requestRender();
6270
+ });
6036
6271
  updateOptionsRow.add(updateNowBox);
6037
6272
  updateOptionsRow.add(laterBox);
6038
6273
  const updateFooter = new TextRenderable5(renderer, {
6039
- content: " \u2191\u2193/jk/PgUp/PgDn scroll \u2190\u2192 select Enter Esc/q close",
6274
+ ...NON_INTERACTIVE_TEXT,
6275
+ content: " \u2191\u2193/jk/PgUp/PgDn scroll \u2190\u2192 select Enter Esc/q close \xB7 click a button click outside to close",
6040
6276
  fg: colors.muted,
6041
6277
  bg: colors.surfaceDim,
6042
6278
  height: 1,
@@ -6671,6 +6907,40 @@ async function launchBrowser(theme_override) {
6671
6907
  paddingX: 1
6672
6908
  });
6673
6909
  root.add(statusBar);
6910
+ const menuButton = new BoxRenderable5(renderer, {
6911
+ position: "absolute",
6912
+ bottom: 1,
6913
+ right: 1,
6914
+ height: 1,
6915
+ paddingX: 1,
6916
+ backgroundColor: colors.surface,
6917
+ focusable: false
6918
+ });
6919
+ const menuButtonLabel = new TextRenderable5(renderer, {
6920
+ ...NON_INTERACTIVE_TEXT,
6921
+ content: "\u2630 menu",
6922
+ fg: colors.muted,
6923
+ bg: colors.surface,
6924
+ height: 1
6925
+ });
6926
+ menuButton.add(menuButtonLabel);
6927
+ root.add(menuButton);
6928
+ menuButton.onMouseDown = (event) => {
6929
+ event.stopPropagation();
6930
+ if (currentOverlay === "leader") showOverlay("none");
6931
+ else showLeaderMenu();
6932
+ renderer.requestRender();
6933
+ };
6934
+ menuButton.onMouseOver = () => {
6935
+ renderer.setMousePointer("pointer");
6936
+ menuButtonLabel.fg = colors.accent;
6937
+ renderer.requestRender();
6938
+ };
6939
+ menuButton.onMouseOut = () => {
6940
+ renderer.setMousePointer("default");
6941
+ menuButtonLabel.fg = colors.muted;
6942
+ renderer.requestRender();
6943
+ };
6674
6944
  const statusMgr = new StatusManager((text) => {
6675
6945
  statusBar.content = ` ${text}`;
6676
6946
  renderer.requestRender();
@@ -7264,6 +7534,158 @@ ${pack.description ?? ""}`;
7264
7534
  if (repo) openUrl(repo.url);
7265
7535
  }
7266
7536
  });
7537
+ attachClickToSelect(renderer, resultsSelect, {
7538
+ keepFocus: true,
7539
+ onOpen: () => resultsSelect.selectCurrent()
7540
+ });
7541
+ function activateOverlaySelection() {
7542
+ switch (currentOverlay) {
7543
+ case "history": {
7544
+ const sel = historySelect.getSelectedOption();
7545
+ if (sel?.value) {
7546
+ const entry = sel.value.entry;
7547
+ showOverlay("none");
7548
+ if (!entry) return;
7549
+ if (entry.mode === "trending") {
7550
+ if (entry.tab)
7551
+ trendingTab = entry.tab;
7552
+ loadTrending();
7553
+ } else {
7554
+ searchInput.value = entry.query;
7555
+ showSearchMode();
7556
+ doSearch(entry.query);
7557
+ }
7558
+ }
7559
+ return;
7560
+ }
7561
+ case "bookmarks": {
7562
+ const sel = bookmarksSelect.getSelectedOption();
7563
+ if (sel?.value)
7564
+ openUrl(sel.value.repo?.url ?? "");
7565
+ return;
7566
+ }
7567
+ case "saved": {
7568
+ const sel = savedSelect.getSelectedOption();
7569
+ if (sel?.value) {
7570
+ const s = sel.value;
7571
+ showOverlay("none");
7572
+ touchSavedSearch(s.name);
7573
+ if (s.mode === "trending") {
7574
+ if (s.tab) trendingTab = s.tab;
7575
+ loadTrending();
7576
+ } else {
7577
+ searchInput.value = s.query;
7578
+ showSearchMode();
7579
+ currentSort = s.sort;
7580
+ currentLimit = s.limit;
7581
+ setToolbar();
7582
+ doSearch(s.query);
7583
+ }
7584
+ }
7585
+ return;
7586
+ }
7587
+ case "topics": {
7588
+ const sel = topicsSelect.getSelectedOption();
7589
+ if (sel?.value) {
7590
+ const topic = sel.value;
7591
+ showOverlay("none");
7592
+ searchInput.value = `topic:${topic.name ?? ""}`;
7593
+ showSearchMode();
7594
+ doSearch(`topic:${topic.name}`);
7595
+ }
7596
+ return;
7597
+ }
7598
+ case "export": {
7599
+ const sel = exportSelect.getSelectedOption();
7600
+ if (sel?.value && currentRepos.length > 0) {
7601
+ const format2 = sel.value;
7602
+ const path = exportToFile(currentRepos, format2);
7603
+ showOverlay("none");
7604
+ setStatus(`\u2713 Exported to ${path}`);
7605
+ } else {
7606
+ setStatus("No results to export");
7607
+ showOverlay("none");
7608
+ }
7609
+ renderer.requestRender();
7610
+ return;
7611
+ }
7612
+ case "share": {
7613
+ const sel = shareSelect.getSelectedOption();
7614
+ if (sel?.value) {
7615
+ const opt = resultsSelect.getSelectedOption();
7616
+ const repo = opt?.value;
7617
+ if (repo) {
7618
+ const text = formatShare(repo, sel.value);
7619
+ copyToClipboard(text).then((ok) => {
7620
+ showOverlay("none");
7621
+ setStatus(ok ? "\u2713 Copied!" : "Clipboard not available");
7622
+ });
7623
+ }
7624
+ }
7625
+ renderer.requestRender();
7626
+ return;
7627
+ }
7628
+ case "help": {
7629
+ showOverlay("none");
7630
+ renderer.requestRender();
7631
+ return;
7632
+ }
7633
+ default:
7634
+ return;
7635
+ }
7636
+ }
7637
+ function attachOverlayMouse(box, select) {
7638
+ attachClickToSelect(renderer, select, {
7639
+ onOpen: () => activateOverlaySelection(),
7640
+ onClickMiss: () => {
7641
+ showOverlay("none");
7642
+ renderer.requestRender();
7643
+ }
7644
+ });
7645
+ attachClickOutside(box, () => {
7646
+ showOverlay("none");
7647
+ renderer.requestRender();
7648
+ });
7649
+ }
7650
+ attachOverlayMouse(historyBox, historySelect);
7651
+ attachOverlayMouse(bookmarksBox, bookmarksSelect);
7652
+ attachOverlayMouse(savedBox, savedSelect);
7653
+ attachOverlayMouse(topicsBox, topicsSelect);
7654
+ attachOverlayMouse(exportBox, exportSelect);
7655
+ attachOverlayMouse(shareBox, shareSelect);
7656
+ for (const panel of [helpBox, orgBox, readmeBox, compareBox, notifsBox]) {
7657
+ attachClickOutside(panel, () => {
7658
+ showOverlay("none");
7659
+ renderer.requestRender();
7660
+ });
7661
+ }
7662
+ for (const scroller of [helpScroll, orgScroll, readmeScroll, updateScroll]) {
7663
+ scroller.onMouseScroll = (event) => {
7664
+ event.stopPropagation();
7665
+ };
7666
+ }
7667
+ attachClickToSelect(renderer, leaderSelect, {
7668
+ onOpen: () => {
7669
+ const entry = leaderSelect.getSelectedOption()?.value;
7670
+ if (!entry) return;
7671
+ if (entry.type === "category") {
7672
+ pushMenuLevel(` Menu > ${entry.name}`, entry.name, entry.children, 0);
7673
+ } else {
7674
+ resetMenu();
7675
+ showOverlay("none");
7676
+ entry.action();
7677
+ }
7678
+ renderer.requestRender();
7679
+ },
7680
+ onClickMiss: () => {
7681
+ showOverlay("none");
7682
+ renderer.requestRender();
7683
+ }
7684
+ });
7685
+ attachClickOutside(leaderDim, () => {
7686
+ showOverlay("none");
7687
+ renderer.requestRender();
7688
+ });
7267
7689
  renderer.keyInput.on("keypress", (key) => {
7268
7690
  if (tokenPromptBox.visible) {
7269
7691
  if (key.name === "escape") {
@@ -7357,21 +7779,7 @@ ${pack.description ?? ""}`;
7357
7779
  return;
7358
7780
  }
7359
7781
  if (key.name === "enter" || key.name === "return") {
7360
- const sel = historySelect.getSelectedOption();
7361
- if (sel?.value) {
7362
- const entry = sel.value.entry;
7363
- showOverlay("none");
7364
- if (!entry) return;
7365
- if (entry.mode === "trending") {
7366
- if (entry.tab)
7367
- trendingTab = entry.tab;
7368
- loadTrending();
7369
- } else {
7370
- searchInput.value = entry.query;
7371
- showSearchMode();
7372
- doSearch(entry.query);
7373
- }
7374
- }
7782
+ activateOverlaySelection();
7375
7783
  return;
7376
7784
  }
7377
7785
  return;
@@ -7390,9 +7798,7 @@ ${pack.description ?? ""}`;
7390
7798
  return;
7391
7799
  }
7392
7800
  if (key.name === "enter" || key.name === "return") {
7393
- const sel = bookmarksSelect.getSelectedOption();
7394
- if (sel?.value)
7395
- openUrl(sel.value.repo?.url ?? "");
7801
+ activateOverlaySelection();
7396
7802
  return;
7397
7803
  }
7398
7804
  return;
@@ -7409,54 +7815,21 @@ ${pack.description ?? ""}`;
7409
7815
  return;
7410
7816
  }
7411
7817
  if (key.name === "enter" || key.name === "return") {
7412
- const sel = savedSelect.getSelectedOption();
7413
- if (sel?.value) {
7414
- const s = sel.value;
7415
- showOverlay("none");
7416
- touchSavedSearch(s.name);
7417
- if (s.mode === "trending") {
7418
- if (s.tab) trendingTab = s.tab;
7419
- loadTrending();
7420
- } else {
7421
- searchInput.value = s.query;
7422
- showSearchMode();
7423
- currentSort = s.sort;
7424
- currentLimit = s.limit;
7425
- setToolbar();
7426
- doSearch(s.query);
7427
- }
7428
- }
7818
+ activateOverlaySelection();
7429
7819
  return;
7430
7820
  }
7431
7821
  return;
7432
7822
  }
7433
7823
  if (currentOverlay === "topics") {
7434
7824
  if (key.name === "enter" || key.name === "return") {
7435
- const sel = topicsSelect.getSelectedOption();
7436
- if (sel?.value) {
7437
- const topic = sel.value;
7438
- showOverlay("none");
7439
- searchInput.value = `topic:${topic.name ?? ""}`;
7440
- showSearchMode();
7441
- doSearch(`topic:${topic.name}`);
7442
- }
7825
+ activateOverlaySelection();
7443
7826
  return;
7444
7827
  }
7445
7828
  return;
7446
7829
  }
7447
7830
  if (currentOverlay === "export") {
7448
7831
  if (key.name === "enter" || key.name === "return") {
7449
- const sel = exportSelect.getSelectedOption();
7450
- if (sel?.value && currentRepos.length > 0) {
7451
- const format2 = sel.value;
7452
- const path = exportToFile(currentRepos, format2);
7453
- showOverlay("none");
7454
- setStatus(`\u2713 Exported to ${path}`);
7455
- } else {
7456
- setStatus("No results to export");
7457
- showOverlay("none");
7458
- }
7459
- renderer.requestRender();
7832
+ activateOverlaySelection();
7460
7833
  return;
7461
7834
  }
7462
7835
  return;
@@ -7485,26 +7858,13 @@ ${pack.description ?? ""}`;
7485
7858
  }
7486
7859
  if (currentOverlay === "share") {
7487
7860
  if (key.name === "enter" || key.name === "return") {
7488
- const sel = shareSelect.getSelectedOption();
7489
- if (sel?.value) {
7490
- const opt = resultsSelect.getSelectedOption();
7491
- const repo = opt?.value;
7492
- if (repo) {
7493
- const text = formatShare(repo, sel.value);
7494
- copyToClipboard(text).then((ok) => {
7495
- showOverlay("none");
7496
- setStatus(ok ? "\u2713 Copied!" : "Clipboard not available");
7497
- });
7498
- }
7499
- }
7500
- renderer.requestRender();
7861
+ activateOverlaySelection();
7501
7862
  return;
7502
7863
  }
7503
7864
  return;
7504
7865
  }
7505
7866
  if (currentOverlay === "help") {
7506
- showOverlay("none");
7507
- renderer.requestRender();
7867
+ activateOverlaySelection();
7508
7868
  return;
7509
7869
  }
7510
7870
  if (currentOverlay === "update") {
@@ -7529,29 +7889,7 @@ ${pack.description ?? ""}`;
7529
7889
  return;
7530
7890
  }
7531
7891
  if (key.name === "enter" || key.name === "return") {
7532
- const action = ["now", "later", "never"][updateSelectedOption];
7533
- if (action === "now") {
7534
- showOverlay("none");
7535
- recordPreUpdateState(cachedVersion ?? "");
7536
- performUpdate().then((ok) => {
7537
- if (ok) {
7538
- setStatus("\u2713 Update complete \u2014 restart ghfind");
7539
- } else {
7540
- setStatus("\u2717 Update failed \u2014 check terminal output");
7541
- }
7542
- }).catch(() => {
7543
- setStatus("\u2717 Update failed \u2014 check terminal output");
7544
- });
7545
- } else if (action === "never") {
7546
- suppressUpdateNotices();
7547
- showOverlay("none");
7548
- setStatus("Update notices suppressed");
7549
- } else {
7550
- snoozeUpdateNotices(3);
7551
- showOverlay("none");
7552
- setStatus("Update reminder snoozed for 3 days");
7553
- }
7554
- renderer.requestRender();
7892
+ runUpdateOption(updateSelectedOption);
7555
7893
  return;
7556
7894
  }
7557
7895
  return;
@@ -7574,8 +7912,14 @@ ${pack.description ?? ""}`;
7574
7912
  toggleGraph();
7575
7913
  return;
7576
7914
  }
7577
- if (key.name === "space" && !searchInput.focused) {
7578
- showLeaderMenu();
7915
+ if (key.name === "escape") {
7916
+ if (currentOverlay !== "none") {
7917
+ showOverlay("none");
7918
+ setToolbar();
7919
+ } else {
7920
+ showLeaderMenu();
7921
+ }
7922
+ renderer.requestRender();
7579
7923
  return;
7580
7924
  }
7581
7925
  if (!searchInput.focused && errorHint) {