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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

render.gno

3.71 Kb · 129 lines
  1package definition
  2
  3import (
  4	"html"
  5	"net/url"
  6	"strconv"
  7	"strings"
  8	"unicode"
  9)
 10
 11func Render(path string) string {
 12	u, err := url.Parse(path)
 13	if err != nil {
 14		return renderError("Invalid URL")
 15	}
 16	query, err := url.ParseQuery(u.RawQuery)
 17	if err != nil {
 18		return renderError("Invalid query")
 19	}
 20	page := 1
 21	if raw := query.Get("page"); raw != "" {
 22		page, err = strconv.Atoi(raw)
 23		if err != nil || page < 1 {
 24			return renderError("Invalid page")
 25		}
 26	}
 27	key := query.Get("key")
 28	output := "# AKKADIA Definition\n\n[All items](./definition:)\n\n" + renderSearchForm(key)
 29	switch u.Path {
 30	case "", "list":
 31		total := GetKeyCount()
 32		output += "## Items (" + strconv.Itoa(total) + ")\n\n"
 33		keys := ListKeys(page, 20)
 34		if len(keys) == 0 {
 35			output += "No entries on this page.\n"
 36		}
 37		for _, key := range keys {
 38			label := key
 39			if itemKey, found := GetField(key, "key")["value"]; found && itemKey != "" {
 40				label += " — " + itemKey
 41			}
 42			output += "* [" + renderLabel(label) + "](./definition:search?key=" + url.QueryEscape(key) + "&page=" + strconv.Itoa(page) + ")\n"
 43		}
 44		pages := 1
 45		if total > 0 {
 46			pages = (total-1)/20 + 1
 47		}
 48		output += "\nPage " + strconv.Itoa(page) + " / " + strconv.Itoa(pages) + "\n"
 49		return output + renderPages("./definition:list?", page, total)
 50	case "search":
 51		if strings.TrimSpace(key) == "" {
 52			return output + "Key is required\n"
 53		}
 54		fields, found := getFields(key)
 55		if !found {
 56			return output + "Entry not found\n"
 57		}
 58		output += "[Back to list](./definition:list?page=" + strconv.Itoa(page) + ")\n\n"
 59		output += "## " + renderLabel(key) + "\n\n"
 60		for _, field := range fields {
 61			output += "### " + renderLabel(field.Key) + "\n\n"
 62			link, err := url.Parse(field.Value)
 63			isLink := err == nil && (link.Scheme == "http" || link.Scheme == "https") && link.Hostname() != "" && link.Opaque == ""
 64			for _, r := range field.Value {
 65				if unicode.IsSpace(r) || unicode.IsControl(r) {
 66					isLink = false
 67				}
 68			}
 69			if isLink {
 70				// Angle-delimited Markdown destinations retain parentheses and ampersands.
 71				target := strings.ReplaceAll(strings.ReplaceAll(link.String(), "<", "%3C"), ">", "%3E")
 72				output += "[" + renderLabel(field.Value) + "](<" + target + ">)\n\n"
 73			} else {
 74				// A fence longer than any user fence keeps HTML and Markdown literal.
 75				longest, run := 2, 0
 76				for _, r := range field.Value {
 77					if r == '`' {
 78						run++
 79						if run > longest {
 80							longest = run
 81						}
 82					} else {
 83						run = 0
 84					}
 85				}
 86				fence := strings.Repeat("`", longest+1)
 87				output += fence + "\n" + field.Value + "\n" + fence + "\n\n"
 88			}
 89		}
 90		return output
 91	default:
 92		return renderError("Page not found")
 93	}
 94}
 95
 96func renderLabel(value string) string {
 97	// Gnoweb's TOC unescapes Markdown punctuation but leaves HTML entities literal.
 98	var output strings.Builder
 99	for _, r := range value {
100		if unicode.IsControl(r) {
101			output.WriteString(" ")
102			continue
103		}
104		if (r >= '!' && r <= '/') || (r >= ':' && r <= '@') || (r >= '[' && r <= '`') || (r >= '{' && r <= '~') {
105			output.WriteString("\\")
106		}
107		output.WriteRune(r)
108	}
109	return output.String()
110}
111
112func renderPages(base string, page, total int) string {
113	output := "\n"
114	if page > 1 {
115		output += "[Previous](" + base + "page=" + strconv.Itoa(page-1) + ") "
116	}
117	if total > 0 && page <= (total-1)/20 {
118		output += "[Next](" + base + "page=" + strconv.Itoa(page+1) + ")"
119	}
120	return output + "\n"
121}
122
123func renderError(message string) string {
124	return "# Definition\n\n" + message + "\n\n[All items](./definition:)\n\n" + renderSearchForm("")
125}
126
127func renderSearchForm(key string) string {
128	return "<gno-form path=\"search\">\n  <gno-input name=\"key\" value=\"" + html.EscapeString(key) + "\" placeholder=\"Item ID (e.g. 1101000)\" />\n</gno-form>\n\n"
129}