mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-16 19:05:13 +03:00
feat(sponsors): drop the sidebar slot and simplify the Sponsors page
The sidebar no longer carries a sponsor card. The backend stops serving the "sidebar" slot, so a sponsors.json entry that still lists it keeps only its other slots and one listing nothing else is dropped. Its rotation, three-sponsor cap and collapsed-rail logo rendering go with it. The Sponsors page now lists only real sponsors: the "Your brand here" card shows only as the empty state instead of sitting in the grid and repeating the header's Become a sponsor button, and the Available placements section and its slot-occupancy helper are removed. Cards are larger, two per row on wide screens. Unused i18n keys are dropped from every locale.
This commit is contained in:
@@ -45,7 +45,6 @@ const (
|
||||
sponsorsErrTTL = 10 * time.Minute
|
||||
maxSponsorsBytes = 256 << 10
|
||||
maxLogoBytes = 256 << 10
|
||||
maxSidebarSlots = 3
|
||||
localSponsorsDir = "../sponsors/3X"
|
||||
sponsorLogoPath = "/sponsors/logo/"
|
||||
)
|
||||
@@ -67,7 +66,7 @@ var (
|
||||
|
||||
// The panel proxies logos from sponsorLogoBase: CSP stays 'self' and no third party sees admin IPs.
|
||||
sponsorLogoRe = regexp.MustCompile(`^[A-Za-z0-9_-][A-Za-z0-9._-]*\.(png|webp|jpg)$`)
|
||||
sponsorSlots = map[string]bool{"dashboard": true, "sidebar": true, "page": true, "login": true}
|
||||
sponsorSlots = map[string]bool{"dashboard": true, "page": true, "login": true}
|
||||
|
||||
sponsorsMu sync.Mutex
|
||||
sponsorsRaw *SponsorList
|
||||
@@ -217,7 +216,6 @@ func activeSponsors(raw *SponsorList, now time.Time) *SponsorList {
|
||||
if strings.HasPrefix(raw.Contact, "https://") {
|
||||
out.Contact = raw.Contact
|
||||
}
|
||||
sidebarTaken := 0
|
||||
for _, sp := range raw.Sponsors {
|
||||
// A missing enable counts as on, so a forgotten field never hides a paid slot.
|
||||
disabled := sp.Enable != nil && !*sp.Enable
|
||||
@@ -233,18 +231,13 @@ func activeSponsors(raw *SponsorList, now time.Time) *SponsorList {
|
||||
}
|
||||
slots := make([]string, 0, len(sp.Slots))
|
||||
for _, slot := range sp.Slots {
|
||||
// The sidebar rotates, so capping it keeps each paid card on screen long enough.
|
||||
if !sponsorSlots[slot] || (slot == "sidebar" && sidebarTaken >= maxSidebarSlots) {
|
||||
continue
|
||||
if sponsorSlots[slot] {
|
||||
slots = append(slots, slot)
|
||||
}
|
||||
slots = append(slots, slot)
|
||||
}
|
||||
if len(slots) == 0 {
|
||||
continue
|
||||
}
|
||||
if slices.Contains(slots, "sidebar") {
|
||||
sidebarTaken++
|
||||
}
|
||||
sp.Slots = slots
|
||||
out.Sponsors = append(out.Sponsors, sp)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
@@ -22,7 +21,7 @@ func validSponsor() Sponsor {
|
||||
return Sponsor{
|
||||
ID: "acme",
|
||||
Name: "Acme",
|
||||
Slots: []string{"dashboard", "sidebar"},
|
||||
Slots: []string{"dashboard"},
|
||||
Until: sponsorTestNow.Add(24 * time.Hour),
|
||||
Logo: "acme.png",
|
||||
Link: "https://acme.example/",
|
||||
@@ -59,34 +58,6 @@ func TestActiveSponsorsFilters(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestActiveSponsorsCapsSidebarAtThree(t *testing.T) {
|
||||
raw := &SponsorList{}
|
||||
for _, id := range []string{"expired", "a", "b", "c", "d", "e"} {
|
||||
sp := validSponsor()
|
||||
sp.ID = id
|
||||
sp.Slots = []string{"sidebar", "page"}
|
||||
if id == "expired" {
|
||||
sp.Until = sponsorTestNow
|
||||
}
|
||||
if id == "e" {
|
||||
sp.Slots = []string{"sidebar"}
|
||||
}
|
||||
raw.Sponsors = append(raw.Sponsors, sp)
|
||||
}
|
||||
got := activeSponsors(raw, sponsorTestNow)
|
||||
want := map[string][]string{
|
||||
"a": {"sidebar", "page"}, "b": {"sidebar", "page"}, "c": {"sidebar", "page"}, "d": {"page"},
|
||||
}
|
||||
if len(got.Sponsors) != len(want) {
|
||||
t.Fatalf("got %d sponsors, want %d (e has only sidebar and must drop)", len(got.Sponsors), len(want))
|
||||
}
|
||||
for _, sp := range got.Sponsors {
|
||||
if !slices.Equal(sp.Slots, want[sp.ID]) {
|
||||
t.Errorf("%s slots = %v, want %v", sp.ID, sp.Slots, want[sp.ID])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestActiveSponsorsLogoName(t *testing.T) {
|
||||
cases := []struct{ logo, want string }{
|
||||
{"acme.png", "/sponsors/logo/acme.png"},
|
||||
@@ -114,7 +85,7 @@ func TestActiveSponsorsLogoName(t *testing.T) {
|
||||
|
||||
func TestActiveSponsorsResolvesLogoAndSlots(t *testing.T) {
|
||||
sp := validSponsor()
|
||||
sp.Slots = []string{"subpage", "login"}
|
||||
sp.Slots = []string{"subpage", "sidebar", "login"}
|
||||
got := activeSponsors(&SponsorList{Contact: "javascript:x", Sponsors: []Sponsor{sp}}, sponsorTestNow)
|
||||
if len(got.Sponsors) != 1 {
|
||||
t.Fatalf("got %d sponsors, want 1", len(got.Sponsors))
|
||||
|
||||
Reference in New Issue
Block a user