refactor(homepage): improve icon search functionality and add case-insensitive string matching

This commit is contained in:
yusing
2025-09-03 20:27:41 +08:00
parent 8339c42470
commit a56de3de08
2 changed files with 58 additions and 25 deletions

View File

@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"slices"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -23,19 +24,20 @@ type (
IconMap map[IconKey]*IconMeta IconMap map[IconKey]*IconMeta
IconList []string IconList []string
IconMeta struct { IconMeta struct {
SVG, PNG, WebP bool SVG bool `json:"SVG"`
Light, Dark bool PNG bool `json:"PNG"`
DisplayName string WebP bool `json:"WebP"`
Tag string Light bool `json:"Light"`
Dark bool `json:"Dark"`
DisplayName string `json:"-"`
Tag string `json:"-"`
} }
IconMetaSearch struct { IconMetaSearch struct {
Source IconSource `json:"Source"` Source IconSource `json:"Source"`
Ref string `json:"Ref"` Ref string `json:"Ref"`
SVG bool `json:"SVG"` *IconMeta
PNG bool `json:"PNG"`
WebP bool `json:"WebP"` rank int
Light bool `json:"Light"`
Dark bool `json:"Dark"`
} }
Cache struct { Cache struct {
Icons IconMap Icons IconMap
@@ -150,31 +152,54 @@ func ListAvailableIcons() (*Cache, error) {
return iconsCache, nil return iconsCache, nil
} }
func SearchIcons(keyword string, limit int) []IconMetaSearch { func SearchIcons(keyword string, limit int) []*IconMetaSearch {
if keyword == "" { if keyword == "" {
return make([]IconMetaSearch, 0) return []*IconMetaSearch{}
} }
if limit == 0 {
limit = 10
}
iconsCache.RLock() iconsCache.RLock()
defer iconsCache.RUnlock() defer iconsCache.RUnlock()
result := make([]IconMetaSearch, 0)
searchLimit := min(limit*5, 50)
results := make([]*IconMetaSearch, 0, searchLimit)
sortByRank := func(a, b *IconMetaSearch) int {
return a.rank - b.rank
}
var rank int
for k, icon := range iconsCache.Icons { for k, icon := range iconsCache.Icons {
if fuzzy.MatchFold(keyword, string(k)) { if strutils.ContainsFold(string(k), keyword) || strutils.ContainsFold(icon.DisplayName, keyword) {
source, ref := k.SourceRef() rank = 0
result = append(result, IconMetaSearch{ } else {
Source: source, rank = fuzzy.RankMatchFold(keyword, string(k))
Ref: ref, if rank == -1 || rank > 3 {
SVG: icon.SVG, continue
PNG: icon.PNG, }
WebP: icon.WebP,
Light: icon.Light,
Dark: icon.Dark,
})
} }
if len(result) >= limit {
source, ref := k.SourceRef()
ranked := &IconMetaSearch{
Source: source,
Ref: ref,
IconMeta: icon,
rank: rank,
}
// Sorted insert based on rank (lower rank = better match)
insertPos, _ := slices.BinarySearchFunc(results, ranked, sortByRank)
results = slices.Insert(results, insertPos, ranked)
if len(results) == searchLimit {
break break
} }
} }
return result
// Extract results and limit to the requested count
return results[:min(len(results), limit)]
} }
func HasIcon(icon *IconURL) bool { func HasIcon(icon *IconURL) bool {

View File

@@ -24,6 +24,14 @@ func Title(s string) string {
return cases.Title(language.AmericanEnglish).String(s) return cases.Title(language.AmericanEnglish).String(s)
} }
func ContainsFold(s, substr string) bool {
return IndexFold(s, substr) >= 0
}
func IndexFold(s, substr string) int {
return strings.Index(strings.ToLower(s), strings.ToLower(substr))
}
func ToLowerNoSnake(s string) string { func ToLowerNoSnake(s string) string {
var buf strings.Builder var buf strings.Builder
for _, r := range s { for _, r := range s {