Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}