1package linktree23import (4 "strconv"5 "strings"67 "chain"8 "chain/runtime/unsafe"910 "gno.land/p/nt/avl/v0"11)1213// link is a single labeled URL on a profile.14type link struct {15 Label string16 URL string17}1819// profile is one address's personal links page.20type profile struct {21 Bio string22 Links []link23}2425// profiles maps an address (string key) to its *profile.26// avl.Tree keeps iteration deterministic for Render.27var profiles avl.Tree2829// 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 = bio34 chain.Emit("BioSet", "addr", addr.String())35}3637// 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}5051// 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}6667// 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 p75}7677// 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}8586func renderIndex() string {87 var b strings.Builder88 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 false101 })102 return b.String()103}104105func renderProfile(addr string) string {106 var b strings.Builder107 b.WriteString("# ")108 b.WriteString(addr)109 b.WriteString("\n\n")110111 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)117118 if p.Bio != "" {119 b.WriteString(p.Bio)120 b.WriteString("\n\n")121 }122123 if len(p.Links) == 0 {124 b.WriteString("_No links yet._\n")125 return b.String()126 }127128 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}138AddLink(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, label string, url string)
RemoveLink(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, index int)
Render(path string) string
SetBio(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, bio string)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.