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

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 := profiles.Get(key)
 56	if v == nil {
 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 := profiles.Get(key); v != nil {
 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 := profiles.Get(addr)
112	if v == nil {
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}