render.gno
1.85 Kb · 73 lines
1package memba_appstore_v3
2
3import (
4 "chain/runtime"
5
6 "gno.land/p/nt/ufmt/v0"
7)
8
9// RenderMaxRows bounds the Render() output so a large catalog can never make the
10// realm render-DoS (mirrors the feed's live-only bounded render).
11const RenderMaxRows = 100
12
13// Render shows the live catalog (bounded) — a read-only trust surface for gnoweb.
14// The frontend reads structured data via the getters, not this markdown.
15func Render(path string) string {
16 sb := ""
17 sb += "# Memba App Store\n\n"
18 sb += ufmt.Sprintf("Curated gno.land dApps. **Listing fee:** %d ugnot → treasury.\n\n", registrationFee)
19 if paused {
20 sb += "> ⏸️ Registration is paused.\n\n"
21 }
22
23 rows := 0
24 live := 0
25 sb += "| App | Package | Category |\n|---|---|---|\n"
26 listings.Iterate("", "", func(_ string, v any) bool {
27 l := v.(*Listing)
28 // Curated gnoweb view: live (Verified) + un-flag-hidden only — never pending/rejected.
29 if l.Status != StatusLive || l.FlagCount >= FlagHideThreshold {
30 return false
31 }
32 live++
33 if rows >= RenderMaxRows {
34 return false // stop appending, keep counting via the header below
35 }
36 rows++
37 sb += ufmt.Sprintf("| %s | `%s` | %s |\n", esc(l.Name), esc(l.PkgPath), esc(l.Category))
38 return false
39 })
40
41 if live == 0 {
42 sb += "\n*No live apps yet.*\n"
43 } else {
44 sb += ufmt.Sprintf("\n*%d live app(s)%s. Block %d.*\n", live, moreNote(live), runtime.ChainHeight())
45 }
46 return sb
47}
48
49func moreNote(live int) string {
50 if live > RenderMaxRows {
51 return ufmt.Sprintf(" (showing first %d)", RenderMaxRows)
52 }
53 return ""
54}
55
56// esc neutralizes the markdown/table metachars that could break the table or inject
57// layout from an attacker-supplied listing field.
58func esc(s string) string {
59 out := ""
60 for _, r := range s {
61 switch r {
62 case '|':
63 out += "\\|"
64 case '\n', '\r':
65 out += " "
66 case '`':
67 out += "'"
68 default:
69 out += string(r)
70 }
71 }
72 return out
73}