aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md6
-rw-r--r--v0.9.5/patches/02_bloated_i18n_implementation.patch153
-rw-r--r--v0.9.5/patches/03_bloated_i18n_implementation.patch157
-rw-r--r--v0.9.5/views/i18n/about.html15
-rw-r--r--v0.9.5/views/i18n/account.html62
-rw-r--r--v0.9.5/views/i18n/chatter.html66
-rw-r--r--v0.9.5/views/i18n/combos.html13
-rw-r--r--v0.9.5/views/i18n/funzone.html16
-rw-r--r--v0.9.5/views/i18n/header.html100
-rw-r--r--v0.9.5/views/i18n/hfcs.html73
-rw-r--r--v0.9.5/views/i18n/honk.html146
-rw-r--r--v0.9.5/views/i18n/honkers.html62
-rw-r--r--v0.9.5/views/i18n/honkform.html49
-rw-r--r--v0.9.5/views/i18n/honkfrags.html11
-rw-r--r--v0.9.5/views/i18n/honkpage.html45
-rw-r--r--v0.9.5/views/i18n/honkpage.js387
-rw-r--r--v0.9.5/views/i18n/login.html12
-rw-r--r--v0.9.5/views/i18n/msg.html7
-rw-r--r--v0.9.5/views/i18n/onts.html16
-rw-r--r--v0.9.5/views/i18n/pleroma.css7
-rw-r--r--v0.9.5/views/i18n/style.css334
-rw-r--r--v0.9.5/views/i18n/xzone.html17
22 files changed, 1596 insertions, 158 deletions
diff --git a/README.md b/README.md
index 2fbed3a..5563d21 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,11 @@ this repository and then copy the contents of the `views` directory on
your honk `views` directory. For patches, I advice you to execute the
following : `man patch`.
+The translations are contained in the `i18n.go` file at the root
+of this repo. The structure is fairly simple, contact me if you need
+help to add translations or notify me for problems with it.
+
# Screenshots
Alternative navigation bar, less uses of the drop down menu:
-[alt nav bar menu for honk](<img src="https://git.les-miquelots.net/honk_custom/plain/scrots/honk_altnavbar.png" alt="">)
+[alt nav bar menu for honk](https://git.les-miquelots.net/honk_custom/plain/scrots/honk_altnavbar.png)
diff --git a/v0.9.5/patches/02_bloated_i18n_implementation.patch b/v0.9.5/patches/02_bloated_i18n_implementation.patch
deleted file mode 100644
index 4e22e8c..0000000
--- a/v0.9.5/patches/02_bloated_i18n_implementation.patch
+++ /dev/null
@@ -1,153 +0,0 @@
-diff --git a/bloat.go b/bloat.go
-index e89675f..ca4a76f 100644
---- a/bloat.go
-+++ b/bloat.go
-@@ -14,3 +14,148 @@
- // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
- package main
-+
-+import (
-+ "net/http"
-+ "regexp"
-+ "log"
-+ "humungus.tedunangst.com/r/webs/login"
-+)
-+
-+type i18n struct {
-+ Home string
-+ Atme string
-+ First string
-+ Combos string
-+ Chatter string
-+ Tags string
-+ Events string
-+ Longago string
-+ Saved string
-+ Honkers string
-+ Hcfs string
-+ Account string
-+ Morestuff string
-+ Myhonks string
-+ About string
-+ Front string
-+ Funzone string
-+ Xzone string
-+ Help string
-+ Search string
-+ Login string
-+
-+ Newhonk string
-+}
-+
-+func getLangCookie(r *http.Request) string {
-+ langCookie, err := r.Cookie("lang")
-+ if err != nil {
-+ return "en"
-+ }
-+ return langCookie.Value
-+}
-+
-+
-+
-+func setLangCookie (w http.ResponseWriter, r *http.Request) {
-+ var lang string
-+ lang = r.FormValue("lang")
-+ var IsLetter = regexp.MustCompile(`^([a-z]+)$`).MatchString
-+
-+ if !IsLetter(lang) {
-+ lang = "wrong" // so !=2 is triggered
-+ if debugMode {
-+ log.Printf("lang cookie value is not letters")
-+ }
-+ }
-+
-+ if len(lang) != 2 {
-+ if debugMode {
-+ log.Printf("lang cookie value is too long or too short. defaulting to eng")
-+ }
-+ lang = "en"
-+ }
-+
-+ maxage := 3600 * 24 * 30 * 12
-+ if !debugMode {
-+ http.SetCookie(w, &http.Cookie{
-+ Name: "lang",
-+ Value: lang,
-+ MaxAge: maxage,
-+ Secure: true,
-+ HttpOnly: true,
-+ })
-+ } else {
-+ http.SetCookie(w, &http.Cookie{
-+ Name: "lang",
-+ Value: lang,
-+ MaxAge: maxage,
-+ Secure: false,
-+ HttpOnly: true,
-+ })
-+ }
-+
-+
-+ u := login.GetUserInfo(r)
-+ if u == nil {
-+ http.Redirect(w, r, "/", http.StatusSeeOther)
-+ }
-+}
-+
-+func setLangStr (lang string) interface{} {
-+ switch lang {
-+ case "fr" :
-+ tlStr := i18n{
-+ "accueil",
-+ "mentions",
-+ "premier (first)",
-+ "combos",
-+ "discutaille",
-+ "balises",
-+ "événements",
-+ "il y a longtemps",
-+ "sauvegardés",
-+ "klaxonneurs",
-+ "filtrer (hcfs)",
-+ "compte",
-+ "plus de choses",
-+ "profil",
-+ "à propos",
-+ "tout le réseau connu",
-+ "zone fun",
-+ "récup",
-+ "aide",
-+ "rechercher",
-+ "connexion",
-+ "klaxonner",
-+ }
-+ return tlStr
-+ default:
-+ tlStr := i18n{
-+ "home",
-+ "@me",
-+ "first",
-+ "combos",
-+ "chatter",
-+ "tags",
-+ "events",
-+ "long ago",
-+ "saved",
-+ "honkers",
-+ "filters",
-+ "account",
-+ "more stuff",
-+ "my honks",
-+ "about",
-+ "front",
-+ "funzone",
-+ "xzone",
-+ "help",
-+ "search",
-+ "login",
-+ "new honk",
-+ }
-+ return tlStr
-+ }
-+}
diff --git a/v0.9.5/patches/03_bloated_i18n_implementation.patch b/v0.9.5/patches/03_bloated_i18n_implementation.patch
index dff14f3..a788f0f 100644
--- a/v0.9.5/patches/03_bloated_i18n_implementation.patch
+++ b/v0.9.5/patches/03_bloated_i18n_implementation.patch
@@ -1,5 +1,5 @@
diff --git a/web.go b/web.go
-index 11adf5b..8e42bae 100644
+index 11adf5b..7c0c184 100644
--- a/web.go
+++ b/web.go
@@ -85,6 +85,9 @@ func getInfo(r *http.Request) map[string]interface{} {
@@ -21,7 +21,57 @@ index 11adf5b..8e42bae 100644
if u == nil || r.URL.Path == "/front" {
switch r.URL.Path {
case "/events":
-@@ -733,6 +736,8 @@ func showhonker(w http.ResponseWriter, r *http.Request) {
+@@ -113,17 +116,32 @@ func homepage(w http.ResponseWriter, r *http.Request) {
+ userid = u.UserID
+ switch r.URL.Path {
+ case "/atme":
+- templinfo["ServerMessage"] = "at me!"
++ switch templinfo["Lang"] {
++ case "fr":
++ templinfo["ServerMessage"] = "pour moi!"
++ default:
++ templinfo["ServerMessage"] = "at me!"
++ }
+ templinfo["PageName"] = "atme"
+ honks = gethonksforme(userid, 0)
+ honks = osmosis(honks, userid, false)
+ case "/longago":
+- templinfo["ServerMessage"] = "long ago and far away!"
++ switch templinfo["Lang"] {
++ case "fr":
++ templinfo["ServerMessage"] = "il y a longtemps"
++ default:
++ templinfo["ServerMessage"] = "long ago and far away!"
++ }
+ templinfo["PageName"] = "longago"
+ honks = gethonksfromlongago(userid, 0)
+ honks = osmosis(honks, userid, false)
+ case "/events":
+- templinfo["ServerMessage"] = "some recent and upcoming events"
++ switch templinfo["Lang"] {
++ case "fr":
++ templinfo["ServerMessage"] = "quelques événements récents et à venir"
++ default:
++ templinfo["ServerMessage"] = "some recent and upcoming events"
++ }
+ templinfo["PageName"] = "events"
+ honks = geteventhonks(userid)
+ honks = osmosis(honks, userid, true)
+@@ -132,7 +150,12 @@ func homepage(w http.ResponseWriter, r *http.Request) {
+ honks = gethonksforuserfirstclass(userid, 0)
+ honks = osmosis(honks, userid, true)
+ case "/saved":
+- templinfo["ServerMessage"] = "saved honks"
++ switch templinfo["Lang"] {
++ case "fr":
++ templinfo["ServerMessage"] = "klaxons enregistrés"
++ default:
++ templinfo["ServerMessage"] = "saved honks"
++ }
+ templinfo["PageName"] = "saved"
+ honks = getsavedhonks(userid, 0)
+ default:
+@@ -733,6 +756,8 @@ func showhonker(w http.ResponseWriter, r *http.Request) {
templinfo["PageArg"] = name
templinfo["ServerMessage"] = msg
templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
@@ -30,7 +80,21 @@ index 11adf5b..8e42bae 100644
honkpage(w, u, honks, templinfo)
}
-@@ -1109,8 +1114,9 @@ func saveuser(w http.ResponseWriter, r *http.Request) {
+@@ -744,7 +769,12 @@ func showcombo(w http.ResponseWriter, r *http.Request) {
+ templinfo := getInfo(r)
+ templinfo["PageName"] = "combo"
+ templinfo["PageArg"] = name
+- templinfo["ServerMessage"] = "honks by combo: " + name
++ switch templinfo["Lang"] {
++ case "fr":
++ templinfo["ServerMessage"] = "klaxons par groupes: " + name
++ default:
++ templinfo["ServerMessage"] = "honks by combo: " + name
++ }
+ templinfo["HonkCSRF"] = login.GetCSRF("honkhonk", r)
+ honkpage(w, u, honks, templinfo)
+ }
+@@ -1109,8 +1139,9 @@ func saveuser(w http.ResponseWriter, r *http.Request) {
options.MapLink = ""
}
options.Reaction = r.FormValue("reaction")
@@ -42,7 +106,92 @@ index 11adf5b..8e42bae 100644
ava := re_avatar.FindString(whatabout)
if ava != "" {
whatabout = re_avatar.ReplaceAllString(whatabout, "")
-@@ -2436,6 +2442,7 @@ func serve() {
+@@ -2179,11 +2210,22 @@ func webhydra(w http.ResponseWriter, r *http.Request) {
+ case "atme":
+ honks = gethonksforme(userid, wanted)
+ honks = osmosis(honks, userid, false)
+- templinfo["ServerMessage"] = "at me!"
++ switch templinfo["Lang"] {
++ case "fr":
++ templinfo["ServerMessage"] = "pour moi!"
++ default:
++ templinfo["ServerMessage"] = "at me!"
++ }
+ case "longago":
+ honks = gethonksfromlongago(userid, wanted)
+ honks = osmosis(honks, userid, false)
+- templinfo["ServerMessage"] = "from long ago"
++
++ switch templinfo["Lang"] {
++ case "fr":
++ templinfo["ServerMessage"] = "il y a longtemps"
++ default:
++ templinfo["ServerMessage"] = "from long ago"
++ }
+ case "home":
+ honks = gethonksforuser(userid, wanted)
+ honks = osmosis(honks, userid, true)
+@@ -2191,26 +2233,53 @@ func webhydra(w http.ResponseWriter, r *http.Request) {
+ case "first":
+ honks = gethonksforuserfirstclass(userid, wanted)
+ honks = osmosis(honks, userid, true)
+- templinfo["ServerMessage"] = "first class only"
++
++ switch templinfo["Lang"] {
++ case "fr":
++ templinfo["ServerMessage"] = "première classe seulement"
++ default:
++ templinfo["ServerMessage"] = "first class only"
++ }
+ case "saved":
+ honks = getsavedhonks(userid, wanted)
+ templinfo["PageName"] = "saved"
+- templinfo["ServerMessage"] = "saved honks"
++ switch templinfo["Lang"] {
++ case "fr":
++ templinfo["ServerMessage"] = "klaxons enregistrés"
++ default:
++ templinfo["ServerMessage"] = "saved honks"
++ }
+ case "combo":
+ c := r.FormValue("c")
+ honks = gethonksbycombo(userid, c, wanted)
+ honks = osmosis(honks, userid, false)
+- templinfo["ServerMessage"] = "honks by combo: " + c
++ switch templinfo["Lang"] {
++ case "fr":
++ templinfo["ServerMessage"] = "klaxons par groupe: " + c
++ default:
++ templinfo["ServerMessage"] = "honks by combo: " + c
++ }
+ case "convoy":
+ c := r.FormValue("c")
+ honks = gethonksbyconvoy(userid, c, wanted)
+ honks = osmosis(honks, userid, false)
+- templinfo["ServerMessage"] = "honks in convoy: " + c
++ switch templinfo["Lang"] {
++ case "fr":
++ templinfo["ServerMessage"] = "klaxons dans le convoi: " + c
++ default:
++ templinfo["ServerMessage"] = "honks in convoy: " + c
++ }
+ case "honker":
+ xid := r.FormValue("xid")
+ honks = gethonksbyxonker(userid, xid, wanted)
+- msg := templates.Sprintf(`honks by honker: <a href="%s" ref="noreferrer">%s</a>`, xid, xid)
+- templinfo["ServerMessage"] = msg
++ switch templinfo["Lang"] {
++ case "fr":
++ msg := templates.Sprintf(`klaxons par le klaxonneur: <a href="%s" ref="noreferrer">%s</a>`, xid, xid)
++ templinfo["ServerMessage"] = msg
++ default:
++ msg := templates.Sprintf(`honks by honker: <a href="%s" ref="noreferrer">%s</a>`, xid, xid)
++ templinfo["ServerMessage"] = msg
++ }
+ default:
+ http.NotFound(w, r)
+ }
+@@ -2436,6 +2505,7 @@ func serve() {
getters.HandleFunc("/server", serveractor)
posters.HandleFunc("/server/inbox", serverinbox)
posters.HandleFunc("/inbox", serverinbox)
diff --git a/v0.9.5/views/i18n/about.html b/v0.9.5/views/i18n/about.html
new file mode 100644
index 0000000..962e7c3
--- /dev/null
+++ b/v0.9.5/views/i18n/about.html
@@ -0,0 +1,15 @@
+{{ template "header.html" . }}
+<main>
+<div class="info">
+{{ .AboutMsg }}<br>
+<p>
+<table style="font-size:0.8em">
+<tbody>
+<tr><td>{{ .i18n.Version }}:<td style="text-align:right">{{ .HonkVersion }}
+<tr><td>{{ .i18n.Memory }}:<td style="text-align:right">{{ printf "%.02f" .Sensors.Memory }}MB
+<tr><td>{{ .i18n.Uptime }}:<td style="text-align:right">{{ printf "%.02f" .Sensors.Uptime }}s
+<tr><td>{{ .i18n.Cputime }}:<td style="text-align:right">{{ printf "%.02f" .Sensors.CPU }}s
+</table>
+<p>
+</div>
+</main>
diff --git a/v0.9.5/views/i18n/account.html b/v0.9.5/views/i18n/account.html
new file mode 100644
index 0000000..2d5a3f3
--- /dev/null
+++ b/v0.9.5/views/i18n/account.html
@@ -0,0 +1,62 @@
+{{ template "header.html" . }}
+<main>
+<div class="info">
+<p>{{ .i18n.Account }} - <a href="/logout?CSRF={{ .LogoutCSRF }}">{{ .i18n.Logout }}</a>
+<p>{{ .i18n.Username }}: {{ .User.Name }}
+<div>
+<form id="aboutform" action="/saveuser" method="POST">
+<input type="hidden" name="CSRF" value="{{ .UserCSRF }}">
+<p>{{ .i18n.Aboutme }}:
+<p><textarea name="whatabout">{{ .WhatAbout }}</textarea>
+
+<p><label class="button" for="skinny">{{ .i18n.Slayout }}:</label>
+<input tabindex=1 type="checkbox" id="skinny" name="skinny" value="skinny" {{ if .User.Options.SkinnyCSS }}checked{{ end }}><span></span>
+
+<p><label class="button" for="omitimages">{{ .i18n.NoImg }}:</label>
+<input tabindex=1 type="checkbox" id="omitimages" name="omitimages" value="omitimages" {{ if .User.Options.OmitImages }}checked{{ end }}><span></span>
+
+<p><label class="button" for="mentionall">{{ .i18n.MentionAll }}:</label>
+<input tabindex=1 type="checkbox" id="mentionall" name="mentionall" value="mentionall" {{ if .User.Options.MentionAll }}checked{{ end }}><span></span>
+
+<p><label class="button" for="maps">{{ .i18n.AppleMapsLinks }}:</label>
+<input tabindex=1 type="checkbox" id="maps" name="maps" value="apple" {{ if eq "apple" .User.Options.MapLink }}checked{{ end }}><span></span>
+
+<p><label class="button" for="reaction">{{ .i18n.Reaction }}:</label>
+<select tabindex=1 name="reaction">
+ <option {{ and (eq .User.Options.Reaction "none") "selected" }}>none</option>
+ <option {{ and (eq .User.Options.Reaction "\U0001F61E") "selected" }}>{{ "\U0001F61E" }}</option>
+ <option {{ and (eq .User.Options.Reaction "\U0001F937") "selected" }}>{{ "\U0001F937" }}</option>
+ <option {{ and (eq .User.Options.Reaction "\U0001F648") "selected" }}>{{ "\U0001F648" }}</option>
+ <option {{ and (eq .User.Options.Reaction "\U0001F9BE") "selected" }}>{{ "\U0001F9BE" }}</option>
+ <option {{ and (eq .User.Options.Reaction "\U0001F346") "selected" }}>{{ "\U0001F346" }}</option>
+ <option {{ and (eq .User.Options.Reaction "\U0001F351") "selected" }}>{{ "\U0001F351" }}</option>
+ <option {{ and (eq .User.Options.Reaction "\U0001F32E") "selected" }}>{{ "\U0001F32E" }}</option>
+ <option {{ and (eq .User.Options.Reaction "\U0001F951") "selected" }}>{{ "\U0001F951" }}</option>
+ <option {{ and (eq .User.Options.Reaction "\U0001F5FF") "selected" }}>{{ "\U0001F5FF" }}</option>
+ <option {{ and (eq .User.Options.Reaction "\U0001F99A") "selected" }}>{{ "\U0001F99A" }}</option>
+ <option {{ and (eq .User.Options.Reaction "\U0001F3BB") "selected" }}>{{ "\U0001F3BB" }}</option>
+ <option {{ and (eq .User.Options.Reaction "\U0001FA93") "selected" }}>{{ "\U0001FA93" }}</option>
+ <option {{ and (eq .User.Options.Reaction "\U0001F1EB") "selected" }}>{{ "\U0001F1EB" }}</option>
+ <option {{ and (eq .User.Options.Reaction "\U0001F1FD") "selected" }}>{{ "\U0001F1FD" }}</option>
+</select>
+
+<p><label class="button" for="lang">{{ .i18n.LanguageLabel }}:</label>
+<select name="lang">
+ <option {{ and (eq .Lang "en") "selected"}}>en</option>
+ <option {{ and (eq .Lang "fr") "selected"}}>fr</option>
+</select>
+
+<p><button>{{ .i18n.UpdateSettings }}</button>
+</form>
+</div>
+<hr>
+<div>
+<form action="/chpass" method="POST">
+<input type="hidden" name="CSRF" value="{{ .LogoutCSRF }}">
+<p>{{ .i18n.ChangePwd }}
+<p><input tabindex=1 type="password" name="oldpass"> - {{ .i18n.OldPwd }}
+<p><input tabindex=1 type="password" name="newpass"> - {{ .i18n.NewPwd }}
+<p><button>{{ .i18n.ChangePwdBtn }}</button>
+</form>
+</div>
+</main>
diff --git a/v0.9.5/views/i18n/chatter.html b/v0.9.5/views/i18n/chatter.html
new file mode 100644
index 0000000..0173224
--- /dev/null
+++ b/v0.9.5/views/i18n/chatter.html
@@ -0,0 +1,66 @@
+{{ template "header.html" . }}
+<main>
+<div class="info">
+<p>
+<form action="/sendchonk" method="POST" enctype="multipart/form-data">
+<h3>{{ .i18n.NewChatter }}</h3>
+<input type="hidden" name="CSRF" value="{{ .ChonkCSRF }}">
+<p><label for=target>{{ .i18n.Target }}:</label><br>
+<input type="text" name="target" value="" autocomplete=off>
+<p><label for=noise>{{ .i18n.Noise }}:</label><br>
+<textarea name="noise" id="noise"></textarea>
+<p><button name="chonk" value="chonk">{{ .i18n.Chonk }}</button>
+<label class=button id="donker">{{ .i18n.Attach }}: <input onchange="updatedonker(this);" type="file" name="donk"><span></span></label>
+</form>
+<script>
+function updatedonker(el) {
+ el = el.parentElement
+ el.children[1].textContent = el.children[0].value.slice(-20)
+}
+</script>
+</div>
+{{ $chonkcsrf := .ChonkCSRF }}
+{{ range .Chatter }}
+<section class="honk">
+<p class="chattarget">
+chatter: {{ .Target }}
+{{ $target := .Target }}
+{{ range .Chonks }}
+<div class="chat">
+<p>
+<span class="chatstamp">{{ .Date.Local.Format "15:04" }} {{ .Handle }}:</span>
+{{ .HTML }}
+{{ range .Donks }}
+{{ if .Local }}
+{{ if eq .Media "text/plain" }}
+<p><a href="/d/{{ .XID }}">{{ .i18n.Attachment }}: {{ .Name }}</a>{{ if not (eq .Desc .Name) }} {{ .Desc }}{{ end }}
+{{ else if eq .Media "application/pdf" }}
+<p><a href="/d/{{ .XID }}">{{ .i18n.Attachment }}: {{ .Name }}</a>{{ if not (eq .Desc .Name) }} {{ .Desc }}{{ end }}
+{{ else }}
+<p><img src="/d/{{ .XID }}" title="{{ .Desc }}" alt="{{ .Desc }}">
+{{ end }}
+{{ else }}
+{{ if .XID }}
+<p><a href="{{ .URL }}" rel=noreferrer>{{ .i18n.ExtAttachment }}: {{ .Name }}</a>{{ if not (eq .Desc .Name) }} {{ .Desc }}{{ end }}
+{{ else }}
+{{ if eq .Media "video/mp4" }}
+<p><video controls src="{{ .URL }}">{{ .Name }}</video>
+{{ else }}
+<p><img src="{{ .URL }}" title="{{ .Desc }}" alt="{{ .Desc }}">
+{{ end }}
+{{ end }}
+{{ end }}
+{{ end }}
+</div>
+{{ end }}
+<form action="/sendchonk" method="POST" enctype="multipart/form-data">
+<input type="hidden" name="CSRF" value="{{ $chonkcsrf }}">
+<input type="hidden" name="target" value="{{ $target }}" autocomplete=off>
+<p><label for=noise>{{ .i18n.Noise }}:</label><br>
+<textarea name="noise" id="noise"></textarea>
+<p><button name="chonk" value="chonk">{{ .i18n.Chonk }}</button>
+<label class=button id="donker">{{ .i18n.Attach }}: <input onchange="updatedonker(this);" type="file" name="donk"><span></span></label>
+</form>
+</section>
+{{ end }}
+</main>
diff --git a/v0.9.5/views/i18n/combos.html b/v0.9.5/views/i18n/combos.html
new file mode 100644
index 0000000..9cd227d
--- /dev/null
+++ b/v0.9.5/views/i18n/combos.html
@@ -0,0 +1,13 @@
+{{ template "header.html" . }}
+<main>
+<div class="info">
+<p>{{ .i18n.Combos }}
+</div>
+{{ range .Combos }}
+<section class="honk">
+<header>
+<p style="font-size: 1.8em"><a href="/c/{{ . }}">{{ . }}</a>
+</header>
+</section>
+{{ end }}
+</main>
diff --git a/v0.9.5/views/i18n/funzone.html b/v0.9.5/views/i18n/funzone.html
new file mode 100644
index 0000000..d5e69e3
--- /dev/null
+++ b/v0.9.5/views/i18n/funzone.html
@@ -0,0 +1,16 @@
+{{ template "header.html" . }}
+<main>
+<div class="info">
+<p>{{ .i18n.FunZoneGreet }}
+<ul>
+{{ range .Emus }}
+<li><img class="emu" src="/emu/{{ . }}.png"> :{{ . }}:
+{{ end }}
+</ul>
+<ul>
+{{ range .Memes }}
+<li>meme: <a href="/meme/{{ . }}">{{ . }}</a>
+{{ end }}
+</ul>
+</div>
+</main>
diff --git a/v0.9.5/views/i18n/header.html b/v0.9.5/views/i18n/header.html
new file mode 100644
index 0000000..21cce5e
--- /dev/null
+++ b/v0.9.5/views/i18n/header.html
@@ -0,0 +1,100 @@
+<!doctype html>
+<html lang="{{ .Lang }}">
+<head>
+ <link rel="shortcut icon" href="/favicon.ico"/>
+<title>honk</title>
+<link href="/style.css{{ .StyleParam }}" rel="stylesheet">
+{{ if .LocalStyleParam }}
+<link href="/local.css{{ .LocalStyleParam }}" rel="stylesheet">
+{{ end }}
+<style>
+{{ .UserStyle }}
+</style>
+<link href="/icon.png" rel="icon">
+<meta name="theme-color" content="#305">
+<meta name="viewport" content="width=device-width">
+<meta name="description" content="Federated ActivityPub instance running the Honk server. things happen">
+<meta name="keywords" content="honk">
+</head>
+<body>
+<header>
+{{ if .UserInfo }}
+<details id="topmenu">
+ <summary>menu <span> {{ .UserInfo.Username }}</span></summary>
+<ul>
+<li><a id="homelink" href="/">{{ .i18n.Home }}</a>
+<li><a id="atmelink" href="/atme">{{ .i18n.Atme }}</a>
+<li><a id="firstlink" href="/first">{{ .i18n.First }}</a>
+<li style="list-style-type:none; margin-left:-1em">
+<details>
+<summary>{{ .i18n.Combos }}</summary>
+<ul>
+{{ range .Combos }}
+<li><a class="combolink" href="/c/{{ . }}">{{ . }}</a>
+{{ end }}
+</ul>
+</details>
+<li><a href="/chatter">{{ .i18n.Chatter }}</a>
+<li><a href="/o">{{ .i18n.Tags }}</a>
+<li><a href="/events">{{ .i18n.Events }}</a>
+<li><a id="longagolink" href="/longago">{{ .i18n.Longago }}</a>
+<li><a id="savedlink" href="/saved">{{ .i18n.Saved }}</a>
+<li><a href="/honkers">{{ .i18n.Honkers }}</a>
+<li><a href="/hfcs">{{ .i18n.Hcfs }}</a>
+<li><a href="/account">{{ .i18n.Account }}</a>
+<li style="list-style-type:none; margin-left:-1em">
+<details>
+<summary>{{ .i18n.Morestuff }}</summary>
+<ul>
+<li><a href="/{{ .UserSep }}/{{ .UserInfo.Username }}">{{ .i18n.Myhonks }}</a>
+<li><a href="/about">{{ .i18n.About }}</a>
+<li><a href="/front">{{ .i18n.Front }}</a>
+<li><a href="/funzone">{{ .i18n.Funzone }}</a>
+<li><a href="/xzone">{{ .i18n.Xzone }}</a>
+</ul>
+</details>
+<li><a href="/help/honk.1.html">{{ .i18n.Help }}</a>
+<li>
+<form action="/q" method="GET">
+<input type="text" name="q" autocomplete=off size=10 placeholder="{{ .i18n.Search }}">
+</form>
+</ul>
+</details>
+
+<!-- CUSTOM HONK NAVIGATION BAR -->
+ <div id="altnavbar">
+ <a href="/newhonk" tabindex="2">{{ .i18n.Newhonk }}</a> |
+ <a href="/front">{{ .i18n.Front }}</a> |
+ <a href="/">{{ .i18n.Home }}</a> |
+ <a href="/xzone">{{ .i18n.Xzone }}</a> |
+ <a href="/atme">{{ .i18n.Atme }}</a> |
+ <a href="/u/{{ .UserInfo.Username}}">{{ .i18n.Myhonks }}</a> |
+ <a href="/account">{{ .i18n.Account }}</a> |
+ <form style="display:inline" action="/q" method="GET">
+ <input type="text" tabindex="1" name="q" autocomplete="off"
+ size="10" placeholder="{{ .i18n.Search }}">
+ </form>
+ </div>
+<!-- END -->
+
+<p id="topspacer"></p>
+{{ else }}
+<span><a id="homelink" href="/">{{ .i18n.Home }}</a></span>
+<span><a href="/o">{{ .i18n.Tags }}</a></span>
+<span><a href="/events">{{ .i18n.Events }}</a></span>
+<span><a href="/about">{{ .i18n.About }}</a></span>
+{{ if .ShowRSS }}
+<span><a href="/rss">rss</a></span>
+{{ end }}
+<span><a href="/login">{{ .i18n.Login }}</a>
+<form style="display:inline" action="/langcookie" method="POST">
+<select name="lang">
+ <option {{ and (eq .Lang "en") "selected"}}>en</option>
+ <option {{ and (eq .Lang "fr") "selected"}}>fr</option>
+</select>
+<button>coom</button>
+</form>
+
+</span>
+{{ end }}
+</header>
diff --git a/v0.9.5/views/i18n/hfcs.html b/v0.9.5/views/i18n/hfcs.html
new file mode 100644
index 0000000..75b6f4f
--- /dev/null
+++ b/v0.9.5/views/i18n/hfcs.html
@@ -0,0 +1,73 @@
+{{ template "header.html" . }}
+<main>
+<div class="info">
+<p>
+{{ .i18n.Hfcs }}
+<form action="/savehfcs" method="POST">
+<input type="hidden" name="CSRF" value="{{ .FilterCSRF }}">
+<hr>
+<h3>{{ .i18n.NewFilter }}</h3>
+<p><label for="name">{{ .i18n.FilterName }}:</label><br>
+<input tabindex=1 type="text" name="name" value="" autocomplete=off>
+<p><label for="filtnotes">{{ .i18n.Notes }}:</label><br>
+<textarea tabindex=1 name="filtnotes" height=4>
+</textarea>
+<hr>
+<h3>{{ .i18n.Matches }}</h3>
+<p><label for="actor">{{ .i18n.WhoWhere }}:</label><br>
+<input tabindex=1 type="text" name="actor" value="" autocomplete=off>
+<p><span><label class=button for="incaud">{{ .i18n.IncludeAudience }}:
+<input tabindex=1 type="checkbox" id="incaud" name="incaud" value="yes"><span></span></label></span>
+<p><label for="filttext">{{ .i18n.TextMatches }}:</label><br>
+<input tabindex=1 type="text" name="filttext" value="" autocomplete=off>
+<p><span><label class=button for="isannounce">{{ .i18n.IsAnnounce }}:
+<input tabindex=1 type="checkbox" id="isannounce" name="isannounce" value="yes"><span></span></label></span>
+<p><label for="announceof">{{ .i18n.AnnounceOf }}:</label><br>
+<input tabindex=1 type="text" name="announceof" value="" autocomplete=off>
+<hr>
+<h3>action</h3>
+<p class="buttonarray">
+<span><label class=button for="doreject">{{ .i18n.Reject }}:
+<input tabindex=1 type="checkbox" id="doreject" name="doreject" value="yes"><span></span></label></span>
+<span><label class=button for="doskipmedia">{{ .i18n.SkipMedia }}:
+<input tabindex=1 type="checkbox" id="doskipmedia" name="doskipmedia" value="yes"><span></span></label></span>
+<span><label class=button for="dohide">{{ .i18n.Hide }}:
+<input tabindex=1 type="checkbox" id="dohide" name="dohide" value="yes"><span></span></label></span>
+<span><label class=button for="docollapse">{{ .i18n.Collapse }}:
+<input tabindex=1 type="checkbox" id="docollapse" name="docollapse" value="yes"><span></span></label></span>
+<p><label for="rewrite">{{ .i18n.Rewrite }}:</label><br>
+<input tabindex=1 type="text" name="filtrewrite" value="" autocomplete=off>
+<p><label for="replace">{{ .i18n.Replace }}:</label><br>
+<input tabindex=1 type="text" name="filtreplace" value="" autocomplete=off>
+<hr>
+<h3>{{ .i18n.Expiration }}</h3>
+<p><label for="filtduration">{{ .i18n.Duration }}:</label><br>
+<input tabindex=1 type="text" name="filtduration" value="" autocomplete=off>
+<hr>
+<p><button>{{ .i18n.BanHammerBtn }}</button>
+</form>
+</div>
+{{ $i18n := .i18n }}
+{{ $csrf := .FilterCSRF }}
+{{ range .Filters }}
+<section class="honk">
+<p>{{ $i18n.FilterName }}: {{ .Name }}
+{{ with .Notes }}<p>{{ $i18n.Notes }}: {{ . }}{{ end }}
+<p>Date: {{ .Date.Format "2006-01-02" }}
+{{ with .Actor }}<p>{{ $i18n.Who }}: {{ . }}{{ end }} {{ with .IncludeAudience }} (inclusive) {{ end }}
+{{ if .IsAnnounce }}<p>Announce: {{ .AnnounceOf }}{{ end }}
+{{ with .Text }}<p>Text: {{ . }}{{ end }}
+<p>{{ $i18n.Action }}: {{ range .Actions }} {{ . }} {{ end }}
+{{ with .Rewrite }}<p>{{ $i18n.Rewrite }}: {{ . }}{{ end }}
+{{ with .Replace }}<p>{{ $i18n.Replace }}: {{ . }}{{ end }}
+{{ if not .Expiration.IsZero }}<p>{{ $i18n.Expiration }}: {{ .Expiration.Format "2006-01-02 03:04" }}{{ end }}
+<form action="/savehfcs" method="POST">
+<input type="hidden" name="CSRF" value="{{ $csrf }}">
+<input type="hidden" name="hfcsid" value="{{ .ID }}">
+<input type="hidden" name="itsok" value="iforgiveyou">
+<button name="pardon" value="pardon">{{ $i18n.Pardon }}</button>
+</form>
+<p>
+</section>
+{{ end }}
+</main>
diff --git a/v0.9.5/views/i18n/honk.html b/v0.9.5/views/i18n/honk.html
new file mode 100644
index 0000000..52f63a7
--- /dev/null
+++ b/v0.9.5/views/i18n/honk.html
@@ -0,0 +1,146 @@
+<article class="honk {{ .Honk.Style }}" data-convoy="{{ .Honk.Convoy }}">
+{{ $bonkcsrf := .BonkCSRF }}
+{{ $IsPreview := .IsPreview }}
+{{ $maplink := .MapLink }}
+{{ $omitimages := .OmitImages }}
+{{ $i18n := .i18n }}
+{{ $lang := .Lang }}
+{{ with .Honk }}
+<header>
+{{ if $bonkcsrf }}
+<a class="honkerlink" href="/h?xid={{ .Honker }}" data-xid="{{ .Honker }}">
+{{ else }}
+<a href="{{ .Honker }}" rel=noreferrer>
+{{ end }}
+<img alt="" src="/a?a={{ .Honker}}">
+{{ if $bonkcsrf }} </a> {{ end }}
+{{ if .Oonker }}
+{{ if $bonkcsrf }}
+<a class="honkerlink" href="/h?xid={{ .Oonker }}" data-xid="{{ .Oonker }}">
+{{ else }}
+<a href="{{ .Oonker }}" rel=noreferrer>
+{{ end }}
+<img alt="" src="/a?a={{ .Oonker}}">
+{{ if $bonkcsrf }} </a> {{ end }}
+{{ end }}
+<p>
+{{ if $bonkcsrf }}
+<a class="honkerlink" href="/h?xid={{ .Honker }}" data-xid="{{ .Honker }}">{{ .Username }}</a>
+{{ else }}
+<a href="{{ .Honker }}" rel=noreferrer>{{ .Username }}</a>
+{{ end }}
+<span class="clip"><a href="{{ .URL }}" rel=noreferrer>{{ .What }}</a> {{ .Date.Local.Format "02 Jan 2006 15:04 -0700" }}</span>
+{{ if .Oonker }}
+<br>
+<span style="margin-left: 1em;" class="clip">
+{{ if $bonkcsrf }}
+{{ $i18n.Original }}: <a class="honkerlink" href="/h?xid={{ .Oonker }}" data-xid="{{ .Oonker }}">{{ .Oondle }}</a>
+{{ else }}
+{{ $i18n.Original }}: <a href="{{ .Oonker }}" rel=noreferrer>{{ .Oondle }}</a>
+{{ end }}
+</span>
+{{ else }}
+{{ if .RID }}
+<br>
+<span style="margin-left: 1em;" class="clip">
+{{ $i18n.InReplyTo }}: <a href="{{ .RID }}" rel=noreferrer>{{ .RID }}</a>
+</span>
+{{ end }}
+{{ end }}
+<br>
+{{ if $bonkcsrf }}
+ <span style="margin-left: 1em;" class="clip">{{ $i18n.Convoy }}: <a class="convoylink" href="/t?c={{ .Convoy }}">{{ .Convoy }}</a></span>
+{{ else }}
+ <span style="margin-left: 1em;" class="clip">{{ $i18n.Convoy }}: {{ .Convoy }}</span>
+
+{{ end }}
+</header>
+<p>
+<details class="noise" {{ .Open }} >
+<summary>{{ .HTPrecis }}<p></summary>
+<p>{{ .HTPrecis }}
+<p>{{ .HTML }}
+{{ with .Time }}
+<p>{{ $i18n.Time }}: {{ .StartTime.Local.Format "03:04PM EDT Mon Jan 02"}}
+{{ if .Duration }}<br>{{ $i18n.Duration }}: {{ .Duration }}{{ end }}
+{{ end }}
+{{ with .Place }}
+<p>{{ $i18n.Location }}: {{ with .Url }}<a href="{{ . }}" rel=noreferrer>{{ end }}{{ .Name }}{{ if .Url }}</a>{{ end }}{{ if or .Latitude .Longitude }} <a href="{{ if eq $maplink "apple" }}https://maps.apple.com/?q={{ or .Name "here" }}&z=16&ll={{ .Latitude }},{{ .Longitude }}{{ else }}https://www.openstreetmap.org/?mlat={{ .Latitude }}&mlon={{ .Longitude}}#map=16/{{ .Latitude }}/{{ .Longitude }}{{ end }}" rel=noreferrer>{{ .Latitude }} {{ .Longitude }}</a>{{ end }}
+{{ end }}
+{{ range .Donks }}
+{{ if .Local }}
+{{ if eq .Media "text/plain" }}
+<p><a href="/d/{{ .XID }}">{{ $i18n.Attachment }}: {{ .Name }}</a>{{ if not (eq .Desc .Name) }} {{ .Desc }}{{ end }}
+{{ else if eq .Media "application/pdf" }}
+<p><a href="/d/{{ .XID }}">{{ $i18n.Attachment }}: {{ .Name }}</a>{{ if not (eq .Desc .Name) }} {{ .Desc }}{{ end }}
+{{ else }}
+{{ if $omitimages }}
+<p><a href="/d/{{ .XID }}">{{ $i18n.Image }}: {{ .Name }}</a>{{ if not (eq .Desc .Name) }} {{ .Desc }}{{ end }}
+{{ else }}
+<p><img src="/d/{{ .XID }}" title="{{ .Desc }}" alt="{{ .Desc }}">
+{{ end }}
+{{ end }}
+{{ else }}
+{{ if .External }}
+<p><a href="{{ .URL }}" rel=noreferrer>{{ $i18n.ExtAttachment }}: {{ .Name }}</a>{{ if not (eq .Desc .Name) }} {{ .Desc }}{{ end }}
+{{ else }}
+{{ if eq .Media "video/mp4" }}
+<p><video controls src="{{ .URL }}">{{ .Name }}</video>
+{{ else }}
+<p><img src="{{ .URL }}" title="{{ .Desc }}" alt="{{ .Desc }}">
+{{ end }}
+{{ end }}
+{{ end }}
+{{ end }}
+</details>
+{{ end }}
+{{ if and $bonkcsrf (not $IsPreview) }}
+<p>
+<details class="actions">
+<summary>{{ $i18n.Actions }}</summary>
+<div>
+<p>
+{{ if .Honk.Public }}
+{{ if .Honk.IsBonked }}
+<button onclick="return unbonk(this, '{{ .Honk.XID }}');">{{ $i18n.Unbonk }}</button>
+{{ else }}
+<button onclick="return bonk(this, '{{ .Honk.XID }}');">{{ $i18n.Bonk }}</button>
+{{ end }}
+{{ else }}
+<button disabled>{{ $i18n.Nope }}</button>
+{{ end }}
+<button onclick="return showhonkform(this, '{{ .Honk.XID }}', '{{ .Honk.Handles }}');"><a href="/newhonk?rid={{ .Honk.XID }}">{{ $i18n.HonkBack }}</a></button>
+<button onclick="return muteit(this, '{{ .Honk.Convoy }}');">{{ $i18n.Mute }}</button>
+ <button onclick="return showelement('evenmore{{ .Honk.ID }}')">{{ $i18n.EvenMore }} </button>
+</div>
+<div id="evenmore{{ .Honk.ID }}" style="display:none">
+<p>
+<button onclick="return zonkit(this, '{{ .Honk.XID }}');">{{ $i18n.Zonk }}</button>
+{{ if .Honk.IsAcked }}
+<button onclick="return flogit(this, 'deack', '{{ .Honk.XID }}');">{{ $i18n.Deack }}</button>
+{{ else }}
+<button onclick="return flogit(this, 'ack', '{{ .Honk.XID }}');">{{ $i18n.Ack }}</button>
+{{ end }}
+{{ if .Honk.IsSaved }}
+<button onclick="return flogit(this, 'unsave', '{{ .Honk.XID }}');">{{ $i18n.Unsave }}</button>
+{{ else }}
+<button onclick="return flogit(this, 'save', '{{ .Honk.XID }}');">{{ $i18n.Save }}</button>
+{{ end }}
+{{ if .Honk.IsUntagged }}
+<button disabled>{{ $i18n.Untagged }}</button>
+{{ else }}
+<button onclick="return flogit(this, 'untag', '{{ .Honk.XID }}');">{{ $i18n.UntagMe }}</button>
+{{ end }}
+<button><a href="/edit?xid={{ .Honk.XID }}">{{ $i18n.Edit }}</a></button>
+{{ if not (eq .Badonk "none") }}
+{{ if .Honk.IsReacted }}
+<button disabled>badonked</button>
+{{ else }}
+<button onclick="return flogit(this, 'react', '{{ .Honk.XID }}');">{{ .Badonk }}</button>
+{{ end }}
+{{ end }}
+</div>
+</details>
+<p>
+{{ end }}
+</article>
diff --git a/v0.9.5/views/i18n/honkers.html b/v0.9.5/views/i18n/honkers.html
new file mode 100644
index 0000000..5b9ecf4
--- /dev/null
+++ b/v0.9.5/views/i18n/honkers.html
@@ -0,0 +1,62 @@
+{{ template "header.html" . }}
+<main>
+{{ $i18n := .i18n }}
+<div class="info">
+<p>
+<form action="/submithonker" method="POST">
+<h3>{{ $i18n.AddNewHonker }}</h3>
+<input type="hidden" name="CSRF" value="{{ .HonkerCSRF }}">
+<p><label for=url>url:</label><br>
+<input tabindex=1 type="text" name="url" value="" autocomplete=off>
+<p><label for=name>{{ $i18n.Name }}:</label><br>
+<input tabindex=1 type="text" name="name" value="" placeholder="{{ $i18n.Optional }}" autocomplete=off>
+<p><label for=combos>{{ $i18n.Combos }}:</label><br>
+<input tabindex=1 type="text" name="combos" value="" placeholder="{{ $i18n.Optional }}">
+<p><span><label class=button for="peep">{{ $i18n.SkipSub }}:
+<input tabindex=1 type="checkbox" id="peep" name="peep" value="peep"><span></span></label></span>
+<p><label for="notes">notes:</label><br>
+<textarea tabindex=1 name="notes">
+</textarea>
+<p><button tabindex=1 name="add honker" value="add honker">{{ $i18n.AddHonkerBtn }}</button>
+</form>
+</div>
+{{ $honkercsrf := .HonkerCSRF }}
+<div class="info">
+<script>
+function expandstuff() {
+ var els = document.querySelectorAll(".honk details")
+ for (var i = 0; i < els.length; i++) {
+ els[i].open = true
+ }
+}
+</script>
+<p><button onclick="expandstuff()">{{ $i18n.Expand }}</button>
+</div>
+{{ range .Honkers }}
+<section class="honk">
+<header>
+<img alt="avatar" src="/a?a={{ .XID }}">
+<p style="font-size: 1.8em"><a href="/h/{{ .Name }}">{{ .Name }}<a>
+</header>
+<p>
+<details>
+<p>url: <a href="{{ .XID }}" rel=noreferrer>{{ .XID }}</a>
+<p>{{ $i18n.Flavor }}: {{ .Flavor }}
+<form action="/submithonker" method="POST">
+<input type="hidden" name="CSRF" value="{{ $honkercsrf }}">
+<input type="hidden" name="honkerid" value="{{ .ID }}">
+<p>{{ $i18n.Name }}: <input type="text" name="name" value="{{ .Name }}">
+<p><label for="notes">{{ $i18n.Notes }}:</label><br>
+<textarea name="notes">{{ .Meta.Notes }}</textarea>
+<p>{{ $i18n.Combos }}: <input type="text" name="combos" value="{{ range .Combos }}{{ . }} {{end}}">
+<p>
+<button name="save" value="save">{{ $i18n.Save }}</button>
+<button name="sub" value="sub">{{ $i18n.Resub }}</button>
+<button name="unsub" value="unsub">{{ $i18n.Unsub }}</button>
+<button name="delete" value="delete">{{ $i18n.Delete }}</button>
+</form>
+</details>
+<p>
+</section>
+{{ end }}
+</main>
diff --git a/v0.9.5/views/i18n/honkform.html b/v0.9.5/views/i18n/honkform.html
new file mode 100644
index 0000000..ce17fca
--- /dev/null
+++ b/v0.9.5/views/i18n/honkform.html
@@ -0,0 +1,49 @@
+<p id="honkformhost">
+<button id="honkingtime" onclick="return showhonkform();" {{ if .IsPreview }}style="display:none"{{ end }}><a href="/newhonk">{{ .i18n.HonkingTime }}</a></button>
+<form id="honkform" action="/honk" method="POST" enctype="multipart/form-data" {{ if not .IsPreview }}style="display: none"{{ end }}>
+<input type="hidden" name="CSRF" value="{{ .HonkCSRF }}">
+<input type="hidden" name="updatexid" id="updatexidinput" value = "{{ .UpdateXID }}">
+<input type="hidden" name="rid" id="ridinput" value="{{ .InReplyTo }}">
+<h3>{{ .i18n.MakeNoise }}</h3>
+<p>
+<details>
+<summary>{{ .i18n.MoreOptions }}</summary>
+<p>
+<label class=button id="donker">{{ .i18n.Attach }}: <input onchange="updatedonker();" type="file" name="donk"><span>{{ .SavedFile }}</span></label>
+<input type="hidden" id="saveddonkxid" name="donkxid" value="{{ .SavedFile }}">
+<p id="donkdescriptor"><label for=donkdesc>{{ .i18n.Description }}:</label><br>
+<input type="text" name="donkdesc" value="{{ .DonkDesc }}" autocomplete=off>
+{{ with .SavedPlace }}
+<p><button id=checkinbutton type=button onclick="fillcheckin()">{{ .i18n.Checkin }}</button>
+<div id=placedescriptor>
+ <p><label>name:</label><br><input type="text" name="placename" id=placenameinput value="{{ .Name }}">
+ <p><label>url:</label><br><input type="text" name="placeurl" id=placeurlinput value="{{ .Url }}">
+ <p><label>lat: </label><input type="text" size=9 name="placelat" id=placelatinput value="{{ .Latitude}}">
+ <label>lon: </label><input type="text" size=9 name="placelong" id=placelonginput value="{{ .Longitude }}">
+</div>
+{{ else }}
+<p><button id=checkinbutton type=button onclick="fillcheckin()">{{ .i18n.Checkin }}</button>
+<div id=placedescriptor style="display: none">
+<p><label>{{ .i18n.Location }}:</label><br><input type="text" name="placename" id=placenameinput value="">
+<p><label>url:</label><br><input type="text" name="placeurl" id=placeurlinput value="">
+<p><label>lat: </label><input type="text" size=9 name="placelat" id=placelatinput value="">
+<label>lon: </label><input type="text" size=9 name="placelong" id=placelonginput value="">
+</div>
+{{ end }}
+<p><button id=addtimebutton type=button
+onclick="showelement('timedescriptor')">{{ .i18n.AddTime }}</button>
+<div id=timedescriptor style="{{ or .ShowTime "display: none" }}">
+<p><label for=timestart>{{ .i18n.Start }}:</label><br>
+<input type="text" name="timestart" value="{{ .StartTime }}">
+<p><label for=timeend>{{ .i18n.Duration }}:</label><br>
+<input type="text" name="timeend" value="{{ .Duration }}">
+</div>
+</details>
+<p>
+<textarea name="noise" id="honknoise">{{ .Noise }}</textarea>
+<p class="buttonarray">
+<button>{{ .i18n.GonBHonked }}</button>
+<button name="preview" value="preview">{{ .i18n.Preview }}</button>
+<button type=button name="cancel" value="cancel"
+onclick="cancelhonking()">{{ .i18n.Cancel }}</button>
+</form>
diff --git a/v0.9.5/views/i18n/honkfrags.html b/v0.9.5/views/i18n/honkfrags.html
new file mode 100644
index 0000000..5ccc2da
--- /dev/null
+++ b/v0.9.5/views/i18n/honkfrags.html
@@ -0,0 +1,11 @@
+<div>{{ .TopHID }}</div>
+{{ $BonkCSRF := .HonkCSRF }}
+{{ $MapLink := .MapLink }}
+{{ $Badonk := .User.Options.Reaction }}
+{{ $OmitImages := .User.Options.OmitImages }}
+<div><p>{{ .ServerMessage }}</div>
+<div>
+{{ range .Honks }}
+{{ template "honk.html" map "Honk" . "MapLink" $MapLink "BonkCSRF" $BonkCSRF "Badonk" $Badonk "OmitImages" $OmitImages }}
+{{ end }}
+</div>
diff --git a/v0.9.5/views/i18n/honkpage.html b/v0.9.5/views/i18n/honkpage.html
new file mode 100644
index 0000000..0ae42ef
--- /dev/null
+++ b/v0.9.5/views/i18n/honkpage.html
@@ -0,0 +1,45 @@
+{{ template "header.html" . }}
+<main>
+<div class="info" id="infobox">
+<div id="srvmsg">
+{{ if .Name }}
+<p>{{ .Name }} <span style="margin-left:1em;"><a href="/u/{{ .Name }}/rss">rss</a></span>
+<p>{{ .WhatAbout }}
+{{ end }}
+<p>{{ .ServerMessage }}
+</div>
+{{ if .HonkCSRF }}
+{{ template "honkform.html" . }}
+<script>
+var csrftoken = {{ .HonkCSRF }}
+var honksforpage = { }
+var curpagestate = { name: "{{ .PageName }}", arg : "{{ .PageArg }}" }
+var tophid = { }
+tophid[curpagestate.name + ":" + curpagestate.arg] = "{{ .TopHID }}"
+var servermsgs = { }
+servermsgs[curpagestate.name + ":" + curpagestate.arg] = "{{ .ServerMessage }}"
+</script>
+<script src="/honkpage.js{{ .JSParam }}"></script>
+{{ end }}
+</div>
+{{ if and .HonkCSRF (not .IsPreview) }}
+<div class="info" id="refreshbox">
+<p><button onclick="refreshhonks(this)">{{ .i18n.Refresh }}</button><span></span>
+<button onclick="oldestnewest(this)">{{ .i18n.ScrollDown }}</button>
+</div>
+{{ if eq .ServerMessage .i18n.MoarHonks }} <script> hideelement("refreshbox")</script> {{ end }}
+{{ end }}
+<div id="honksonpage">
+<div>
+{{ $BonkCSRF := .HonkCSRF }}
+{{ $IsPreview := .IsPreview }}
+{{ $MapLink := .MapLink }}
+{{ $Badonk := .User.Options.Reaction }}
+{{ $i18n := .i18n }}
+{{ $OmitImages := .User.Options.OmitImages }}
+{{ range .Honks }}
+{{ template "honk.html" map "Honk" . "MapLink" $MapLink "BonkCSRF" $BonkCSRF "IsPreview" $IsPreview "Badonk" $Badonk "OmitImages" $OmitImages "i18n" $i18n }}
+{{ end }}
+</div>
+</div>
+</main>
diff --git a/v0.9.5/views/i18n/honkpage.js b/v0.9.5/views/i18n/honkpage.js
new file mode 100644
index 0000000..49d2cf6
--- /dev/null
+++ b/v0.9.5/views/i18n/honkpage.js
@@ -0,0 +1,387 @@
+function getCookie(name){
+ if(document.cookie.length == 0)
+ return null;
+
+ var regSepCookie = new RegExp('(; )', 'g');
+ var cookies = document.cookie.split(regSepCookie);
+
+ for(var i = 0; i < cookies.length; i++){
+ var regInfo = new RegExp('=', 'g');
+ var infos = cookies[i].split(regInfo);
+ if(infos[0] == name){
+ return unescape(infos[1]);
+ }
+ }
+ return null;
+}
+
+
+function encode(hash) {
+ var s = []
+ for (var key in hash) {
+ var val = hash[key]
+ s.push(escape(key) + "=" + escape(val))
+ }
+ return s.join("&")
+}
+function post(url, data) {
+ var x = new XMLHttpRequest()
+ x.open("POST", url)
+ x.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
+ x.send(data)
+}
+function get(url, whendone) {
+ var x = new XMLHttpRequest()
+ x.open("GET", url)
+ x.responseType = "document"
+ x.onload = function() { whendone(x) }
+ x.send()
+}
+function bonk(el, xid) {
+ el.innerHTML = i18n_bonked
+ el.disabled = true
+ post("/bonk", encode({"js": "2", "CSRF": csrftoken, "xid": xid}))
+ return false
+}
+function unbonk(el, xid) {
+ el.innerHTML = i18n_unbonked
+ el.disabled = true
+ post("/zonkit", encode({"CSRF": csrftoken, "wherefore": "unbonk", "what": xid}))
+}
+function muteit(el, convoy) {
+ el.innerHTML = i18n_muted
+ el.disabled = true
+ post("/zonkit", encode({"CSRF": csrftoken, "wherefore": "zonvoy", "what": convoy}))
+ var els = document.querySelectorAll('article.honk')
+ for (var i = 0; i < els.length; i++) {
+ var e = els[i]
+ if (e.getAttribute("data-convoy") == convoy) {
+ e.remove()
+ }
+ }
+}
+function zonkit(el, xid) {
+ el.innerHTML = i18n_zonked
+ el.disabled = true
+ post("/zonkit", encode({"CSRF": csrftoken, "wherefore": "zonk", "what": xid}))
+ var p = el
+ while (p && p.tagName != "ARTICLE") {
+ p = p.parentElement
+ }
+ if (p) {
+ p.remove()
+ }
+}
+function flogit(el, how, xid) {
+ var s = how
+ if (honklang == 'en') {
+ if (s[s.length-1] != "e") { s += "e" }
+ s += "d"
+ if (s == "untaged") s = i18n_untagged
+ if (s == "reacted") s = i18n_badonked
+ } else {
+ if (s == "deack") s = i18n_deack
+ if (s == "ack") s = i18n_ack
+ if (s == "unsave") s = i18n_unsave
+ if (s == "save") s = i18n_save
+ if (s == "untag") s = i18n_untagged
+ if (s == "react") s = i18n_badonked
+ }
+
+ el.innerHTML = s
+ el.disabled = true
+ post("/zonkit", encode({"CSRF": csrftoken, "wherefore": how, "what": xid}))
+}
+
+var lehonkform = document.getElementById("honkform")
+var lehonkbutton = document.getElementById("honkingtime")
+
+function oldestnewest(btn) {
+ var els = document.getElementsByClassName("glow")
+ if (els.length) {
+ els[els.length-1].scrollIntoView()
+ }
+}
+function removeglow() {
+ var els = document.getElementsByClassName("glow")
+ while (els.length) {
+ els[0].classList.remove("glow")
+ }
+}
+
+function fillinhonks(xhr, glowit) {
+ var doc = xhr.responseXML
+ var stash = curpagestate.name + ":" + curpagestate.arg
+ tophid[stash] = doc.children[0].children[1].children[0].innerText
+ var srvmsg = doc.children[0].children[1].children[1]
+ var honks = doc.children[0].children[1].children[2].children
+
+ var srvel = document.getElementById("srvmsg")
+ while (srvel.children[0]) {
+ srvel.children[0].remove()
+ }
+ srvel.prepend(srvmsg)
+
+ var frontload = true
+ if (curpagestate.name == "convoy") {
+ frontload = false
+ }
+
+ var honksonpage = document.getElementById("honksonpage")
+ var holder = honksonpage.children[0]
+ var lenhonks = honks.length
+ for (var i = honks.length; i > 0; i--) {
+ var h = honks[i-1]
+ if (glowit)
+ h.classList.add("glow")
+ if (frontload) {
+ holder.prepend(h)
+ } else {
+ holder.append(h)
+ }
+
+ }
+ relinklinks()
+ return lenhonks
+}
+function hydrargs() {
+ var name = curpagestate.name
+ var arg = curpagestate.arg
+ var args = { "page" : name }
+ if (name == "convoy") {
+ args["c"] = arg
+ } else if (name == "combo") {
+ args["c"] = arg
+ } else if (name == "honker") {
+ args["xid"] = arg
+ }
+ return args
+}
+function refreshupdate(msg) {
+ var el = document.querySelector("#refreshbox p span")
+ if (el) {
+ el.innerHTML = msg
+ }
+}
+function refreshhonks(btn) {
+ removeglow()
+ btn.innerHTML = i18n_refreshing
+ btn.disabled = true
+ var args = hydrargs()
+ var stash = curpagestate.name + ":" + curpagestate.arg
+ args["tophid"] = tophid[stash]
+ get("/hydra?" + encode(args), function(xhr) {
+ var lenhonks = fillinhonks(xhr, true)
+ btn.innerHTML = "{{ .i18n.Duration }}"
+ btn.disabled = false
+ refreshupdate(" " + lenhonks + " new")
+ })
+}
+function statechanger(evt) {
+ var data = evt.state
+ if (!data) {
+ return
+ }
+ switchtopage(data.name, data.arg)
+}
+function switchtopage(name, arg) {
+ var stash = curpagestate.name + ":" + curpagestate.arg
+ var honksonpage = document.getElementById("honksonpage")
+ var holder = honksonpage.children[0]
+ holder.remove()
+ var srvel = document.getElementById("srvmsg")
+ var msg = srvel.children[0]
+ if (msg) {
+ msg.remove()
+ servermsgs[stash] = msg
+ }
+ showelement("refreshbox")
+
+ honksforpage[stash] = holder
+
+ curpagestate.name = name
+ curpagestate.arg = arg
+ // get the holder for the target page
+ var stash = name + ":" + arg
+ holder = honksforpage[stash]
+ if (holder) {
+ honksonpage.prepend(holder)
+ msg = servermsgs[stash]
+ if (msg) {
+ srvel.prepend(msg)
+ }
+ } else {
+ // or create one and fill it
+ honksonpage.prepend(document.createElement("div"))
+ var args = hydrargs()
+ get("/hydra?" + encode(args), function(xhr) { fillinhonks(xhr, false) })
+ }
+ refreshupdate("")
+}
+function newpagestate(name, arg) {
+ return { "name": name, "arg": arg }
+}
+function pageswitcher(name, arg) {
+ return function(evt) {
+ var topmenu = document.getElementById("topmenu")
+ topmenu.open = false
+ if (name == curpagestate.name && arg == curpagestate.arg) {
+ return false
+ }
+ switchtopage(name, arg)
+ var url = evt.srcElement.href
+ if (!url) {
+ url = evt.srcElement.parentElement.href
+ }
+ history.pushState(newpagestate(name, arg), "some title", url)
+ window.scrollTo(0, 0)
+ return false
+ }
+}
+function relinklinks() {
+ var els = document.getElementsByClassName("convoylink")
+ while (els.length) {
+ els[0].onclick = pageswitcher("convoy", els[0].text)
+ els[0].classList.remove("convoylink")
+ }
+ els = document.getElementsByClassName("combolink")
+ while (els.length) {
+ els[0].onclick = pageswitcher("combo", els[0].text)
+ els[0].classList.remove("combolink")
+ }
+ els = document.getElementsByClassName("honkerlink")
+ while (els.length) {
+ var el = els[0]
+ var xid = el.getAttribute("data-xid")
+ el.onclick = pageswitcher("honker", xid)
+ el.classList.remove("honkerlink")
+ }
+}
+(function() {
+ var el = document.getElementById("homelink")
+ el.onclick = pageswitcher("home", "")
+ el = document.getElementById("atmelink")
+ el.onclick = pageswitcher("atme", "")
+ el = document.getElementById("firstlink")
+ el.onclick = pageswitcher("first", "")
+ el = document.getElementById("savedlink")
+ el.onclick = pageswitcher("saved", "")
+ el = document.getElementById("longagolink")
+ el.onclick = pageswitcher("longago", "")
+ relinklinks()
+ window.onpopstate = statechanger
+ history.replaceState(curpagestate, "some title", "")
+})();
+(function() {
+ hideelement("donkdescriptor")
+})();
+function showhonkform(elem, rid, hname) {
+ var form = lehonkform
+ form.style = "display: block"
+ if (elem) {
+ form.remove()
+ elem.parentElement.parentElement.parentElement.insertAdjacentElement('beforebegin', form)
+ } else {
+ hideelement(lehonkbutton)
+ elem = document.getElementById("honkformhost")
+ elem.insertAdjacentElement('afterend', form)
+ }
+ var ridinput = document.getElementById("ridinput")
+ if (rid) {
+ ridinput.value = rid
+ honknoise.value = hname + " "
+ } else {
+ ridinput.value = ""
+ honknoise.value = ""
+ }
+ var updateinput = document.getElementById("updatexidinput")
+ updateinput.value = ""
+ document.getElementById("honknoise").focus()
+ return false
+}
+function cancelhonking() {
+ hideelement(lehonkform)
+ showelement(lehonkbutton)
+}
+function showelement(el) {
+ if (typeof(el) == "string")
+ el = document.getElementById(el)
+ if (!el) return
+ el.style.display = "block"
+}
+function hideelement(el) {
+ if (typeof(el) == "string")
+ el = document.getElementById(el)
+ if (!el) return
+ el.style.display = "none"
+}
+function updatedonker() {
+ var el = document.getElementById("donker")
+ el.children[1].textContent = el.children[0].value.slice(-20)
+ var el = document.getElementById("donkdescriptor")
+ el.style.display = ""
+ var el = document.getElementById("saveddonkxid")
+ el.value = ""
+}
+var checkinprec = 100.0
+var gpsoptions = {
+ enableHighAccuracy: false,
+ timeout: 1000,
+ maximumAge: 0
+};
+function fillcheckin() {
+ if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition(function(pos) {
+ showelement("placedescriptor")
+ var el = document.getElementById("placelatinput")
+ el.value = Math.round(pos.coords.latitude * checkinprec) / checkinprec
+ el = document.getElementById("placelonginput")
+ el.value = Math.round(pos.coords.longitude * checkinprec) / checkinprec
+ checkinprec = 10000.0
+ gpsoptions.enableHighAccuracy = true
+ gpsoptions.timeout = 2000
+ }, function(err) {
+ showelement("placedescriptor")
+ el = document.getElementById("placenameinput")
+ el.value = err.message
+ }, gpsoptions)
+ }
+}
+
+window.getCookie = function(name) {
+ var match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
+ if (match) return match[2];
+}
+
+var honklang = getCookie('lang')
+switch(honklang) {
+ case "fr":
+ var i18n_deack = "marqué non lu"
+ var i18n_ack = "marqué lu"
+ var i18n_unsave = "oublié"
+ var i18n_save = "sauvegardé"
+
+ var i18n_bonked = "reposté"
+ var i18n_unbonked = "départagé"
+ var i18n_muted = "mis en sourdine"
+ var i18n_badonked = "réagi"
+ var i18n_zonked = "supprimé"
+ var i18n_untagged = "débalisé"
+ var i18n_refreshing = "rafraîchissement"
+ break;
+
+ default:
+ var i18n_deack = "deacked"
+ var i18n_ack = "ack"
+ var i18n_unsave = "unsave"
+ var i18n_save = "save"
+
+ var i18n_bonked = "bonked"
+ var i18n_unbonked = "unbonked"
+ var i18n_muted = "muted"
+ var i18n_badonked = "badonked"
+ var i18n_untagged = "untagged"
+ var i18n_zonked = "zonked"
+ var i18n_refreshing = "refreshing"
+
+}
diff --git a/v0.9.5/views/i18n/login.html b/v0.9.5/views/i18n/login.html
new file mode 100644
index 0000000..e5ea195
--- /dev/null
+++ b/v0.9.5/views/i18n/login.html
@@ -0,0 +1,12 @@
+{{ template "header.html" . }}
+<main>
+<div class="info">
+{{ .LoginMsg }}
+<form action="/dologin" method="POST">
+ <p><input tabindex=1 type="text" name="username" autocomplete=off> -
+ {{ .i18n.Username }}
+ <p><input tabindex=1 type="password" name="password"> - {{ .i18n.Pwd }}
+ <p><button tabindex=1 name="login" value="login">{{ .i18n.Login }}</button>
+</form>
+</div>
+</main>
diff --git a/v0.9.5/views/i18n/msg.html b/v0.9.5/views/i18n/msg.html
new file mode 100644
index 0000000..b729caa
--- /dev/null
+++ b/v0.9.5/views/i18n/msg.html
@@ -0,0 +1,7 @@
+{{ template "header.html" . }}
+<main>
+<div class="info">
+<p>
+{{ .ServerMessage }}
+</div>
+</main>
diff --git a/v0.9.5/views/i18n/onts.html b/v0.9.5/views/i18n/onts.html
new file mode 100644
index 0000000..4a208c8
--- /dev/null
+++ b/v0.9.5/views/i18n/onts.html
@@ -0,0 +1,16 @@
+{{ template "header.html" . }}
+<main>
+<div class="info">
+<p>{{ .i18n.Onts }}
+{{ $letter := 0 }}
+<ul>
+{{ range .Onts }}
+{{ if not (eq $letter (index .Name 0)) }}
+{{ $letter = (index .Name 0) }}
+<li><p>
+{{ end }}
+<span style="white-space: nowrap;"><a href="/o/{{ .Name }}">#{{ .Name }}</a> ({{ .Count }})</span>
+{{ end }}
+</ul>
+</div>
+</main>
diff --git a/v0.9.5/views/i18n/pleroma.css b/v0.9.5/views/i18n/pleroma.css
new file mode 100644
index 0000000..b25439a
--- /dev/null
+++ b/v0.9.5/views/i18n/pleroma.css
@@ -0,0 +1,7 @@
+html {
+ --bg-page: #1b2735;
+ --bg-dark: #121a24;
+ --fg: #b9b9ba;
+ --hl: #d8a070;
+ --fg-subtle: rgba(185, 185, 186, 0.5);
+}
diff --git a/v0.9.5/views/i18n/style.css b/v0.9.5/views/i18n/style.css
new file mode 100644
index 0000000..b1da23d
--- /dev/null
+++ b/v0.9.5/views/i18n/style.css
@@ -0,0 +1,334 @@
+html {
+ --bg-page: #306;
+ --bg-dark: #002;
+ --fg: #dcf;
+ --hl: #dcf;
+ --fg-subtle: #a9c;
+ --fg-limited: #a79;
+}
+
+body {
+ background: var(--bg-page);
+ color: var(--fg);
+ font-size: 1em;
+ word-wrap: break-word;
+ font-family: sans-serif, "Noto Color Emoji";
+ line-height: 1.2;
+ overscroll-behavior-y: contain;
+}
+pre, code {
+ white-space: pre-wrap;
+}
+blockquote {
+ margin-left: 0em;
+ padding-left: 0.5em;
+ border-left: 1px solid var(--fg-subtle);
+}
+blockquote cite {
+ margin-left: 2em;
+}
+table {
+ display: block;
+ max-width: 100%;
+ overflow-x: auto;
+}
+a {
+ color: var(--fg);
+}
+form, input, textarea {
+ font-family: monospace, "Noto Color Emoji";
+}
+p {
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+input {
+ background: var(--bg-page);
+ color: var(--fg);
+ font-size: 1.0em;
+ line-height: 1.2em;
+ padding: 0.4em;
+}
+#honkform input {
+ font-size: 0.8em;
+}
+body > header {
+ margin: 1em auto;
+ font-size: 1.5em;
+}
+body > header span {
+ margin-left: 2em;
+}
+body > header p {
+ padding: 1em;
+}
+header > details {
+ background: var(--bg-page);
+ padding: 1em 1em 1em 1em;
+ position: fixed;
+ top: 0;
+ left: 0;
+ display: inline;
+ max-height: calc(100% - 1em);
+ overflow: auto;
+ opacity: 0.7;
+ overscroll-behavior: contain;
+}
+header > details[open] {
+ padding: 1em 1em 0em 1em;
+ background: var(--bg-dark);
+ border: 1px solid var(--hl);
+ margin-bottom: 1em;
+ opacity: 1.0;
+}
+header > details summary span {
+ display: none;
+}
+header > details[open] summary span {
+ display: inline;
+}
+header > details li {
+ margin: 1em 0em 1em 0em;
+}
+details summary {
+ display: inline;
+}
+@supports (-moz-appearance:none) {
+ details summary {
+ display: list-item;
+ }
+}
+main {
+ max-width: 1200px;
+ margin: auto;
+ font-size: 1.5em;
+}
+hr {
+ border-color: var(--hl);
+}
+.info {
+ background: var(--bg-dark);
+ border: 1px solid var(--hl);
+ margin-bottom: 1em;
+ padding: 0em 1em 0em 1em;
+}
+.info div {
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+label {
+ font-size: 0.8em;
+}
+label.button, button, select {
+ font-size: 16px;
+ font-family: monospace;
+ color: var(--fg);
+ background: var(--bg-page);
+ border: 1px solid var(--hl);
+ padding: 0.5em;
+ white-space: nowrap;
+}
+.buttonarray {
+ margin-top: -2.0em;
+}
+.buttonarray button, .buttonarray > span {
+ margin-top: 2.0em;
+ display: inline-block;
+}
+button a {
+ text-decoration: none;
+}
+form {
+ margin-top: 1em;
+}
+textarea {
+ padding: 0.5em;
+ font-size: 1em;
+ background: var(--bg-page);
+ color: var(--fg);
+ width: 600px;
+ height: 4em;
+ margin-bottom: 0.5em;
+ box-sizing: border-box;
+ max-width: 100%;
+}
+textarea#honknoise {
+ height: 10em;
+}
+input[type="checkbox"] {
+ position: fixed;
+ top: -9999px;
+}
+input[type="checkbox"] + span:after {
+ content: "no";
+}
+input[type="checkbox"]:checked + span:after {
+ content: "yes";
+}
+input[type="checkbox"]:focus + span:after {
+ outline: 1px solid var(--fg);
+}
+input[type=file] {
+ display: none;
+}
+
+.glow {
+ /* box-shadow: 0px 0px 16px var(--hl);*/
+ box-shadow: 0px 0px 16px black;
+}
+
+.honk {
+ margin: auto;
+ background: var(--bg-dark);
+ border: 1px solid var(--hl);
+ border-radius: 1em;
+ margin-bottom: 1em;
+ padding-left: 1em;
+ padding-right: 1em;
+ padding-top: 0;
+ overflow: hidden;
+}
+
+.chat {
+ border-bottom: 0.5px solid var(--fg-subtle);
+ padding-left: 1em;
+}
+.chat p {
+ margin-top: 0.2em;
+ margin-bottom: 0.2em;
+}
+.chattarget {
+ border-bottom: 1px solid var(--fg-subtle);
+}
+.chatstamp {
+ margin-left: -1em;
+}
+
+.honk #honkform {
+ padding: 1em;
+ border: 1px solid var(--fg);
+ }
+.honk a {
+ color: var(--fg);
+ }
+.honk header {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-size: 0.8em;
+ line-height: 1.1;
+ margin-top: 1em;
+ height: 64px;
+ }
+
+.honk header .clip a {
+ color: var(--fg-subtle);
+ }
+.honk header img {
+ float: left;
+ margin-right: 1em;
+ width: 64px;
+ height: 64px;
+ }
+.honk header p {
+ margin-top: 0px;
+ }
+.honk .actions button {
+ margin-left: 4em;
+ margin-top: 2em;
+ }
+.honk .noise {
+ line-height: 1.4;
+ }
+
+.honk .noise code .kw { font-weight: bold; }
+.honk .noise code .bi { font-weight: bold; }
+.honk .noise code .st { color: var(--fg-subtle); }
+.honk .noise code .nm { color: #ba88ff; }
+.honk .noise code .op { color: #ba88ff; }
+.honk .noise code .tp { font-weight: bold; }
+.honk .noise code .cm { color: var(--fg-subtle); font-style: italic; }
+.honk .noise code .al { color: #aaffbb; }
+.honk .noise code .dl { color: #ffaabb; }
+
+.honk details.actions summary {
+ color: var(--fg-subtle);
+}
+.subtle .noise {
+ color: var(--fg-subtle);
+ font-size: 0.8em;
+}
+.subtle .noise a {
+ color: var(--fg-subtle);
+}
+.limited {
+ border: 1px solid var(--fg-limited);
+ color: var(--fg-limited);
+}
+.limited .glow {
+ box-shadow: 0px 0px 16px var(--fg-limited);
+}
+.limited .noise {
+ color: var(--fg-limited);
+ }
+.limited .noise a {
+ color: var(--fg-limited);
+ }
+.limited details.actions summary {
+ color: var(--fg-limited);
+ }
+details.noise[open] summary {
+ display: none;
+}
+h1, h2 {
+ font-size: 1.2em;
+}
+h3, h4 {
+ font-size: 1.1em;
+}
+
+img:not(.emu) {
+ background: var(--bg-page);
+}
+img, video {
+ max-width: 100%;
+ max-height: 600px;
+}
+.noise img:not(.emu) {
+ display: block;
+}
+img.emu {
+ width: 2em;
+ height: 2em;
+ vertical-align: middle;
+ margin: -2px;
+ object-fit: contain;
+}
+@media screen and (max-width: 740px) {
+ body {
+ font-size: 12px;
+ }
+ .honk header {
+ height: 52px;
+ }
+ .honk header img {
+ width: 48px;
+ height: 48px;
+ }
+ details summary {
+ outline: none;
+ }
+}
+@media print {
+ #topmenu, #topspacer, #infobox, #refreshbox, .actions {
+ display: none;
+ }
+
+ html {
+ --bg-page: white;
+ --bg-dark: white;
+ --fg: black;
+ --fg-subtle: black;
+ --fg-limited: #a79;
+ }
+}
diff --git a/v0.9.5/views/i18n/xzone.html b/v0.9.5/views/i18n/xzone.html
new file mode 100644
index 0000000..e2ab0d7
--- /dev/null
+++ b/v0.9.5/views/i18n/xzone.html
@@ -0,0 +1,17 @@
+{{ template "header.html" . }}
+<main>
+<div class="info">
+<form action="/ximport" method="POST">
+<input type="hidden" name="CSRF" value="{{ .XCSRF }}">
+<p><span class="title">{{ .i18n.Import }}</span>
+<p><input tabindex=1 type="text" name="xid" autocomplete=off> - xid
+<p><button tabindex=1 name="fetch" value="{{ .i18n.Fetch }}">{{ .i18n.Fetch }}</button>
+</form>
+</div>
+{{ $i18n := .i18n }}
+{{ range .Honkers }}
+<section class="honk">
+<p><a href="/h?xid={{ .XID }}">{{ $i18n.Honks }}</a> {{ $i18n.HonksFrom }} {{ .Handle }}
+</section>
+{{ end }}
+</main>