1package threads23import (4 "strconv"5 "strings"67 "chain/runtime"89 "gno.land/p/moul/kit/store/v0"10 "gno.land/p/moul/kit/ui/v0"11 "gno.land/p/moul/md/v0"12 "gno.land/p/moul/realmpath/v0"13 th "gno.land/p/moul/x/social/threads/v0"14)1516// Render routes three views: the realm's own index, one page's threads, and17// one thread.18//19// A page key contains slashes, so "page/<key>" is split on the prefix rather20// than by path element: realmpath gives the parts, and rejoining them would21// only be a way to get it wrong.22func Render(path string) string {23 req := realmpath.Parse(path)2425 switch {26 case strings.HasPrefix(req.Path, "page/"):27 return renderPage(strings.TrimPrefix(req.Path, "page/"))28 case strings.HasPrefix(req.Path, "thread/"):29 return renderThread(strings.TrimPrefix(req.Path, "thread/"))30 case req.Path == "":31 return renderIndex()32 default:33 return md.H1("Not found") + "No such view: " + ui.Inline(req.Path)34 }35}3637func renderIndex() string {38 out := md.H1("Threads")39 out += "A discussion block any realm can embed, keyed on the page it appears on.\n\n"4041 t := ui.NewTable("threads", "pages", "THREAD supply")42 t.Row(strconv.Itoa(board.Len()), strconv.Itoa(board.Pages()), strconv.FormatInt(points.Supply(), 10))43 out += t.String()4445 out += md.H2("Recent, across every page")46 out += listing(board.Recent(20), true)4748 out += md.H2("The token")49 out += points.Render()5051 out += md.H2("Embedding it")52 out += "Two lines in the host realm, and it stores nothing:\n\n"53 out += md.LanguageCodeBlock("go", `import "`+realmPath+`"5455func Render(path string) string {56 return body + threads.RenderBlock()57}`)58 out += "The page key is the host realm's own path, read off the call. " +59 md.InlineCode("RenderBlockFor(page)") + " names one explicitly.\n"6061 return out62}6364func renderPage(page string) string {65 if !th.ValidPage(page) {66 return md.H1("Threads") + "Not a page key: " + ui.Inline(page)67 }68 out := md.H1("Threads on " + ui.Inline(page))69 out += md.Link("the page itself", th.RealmURL(page)) + "\n\n"70 out += listing(board.List(page, runtime.ChainHeight(), 0), false)71 out += "\n" + ui.Action("💬 Post here", "Post", "page", page, "body", "") + "\n"72 return out73}7475func renderThread(raw string) string {76 id, ok := store.ParseID(raw)77 if !ok {78 return md.H1("Threads") + "Not a thread id: " + ui.Inline(raw)79 }80 t, found := board.Get(id)81 if !found {82 return md.H1("Threads") + "No thread #" + id.String()83 }8485 out := md.H1("Thread #" + id.String())86 out += md.Blockquote(ui.Inline(t.Body)) + "\n"87 out += md.Italic("by "+ui.Addr(t.Author)+" · on "+88 md.Link(t.Page, th.PageURL(realmPath, t.Page))+89 " · "+strconv.Itoa(t.Repliers())+" earned") + "\n\n"9091 if t.PinnedUntil > runtime.ChainHeight() {92 out += "📌 pinned until block " + strconv.FormatInt(t.PinnedUntil, 10) + "\n\n"93 }9495 out += md.H2("Replies")96 if len(t.Replies) == 0 {97 out += ui.Empty("Nobody has replied yet.")98 } else {99 r := ui.NewTable("by", "said", "block")100 for _, rep := range t.Replies {101 r.Row(ui.Addr(rep.Author), ui.Cell(rep.Body), strconv.FormatInt(rep.At, 10))102 }103 out += r.String()104 }105106 out += "\n" + ui.Action("💬 Reply", "Reply", "threadID", id.String(), "body", "") +107 " · " + ui.Action("📌 Boost", "Boost", "threadID", id.String()) + "\n"108 return out109}110111// listing renders a set of threads. withPage adds the page column, which the112// index needs and a page view would only repeat.113func listing(items []th.Listing, withPage bool) string {114 if len(items) == 0 {115 return ui.Empty("No threads yet.")116 }117 headers := []string{"", "#", "thread", "by", "replies"}118 if withPage {119 headers = append(headers, "page")120 }121 t := ui.NewTable(headers...)122 now := runtime.ChainHeight()123 for _, it := range items {124 mark := ""125 if it.Thread.Pinned(now) {126 mark = "📌"127 }128 cells := []string{129 mark,130 // The link title is the id, never the body: md.Link escapes131 // markdown but not a pipe, and a pipe in a table cell opens a132 // column. ui.Cell is the only thing that rewrites it.133 md.Link("#"+it.ID.String(), th.ThreadURL(realmPath, it.ID)),134 ui.Cell(ui.ShortN(it.Thread.Body, th.ExcerptLen, 0)),135 ui.Addr(it.Thread.Author),136 strconv.Itoa(len(it.Thread.Replies)),137 }138 if withPage {139 cells = append(cells, ui.Cell(it.Thread.Page))140 }141 t.Row(cells...)142 }143 return t.String()144}145Allowance(owner string, spender string) int64
Approve(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}, spender string, amount int64)
BalanceOf(owner string) int64
Boost(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}, threadID int64)
Count(page string) int
Pages() int
PinnedUntil(threadID int64) int64
Post(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}, page string, body string) int64
Render(path string) string
RenderBlock() string
RenderBlockFor(page string) string
Repliers(threadID int64) int
Reply(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}, threadID int64, body string)
TokenKey() string
Total() int
TotalSupply() int64
Transfer(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}, to string, amount int64)
TransferFrom(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}, from string, to string, amount int64)
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.