Search Apps Documentation Source Content File Folder Download Copy Actions Download

linktree.gno

3.06 Kb · 137 lines
  1package linktree
  2
  3import (
  4	"strconv"
  5	"strings"
  6
  7	"chain"
  8	"chain/runtime/unsafe"
  9
 10	"gno.land/p/nt/avl/v0"
 11)
 12
 13// link is a single labeled URL on a profile.
 14type link struct {
 15	Label string
 16	URL   string
 17}
 18
 19// profile is one address's personal links page.
 20type profile struct {
 21	Bio   string
 22	Links []link
 23}
 24
 25// profiles maps an address (string key) to its *profile.
 26// avl.Tree keeps iteration deterministic for Render.
 27var profiles avl.Tree
 28
 29// SetBio sets (or replaces) the calling address's bio.
 30func SetBio(cur realm, bio string) {
 31	addr := unsafe.PreviousRealm().Address()
 32	p := getOrCreate(addr.String())
 33	p.Bio = bio
 34	chain.Emit("BioSet", "addr", addr.String())
 35}
 36
 37// AddLink appends a labeled URL to the calling address's profile.
 38func AddLink(cur realm, label string, url string) {
 39	if label == "" {
 40		panic("label must not be empty")
 41	}
 42	if url == "" {
 43		panic("url must not be empty")
 44	}
 45	addr := unsafe.PreviousRealm().Address()
 46	p := getOrCreate(addr.String())
 47	p.Links = append(p.Links, link{Label: label, URL: url})
 48	chain.Emit("LinkAdded", "addr", addr.String(), "label", label)
 49}
 50
 51// RemoveLink deletes the link at index from the calling address's profile.
 52func RemoveLink(cur realm, index int) {
 53	addr := unsafe.PreviousRealm().Address()
 54	key := addr.String()
 55	v, ok := profiles.Get(key)
 56	if !ok {
 57		panic("no profile for caller")
 58	}
 59	p := v.(*profile)
 60	if index < 0 || index >= len(p.Links) {
 61		panic("index out of range")
 62	}
 63	p.Links = append(p.Links[:index], p.Links[index+1:]...)
 64	chain.Emit("LinkRemoved", "addr", key, "index", strconv.Itoa(index))
 65}
 66
 67// getOrCreate returns the profile for key, creating an empty one if absent.
 68func getOrCreate(key string) *profile {
 69	if v, ok := profiles.Get(key); ok {
 70		return v.(*profile)
 71	}
 72	p := &profile{}
 73	profiles.Set(key, p)
 74	return p
 75}
 76
 77// Render shows all profiles at root, or a single profile at "/<address>".
 78func Render(path string) string {
 79	addr := strings.Trim(path, "/")
 80	if addr == "" {
 81		return renderIndex()
 82	}
 83	return renderProfile(addr)
 84}
 85
 86func renderIndex() string {
 87	var b strings.Builder
 88	b.WriteString("# Linktree\n\n")
 89	if profiles.Size() == 0 {
 90		b.WriteString("_No profiles yet. Call `SetBio` or `AddLink` to create one._\n")
 91		return b.String()
 92	}
 93	b.WriteString("Profiles:\n\n")
 94	profiles.Iterate("", "", func(key string, _ interface{}) bool {
 95		b.WriteString("- [")
 96		b.WriteString(key)
 97		b.WriteString("](/r/REPLACE_ADDR/linktree:")
 98		b.WriteString(key)
 99		b.WriteString(")\n")
100		return false
101	})
102	return b.String()
103}
104
105func renderProfile(addr string) string {
106	var b strings.Builder
107	b.WriteString("# ")
108	b.WriteString(addr)
109	b.WriteString("\n\n")
110
111	v, ok := profiles.Get(addr)
112	if !ok {
113		b.WriteString("_No profile for this address._\n")
114		return b.String()
115	}
116	p := v.(*profile)
117
118	if p.Bio != "" {
119		b.WriteString(p.Bio)
120		b.WriteString("\n\n")
121	}
122
123	if len(p.Links) == 0 {
124		b.WriteString("_No links yet._\n")
125		return b.String()
126	}
127
128	b.WriteString("## Links\n\n")
129	for _, l := range p.Links {
130		b.WriteString("- [")
131		b.WriteString(l.Label)
132		b.WriteString("](")
133		b.WriteString(l.URL)
134		b.WriteString(")\n")
135	}
136	return b.String()
137}