// Package home is David G.'s profile page on gno.land. // // gnoweb renders a profile by calling Render("") on the realm at exactly // /r/
/home, so this path carries no /vN suffix. // // The page is a list of sections, each a markdown body the owner edits with // one Set transaction, so changing the text never needs a redeploy. The slot // named "order" lists the sections to show. Six sections are drawn rather // than stored: "hero", the page banner, "banner" and "reviews", the activity // charts, "areachart", the ring of pull requests by area, "game", today's // Gnordle, and "hearts", the heart count and wall. package home import ( "strconv" "strings" "chain/runtime" "gno.land/p/nt/avl/v0" ) // admin is the only address allowed to edit sections. const admin = address("g1rayfgklwl0aspz488wvrcrvt7t2quy6q06lgk2") const ( home = "/r/g1rayfgklwl0aspz488wvrcrvt7t2quy6q06lgk2/home" orderSlot = "order" maxNameLen = 32 // maxPathLen is far above any link the page writes, and turns a long // crafted link away before the query parser, whose cost grows with it. maxPathLen = 256 ) var ( slots = avl.NewTree() // section name -> markdown body hearts = avl.NewTree() // address -> block height of the heart ) func init() { for _, s := range defaultSlots { slots.Set(s.name, s.body) } } // Set writes one section of the page. Only the admin can call it. // // name is the section to write and body is its markdown. The text sections are // "header", the bio at the top, then "gno", "projects", "peerdev" and // "unrelated"; any other name creates a new section. // // name "order" decides which sections show and in which order. Its body lists // every section name, comma separated, with no spaces. A name left out of the // list is hidden, so copy the current list, from Get("order"), and edit it. // // name "herotext" is the banner's text: "key value" pairs split by ";", with // the keys kicker, the small line on top, title, subtitle, tagline, footline, // the small line at the bottom, and alt, the banner's description for screen // readers. A key left out keeps its current default. // // "hero", "banner", "reviews", "areachart", "game" and "hearts" are drawn by // the page itself: they cannot be set, only placed through "order". // // Examples, as the two fields of the form: // // name: header // body: I'm a developer engineer at [Samouraï Coop](https://www.samourai.world/). // // name: herotext // body: title David G.; subtitle Developer engineer at Samouraï Coop; footline Open for contribution // // name: talks // body: ## Talks // // name: order // body: hero,header,activityhead,banner,reviews,gno,areachart,projects,peerdev,unrelated,talks,game,hearts func Set(cur realm, name, body string) { assertAdmin(cur) if name == "" || len(name) > maxNameLen || strings.ContainsAny(name, ", \n") { panic("invalid section name") } if drawnSections[name] { panic("reserved section: " + name) } slots.Set(name, body) } // SetActivity refreshes the three charts in one transaction. Only the admin // can call it. Its three arguments are the bodies of the "activity", // "reviewactivity" and "areas" sections, as scripts/activity.py prints them; // an empty argument leaves that chart as it is. func SetActivity(cur realm, activity, reviews, areas string) { assertAdmin(cur) for _, s := range []struct{ name, body string }{ {activitySlot, activity}, {reviewActivitySlot, reviews}, {areasSlot, areas}, } { if s.body != "" { slots.Set(s.name, s.body) } } } // Delete removes one section. Only the admin can call it. // // name is the section to remove, "talks" for example. Its name can stay in // "order": a name with no section shows nothing. func Delete(cur realm, name string) { assertAdmin(cur) if _, ok := slots.Remove(name); !ok { panic("no such section: " + name) } } // Get returns a section's markdown, or "" when it does not exist. // Get("order") returns the current section list, the one to edit before // setting "order". func Get(name string) string { if v := slots.Get(name); v != nil { return v.(string) } return "" } // Heart records one heart from the caller. Each address can send exactly one. func Heart(cur realm) { from := cur.Previous().Address().String() if hearts.Has(from) { panic("already sent: one heart per address") } hearts.Set(from, runtime.ChainHeight()) } // Hearts returns how many distinct addresses sent a heart. func Hearts() int { return hearts.Size() } // HasHeart reports whether addr already sent a heart. func HasHeart(addr address) bool { return hearts.Has(addr.String()) } func assertAdmin(cur realm) { if cur.Previous().Address() != admin { panic("unauthorized") } } // Render serves: // // "" the profile page // "wordle/..." the Gnordle game // "hearts[/