1package gnocialview23import (4 "strconv"5 "strings"67 gnocial "gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/gnocial"8)910const pageSize = 81112// Sheet is the stable text board the website reads.13// kind selects the page. a, b, and c are cursors or decimal limits.14// Gated bodies are left empty. The fund realm still stores them.15func Sheet(kind, a, b, c string) string {16 var body string17 switch kind {18 case "feed":19 body = sheetPosts(gnocial.Feed(atoiOpt(a), atoiLimit(b)), false)20 case "author":21 p, ok := gnocial.ProfileByHandle(a)22 if !ok {23 panic("unknown handle")24 }25 body = sheetPosts(gnocial.PostsBy(p.Addr, atoiOpt(b), atoiLimit(c)), false)26 case "replies":27 body = sheetPosts(gnocial.Replies(atoiReq(a), atoiOpt(b), atoiLimit(c)), true)28 case "post":29 body = sheetOne(atoiReq(a))30 case "people":31 body = sheetProfiles(gnocial.Handles(a, atoiLimit(b)))32 case "profile":33 body = sheetProfile(a)34 case "account":35 body = sheetAccount(a)36 case "hot":37 body = sheetPosts(gnocial.Hot(atoiLimit(a)), false)38 case "followfeed":39 body = sheetFollowFeed(a, atoiLimit(b))40 case "graph":41 body = sheetProfiles(gnocial.FollowingList(mustAddr(a), optAddr(b), atoiLimit(c)))42 case "fans":43 body = sheetProfiles(gnocial.FollowerList(mustAddr(a), optAddr(b), atoiLimit(c)))44 case "holdings":45 body = sheetHoldings(gnocial.Holdings(mustAddr(a), optAddr(b), atoiLimit(c)))46 case "meta":47 body = sheetMeta()48 case "quotebuy":49 body = quoteLine(true, a, b, c)50 case "quotesell":51 body = quoteLine(false, a, b, c)52 case "shares":53 body = strconv.Itoa(gnocial.SharesOf(mustAddr(a), b))54 default:55 panic("kind")56 }57 return "v1\n" + body58}5960func sheetPosts(posts []gnocial.Article, full bool) string {61 var b strings.Builder62 for i, p := range posts {63 if i > 0 {64 b.WriteString("\n")65 }66 if full {67 b.WriteString(postLine(p))68 } else {69 b.WriteString(cardLine(p))70 }71 }72 return b.String()73}7475func sheetOne(id int) string {76 p, ok := gnocial.GetPost(id)77 if !ok {78 return ""79 }80 return postLine(p)81}8283func sheetProfile(handle string) string {84 p, ok := gnocial.ProfileByHandle(handle)85 if !ok {86 return ""87 }88 return profileLine(p)89}9091func sheetAccount(raw string) string {92 who := optAddr(raw)93 if !who.IsValid() {94 return ""95 }96 p, ok := gnocial.ProfileOf(who)97 if !ok {98 return ""99 }100 return profileLine(p)101}102103func sheetProfiles(rows []gnocial.Profile) string {104 var b strings.Builder105 for i, p := range rows {106 if i > 0 {107 b.WriteString("\n")108 }109 b.WriteString(p.Handle + "\t" + esc(p.Name) + "\t" + strconv.Itoa(p.Followers) + "\t" + strconv.Itoa(p.Posts))110 }111 return b.String()112}113114func sheetHoldings(rows []gnocial.Holding) string {115 var b strings.Builder116 for i, h := range rows {117 if i > 0 {118 b.WriteString("\n")119 }120 b.WriteString(h.Creator.String() + "\t" + esc(h.Handle) + "\t" + strconv.Itoa(h.Amount))121 }122 return b.String()123}124125func sheetFollowFeed(raw string, limit int) string {126 who := mustAddr(raw)127 posts := gnocial.Feed(0, limit)128 var b strings.Builder129 n := 0130 for _, p := range posts {131 prof, ok := gnocial.ProfileOf(p.Author)132 if !ok || !gnocial.Following(who, prof.Handle) {133 continue134 }135 if n > 0 {136 b.WriteString("\n")137 }138 b.WriteString(cardLine(p))139 n++140 }141 return b.String()142}143144func sheetMeta() string {145 paused := "0"146 if gnocial.Paused() {147 paused = "1"148 }149 return strings.Join([]string{150 gnocial.Version(),151 gnocial.Successor(),152 paused,153 strconv.FormatInt(gnocial.TotalReserve(), 10),154 gnocial.Admin().String(),155 gnocial.Treasury().String(),156 strconv.Itoa(gnocial.MemberCount()),157 strconv.Itoa(gnocial.FeedCount()),158 strconv.Itoa(gnocial.LatestID()),159 }, "\t")160}161162func profileLine(p gnocial.Profile) string {163 supply, reserve, holders := gnocial.SupplyOf(p.Handle)164 next := "-1"165 if supply < gnocial.MaxSupply {166 due, _, _, _, _ := gnocial.QuoteBuy(p.Handle, 1)167 next = strconv.FormatInt(due, 10)168 }169 ref := ""170 if p.Referrer.IsValid() {171 ref = p.Referrer.String()172 }173 return strings.Join([]string{174 p.Handle,175 esc(p.Name),176 esc(p.Bio),177 p.Addr.String(),178 ref,179 strconv.Itoa(p.Posts),180 strconv.Itoa(p.Followers),181 strconv.Itoa(p.Following),182 strconv.FormatInt(p.TipsIn, 10),183 strconv.FormatInt(p.TipsOut, 10),184 strconv.Itoa(supply),185 strconv.FormatInt(reserve, 10),186 strconv.Itoa(holders),187 next,188 strconv.FormatInt(p.CreatorFee, 10),189 }, "\t")190}191192// quoteLine prices a trade. c is an optional buyer or seller address.193// Empty c is the anonymous quote. The first number is the amount due or the proceeds.194func quoteLine(buy bool, handle, amount, trader string) string {195 n := atoiReq(amount)196 var head, curve, cf, pf, rf int64197 trader = strings.TrimSpace(trader)198 if buy {199 if trader == "" {200 head, curve, cf, pf, rf = gnocial.QuoteBuy(handle, n)201 } else {202 head, curve, cf, pf, rf = gnocial.QuoteBuyFor(handle, trader, n)203 }204 } else if trader == "" {205 head, curve, cf, pf, rf = gnocial.QuoteSell(handle, n)206 } else {207 head, curve, cf, pf, rf = gnocial.QuoteSellFor(handle, trader, n)208 }209 return join5(head, curve, cf, pf, rf)210}211212func cardLine(p gnocial.Article) string {213 h, name := namesOf(p.Author)214 return strings.Join([]string{215 strconv.Itoa(p.ID),216 esc(h),217 esc(name),218 strconv.Itoa(p.Likes),219 strconv.Itoa(p.Replies),220 strconv.FormatInt(p.Tips, 10),221 bit(p.Gated),222 esc(openPreview(p)),223 }, "\t")224}225226func postLine(p gnocial.Article) string {227 h, name := namesOf(p.Author)228 body := ""229 if !p.Gated && !p.Deleted {230 body = p.Body231 }232 return strings.Join([]string{233 strconv.Itoa(p.ID),234 p.Author.String(),235 esc(h),236 esc(name),237 strconv.Itoa(p.ReplyTo),238 strconv.Itoa(p.Likes),239 strconv.Itoa(p.Replies),240 strconv.FormatInt(p.Tips, 10),241 bit(p.Gated),242 bit(p.Deleted),243 strconv.FormatInt(p.Height, 10),244 esc(body),245 }, "\t")246}247248func namesOf(a address) (handle, name string) {249 p, ok := gnocial.ProfileOf(a)250 if !ok {251 return a.String(), a.String()252 }253 return p.Handle, p.Name254}255256func openPreview(p gnocial.Article) string {257 if p.Gated || p.Deleted {258 return ""259 }260 return preview(strings.ReplaceAll(p.Body, "\n", " "), 160)261}262263func bit(v bool) string {264 if v {265 return "1"266 }267 return "0"268}269270func esc(s string) string {271 s = strings.ReplaceAll(s, "\\", "\\\\")272 s = strings.ReplaceAll(s, "\r", "")273 s = strings.ReplaceAll(s, "\n", "\\n")274 s = strings.ReplaceAll(s, "\t", "\\t")275 return s276}277278func join5(a, b, c, d, e int64) string {279 return strconv.FormatInt(a, 10) + "\t" + strconv.FormatInt(b, 10) + "\t" + strconv.FormatInt(c, 10) + "\t" + strconv.FormatInt(d, 10) + "\t" + strconv.FormatInt(e, 10)280}281282func atoiOpt(s string) int {283 s = strings.TrimSpace(s)284 if s == "" {285 return 0286 }287 n, err := strconv.Atoi(s)288 if err != nil || n < 0 {289 panic("cursor")290 }291 return n292}293294func atoiReq(s string) int {295 n := atoiOpt(s)296 if n < 1 {297 panic("id")298 }299 return n300}301302func atoiLimit(s string) int {303 s = strings.TrimSpace(s)304 if s == "" {305 return pageSize306 }307 n, err := strconv.Atoi(s)308 if err != nil {309 panic("limit")310 }311 if n < 1 || n > 20 {312 panic("limit")313 }314 return n315}316317func mustAddr(s string) address {318 a := optAddr(s)319 if !a.IsValid() {320 panic("bad address")321 }322 return a323}324325func optAddr(s string) address {326 s = strings.TrimSpace(s)327 if s == "" {328 return address("")329 }330 if len(s) > 60 {331 panic("bad address")332 }333 return address(s)334}335336func preview(s string, n int) string {337 if len(s) <= n {338 return s339 }340 i := n341 for i > 0 && s[i]&0xC0 == 0x80 {342 i--343 }344 return s[:i] + "…"345}346Render(path string) string
Sheet(kind string, a string, b string, c string) 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.