1
0

Moving to a Templ template engine stage 1.

This commit is contained in:
Alexander Andreev 2024-06-13 04:49:55 +04:00
parent 2667ec93ce
commit 52439ee836
Signed by: Arav
GPG Key ID: 25969B23DCB5CA34
10 changed files with 299 additions and 7 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
.vscode
*.pug.go
*_templ.go
bin/dwelling-home
*.sqlite
*.sqlite*

View File

@ -12,7 +12,7 @@ LDFLAGS=-ldflags "-s -w -X main.version=${VERSION}" -tags osusergo,netgo
.PHONY: run install uninstall clean
${TARGET}: web/*.pug.go
${TARGET}: web/*_templ.go
go build -o bin/$@ ${LDFLAGS} ${FLAGS} cmd/$@/main.go
web/*.pug.go: web/templates/*.pug
@ -21,6 +21,12 @@ ifeq (,$(wildcard $(shell go env GOPATH)/bin/jade))
endif
go generate web/web.go
web/*_templ.go: web/*.templ
ifeq (,$(wildcard $(shell go env GOPATH)/bin/templ))
go install github.com/a-h/templ/cmd/templ@latest
endif
TEMPL_EXPERIMENT=rawgo $(shell go env GOPATH)/bin/templ generate
run:
bin/${TARGET} -listen 127.0.0.1:18123 -database-path . -captcha-expiry 10m \
-guestbook-page-size 10

5
go.mod
View File

@ -1,10 +1,13 @@
module git.arav.su/Arav/dwelling-home
go 1.17
go 1.21
toolchain go1.22.4
require (
git.arav.su/Arav/justcaptcha/v2 v2.1.0
git.arav.su/Arav/justguestbook v1.3.2
github.com/a-h/templ v0.2.707
github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386
github.com/pkg/errors v0.9.1
)

5
go.sum
View File

@ -4,12 +4,16 @@ git.arav.su/Arav/justcaptcha/v2 v2.1.0 h1:EFerW2mP60rDczu1mPbJb7DiDeAQOcw83/KiWc
git.arav.su/Arav/justcaptcha/v2 v2.1.0/go.mod h1:Ntab6TUAqCo/H9LSWOd4IQ9ANnp8G1PEygXtORcdTOY=
git.arav.su/Arav/justguestbook v1.3.2 h1:B1AzS14dW3bVlp0Qj6pu1PrJlWaPK1mplmx3UNBcxo4=
git.arav.su/Arav/justguestbook v1.3.2/go.mod h1:Umo/AzSOKu+OU+03V7/QQ6m6N0nXyeRAGOZT4P5ol98=
github.com/a-h/templ v0.2.707 h1:T1Gkd2ugbRglZ9rYw/VBchWOSZVKmetDbBkm4YubM7U=
github.com/a-h/templ v0.2.707/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydLhM8=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 h1:EcQR3gusLHN46TAD+G+EbaaqJArt5vHhNpXAa12PQf4=
github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
@ -44,7 +48,6 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

View File

@ -1,6 +1,7 @@
package http
import (
"context"
"log"
"math"
"net/http"
@ -36,7 +37,7 @@ func NewHandlers(captchaExpire time.Duration, owner string, gbPageSize int64,
}
func (h *Handlers) Index(w http.ResponseWriter, r *http.Request) {
web.Index("", w)
web.Index().Render(context.Background(), w)
}
func (h *Handlers) Privacy(w http.ResponseWriter, r *http.Request) {
@ -146,7 +147,7 @@ func (h *Handlers) Guestbook(w http.ResponseWriter, r *http.Request) {
dwc := dwcaptcha.NewDwellingCaptcha(h.captchaExpire)
_, id := inmemdb.New(r.RemoteAddr, dwc)
web.Guestbook("/ Guestbook", h.owner, string(id), pageCount, entries, r, w)
web.Guestbook(string(id), h.owner, entries, pageCount, r).Render(context.Background(), w)
}
func (h *Handlers) GuestbookAdmin(w http.ResponseWriter, r *http.Request) {
@ -174,7 +175,7 @@ func (h *Handlers) RSS(w http.ResponseWriter, r *http.Request) {
scheme = "http"
}
web.RSS(scheme+"://"+r.Host, h.owner, posts, r, w)
web.RSS(scheme+"://"+r.Host, h.owner, posts, r).Render(context.Background(), w)
}
func ServeAsset(path string) func(http.ResponseWriter, *http.Request) {
@ -186,7 +187,7 @@ func ServeAsset(path string) func(http.ResponseWriter, *http.Request) {
func Error(w http.ResponseWriter, code int, reason, message, referer string) {
w.WriteHeader(code)
web.ErrorXXX("/ "+http.StatusText(code), reason, message, referer, code, w)
web.ErrorXXX(code, reason, message, referer).Render(context.Background(), w)
}
func cleanupNewlines(text string) (out string) {

59
web/base.templ Normal file
View File

@ -0,0 +1,59 @@
package web
import "strings"
templ base(title, description, keywords, canonical, heading string, head templ.Component) {
<!doctype html>
<html>
<head>
if title != "" {
<title>{ title } - Arav's dwelling</title>
} else {
<title>Arav's dwelling</title>
}
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#cd2682" />
<meta name="description" content={ description } />
<meta name="keywords" content={ keywords } />
<link rel="icon" href="/assets/img/favicon.svg" sizes="any" type="image/svg+xml" />
<link rel="stylesheet" href="/assets/css/main.css" />
<link rel="alternate" href="/rss.xml" type="application/rss+xml" title="Arav's dwelling" />
<link rel="canonical" href={ string(templ.URL(canonical)) }/>
@head
</head>
<body>
<header>
<svg id="logo" viewBox="0 -25 216 40">
<text class="logo">Arav's dwelling</text>
<text class="under" y="11">Welcome to my sacred place, wanderer</text>
</svg>
<nav>
@navigation(heading)
</nav>
</header>
{ children... }
<footer>
<a href="/rss.xml" title="Stay up to date on what's going on.">RSS feed</a>
<br>
&copy; 2017&mdash;2024 Alexander &quot;Arav&quot; Andreev &lt;<a href="mailto:me@arav.su">me@arav.su</a>&gt; <a href="/privacy">Privacy statements</a>
</footer>
</body>
</html>
}
var navElements = []string{"Stuff", "Mindflow", "About", "Guestbook"}
templ navigation(heading string) {
for _, n := range navElements {
if n == heading {
continue
}
<a href={ templ.SafeURL("/" + strings.ToLower(n)) }>{ n }</a>
}
if heading != "" {
<h1>{ heading }</h1>
}
}

26
web/errorXXX.templ Normal file
View File

@ -0,0 +1,26 @@
package web
import "fmt"
import "net/http"
templ ErrorXXX(errCode int, reason, message, referer string) {
{{ errText := http.StatusText(errCode) }}
@base(errText, errText,
"", "", errText, nil) {
<section id="error">
<h1>{ fmt.Sprint(errCode) }</h1>
{ errText }
if reason != "" {
<p>{ reason }</p>
}
if message != "" {
<p>{ message }</p>
}
</section>
if referer != "" {
<section>
<h2><a href={ templ.URL(referer) }>Go back</a></h2>
</section>
}
}
}

88
web/guestbook.templ Normal file
View File

@ -0,0 +1,88 @@
package web
import "fmt"
import "strconv"
import "strings"
import "net/http"
import "git.arav.su/Arav/justguestbook"
import "git.arav.su/Arav/dwelling-home/pkg/util"
templ Guestbook(captchaID, owner string, entries []*justguestbook.Entry, pageCount int64, r *http.Request) {
@base("Guestbook",
"This is my guestbook. Welcome.",
"guestbook, personal", "/guestbook", "Guestbook", guestbookHead()) {
<form id="new-post" action="/api/guestbook" method="POST">
<input type="text" name="name" maxlength="80" placeholder="Name (Anonymous if left blank)" />
<input type="text" name="website" maxlength="255" placeholder="Website (optional)" />
<textarea name="message" maxlength="4096" placeholder="Your message" required></textarea>
<span class="checkboxes">
<input type="checkbox" name="hide_website" id="hide-website" checked />
<label for="hide-website">Hide website <small>(only I can see if set)</small></label>
<br>
<small>Use &gt; to make a quote.</small>
</span>
<span class="captcha">
<input type="hidden" name="captcha_id" value={ captchaID } />
<img src={ string(templ.URL(fmt.Sprintf("/api/captcha/%s/image", captchaID))) } alt="CAPTCHA" width="160" height="40" />
<input type="text" name="captcha_answer" maxlength="6" placeholder="CAPTCHA" required />
<small>Valid for <b>10</b> minutes.</small>
</span>
<input type="submit" value="Send a post" />
</form>
<section id="posts">
for _, entry := range entries {
<article>
<header>
{{ created_tz := util.ToClientTimezone(entry.Created, r).Format("Monday _2 January 2006 15:04:05 -07:00") }}
{{ website := "" }}
if !entry.HideWebsite {
{{ website = entry.Website }}
}
{ strconv.FormatInt(entry.ID, 10) } by <span class="highlighted">{ entry.Name }</span> <em>{ website }</em> on <time datetime={ created_tz }>{ created_tz }</time>
</header>
for _, line := range strings.Split(entry.Message, "\n") {
if len(line) > 0 && line[0] == '>' {
<p class="quote">{ line }</p>
} else {
<p>{ line }</p>
}
}
if entry.Reply != nil {
<div class="reply">
<header>
{{ reply_created_tz := util.ToClientTimezone(entry.Reply.Created, r).Format("Monday _2 January 2006 15:04:05 -07:00") }}
Reply by <span class="highlighted">{ owner }</span> in <time datetime={ reply_created_tz }>{ reply_created_tz }</time>
</header>
for _, line := range strings.Split(entry.Reply.Message, "\n") {
if len(line) > 0 && line[0] == '>' {
<p class="quote">{ line }</p>
} else {
<p>{ line }</p>
}
}
</div>
}
</article>
}
if len(entries) == 0 {
<p class="center">No posts.</p>
}
</section>
if pageCount > 1 {
<section id="pagination">
{{ var n int64 = 1 }}
for ; n <= pageCount; n++ {
{{ strN := strconv.FormatInt(n, 10) }}
<a href={ templ.URL("/guestbook?p="+strN) }>{ strN }</a>
}
</section>
}
}
}
templ guestbookHead() {
<link rel="stylesheet" href="/assets/css/guestbook.css" />
<script src="/assets/js/captcha_refresh.js" defer></script>
}

67
web/index.templ Normal file
View File

@ -0,0 +1,67 @@
package web
templ Index() {
@base("",
"A homepage of a russian guy Alexander aka Arav. Not just homepage, but something more...", "/",
"homepage, personal, blog, services, self-hosting", "", nil) {
<section id="services">
<span>
<a href="https://arav.su">arav.su</a>
.
<a href="http://moq7aejnf4xk5k2bkaltli3ftkhusy2mbrd3pj23nrca343ku2mgk4yd.onion">onion</a>
.
<a href="http://[300:a98d:d6d0:8a08::f]">ygg</a>
.
<a href="http://arav.i2p">i2p</a>
<sup><a href="http://arav.i2p/?i2paddresshelper=5Kl-DiWbbk6wf7m0v6zBSNHYq3sXlnrWLIWVeGdpPbPyc9CBS~zrzDYpP43rv1fRiIkbVCD5hTEpY6joQGlk-dFkWWD6201qa6ecsDVQMaE3Q7UTYICd0VEBRoqDUSrvsM-P2y5oG4Z-77RmoGKpbcRgNuMVbQ7AGJNqVSGej-lSyscDWTIZT5dCT505lfRwprdD~emZqkwnn22X16Wpj-X4A4ifph4idrThGioz4UW6PrCpa-oebMCo217s0Zyl9VKaU-o9cx5eFUEwnshoUjqwh7VE-S45NDz854J08xldCATM3wwTRVXhc2NUypsJLKFKiV0z3EXN-ApCdxsV60C-eiXUTX5vYcHHH~imA79v8WKFybjnsyUBst5BBEPQIUifTceLUrTmQ9TUpaMV90EsD5SCshmCfOs8R5y2dK6EfQu8iyYAB5VFSH4M1CLiBZUsDTEFiOomn2JGMDnbPho8lMB8ss4SMuwZShb2LlGqLxJ38kRHlvC68VmJO7InBQAEAAcAAA==" title="Address helper">ah</a></sup>
</span>
<a href="https://arav.neocities.org">arav.neocities.org</a>
<br>
<span>
<a href="https://radio.arav.su">radio</a>
.
<a href="http://wsmkgnmhmzqm7kyzv7jnzzafvgm7xlmlfvzhgorpapd5or2arnhuktqd.onion">onion</a>
.
<a href="http://[300:a98d:d6d0:8a08::e]">ygg</a>
.
<a href="http://radio.arav.i2p">i2p</a>
<sup><a href="http://radio.arav.i2p/?i2paddresshelper=NfCKBu9vjLFiBMEPQGiZT9AzGlhkKHzYrKM66FL-ESeDbnYUY--NzukO9UA28s3WThhDQVge2TmyfYsaZiUw~AjuLsykxS13pebs7lkAVY1jm77La-eFFIAQ22Vtd2YgS0vbhRMzuDxKkCR1vPwNax8R2o6a07xsQvvDml6UQxG4p5vt44JA2geQNvQfm8cEiSa6gNJZJSW3rWuLDg6~1Jy3D70oSVSlNfihmG4JtNV6tVBjJE2h5gUxfhYZACttpGTPM~UNF~lrSujlBQsCqdzvLswdMw~FnvpfGzJcJroeFTerRyH6oUkkDSOK7uWwl0e70vKxrIbFgJjKtjlLWlUCI5N0TnJP4Hzt2pttB~R0hSr2vVl8ky0yJEtN3rwnrJkw7q0ZIH30ngTfxsCTbolAzl6liN9Ez5YF97zDOPnVFmvQ6Eg1PyFdypQO1PiUHqF56SWhx3utGwecUS6jJCvsKIJJVEIMVcD6h7S0z1g1rqQ4jbg5UfXPwFEgFOlzBQAEAAcAAA==" title="Address helper">ah</a></sup>
</span>
<span>
<a href="https://files.arav.su">files</a>
.
<a href="http://qf5e43nlhvnrutmikuvbdfj3cmtthokpbaxtkm6mjlslttzvtgm4fxid.onion">onion</a>
.
<a href="http://[300:a98d:d6d0:8a08::e]">ygg</a>
.
<a href="http://files.arav.i2p">i2p</a>
<sup><a href="http://files.arav.i2p/?i2paddresshelper=48vtYgeVnju7B2FaR0zxUL3MQXN9QjK~Ggya45aANwm86mtpemuEkaskJmEQaFSd4FcDAFIiXHfNpfGqoupLwNmtgBmGRcuVV8xb2W~W6lM0oOhovjB37EUaMWs3AI5aIES84QOqApgwYX-ANIcwa~Kg6AbMuX8D8qnejuhBbuCffYah-TD8e~O0cnyqxzLTmxIGCyk2egdYXwanJyYFDocomIVfcqfJ0MgjIHhFQtkcb0e84bxvDzcAFIpEDrzAo4GVrFn-TCu0Lyf2ccqmVpucFl0UGhuVRxEt19KLd3PxlfwHv2lmzTZtq9CbnfaoPntUPx1sf84QnZDmrXWhVK8p3VvuPZMxjyz9KyhPjrGkO4E0oibDlvKuMsGEm-GkZsKxgXo~CrdcVtN8suAwW6rACAuk8gq2jUMBZBZ12migPZ7miHftEkOFHfgfUiKBwirrw~y9Zi261WX4-EVe2oD4pkhQOrqOKIoI-vv5z9CpQ7PKL531kgkipcBseXybBQAEAAcAAA==" title="Address helper">ah</a></sup>
</span>
<span>
<a href="https://upload.arav.su">upload</a>
.
<a href="http://4usftbmjpfexkr2x5xbp5ukmygpmg4fgrnx2wbifsexqctooz5hmviyd.onion">onion</a>
.
<a href="http://[300:a98d:d6d0:8a08::e]">ygg</a>
.
<a href="http://upload.arav.i2p">i2p</a>
<sup><a href="http://upload.arav.i2p/?i2paddresshelper=b5NWA2vNydWSv6~8KN4e~td2UVGkYsayKPa1PnXI87A3gsg6m978tIehHLVN4XcCfUq4aB-59hqqZicorRnHKfV3lVdx9mdhC8Bhj~bMAcwMgWoXidqZNrWMoFGzWotFsa3nWh4zsRUSfrokecC8u9Y06byfSS1siyak0J6xpsggXRqqgNF0-8ncPeqvzBxHB9NRDXWEVJGS9HSpydWl1UpjgZffcd~NZroxkSAfughHcFAn2OLKkaZRe6WqCJQfJoXTCyz4wkFmYbH1CSddWlddmWaaU7icsbQrZm3XEqKTVKvm86G6ehxmzyHqCumc4GOWswcP0E51UQVOv-WA8R6SWQAj6ZnZhnCoCNFFEfW2lBiDmTnLJbfm-C-AdI6G1~dQ1~3FCH6wXWy-2DebpyoVVt9epzU7l4l2MVeaOUahbf6wcol1UbxPoR0XlGCXDe9700TYePjtpOU9vNkk2B1dQiZ1usgwseYuO26cRogSvbi8poz4BlCNO733HR1XBQAEAAcAAA==" title="Address helper">ah</a></sup>
</span>
<span>
<a href="https://git.arav.su">git</a>
.
<a href="http://qqitm7qlsbbubwmjos4cqzmvkqidg34rfnbyhuydhalep33fbvh22xyd.onion">onion</a>
.
<a href="http://[300:a98d:d6d0:8a08::e]">ygg</a>
.
<a href="http://git.arav.i2p">i2p</a>
<sup><a href="http://git.arav.i2p/?i2paddresshelper=eFIfcBUv3lHFSnglHfncs5XXtYwm9gCpmAYuio~9CeENBAXKRggPiY1tQC-otCon2hCSpr56WlVBeZk1txKuUnbjHTN7GBFaKW5wJEO2WmKEWPKdcjUDOYZN0D3TwXaYfiBuELD3200lBfDmPEJ01iC2o7B5yvpOqtEKDcaqkIp4vafDuPPumJ~XiCGdUAe~vr52w3Tbuz5x7wbltk-gUELY0-ZAQBos4jOJ6QT1W1lhycHPhAK8qslgwfk94opyIl2pkRyuJhU-2VHc6Fsd621VXC86YAMT1SIfTZlFpoGVCFXDM~BXaLvygFaKf62qardAe0T48Ax6GxosAKXe-yLCVRaiD3KErULfwZXl23kQzRfxM4odG4DWeXawtuvypOmTjHT1skQHU0h52ujye5nT~2bOy14HkCoCnxJ7gSj3MjkmWLd1JhBsPH4ymRmI7jFJR1GYl8Wp5IigMBBzWfJUEEjS7QDHaRo5TCZJ9SXz6sgkGdfh74~r8FWL559gBQAEAAcAAA==" title="Address helper">ah</a></sup>
</span>
</section>
<section>
<p>
Welcome, Anon. I'm Alexander aka Arav, I self-host some services for myself, friends and you. Not much I have to offer, but maybe you'll find something useful for yourself. :)
</p>
</section>
}
}

38
web/rss.templ Normal file
View File

@ -0,0 +1,38 @@
package web
import "time"
import "strings"
import "net/http"
import "git.arav.su/Arav/dwelling-home/pkg/mindflow"
import "git.arav.su/Arav/dwelling-home/pkg/util"
templ RSS(host, author string, posts []mindflow.Post, r *http.Request) {
@templ.Raw("<?xml version=\"1.0\" encoding=\"utf-8\" ?>")
<rss version="2.0">
<channel>
<title>Arav's dwelling</title>
<description>What's going on with me and my machines.</description>
<language>en-gb</language>
<link>{ host }</link>
for _, post := range posts {
<item>
<title>{ post.Title }</title>
<category>{ post.Category.Name }</category>
<guid>{ post.PostID() }</guid>
<pubDate>{ util.ToClientTimezone(post.Date, r).Format(time.RFC1123Z) }</pubDate>
<link>{ post.PostURL(r.Host, true) }</link>
<author>{ author }</author>
<description>
@templ.Raw("<![CDATA[")
for _, line := range strings.Split(post.Body, "\n") {
<p>{ line }</p>
}
@templ.Raw("]]>")
</description>
</item>
}
</channel>
</rss>
}