diff --git a/src/components/ProviderCatalog.astro b/src/components/ProviderCatalog.astro index 63198c43b..98014be1f 100644 --- a/src/components/ProviderCatalog.astro +++ b/src/components/ProviderCatalog.astro @@ -49,6 +49,12 @@ function categoryLabel(slug: string): string { return CATEGORY_LABELS[slug] ?? slug.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()) } +// First letter of a connector name — used as a graceful fallback when the logo fails to load. +function initial(title: string): string { + const c = title.trim().charAt(0) + return c ? c.toUpperCase() : '?' +} + // Derive category order from catalog data; put 'Other' last const allCategories = Array.from(new Set(items.flatMap((i) => i.categories))).sort((a, b) => { if (a === 'other') return 1 @@ -73,6 +79,12 @@ const slugToTitle = Object.fromEntries(items.map((i) => [i.slug, i.title])) ---
+
+ {items.length} connectors + + {grouped.length} categories +
+
@@ -93,10 +105,17 @@ const slugToTitle = Object.fromEntries(items.map((i) => [i.slug, i.title]))
+

+ { grouped.map((group) => (
-

{categoryLabel(group.name)}

+

+ {categoryLabel(group.name)} + + {group.items.length} + +

{group.items.map((item) => ( [i.slug, i.title])) data-categories={item.categories.join(',')} >
- @@ -139,6 +158,7 @@ const slugToTitle = Object.fromEntries(items.map((i) => [i.slug, i.title])) const categorySelect = catalogEl?.querySelector('#category-select') as HTMLSelectElement | null const authSelect = catalogEl?.querySelector('#auth-select') as HTMLSelectElement | null const noResults = catalogEl?.querySelector('.no-results') as HTMLElement | null + const resultCount = catalogEl?.querySelector('.result-count') as HTMLElement | null const toolResultsEl = catalogEl?.querySelector('.tool-results') as HTMLElement | null const toolResultsList = toolResultsEl?.querySelector('.tool-results-list') as HTMLElement | null @@ -182,13 +202,33 @@ const slugToTitle = Object.fromEntries(items.map((i) => [i.slug, i.title])) }) section.style.display = sectionVisible > 0 ? '' : 'none' + + // Keep the per-category count badge in sync with what's actually visible. + const countEl = section.querySelector('.category-heading .count') + if (countEl) { + const total = countEl.dataset.total ?? String(sectionVisible) + countEl.textContent = searchQuery ? `${sectionVisible} of ${total}` : total + } + totalVisible += sectionVisible }) + updateResultCount(totalVisible) // noResults visibility is decided after tool results are also considered (see updateNoResults) return totalVisible } + // Show a running total only while the view is filtered; stay quiet at rest. + function updateResultCount(connectorVisible: number): void { + if (!resultCount) return + if (searchQuery || activeCategory !== 'all' || activeAuth !== 'all') { + const noun = connectorVisible === 1 ? 'connector' : 'connectors' + resultCount.textContent = `Showing ${connectorVisible} ${noun}` + } else { + resultCount.textContent = '' + } + } + async function loadToolIndex(): Promise< Array<{ slug: string; name: string; description: string }> > { @@ -303,10 +343,6 @@ const slugToTitle = Object.fromEntries(items.map((i) => [i.slug, i.title])) return str.length > n ? str.slice(0, n - 1) + '…' : str } - function escapeHtml(str: string) { - return str.replace(/&/g, '&').replace(//g, '>') - } - function updateNoResults(connectorVisible: number, hasToolMatches: boolean) { if (!noResults) return const anyVisible = connectorVisible > 0 || hasToolMatches @@ -314,7 +350,6 @@ const slugToTitle = Object.fromEntries(items.map((i) => [i.slug, i.title])) } // Main search handler (connectors + tools) - let searchDebounce: number | null = null searchInput?.addEventListener('input', async (e) => { searchQuery = (e.target as HTMLInputElement).value.toLowerCase().trim() @@ -349,9 +384,12 @@ const slugToTitle = Object.fromEntries(items.map((i) => [i.slug, i.title])) updateNoResults(connectorVisible, !!hasTools) }) + // Graceful logo fallback: if a provider icon fails to load, hide the broken + // image and reveal the connector's initial letter (rendered via CSS ::after). catalogEl?.querySelectorAll('.provider-logo img').forEach((img) => { img.addEventListener('error', () => { img.style.display = 'none' + img.closest('.provider-logo')?.classList.add('logo-fallback') }) }) @@ -362,20 +400,39 @@ const slugToTitle = Object.fromEntries(items.map((i) => [i.slug, i.title]))