diff --git a/dashboard.go b/dashboard.go index 4698d35..1b863a6 100644 --- a/dashboard.go +++ b/dashboard.go @@ -1226,6 +1226,7 @@ func (c *Crawler) handleAPIFilter(w http.ResponseWriter, r *http.Request) { domainStatus := r.URL.Query().Get("domainStatus") languages := r.URL.Query().Get("languages") // comma-separated list show := r.URL.Query().Get("show") // "feeds" or "domains" + sort := r.URL.Query().Get("sort") // "alpha" or "feeds" limit := 100 offset := 0 @@ -1262,11 +1263,11 @@ func (c *Crawler) handleAPIFilter(w http.ResponseWriter, r *http.Request) { if show == "feeds" { c.filterFeeds(w, tld, domain, feedStatus, langList, limit, offset) } else { - c.filterDomains(w, tld, domainStatus, limit, offset) + c.filterDomains(w, tld, domainStatus, sort, limit, offset) } } -func (c *Crawler) filterDomains(w http.ResponseWriter, tld, status string, limit, offset int) { +func (c *Crawler) filterDomains(w http.ResponseWriter, tld, status, sort string, limit, offset int) { var args []interface{} argNum := 1 query := ` @@ -1290,7 +1291,12 @@ func (c *Crawler) filterDomains(w http.ResponseWriter, tld, status string, limit argNum++ } - query += fmt.Sprintf(" ORDER BY d.host ASC LIMIT $%d OFFSET $%d", argNum, argNum+1) + // Sort by feed count descending or alphabetically + if sort == "feeds" { + query += fmt.Sprintf(" ORDER BY feed_count DESC, d.host ASC LIMIT $%d OFFSET $%d", argNum, argNum+1) + } else { + query += fmt.Sprintf(" ORDER BY d.host ASC LIMIT $%d OFFSET $%d", argNum, argNum+1) + } args = append(args, limit, offset) rows, err := c.db.Query( query, args...) diff --git a/static/dashboard.js b/static/dashboard.js index 5889118..26c25cb 100644 --- a/static/dashboard.js +++ b/static/dashboard.js @@ -16,6 +16,7 @@ function initDashboard() { let isLoadingMore = false; let availableLanguages = []; let selectedLanguages = new Set(); + let sortBy = 'alpha'; // 'alpha' or 'feeds' // Update command input to reflect current filters function updateCommandInput() { @@ -81,7 +82,20 @@ function initDashboard() { parts.push('lang:' + escapeHtml(Array.from(selectedLanguages).join(',')) + ' ✕'); } - breadcrumb.innerHTML = parts.join(' / '); + // Add sort toggle on the right if viewing domains + let sortToggle = ''; + if (currentFilters.tld && !currentFilters.domain && !currentFilters.feedStatus) { + const alphaStyle = sortBy === 'alpha' ? 'color: #0f0;' : 'color: #666;'; + const feedsStyle = sortBy === 'feeds' ? 'color: #0f0;' : 'color: #666;'; + sortToggle = '' + + 'A-Z' + + ' | ' + + '#feeds' + + ''; + } + + breadcrumb.innerHTML = '
' + + parts.join(' / ') + sortToggle + '
'; breadcrumb.style.display = parts.length > 1 ? 'block' : 'none'; // Add click handlers @@ -105,6 +119,17 @@ function initDashboard() { selectedLanguages.clear(); updateLangButton(); updateLangDropdown(); + } + }); + }); + + // Add sort toggle handlers + breadcrumb.querySelectorAll('.sort-toggle').forEach(el => { + el.addEventListener('click', () => { + const newSort = el.dataset.sort; + if (newSort !== sortBy) { + sortBy = newSort; + executeFilters(); executeFilters(); } }); @@ -353,6 +378,7 @@ function initDashboard() { if (hasLang) params.set('languages', Array.from(selectedLanguages).join(',')); if (showFeeds) params.set('show', 'feeds'); else if (showDomains) params.set('show', 'domains'); + if (sortBy) params.set('sort', sortBy); // Fetch TLD stats if viewing a TLD let tldStatsHtml = '';