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 04d823d616
commit a9847b6f81
2 changed files with 58 additions and 25 deletions

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"io"
"net/http"
"slices"
"strings"
"sync"
"time"
@@ -23,19 +24,20 @@ type (
IconMap map[IconKey]*IconMeta
IconList []string
IconMeta struct {
SVG, PNG, WebP bool
Light, Dark bool
DisplayName string
Tag string
SVG bool `json:"SVG"`
PNG bool `json:"PNG"`
WebP bool `json:"WebP"`
Light bool `json:"Light"`
Dark bool `json:"Dark"`
DisplayName string `json:"-"`
Tag string `json:"-"`
}
IconMetaSearch struct {
Source IconSource `json:"Source"`
Ref string `json:"Ref"`
SVG bool `json:"SVG"`
PNG bool `json:"PNG"`
WebP bool `json:"WebP"`
Light bool `json:"Light"`
Dark bool `json:"Dark"`
*IconMeta
rank int
}
Cache struct {
Icons IconMap
@@ -150,31 +152,54 @@ func ListAvailableIcons() (*Cache, error) {
return iconsCache, nil
}
func SearchIcons(keyword string, limit int) []IconMetaSearch {
func SearchIcons(keyword string, limit int) []*IconMetaSearch {
if keyword == "" {
return make([]IconMetaSearch, 0)
return []*IconMetaSearch{}
}
if limit == 0 {
limit = 10
}
iconsCache.RLock()
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 {
if fuzzy.MatchFold(keyword, string(k)) {
source, ref := k.SourceRef()
result = append(result, IconMetaSearch{
Source: source,
Ref: ref,
SVG: icon.SVG,
PNG: icon.PNG,
WebP: icon.WebP,
Light: icon.Light,
Dark: icon.Dark,
})
if strutils.ContainsFold(string(k), keyword) || strutils.ContainsFold(icon.DisplayName, keyword) {
rank = 0
} else {
rank = fuzzy.RankMatchFold(keyword, string(k))
if rank == -1 || rank > 3 {
continue
}
}
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
}
}
return result
// Extract results and limit to the requested count
return results[:min(len(results), limit)]
}
func HasIcon(icon *IconURL) bool {

View File

@@ -24,6 +24,14 @@ func Title(s string) string {
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 {
var buf strings.Builder
for _, r := range s {