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.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}