render.gno
4.21 Kb Β· 144 lines
1package threads
2
3import (
4 "strconv"
5 "strings"
6
7 "chain/runtime"
8
9 "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)
15
16// Render routes three views: the realm's own index, one page's threads, and
17// one thread.
18//
19// A page key contains slashes, so "page/<key>" is split on the prefix rather
20// than by path element: realmpath gives the parts, and rejoining them would
21// only be a way to get it wrong.
22func Render(path string) string {
23 req := realmpath.Parse(path)
24
25 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}
36
37func 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"
40
41 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()
44
45 out += md.H2("Recent, across every page")
46 out += listing(board.Recent(20), true)
47
48 out += md.H2("The token")
49 out += points.Render()
50
51 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+`"
54
55func 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"
60
61 return out
62}
63
64func 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 out
73}
74
75func 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 }
84
85 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"
90
91 if t.PinnedUntil > runtime.ChainHeight() {
92 out += "π pinned until block " + strconv.FormatInt(t.PinnedUntil, 10) + "\n\n"
93 }
94
95 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 }
105
106 out += "\n" + ui.Action("π¬ Reply", "Reply", "threadID", id.String(), "body", "") +
107 " Β· " + ui.Action("π Boost", "Boost", "threadID", id.String()) + "\n"
108 return out
109}
110
111// listing renders a set of threads. withPage adds the page column, which the
112// 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 escapes
131 // markdown but not a pipe, and a pipe in a table cell opens a
132 // 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}