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

3.58 Kb · 128 lines
  1package kitindexdemo
  2
  3import (
  4	"strconv"
  5
  6	"gno.land/p/moul/kit/store/v0"
  7	"gno.land/p/moul/kit/ui/v0"
  8	"gno.land/p/moul/md/v0"
  9	"gno.land/p/moul/realmpath/v0"
 10)
 11
 12// realmURL is this realm as gnoweb serves it. Links are absolute because the
 13// path ends in /v0: a relative "./kitindexdemo:tag/x" resolves against the
 14// parent directory to .../kitindexdemo/kitindexdemo:tag/x, which does not
 15// exist. Not derived at runtime: a Render has no cur, and CurrentRealm there
 16// is the caller. TestRealmURLMatchesTheModule pins it to the module line.
 17const realmURL = "/r/moul/x/kitindexdemo/v0"
 18
 19// Render is the whole public surface. Three pages: the tag list, one tag, one
 20// author.
 21//
 22// Every row goes through ui.NewTable rather than concatenated pipes, and every
 23// caller string through ui.Cell, because a public path is immutable and a
 24// Render that ships wrong ships forever.
 25func Render(path string) string {
 26	req := realmpath.Parse(path)
 27	switch req.PathPart(0) {
 28	case "":
 29		return renderIndex(req)
 30	case "tag":
 31		return renderTag(req.PathPart(1))
 32	case "author":
 33		return renderAuthor(req.PathPart(1))
 34	default:
 35		return md.H1("Not found") + ui.Empty("No such page.")
 36	}
 37}
 38
 39func renderIndex(req *realmpath.Request) string {
 40	// The page number is user input: clamp it into 1..pages, or ?page=-1
 41	// renders an empty board under a footer saying "page -1 of 2".
 42	pages := byTag.Pages(PageSize)
 43	page := 1
 44	if n, err := strconv.Atoi(req.Query.Get("page")); err == nil {
 45		page = n
 46	}
 47	if page < 1 {
 48		page = 1
 49	}
 50	if page > pages {
 51		page = pages
 52	}
 53
 54	t := ui.NewTable("tag", "notes")
 55	for _, e := range byTag.Page(page, PageSize) {
 56		t.Row(
 57			md.Link(e.Key, realmURL+":tag/"+e.Key),
 58			strconv.Itoa(len(e.IDs)),
 59		)
 60	}
 61
 62	return ui.Join("\n",
 63		md.H1("Notes by tag"),
 64		md.Paragraph(strconv.Itoa(notes.Len())+" note(s) under "+
 65			strconv.Itoa(byTag.Keys())+" tag(s), by "+
 66			strconv.Itoa(byAuthor.Keys())+" author(s)."),
 67		t.OrEmpty("Nothing posted yet."),
 68		pager(page, pages),
 69		md.Paragraph(ui.Action("Post a note", "Post", "tag", "", "body", "")),
 70	)
 71}
 72
 73func renderTag(tag string) string {
 74	if tag == "" {
 75		return md.H1("Not found") + ui.Empty("No tag given.")
 76	}
 77	return md.H1("#"+ui.Cell(tag)) + notesTable(byTag.Lookup(tag), "No notes under this tag.")
 78}
 79
 80func renderAuthor(addr string) string {
 81	if addr == "" {
 82		return md.H1("Not found") + ui.Empty("No author given.")
 83	}
 84	return md.H1("Notes by "+ui.AddrOf(addr)) +
 85		notesTable(byAuthor.Lookup(addr), "This address has posted nothing.")
 86}
 87
 88// notesTable renders a set of ids as rows, skipping any id the store no longer
 89// holds.
 90//
 91// Skipping rather than panicking: an index and a store are two containers, and
 92// a reader should see the records that exist rather than a page that aborts.
 93// Retract keeps the two in step, so a miss here means a bug worth surviving,
 94// not worth hiding.
 95func notesTable(hits []store.ID, empty string) string {
 96	t := ui.NewTable("#", "author", "tag", "note", "height")
 97	for _, id := range hits {
 98		v, ok := notes.Get(id)
 99		if !ok {
100			continue
101		}
102		n := v.(*note)
103		t.Row(
104			id.String(),
105			ui.Addr(n.Author),
106			md.InlineCode(n.Tag),
107			ui.Cell(n.Body),
108			strconv.FormatInt(n.Height, 10),
109		)
110	}
111	return t.OrEmpty(empty)
112}
113
114// pager renders the previous/next links, or nothing when there is one page.
115func pager(page, pages int) string {
116	if pages < 2 {
117		return ""
118	}
119	out := ""
120	if page > 1 {
121		out += md.Link("previous", realmURL+":?page="+strconv.Itoa(page-1)) + " "
122	}
123	out += "page " + strconv.Itoa(page) + " of " + strconv.Itoa(pages)
124	if page < pages {
125		out += " " + md.Link("next", realmURL+":?page="+strconv.Itoa(page+1))
126	}
127	return out
128}