render.gno
1.71 Kb · 72 lines
1package memba_appstore_v2
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 if !isVisible(l) {
29 return false
30 }
31 live++
32 if rows >= RenderMaxRows {
33 return false // stop appending, keep counting via the header below
34 }
35 rows++
36 sb += ufmt.Sprintf("| %s | `%s` | %s |\n", esc(l.Name), esc(l.PkgPath), esc(l.Category))
37 return false
38 })
39
40 if live == 0 {
41 sb += "\n*No live apps yet.*\n"
42 } else {
43 sb += ufmt.Sprintf("\n*%d live app(s)%s. Block %d.*\n", live, moreNote(live), runtime.ChainHeight())
44 }
45 return sb
46}
47
48func moreNote(live int) string {
49 if live > RenderMaxRows {
50 return ufmt.Sprintf(" (showing first %d)", RenderMaxRows)
51 }
52 return ""
53}
54
55// esc neutralizes the markdown/table metachars that could break the table or inject
56// layout from an attacker-supplied listing field.
57func esc(s string) string {
58 out := ""
59 for _, r := range s {
60 switch r {
61 case '|':
62 out += "\\|"
63 case '\n', '\r':
64 out += " "
65 case '`':
66 out += "'"
67 default:
68 out += string(r)
69 }
70 }
71 return out
72}