|
54 | 54 | let searchLoading = $state(false); |
55 | 55 | let searchDebounceTimer: ReturnType<typeof setTimeout> | null = null; |
56 | 56 |
|
| 57 | + let npmSearch = $state(''); |
| 58 | + let npmSearchResults = $state<SearchResult[]>([]); |
| 59 | + let npmSearchLoading = $state(false); |
| 60 | + let npmSearchDebounceTimer: ReturnType<typeof setTimeout> | null = null; |
| 61 | +
|
57 | 62 | let showImportModal = $state(false); |
58 | 63 | let brewfileContent = $state(''); |
59 | 64 | let importLoading = $state(false); |
|
111 | 116 | }, 300); |
112 | 117 | } |
113 | 118 |
|
| 119 | + async function searchNpm(query: string) { |
| 120 | + if (query.length < 2) { |
| 121 | + npmSearchResults = []; |
| 122 | + return; |
| 123 | + } |
| 124 | +
|
| 125 | + npmSearchLoading = true; |
| 126 | + try { |
| 127 | + const response = await fetch(`/api/npm/search?q=${encodeURIComponent(query)}`); |
| 128 | + const data = await response.json(); |
| 129 | + npmSearchResults = data.results || []; |
| 130 | + } catch (e) { |
| 131 | + console.error('npm search failed:', e); |
| 132 | + npmSearchResults = []; |
| 133 | + } finally { |
| 134 | + npmSearchLoading = false; |
| 135 | + } |
| 136 | + } |
| 137 | +
|
| 138 | + function handleNpmSearchInput(value: string) { |
| 139 | + npmSearch = value; |
| 140 | + if (npmSearchDebounceTimer) { |
| 141 | + clearTimeout(npmSearchDebounceTimer); |
| 142 | + } |
| 143 | + if (value.length < 2) { |
| 144 | + npmSearchResults = []; |
| 145 | + return; |
| 146 | + } |
| 147 | + npmSearchDebounceTimer = setTimeout(() => { |
| 148 | + searchNpm(value); |
| 149 | + }, 300); |
| 150 | + } |
| 151 | +
|
114 | 152 | function getExtraPackages(): string[] { |
115 | 153 | const presetPkgs = new Set(getPresetPackages(formData.base_preset)); |
116 | 154 | return Array.from(selectedPackages.keys()).filter((pkg) => !presetPkgs.has(pkg)); |
|
601 | 639 | <span class="group-empty">No npm packages</span> |
602 | 640 | {/if} |
603 | 641 | </div> |
| 642 | + <div class="packages-search"> |
| 643 | + <input |
| 644 | + type="text" |
| 645 | + class="search-input" |
| 646 | + value={npmSearch} |
| 647 | + oninput={(e) => handleNpmSearchInput(e.currentTarget.value)} |
| 648 | + placeholder="Search npm packages (e.g. typescript, eslint)" |
| 649 | + /> |
| 650 | + </div> |
| 651 | + {#if npmSearchLoading} |
| 652 | + <div class="search-status">Searching npm...</div> |
| 653 | + {:else if npmSearch.length >= 2 && npmSearchResults.length === 0} |
| 654 | + <div class="search-status">No npm packages found for "{npmSearch}"</div> |
| 655 | + {:else if npmSearch.length >= 2} |
| 656 | + <div class="packages-grid"> |
| 657 | + {#each npmSearchResults as result} |
| 658 | + <button type="button" class="package-item" class:selected={selectedPackages.has(result.name)} onclick={() => togglePackage(result.name, 'npm')}> |
| 659 | + <span class="check-indicator">{selectedPackages.has(result.name) ? '✓' : ''}</span> |
| 660 | + <div class="package-content"> |
| 661 | + <div class="package-info"> |
| 662 | + <span class="package-name">{result.name}</span> |
| 663 | + <span class="package-type">npm</span> |
| 664 | + </div> |
| 665 | + {#if result.desc} |
| 666 | + <span class="package-desc">{result.desc.slice(0, 60)}{result.desc.length > 60 ? '...' : ''}</span> |
| 667 | + {/if} |
| 668 | + </div> |
| 669 | + </button> |
| 670 | + {/each} |
| 671 | + </div> |
| 672 | + {:else} |
| 673 | + <div class="search-hint">Type at least 2 characters to search npm packages</div> |
| 674 | + {/if} |
604 | 675 | </div> |
605 | 676 | <div class="packages-search"> |
606 | 677 | <input |
607 | 678 | type="text" |
608 | 679 | class="search-input" |
609 | 680 | value={packageSearch} |
610 | 681 | oninput={(e) => handleSearchInput(e.currentTarget.value)} |
611 | | - placeholder="Search packages or enter tap (e.g. steipete/tap/codexbar)" |
| 682 | + placeholder="Search Homebrew packages or enter tap (e.g. steipete/tap/codexbar)" |
612 | 683 | /> |
613 | 684 | </div> |
614 | 685 | {#if searchLoading} |
615 | | - <div class="search-status">Searching...</div> |
| 686 | + <div class="search-status">Searching Homebrew...</div> |
616 | 687 | {:else if packageSearch.length >= 2 && searchResults.length === 0} |
617 | | - <div class="search-status">No packages found for "{packageSearch}"</div> |
| 688 | + <div class="search-status">No Homebrew packages found for "{packageSearch}"</div> |
618 | 689 | {:else if packageSearch.length >= 2} |
619 | 690 | <div class="packages-grid"> |
620 | 691 | {#each searchResults as result} |
|
0 commit comments