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

sitelink.gno

2.92 Kb · 121 lines
  1package kourtv3
  2import (
  3	"chain"
  4	"strconv"
  5	"strings"
  6)
  7const maxSiteDomainLen = 100
  8var siteDomain string
  9func siteDomainFault(d string) string {
 10	if len(d) < 4 || len(d) > maxSiteDomainLen {
 11		return "a site domain is 4..100 characters"
 12	}
 13	if !strings.Contains(d, ".") {
 14		return "a site domain needs a dot — it is a hostname, not a word"
 15	}
 16	if d[0] == '.' || d[0] == '-' || d[len(d)-1] == '.' || d[len(d)-1] == '-' {
 17		return "a site domain may not begin or end with a dot or a hyphen"
 18	}
 19	for i := 0; i < len(d); i++ {
 20		c := d[i]
 21		ok := (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.' || c == '-'
 22		if !ok {
 23			return "a site domain is lowercase letters, digits, dots and " +
 24				"hyphens only — no scheme, no path, no port"
 25		}
 26	}
 27	if strings.Contains(d, "..") {
 28		return "a site domain may not contain an empty label"
 29	}
 30	return ""
 31}
 32func mustSiteDomain(d string) {
 33	if fault := siteDomainFault(d); fault != "" {
 34		panic("kourtv3: " + fault)
 35	}
 36}
 37func SetSiteDomain(cur realm, domain string) {
 38	if !cur.IsCurrent() {
 39		panic(errStaleRealm)
 40	}
 41	d := ensureGlobalDAO()
 42	if cur.Previous().Address() != d.admin {
 43		panic("kourtv3: only the global DAO admin sets the site domain")
 44	}
 45	mustSiteDomain(domain)
 46	siteDomain = domain
 47	chain.Emit(globalActEvent,
 48		"court", "*",
 49		"claim", "0",
 50		"act", "set-site-domain:"+domain,
 51		"by", cur.Previous().Address().String(),
 52		"height", eventHeight(),
 53	)
 54}
 55func ClearSiteDomain(cur realm) {
 56	if !cur.IsCurrent() {
 57		panic(errStaleRealm)
 58	}
 59	d := ensureGlobalDAO()
 60	if cur.Previous().Address() != d.admin {
 61		panic("kourtv3: only the global DAO admin clears the site domain")
 62	}
 63	if siteDomain == "" {
 64		panic("kourtv3: no site domain is set")
 65	}
 66	siteDomain = ""
 67	chain.Emit(globalActEvent,
 68		"court", "*",
 69		"claim", "0",
 70		"act", "clear-site-domain",
 71		"by", cur.Previous().Address().String(),
 72		"height", eventHeight(),
 73	)
 74}
 75func SiteDomain() string { return siteDomain }
 76func sitePath(path string) string {
 77	parts := strings.SplitN(path, "/", 4)
 78	if path == "" || parts[0] == "" {
 79		return "#/"
 80	}
 81	if len(parts) == 1 {
 82		if parts[0] == helpPath {
 83			return "#/"
 84		}
 85		if parts[0] == adminParamsPath {
 86			return "#/params"
 87		}
 88		return "#/c/" + parts[0]
 89	}
 90	switch parts[1] {
 91	case "me":
 92		return "#/me"
 93	case "mod":
 94		return "#/c/" + parts[0]
 95	}
 96	if _, err := strconv.ParseUint(parts[1], 10, 64); err != nil {
 97		return "#/c/" + parts[0]
 98	}
 99	return "#/c/" + parts[0] + "/" + parts[1]
100}
101func withSiteBanner(body, path string) string {
102	bar := siteBanner(path)
103	if bar == "" {
104		return body
105	}
106	if !strings.HasPrefix(body, "# ") {
107		return bar + body
108	}
109	i := strings.Index(body, "\n")
110	if i < 0 {
111		return body + "\n\n" + bar
112	}
113	return body[:i+1] + "\n" + bar + strings.TrimLeft(body[i+1:], "\n")
114}
115func siteBanner(path string) string {
116	if siteDomain == "" || siteDomainFault(siteDomain) != "" {
117		return ""
118	}
119	return "[Read this on " + siteDomain + " →](https://" + siteDomain + "/" +
120		sitePath(path) + ")\n\n"
121}