Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

render.gno

4.71 Kb · 181 lines
  1package home
  2
  3import (
  4	"strconv"
  5	"strings"
  6
  7	"chain/runtime"
  8)
  9
 10const (
 11	realmPath = "gno.land/r/g1747t5m2f08plqjlrjk2q0qld7465hxz8gkx59c/home"
 12	webPath   = "/r/g1747t5m2f08plqjlrjk2q0qld7465hxz8gkx59c/home"
 13)
 14
 15// fallbackLayout renders when the layout slot is deleted.
 16const fallbackLayout = "# zôÖma\n\n:bio:\n\n:hearts:\n"
 17
 18// Render serves:
 19//
 20//	""             the profile page
 21//	"slots"        the slot index
 22//	"slots/<slug>" one slot's raw markdown
 23//	"hearts"       everyone who sent a <3
 24func Render(path string) string {
 25	switch {
 26	case path == "":
 27		return renderPage()
 28	case path == "slots":
 29		return renderIndex()
 30	case strings.HasPrefix(path, "slots/"):
 31		return renderSlot(strings.TrimPrefix(path, "slots/"))
 32	case path == "hearts":
 33		return renderHearts()
 34	}
 35	return "# Not found\n\nNo such path: " + strconv.Quote(path) + "\n\n[Back home](" + webPath + ")\n"
 36}
 37
 38func renderPage() string {
 39	layout := Get(layoutSlug)
 40	if layout == "" {
 41		layout = fallbackLayout
 42	}
 43	return fill(layout)
 44}
 45
 46// fill replaces every :slug: in s, in one left-to-right pass. Replacement text
 47// is never rescanned, so a slot cannot expand into another slot. A placeholder
 48// with no value stays verbatim: a missing section should be visible.
 49func fill(s string) string {
 50	var b strings.Builder
 51	for {
 52		i := strings.IndexByte(s, ':')
 53		if i < 0 {
 54			b.WriteString(s)
 55			return b.String()
 56		}
 57		b.WriteString(s[:i])
 58		s = s[i:]
 59		j := strings.IndexByte(s[1:], ':')
 60		if j < 0 {
 61			b.WriteString(s)
 62			return b.String()
 63		}
 64		slug := s[1 : j+1]
 65		if v, ok := lookup(slug); ok {
 66			b.WriteString(v)
 67			s = s[j+2:]
 68			continue
 69		}
 70		// Not a placeholder: emit the first ':' and rescan from the next one,
 71		// so "a:b :bio:" still finds :bio:.
 72		b.WriteString(":")
 73		s = s[1:]
 74	}
 75}
 76
 77func lookup(slug string) (string, bool) {
 78	if !validSlug(slug) {
 79		return "", false
 80	}
 81	switch slug {
 82	case "chainid":
 83		return runtime.ChainID(), true
 84	case "height":
 85		return strconv.FormatInt(runtime.ChainHeight(), 10), true
 86	case "hearts":
 87		return heartsBlock(), true
 88	case "hero":
 89		return heroImage(), true
 90	case "realm":
 91		return realmPath, true
 92	case "rev":
 93		return strconv.Itoa(rev), true
 94	}
 95	if strings.HasPrefix(slug, galleryPrefix) {
 96		return renderGallery(strings.TrimPrefix(slug, galleryPrefix))
 97	}
 98	if v := slots.Get(slug); v != nil {
 99		return v.(*slot).body, true
100	}
101	return "", false
102}
103
104func heartsBlock() string {
105	n := hearts.Size()
106	var b strings.Builder
107	b.WriteString("**")
108	b.WriteString(strconv.Itoa(n))
109	if n == 1 {
110		b.WriteString("** heart so far.")
111	} else {
112		b.WriteString("** hearts so far.")
113	}
114	b.WriteString(" [Send yours](" + webPath + "$help&func=Heart): one transaction, one per address, forever on chain.")
115	if len(recent) > 0 {
116		b.WriteString("\n\nLatest: ")
117		for i, a := range recent {
118			if i > 0 {
119				b.WriteString(" · ")
120			}
121			b.WriteString(userLink(a))
122		}
123		b.WriteString(" · [all](" + webPath + ":hearts)")
124	}
125	return b.String()
126}
127
128// userLink renders an address as a short link to its gno.land profile.
129func userLink(a address) string {
130	s := a.String()
131	short := s
132	if len(s) > 12 {
133		short = s[:7] + "…" + s[len(s)-4:]
134	}
135	return "[" + short + "](/u/" + s + ")"
136}
137
138func renderHearts() string {
139	var b strings.Builder
140	b.WriteString("# <3\n\n")
141	b.WriteString(strconv.Itoa(hearts.Size()) + " address(es) sent a heart. [Send yours](" + webPath + "$help&func=Heart).\n\n")
142	if hearts.Size() == 0 {
143		b.WriteString("_Be the first._\n")
144		return b.String()
145	}
146	b.WriteString("| from | block |\n| --- | ---: |\n")
147	hearts.Iterate("", "", func(key string, value any) bool {
148		b.WriteString("| " + userLink(address(key)) + " | " + strconv.FormatInt(value.(int64), 10) + " |\n")
149		return false
150	})
151	b.WriteString("\n[Back home](" + webPath + ")\n")
152	return b.String()
153}
154
155func renderIndex() string {
156	var b strings.Builder
157	b.WriteString("# Slots\n\nrev " + strconv.Itoa(rev) + " · " + strconv.Itoa(slots.Size()) + " slot(s)\n\n")
158	b.WriteString("| slot | bytes | rev | height |\n| --- | ---: | ---: | ---: |\n")
159	slots.Iterate("", "", func(key string, value any) bool {
160		s := value.(*slot)
161		b.WriteString("| [" + key + "](" + webPath + ":slots/" + key + ") | " +
162			strconv.Itoa(len(s.body)) + " | " + strconv.Itoa(s.rev) + " | " +
163			strconv.FormatInt(s.updated, 10) + " |\n")
164		return false
165	})
166	return b.String()
167}
168
169func renderSlot(slug string) string {
170	v := slots.Get(slug)
171	if v == nil {
172		return "# Not found\n\nNo slot named " + strconv.Quote(slug) + ".\n\n[Slot index](" + webPath + ":slots)\n"
173	}
174	s := v.(*slot)
175	body := s.body
176	if !strings.HasSuffix(body, "\n") {
177		body += "\n"
178	}
179	return "# " + slug + "\n\n" + strconv.Itoa(len(s.body)) + " bytes · rev " + strconv.Itoa(s.rev) +
180		" · written at height " + strconv.FormatInt(s.updated, 10) + "\n\n````\n" + body + "````\n"
181}