Recently added, shop sorting, and outfit cards with T5 products
CI / build (push) Successful in 34s

- The inventory's Recently Added puts T5 products added to the library in the last 30 days first.
- The shop's T5 listing follows its sort menu: Best Match ranks by name, Newest by upload date
  (oldest first ascending); ratings and prices (T5 products have neither) sort by newest.
- Outfit cards: outfit_products.php gets the outfit's community products added (not for sale),
  find_bundle.php is asked about IMVU's products only, and removing one in the card (remove_pids)
  keeps it from IMVU and forgets it in the outfit.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-30 02:45:02 -07:00
1 parent af9158513b
commit 171e8173af
11 files changed
+463 -7

No files matched your search

+39
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"net/http"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
@@ -37,6 +38,8 @@ func (p *Products) interceptShopSearch(x *proxy.Exchange) error {
Page json.Number `json:"page"`
Skip json.Number `json:"skip"`
PerPage json.Number `json:"results_per_page"`
Sort string `json:"sort_field"`
Dir string `json:"sort_direction"`
Limit json.Number `json:"solr_limit"`
}
if json.Unmarshal(x.Body, &req) != nil || req.Rating != ShopRating {
@@ -67,6 +70,7 @@ func (p *Products) interceptShopSearch(x *proxy.Exchange) error {
}
p.mu.Unlock()
list = inCategories(list, categoryIDs(req.Category))
sortShop(list, query, req.Sort, req.Dir)
p.fetchForShop(ctx, list)
var rows []shopRow
@@ -244,6 +248,41 @@ func shopAnswer(rows []shopRow, page, skip, perPage, limit int) []byte {
return b
}
// sortShop orders products for the shop's sort menu. T5 products have no ratings or prices, so those sorts are by
// newest; Best Match (conversion_score_b, score) with a search puts the closest names first (the same name, then
// names starting with it, then words starting with it, then the rest), newest first among equals; Newest
// (id) is newest first, or oldest first ascending.
func sortShop(list []hub.Product, query, field, direction string) {
q := strings.ToLower(strings.TrimSpace(query))
rank := func(hp hub.Product) int {
if q == "" || (field != "conversion_score_b" && field != "score" && field != "") {
return 0
}
name := strings.ToLower(hp.Name)
switch {
case name == q:
return 0
case strings.HasPrefix(name, q):
return 1
case strings.Contains(" "+name, " "+q):
return 2
case strings.Contains(name, q):
return 3
}
return 4 // matched by creator
}
oldest := field == "id" && direction == "asc"
sort.SliceStable(list, func(i, j int) bool {
if ri, rj := rank(list[i]), rank(list[j]); ri != rj {
return ri < rj
}
if oldest {
return list[i].CreatedAt.Before(list[j].CreatedAt)
}
return list[i].CreatedAt.After(list[j].CreatedAt)
})
}
// categoryIDs reads the shop's category field: IDs separated by commas, as a string or number (empty: all).
func categoryIDs(raw json.RawMessage) map[int64]bool {
var s string