Search Apps Documentation Source Content File Folder Download Copy Actions Download

render.gno

1.77 Kb · 59 lines
 1package memba_points_v1
 2
 3import (
 4	"strings"
 5
 6	"gno.land/p/nt/ufmt/v0"
 7)
 8
 9// Render — human gnoweb view. "" → home (totals + owner + awarders); "a/<addr>" → one balance.
10func Render(path string) string {
11	if path == "" {
12		return ufmt.Sprintf(
13			"# Memba Points (MP)\n\nSoulbound, non-transferable reputation points.\n\n"+
14				"- Holders: **%d**\n- Total points: **%d**\n- Paused: **%t**\n- Owner: `%s`\n- Awarders: %s\n",
15			holderCount, totalPoints, paused, owner.String(), renderAwarders())
16	}
17	if strings.HasPrefix(path, "a/") {
18		addr := strings.TrimPrefix(path, "a/")
19		pts := GetPoints(addr) // raw addr for the lookup; the displayed value is sanitized
20		return ufmt.Sprintf("# Points for `%s`\n\n**%d** MP · Tier: %s\n",
21			mdInlineEscape(addr), pts, mdInlineEscape(tierFor(pts)))
22	}
23	return "# 404\n"
24}
25
26// mdInlineEscape drops markdown-significant characters from path-derived / untrusted values before
27// writing them into Render output, preventing markdown/code-span injection (gno contract-review #10).
28// Well-formed bech32 addresses and tier names contain none of these, so this is loss-free in practice.
29func mdInlineEscape(s string) string {
30	var sb strings.Builder
31	for _, r := range s {
32		switch r {
33		case '`', '|', '<', '>', '\n', '\r', '\t', '*', '_', '[', ']', '#':
34			// drop markdown-significant runes
35		default:
36			sb.WriteString(string(r))
37		}
38	}
39	return sb.String()
40}
41
42func renderAwarders() string {
43	var sb strings.Builder
44	first := true
45	awarders.Iterate("", "", func(k string, _ any) bool {
46		if !first {
47			sb.WriteString(", ")
48		}
49		first = false
50		sb.WriteString("`")
51		sb.WriteString(mdInlineEscape(k)) // awarder pkgpath is owner-set but still escaped (checklist #10)
52		sb.WriteString("`")
53		return false
54	})
55	if first {
56		return "_none_"
57	}
58	return sb.String()
59}