render.gno
2.81 Kb · 78 lines
1package memba_notes_v1
2
3import (
4 "strconv"
5 "strings"
6)
7
8// gnoweb uses bounded pages. A revision in every continuation link prevents
9// assembling a whitepaper from different edits; the app reads binary chunks.
10func Render(path string) string {
11 if path == "" {
12 return "# Notes\n\nUse ListPublicJSON for the public directory.\n"
13 }
14 if len(path) > 80 {
15 panic("notes: render path")
16 }
17 parts := strings.Split(path, "/")
18 if len(parts) != 1 && len(parts) != 3 {
19 panic("notes: render path")
20 }
21 n := find(readID(parts[0]))
22 if n.Deleted {
23 return "# Deleted note\n\nPast versions remain in chain history.\n"
24 }
25 page := 0
26 if len(parts) == 3 {
27 rev, err := strconv.ParseUint(parts[1], 10, 64)
28 if err != nil || rev != n.Revision {
29 return "# Note changed\n\n[Start again](:" + idText(n.ID) + ").\n"
30 }
31 page, err = strconv.Atoi(parts[2])
32 if err != nil {
33 panic("notes: render page")
34 }
35 }
36 if !public(n.Mode) {
37 return "# Encrypted note\n\n" + seal(n.Body) + "\nOwner: " + string(n.Owner) + "\n\nEncrypted bytes: " + strconv.Itoa(n.BodyBytes) + " · Revision " + strconv.FormatUint(n.Revision, 10) + "\n\nOpen this note in Memba to decrypt. This seal is decorative.\n"
38 }
39 pages := (n.BodyBytes + MaxBodyChunkBytes - 1) / MaxBodyChunkBytes
40 if pages == 0 {
41 pages = 1
42 }
43 if page < 0 || page >= pages {
44 panic("notes: render page")
45 }
46 start := renderBoundary(n.Body, page*MaxBodyChunkBytes)
47 end := renderBoundary(n.Body, (page+1)*MaxBodyChunkBytes)
48 title := strings.NewReplacer("\\", "\\\\", "[", "\\[", "]", "\\]", "*", "\\*", "_", "\\_", "`", "\\`", "#", "\\#", "<", "<", ">", ">").Replace(string(n.Title))
49 body := strings.ReplaceAll(strings.ReplaceAll(string(n.Body[start:end]), "<", "<"), ">", ">")
50 // Entity tokens are text, not Markdown image syntax, even after a backslash.
51 // Keep ordinary links available without automatically fetching remote images.
52 body = strings.ReplaceAll(body, " + ") "
59 }
60 if page+1 < pages {
61 nav += "[Next](" + base + strconv.Itoa(page+1) + ") "
62 }
63 nav += "[Read the complete note in Memba](https://memba.club/os/notes/" + idText(n.ID) + ")\n"
64 }
65 return "# " + title + "\n\n" + body + nav
66}
67
68// Advance at most three continuation bytes; adjacent pages use the same
69// boundary, so no UTF8 byte is lost or repeated.
70func renderBoundary(body []byte, pos int) int {
71 if pos >= len(body) {
72 return len(body)
73 }
74 for pos < len(body) && body[pos]&0xc0 == 0x80 {
75 pos++
76 }
77 return pos
78}