Remove useless template includes and start rendering parts of the HTML directly with Go instead of templates

pull/25/head
Jan-Lukas Else 9 months ago
parent a3517a9a97
commit fa82364b70
  1. 2
      go.mod
  2. 4
      go.sum
  3. 8
      render.go
  4. 10
      templateAssets.go
  5. 10
      templates/author.gohtml
  6. 38
      templates/base.gohtml
  7. 4
      templates/blogroll.gohtml
  8. 4
      templates/blogstats.gohtml
  9. 4
      templates/comment.gohtml
  10. 8
      templates/editorpreview.gohtml
  11. 16
      templates/footer.gohtml
  12. 4
      templates/geomap.gohtml
  13. 25
      templates/header.gohtml
  14. 2
      templates/index.gohtml
  15. 22
      templates/interactions.gohtml
  16. 2
      templates/login.gohtml
  17. 19
      templates/mentions.gohtml
  18. 5
      templates/oldcontentwarning.gohtml
  19. 49
      templates/post.gohtml
  20. 18
      templates/postactions.gohtml
  21. 12
      templates/postmeta.gohtml
  22. 15
      templates/posttax.gohtml
  23. 8
      templates/statichome.gohtml
  24. 19
      templates/trackdetails.gohtml
  25. 6
      templates/trackheader.gohtml
  26. 1
      testdata/interactionstest.html
  27. 4
      tts.go
  28. 216
      ui.go
  29. 154
      ui_test.go

@ -55,7 +55,7 @@ require (
github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38
github.com/yuin/goldmark-highlighting v0.0.0-20210516132338-9216f9c5aa01
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f
golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/text v0.3.7
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b

@ -533,8 +533,8 @@ golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM=
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d h1:62NvYBuaanGXR2ZOfwDFkhhl6X1DUgf8qg3GuQvxZsE=
golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=

@ -63,6 +63,12 @@ func (a *goBlog) initRendering() error {
"liketitle": a.likeTitle,
"photolinks": a.photoLinks,
"gettrack": a.getTrack,
// Code based rendering
"posttax": a.renderPostTax,
"oldcontentwarning": a.renderOldContentWarning,
"interactions": a.renderInteractions,
"author": a.renderAuthor,
"tor": a.renderTorNotice,
// Others
"dateformat": dateFormat,
"isodate": isoDateFormat,
@ -72,9 +78,7 @@ func (a *goBlog) initRendering() error {
"string": a.ts.GetTemplateStringVariantFunc(),
"include": a.includeRenderedTemplate,
"urlize": urlize,
"sort": sortedStrings,
"absolute": a.getFullAddress,
"mentions": a.db.getWebmentionsByAddress,
"geotitle": a.geoTitle,
"geolink": geoOSMLink,
"opensearch": openSearchUrl,

@ -21,10 +21,10 @@ type assetFile struct {
body []byte
}
func (a *goBlog) initTemplateAssets() (err error) {
func (a *goBlog) initTemplateAssets() error {
a.assetFileNames = map[string]string{}
a.assetFiles = map[string]*assetFile{}
err = filepath.Walk(assetsFolder, func(path string, info os.FileInfo, err error) error {
if err := filepath.Walk(assetsFolder, func(path string, info os.FileInfo, err error) error {
if info.Mode().IsRegular() {
// Open file
file, err := os.Open(path)
@ -43,13 +43,11 @@ func (a *goBlog) initTemplateAssets() (err error) {
}
}
return nil
})
if err != nil {
}); err != nil {
return err
}
// Add syntax highlighting CSS
err = a.initChromaCSS()
if err != nil {
if err := a.initChromaCSS(); err != nil {
return err
}
return nil

@ -1,10 +0,0 @@
{{ define "author" }}
{{ with .User }}
<div class="p-author h-card hide">
{{ with .Picture }}<data class="u-photo" value="{{ . }}"></data>{{ end }}
{{ if .Name }}
<a href="{{ with .Link }}{{ . }}{{ else }}/{{ end }}" class="p-name u-url" rel="me">{{ .Name }}</a>
{{ end }}
</div>
{{ end }}
{{ end }}

@ -5,9 +5,7 @@
<meta name=viewport content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="{{ asset "css/styles.css" }}">
{{ with .Canonical }}<link rel="canonical" href="{{ . }}">{{ end }}
{{ block "title" . }}
<title>{{ mdtitle .Blog.Title }}</title>
{{ end }}
{{ block "title" . }}<title>{{ mdtitle .Blog.Title }}</title>{{ end }}
<link rel="alternate" type="application/rss+xml" title="RSS ({{ mdtitle .Blog.Title }})" href="{{ .Blog.Path }}.rss"/>
<link rel="alternate" type="application/atom+xml" title="Atom ({{ mdtitle .Blog.Title }})" href="{{ .Blog.Path }}.atom"/>
<link rel="alternate" type="application/feed+json" title="JSON Feed ({{ mdtitle .Blog.Title }})" href="{{ .Blog.Path }}.json"/>
@ -17,14 +15,32 @@
<link rel="token_endpoint" href="/indieauth/token" />
{{ with .User }}{{ range .Identities }}<link rel="me" href="{{ . }}" />{{ end }}{{ end }}
{{ $os := opensearch .Blog }}
{{ if $os }}
<link rel="search" type="application/opensearchdescription+xml" href="{{ $os }}" title="{{ mdtitle .Blog.Title }}" />
{{ end }}
{{ include "header" . }}
{{ if $os }}<link rel="search" type="application/opensearchdescription+xml" href="{{ $os }}" title="{{ mdtitle .Blog.Title }}" />{{ end }}
{{ with .Blog.Announcement }}{{ with .Text }}<div id="announcement" data-nosnippet>{{ md . }}</div>{{ end }}{{ end }}
<header>
<h1><a href="{{ .Blog.RelativePath "/" }}" rel="home" title="{{ mdtitle .Blog.Title }}" translate="no">{{ mdtitle .Blog.Title }}</a></h1>
{{ with .Blog.Description }}<p><i>{{ . }}</i></p>{{ end }}
{{ with index .Blog.Menus "main" }}
<nav>{{ range $i, $item := .Items }}{{ if ne $i 0 }} &bull; {{ end }}<a href="{{ $item.Link }}">{{ mdtitle $item.Title }}</a>{{ end }}</nav>
{{ end }}
{{ if .LoggedIn }}
<nav>
<a href="{{ .Blog.RelativePath "/editor" }}">{{ string .Blog.Lang "editor" }}</a>
&bull; <a href="/notifications">{{ string .Blog.Lang "notifications" }}</a>
{{ if .WebmentionReceivingEnabled }}&bull; <a href="/webmention">{{ string .Blog.Lang "webmentions" }}</a>{{ end }}
{{ if .CommentsEnabled }}&bull; <a href="{{ .Blog.RelativePath "/comment" }}">{{ string .Blog.Lang "comments" }}</a>{{ end }}
&bull; <a href="/logout">{{ string .Blog.Lang "logout" }}</a>
</nav>
{{ end }}
</header>
{{ block "main" . }}{{ end }}
{{ include "footer" . }}
{{ if .EasterEgg }}
<script defer src="{{ asset "js/easteregg.js" }}"></script>
{{ end }}
<footer>
{{ with index .Blog.Menus "footer" }}
<nav>{{ range $i, $item := .Items }}{{ if ne $i 0 }} &bull; {{ end }}<a href="{{ $item.Link }}">{{ mdtitle $item.Title }}</a>{{ end }}</nav>
{{ end }}
<p translate="no">&copy; {{ dateformat now "2006" }} {{ with .User.Name }}{{ . }}{{ else }}{{ mdtitle .Blog.Title }}{{ end }}</p>
{{ tor .Blog .TorUsed .TorAddress }}
</footer>
{{ if .EasterEgg }}<script defer src="{{ asset "js/easteregg.js" }}"></script>{{ end }}
</html>
{{ end }}

@ -21,9 +21,7 @@
</ul>
{{ end }}
</main>
{{ if .CommentsEnabled }}
{{ include "interactions" . }}
{{ end }}
{{ if .CommentsEnabled }}{{ interactions .Blog .Canonical }}{{ end }}
{{ end }}
{{ define "blogroll" }}

@ -9,9 +9,7 @@
<p id="loading" data-table="{{.Data.TableUrl}}">{{ string .Blog.Lang "loading" }}</p>
<script defer src="{{ asset "js/blogstats.js" }}"></script>
</main>
{{ if .CommentsEnabled }}
{{ include "interactions" . }}
{{ end }}
{{ if .CommentsEnabled }}{{ interactions .Blog .Canonical }}{{ end }}
{{ end }}
{{ define "blogstats" }}

@ -13,9 +13,7 @@
{{ html .Data.Comment }}
</p>
</main>
{{ if .CommentsEnabled }}
{{ include "interactions" . }}
{{ end }}
{{ if .CommentsEnabled }}{{ interactions .Blog .Canonical }}{{ end }}
{{ end }}
{{ define "comment" }}

@ -1,10 +1,6 @@
{{ define "editorpreview" }}
{{ with .Data.RenderedTitle }}<h1>{{ . }}</h1>{{ end }}
{{ include "summaryandpostmeta" . }}
{{ if .Data.Content }}
<div>
{{ content .Data true }}
</div>
{{ end }}
{{ include "posttax" . }}
{{ if .Data.Content }}<div>{{ content .Data true }}</div>{{ end }}
{{ posttax .Data .Blog }}
{{ end }}

@ -1,16 +0,0 @@
{{ define "footer" }}
<footer>
{{ with index .Blog.Menus "footer" }}
<nav>{{ range $i, $item := .Items }}{{ if ne $i 0 }} &bull; {{ end }}<a href="{{ $item.Link }}">{{ mdtitle $item.Title }}</a>{{ end }}
</nav>
{{ end }}
<p translate="no">&copy; {{ dateformat now "2006" }} {{ with .User.Name }}{{ . }}{{ else }}{{ mdtitle .Blog.Title }}{{ end }}</p>
{{ if .TorUsed }}
<p id="tor">🔐 {{ string .Blog.Lang "connectedviator" }}</p>
{{ else }}
{{ if .TorAddress }}
<p id="tor">🔓 <a href="{{ .TorAddress }}">{{ string .Blog.Lang "connectviator" }}</a> <a href="https://www.torproject.org/" target="_blank" rel="nofollow noopener noreferrer">{{ string .Blog.Lang "whatistor" }}</a></p>
{{ end }}
{{ end }}
</footer>
{{ end }}

@ -21,9 +21,7 @@
<script defer src="{{ asset "js/geomap.js" }}"></script>
{{ end }}
</main>
{{ if .CommentsEnabled }}
{{ include "interactions" . }}
{{ end }}
{{ if .CommentsEnabled }}{{ interactions .Blog .Canonical }}{{ end }}
{{ end }}
{{ define "geomap" }}

@ -1,25 +0,0 @@
{{ define "header" }}
{{ with .Blog.Announcement }}{{ with .Text }}
<div id="announcement" data-nosnippet>
{{ md . }}
</div>
{{ end }}{{ end }}
<header>
<h1><a href="{{ .Blog.RelativePath "/" }}" rel="home" title="{{ mdtitle .Blog.Title }}" translate="no">{{ mdtitle .Blog.Title }}</a></h1>
{{ with .Blog.Description }}<p><i>{{ . }}</i></p>{{ end }}
<nav>
{{ with index .Blog.Menus "main" }}
{{ range $i, $item := .Items }}{{ if ne $i 0 }} &bull; {{ end }}<a href="{{ $item.Link }}">{{ mdtitle $item.Title }}</a>{{ end }}
{{ end }}
</nav>
{{ if .LoggedIn }}
<nav>
<a href="{{ .Blog.RelativePath "/editor" }}">{{ string .Blog.Lang "editor" }}</a>
&bull; <a href="/notifications">{{ string .Blog.Lang "notifications" }}</a>
{{ if .WebmentionReceivingEnabled }}&bull; <a href="/webmention">{{ string .Blog.Lang "webmentions" }}</a>{{ end }}
{{ if .CommentsEnabled }}&bull; <a href="{{ .Blog.RelativePath "/comment" }}">{{ string .Blog.Lang "comments" }}</a>{{ end }}
&bull; <a href="/logout">{{ string .Blog.Lang "logout" }}</a>
</nav>
{{ end }}
</header>
{{ end }}

@ -27,7 +27,7 @@
{{ if .Data.HasNext }}
<p><a href="{{ .Data.Next }}">{{ string .Blog.Lang "next" }}</a></p>
{{ end }}
{{ include "author" . }}
{{ author }}
</main>
{{ end }}

@ -1,22 +0,0 @@
{{ define "interactions" }}
<details class="p" id="interactions">
<summary><b>{{ string .Blog.Lang "interactions" }}</b></summary>
{{ $rd := . }}
{{ with ( mentions .Canonical ) }}
{{ include "mentions" $rd . }}
{{ end }}
<form class="fw p" method="post" action="/webmention">
<label for="wm-source" class="p">{{ string .Blog.Lang "interactionslabel" }}</label>
<input id="wm-source" type="url" name="source" placeholder="URL" required>
<input type="hidden" name="target" value="{{ .Canonical }}">
<input type="submit" value="{{ string .Blog.Lang "send" }}">
</form>
<form class="fw p" method="post" action="{{ .Blog.RelativePath "/comment" }}">
<input type="hidden" name="target" value="{{ .Canonical }}">
<input type="text" name="name" placeholder="{{ string .Blog.Lang "nameopt" }}">
<input type="url" name="website" placeholder="{{ string .Blog.Lang "websiteopt" }}">
<textarea name="comment" required placeholder="{{ string .Blog.Lang "comment" }}"></textarea>
<input type="submit" value="{{ string .Blog.Lang "docomment" }}">
</form>
</details>
{{ end }}

@ -17,7 +17,7 @@
{{ end }}
<input type="submit" value="{{ string .Blog.Lang "login" }}">
</form>
{{ include "author" . }}
{{ author }}
</main>
{{ end }}

@ -1,19 +0,0 @@
{{ define "mentions" }}
{{ $rd := . }}
<ul>
{{ range $i, $mention := .Data }}
<li>
<a href="{{ $mention.Url }}" target="_blank" rel="nofollow noopener noreferrer ugc">
{{ if $mention.Author }}
{{ $mention.Author }}
{{ else }}
{{ $mention.Url }}
{{ end }}
</a>
{{ with $mention.Title }} <b>{{.}}</b>{{ end }}
{{ with $mention.Content }} <i>{{.}}</i>{{ end }}
{{ with $mention.Submentions }}{{ include "mentions" $rd . }}{{ end }}
</li>
{{ end }}
</ul>
{{ end }}

@ -1,5 +0,0 @@
{{ define "oldcontentwarning" }}
{{ if .Data.Old }}
<strong class="p border-top border-bottom">{{ string .Blog.Lang "oldcontent" }}</strong>
{{ end }}
{{ end }}

@ -3,7 +3,10 @@
<title>{{ with .Data.RenderedTitle }}{{ . }} - {{end}}{{ mdtitle .Blog.Title }}</title>
{{ include "postheadmeta" . }}
{{ with shorturl .Data }}<link rel="shortlink" href="{{ . }}">{{ end }}
{{ include "trackheader" . }}
{{ if .Data.HasTrack }}
<link rel="stylesheet" href="/-/leaflet/leaflet.css"/>
<script src="/-/leaflet/leaflet.js"></script>
{{ end }}
{{ end }}
{{ define "main" }}
@ -11,18 +14,40 @@
<article>
<data class="u-url hide" value="{{ absolute .Data.Path }}"></data>
{{ with .Data.RenderedTitle }}<h1 class=p-name>{{ . }}</h1>{{ end }}
{{ include "postmeta" . }}
{{ include "postactions" . }}
<div class="p">
{{ include "summaryandpostmeta" . }}
{{ $translations := (translations .Data) }}
{{ if gt (len $translations) 0 }}
<div>{{ string .Blog.Lang "translations" }}: {{ $delimiter := "" }}{{ range $i, $t := $translations }}{{ $delimiter }}<a href="{{ $t.Path }}" translate="no">{{ $t.RenderedTitle }}</a>{{ $delimiter = ", " }}{{ end }}</div>
{{ end }}
{{ $short := shorturl .Data }}
{{ if $short }}<div>{{ string .Blog.Lang "shorturl" }} <a href="{{ $short }}" rel="shortlink">{{ $short }}</a></div>{{ end }}
{{ if ne .Data.Status "published" }}<div>{{ string .Blog.Lang "status" }}: {{ .Data.Status }}</div>{{ end }}
</div>
<div id="post-actions">
<a href="https://www.addtoany.com/share#url={{ shorturl .Data }}{{ with .Data.RenderedTitle }}&title={{ . }}{{ end }}" target="_blank" rel="nofollow noopener noreferrer" class="button">{{ string .Blog.Lang "share" }}</a>
<a id="translateBtn" href="https://translate.google.com/translate?u={{ absolute .Data.Path }}" target="_blank" rel="nofollow noopener noreferrer" class="button">{{ string .Blog.Lang "translate" }}</a>
<script defer src="{{ asset "js/translate.js" }}"></script>
<button id="speakBtn" class="hide" data-speak="{{ string .Blog.Lang "speak" }}" data-stopspeak="{{ string .Blog.Lang "stopspeak" }}"></button>
<script defer src="{{ if .Data.TTS }}{{ asset "js/tts.js" }}{{ else }}{{ asset "js/speak.js" }}{{ end }}"></script>
</div>
{{ if .Data.TTS }}<div class="p hide" id="tts"><audio controls preload=none id="tts-audio"><source src="{{ .Data.TTS }}"/></audio></div>{{ end }}
{{ if .Data.Content }}
{{ include "oldcontentwarning" . }}
<div class=e-content>
{{ content .Data false }}
</div>
{{ oldcontentwarning .Data .Blog }}
<div class=e-content>{{ content .Data false }}</div>
{{ end }}
{{ if .Data.HasTrack }}
{{ $track := (gettrack .Data) }}
{{ if $track }}{{ if $track.HasPoints }}
{{ $lang := .Blog.Lang }}
<p>{{ with $track.Name }}<b>{{ . }}</b> {{ end }}{{ with $track.Kilometers }}🏁 {{ . }} {{ string $lang "kilometers" }} {{ end }}{{ with $track.Hours }}⌛ {{ . }}{{ end }}</p>
<div class="p" id="map" data-paths="{{ $track.PathsJSON }}" data-points="{{ $track.PointsJSON }}" data-minzoom={{ $track.MinZoom }} data-maxzoom={{ $track.MaxZoom }} data-attribution="{{ $track.MapAttribution }}"></div>
<script defer src="{{ asset "js/geotrack.js" }}"></script>
{{ end }}{{ end }}
{{ end }}
{{ include "trackdetails" . }}
{{ include "posttax" . }}
{{ posttax .Data .Blog }}
</article>
{{ include "author" . }}
{{ author }}
</main>
{{ if .LoggedIn }}
<div id="posteditactions" class="p">
@ -53,9 +78,7 @@
<script defer src="{{ asset "js/formconfirm.js" }}"></script>
</div>
{{ end }}
{{ if .CommentsEnabled }}
{{ include "interactions" . }}
{{ end }}
{{ if .CommentsEnabled }}{{ interactions .Blog .Canonical }}{{ end }}
{{ end }}
{{ define "post" }}

@ -1,18 +0,0 @@
{{ define "postactions" }}
<div id="post-actions">
<a href="https://www.addtoany.com/share#url={{ shorturl .Data }}{{ with .Data.RenderedTitle }}&title={{ . }}{{ end }}" target="_blank" rel="nofollow noopener noreferrer" class="button">{{ string .Blog.Lang "share" }}</a>
<a id="translateBtn" href="https://translate.google.com/translate?u={{ absolute .Data.Path }}" target="_blank" rel="nofollow noopener noreferrer" class="button">{{ string .Blog.Lang "translate" }}</a>
<script defer src="{{ asset "js/translate.js" }}"></script>
<button id="speakBtn" class="hide" data-speak="{{ string .Blog.Lang "speak" }}" data-stopspeak="{{ string .Blog.Lang "stopspeak" }}"></button>
{{ if .Data.TTS }}
<script defer src="{{ asset "js/tts.js" }}"></script>
{{ else }}
<script defer src="{{ asset "js/speak.js" }}"></script>
{{ end }}
</div>
{{ if .Data.TTS }}
<div class="p hide" id="tts">
<audio controls preload=none id="tts-audio"><source src="{{ .Data.TTS }}"/></audio>
</div>
{{ end }}
{{ end }}

@ -1,12 +0,0 @@
{{ define "postmeta" }}
<div class="p">
{{ include "summaryandpostmeta" . }}
{{ $translations := (translations .Data) }}
{{ if gt (len $translations) 0 }}
<div>{{ string .Blog.Lang "translations" }}: {{ $delimiter := "" }}{{ range $i, $t := $translations }}{{ $delimiter }}<a href="{{ $t.Path }}" translate="no">{{ $t.RenderedTitle }}</a>{{ $delimiter = ", " }}{{ end }}</div>
{{ end }}
{{ $short := shorturl .Data }}
{{ if $short }}<div>{{ string .Blog.Lang "shorturl" }} <a href="{{ $short }}" rel="shortlink">{{ $short }}</a></div>{{ end }}
{{ if ne .Data.Status "published" }}<div>{{ string .Blog.Lang "status" }}: {{ .Data.Status }}</div>{{ end }}
</div>
{{ end }}

@ -1,15 +0,0 @@
{{ define "posttax" }}
{{ $post := .Data }}
{{ $blog := .Blog }}
{{ range $i, $tax := $blog.Taxonomies }}
{{ $tvs := sort (ps $post $tax.Name) }}
{{ if $tvs }}{{ if not (eq (len $tvs) 0) }}
<p>
<b>{{ mdtitle $tax.Title }}</b>:
{{ range $j, $tv := $tvs }}
<a class="p-category" rel="tag" href="{{ $blog.RelativePath ( printf "/%s/%s" $tax.Name (urlize $tv) ) }}">{{ mdtitle $tv }}</a>
{{ end }}
</p>
{{ end }}{{ end }}
{{ end }}
{{ end }}

@ -7,13 +7,9 @@
<main class=h-entry>
<article>
<data class="u-url hide" value="{{ absolute .Data.Path }}"></data>
{{ if .Data.Content }}
<div class=e-content>
{{ content .Data false }}
</div>
{{ end }}
{{ if .Data.Content }}<div class=e-content>{{ content .Data false }}</div>{{ end }}
</article>
{{ include "author" . }}
{{ author }}
</main>
{{ if .LoggedIn }}
<div id="posteditactions" class="p">

@ -1,19 +0,0 @@
{{ define "trackdetails" }}
{{ if .Data.HasTrack }}
{{ $track := (gettrack .Data) }}
{{ if $track }}
{{ if $track.HasPoints }}
{{ $lang := .Blog.Lang }}
<p>{{ with $track.Name }}<b>{{ . }}</b> {{ end }}{{ with $track.Kilometers }}🏁 {{ . }} {{ string $lang "kilometers" }} {{ end }}{{ with $track.Hours }}⌛ {{ . }}{{ end }}</p>
<div class="p" id="map"
data-paths="{{ $track.PathsJSON }}"
data-points="{{ $track.PointsJSON }}"
data-minzoom={{ $track.MinZoom }}
data-maxzoom={{ $track.MaxZoom }}
data-attribution="{{ $track.MapAttribution }}"
></div>
<script defer src="{{ asset "js/geotrack.js" }}"></script>
{{ end }}
{{ end }}
{{ end }}
{{ end }}

@ -1,6 +0,0 @@
{{ define "trackheader" }}
{{ if .Data.HasTrack }}
<link rel="stylesheet" href="/-/leaflet/leaflet.css"/>
<script src="/-/leaflet/leaflet.js"></script>
{{ end }}
{{ end }}

@ -0,0 +1 @@
<details class="p" id="interactions"><summary><strong>Interactions &amp; Comments</strong></summary><ul><li><a href="https://example.com/testpost2" target="_blank" rel="nofollow noopener noreferrer ugc">https://example.com/testpost2</a> <strong>Test-Title</strong> <i>Test</i><ul><li><a href="https://example.com/testpost3" target="_blank" rel="nofollow noopener noreferrer ugc">https://example.com/testpost3</a> <strong>Test-Title</strong> <i>Test</i></li></ul></li></ul><form class="fw p" method="post" action="/webmention"><label for="wm-source" class="p">Have you published a response to this? Paste the URL here.</label><input id="wm-source" type="url" name="source" placeholder="URL" required=""><input type="hidden" name="target" value="https://example.com/testpost1"><input type="submit" value="Send (to review)"></form><form class="fw p" method="post" action="/comment"><input type="hidden" name="target" value="https://example.com/testpost1"><input type="text" name="name" placeholder="Name (optional)"><input type="url" name="website" placeholder="Website (optional)"><textarea name="comment" required="" placeholder="Comment"></textarea><input type="submit" value="Comment"></form></details>

@ -106,7 +106,9 @@ func (a *goBlog) createPostTTSAudio(p *post) error {
// Merge partsBuffers into final buffer
var final bytes.Buffer
mp3merge.MergeMP3(&final, partsBuffers...)
if err := mp3merge.MergeMP3(&final, partsBuffers...); err != nil {
return err
}
// Save audio
audioReader := bytes.NewReader(final.Bytes())

216
ui.go

@ -0,0 +1,216 @@
package main
import (
"fmt"
"html/template"
"strings"
)
// This file includes some functions that render parts of the HTML
type htmlBuilder struct {
strings.Builder
}
func (h *htmlBuilder) write(s string) {
_, _ = h.WriteString(s)
}
func (h *htmlBuilder) writeEscaped(s string) {
if len(s) == 0 {
return
}
template.HTMLEscape(h, []byte(s))
}
func (h *htmlBuilder) writeAttribute(attr, val string) {
h.write(` `)
h.write(attr)
h.write(`="`)
h.writeEscaped(val)
h.write(`"`)
}
func (h *htmlBuilder) writeElementOpen(tag string, attrs ...string) {
h.write(`<`)
h.write(tag)
for i := 0; i < len(attrs); i += 2 {
h.writeAttribute(attrs[i], attrs[i+1])
}
h.write(`>`)
}
func (h *htmlBuilder) writeElementClose(tag string) {
h.write(`</`)
h.write(tag)
h.write(`>`)
}
func (h *htmlBuilder) html() template.HTML {
return template.HTML(h.String())
}
// Render the HTML to show the list of post taxonomy values (tags, series, etc.)
func (a *goBlog) renderPostTax(p *post, b *configBlog) template.HTML {
if b == nil || p == nil {
return ""
}
var hb htmlBuilder
// Iterate over all taxonomies
for _, tax := range b.Taxonomies {
// Get all sorted taxonomy values for this post
if taxValues := sortedStrings(p.Parameters[tax.Name]); len(taxValues) > 0 {
// Start new paragraph
hb.writeElementOpen("p")
// Add taxonomy name
hb.writeElementOpen("strong")
hb.writeEscaped(a.renderMdTitle(tax.Title))
hb.writeElementClose("strong")
hb.write(": ")
// Add taxonomy values
for i, taxValue := range taxValues {
if i > 0 {
hb.write(", ")
}
hb.writeElementOpen(
"a",
"class", "p-category",
"rel", "tag",
"href", b.getRelativePath(fmt.Sprintf("/%s/%s", tax.Name, urlize(taxValue))),
)
hb.writeEscaped(a.renderMdTitle(taxValue))
hb.writeElementClose("a")
}
// End paragraph
hb.writeElementClose("p")
}
}
return hb.html()
}
// Render the HTML to show a warning for old posts
func (a *goBlog) renderOldContentWarning(p *post, b *configBlog) template.HTML {
if b == nil || p == nil || !p.Old() {
return ""
}
var hb htmlBuilder
hb.writeElementOpen("strong", "class", "p border-top border-bottom")
hb.writeEscaped(a.ts.GetTemplateStringVariant(b.Lang, "oldcontent"))
hb.writeElementClose("strong")
return hb.html()
}
// Render the HTML to show interactions
func (a *goBlog) renderInteractions(b *configBlog, canonical string) template.HTML {
if b == nil || canonical == "" {
return ""
}
var hb htmlBuilder
// Start accordion
hb.writeElementOpen("details", "class", "p", "id", "interactions")
hb.writeElementOpen("summary")
hb.writeElementOpen("strong")
hb.writeEscaped(a.ts.GetTemplateStringVariant(b.Lang, "interactions"))
hb.writeElementClose("strong")
hb.writeElementClose("summary")
// Render mentions
var renderMentions func(m []*mention)
renderMentions = func(m []*mention) {
if len(m) == 0 {
return
}
hb.writeElementOpen("ul")
for _, mention := range m {
hb.writeElementOpen("li")
hb.writeElementOpen("a", "href", mention.Url, "target", "_blank", "rel", "nofollow noopener noreferrer ugc")
hb.writeEscaped(defaultIfEmpty(mention.Author, mention.Url))
hb.writeElementClose("a")
if mention.Title != "" {
hb.write(" ")
hb.writeElementOpen("strong")
hb.writeEscaped(mention.Title)
hb.writeElementClose("strong")
}
if mention.Content != "" {
hb.write(" ")
hb.writeElementOpen("i")
hb.writeEscaped(mention.Content)
hb.writeElementClose("i")
}
if len(mention.Submentions) > 0 {
renderMentions(mention.Submentions)
}
hb.writeElementClose("li")
}
hb.writeElementClose("ul")
}
renderMentions(a.db.getWebmentionsByAddress(canonical))
// Show form to send a webmention
hb.writeElementOpen("form", "class", "fw p", "method", "post", "action", "/webmention")
hb.writeElementOpen("label", "for", "wm-source", "class", "p")
hb.writeEscaped(a.ts.GetTemplateStringVariant(b.Lang, "interactionslabel"))
hb.writeElementClose("label")
hb.writeElementOpen("input", "id", "wm-source", "type", "url", "name", "source", "placeholder", "URL", "required", "")
hb.writeElementOpen("input", "type", "hidden", "name", "target", "value", canonical)
hb.writeElementOpen("input", "type", "submit", "value", a.ts.GetTemplateStringVariant(b.Lang, "send"))
hb.writeElementClose("form")
// Show form to create a new comment
hb.writeElementOpen("form", "class", "fw p", "method", "post", "action", "/comment")
hb.writeElementOpen("input", "type", "hidden", "name", "target", "value", canonical)
hb.writeElementOpen("input", "type", "text", "name", "name", "placeholder", a.ts.GetTemplateStringVariant(b.Lang, "nameopt"))
hb.writeElementOpen("input", "type", "url", "name", "website", "placeholder", a.ts.GetTemplateStringVariant(b.Lang, "websiteopt"))
hb.writeElementOpen("textarea", "name", "comment", "required", "", "placeholder", a.ts.GetTemplateStringVariant(b.Lang, "comment"))
hb.writeElementClose("textarea")
hb.writeElementOpen("input", "type", "submit", "value", a.ts.GetTemplateStringVariant(b.Lang, "docomment"))
hb.writeElementClose("form")
// Finish accordion
hb.writeElementClose("details")
return hb.html()
}
// Render HTML for author h-card
func (a *goBlog) renderAuthor() template.HTML {
user := a.cfg.User
if user == nil {
return ""
}
var hb htmlBuilder
hb.writeElementOpen("div", "class", "p-author h-card hide")
if user.Picture != "" {
hb.writeElementOpen("data", "class", "u-photo", "value", user.Picture)
hb.writeElementClose("data")
}
if user.Name != "" {
hb.writeElementOpen("a", "class", "p-name u-url", "rel", "me", "href", defaultIfEmpty(user.Link, "/"))
hb.writeEscaped(user.Name)
hb.writeElementClose("a")
}
hb.writeElementClose("div")
return hb.html()
}
// Render HTML for TOR notice in the footer
func (a *goBlog) renderTorNotice(b *configBlog, torUsed bool, torAddress string) template.HTML {
if !a.cfg.Server.Tor || b == nil || !torUsed && torAddress == "" {
return ""
}
var hb htmlBuilder
if torUsed {
hb.writeElementOpen("p", "id", "tor")
hb.writeEscaped("🔐 ")
hb.writeEscaped(a.ts.GetTemplateStringVariant(b.Lang, "connectedviator"))
hb.writeElementClose("p")
} else if torAddress != "" {
hb.writeElementOpen("p", "id", "tor")
hb.writeEscaped("🔓 ")
hb.writeElementOpen("a", "href", torAddress)
hb.writeEscaped(a.ts.GetTemplateStringVariant(b.Lang, "connectviator"))
hb.writeElementClose("a")
hb.writeEscaped(" ")
hb.writeElementOpen("a", "href", "https://www.torproject.org/", "target", "_blank", "rel", "nofollow noopener noreferrer")
hb.writeEscaped(a.ts.GetTemplateStringVariant(b.Lang, "whatistor"))
hb.writeElementClose("a")
hb.writeElementClose("p")
}
return hb.html()
}

@ -0,0 +1,154 @@
package main
import (
"html/template"
"os"
"strings"
"testing"
"github.com/PuerkitoBio/goquery"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_renderPostTax(t *testing.T) {
app := &goBlog{
cfg: createDefaultTestConfig(t),
}
_ = app.initConfig()
_ = app.initDatabase(false)
app.initComponents(false)
p := &post{
Parameters: map[string][]string{
"tags": {"Foo", "Bar"},
},
}
res := app.renderPostTax(p, app.cfg.Blogs["default"])
_, err := goquery.NewDocumentFromReader(strings.NewReader(string(res)))
require.NoError(t, err)
assert.Equal(t, template.HTML("<p><strong>Tags</strong>: <a class=\"p-category\" rel=\"tag\" href=\"/tags/bar\">Bar</a>, <a class=\"p-category\" rel=\"tag\" href=\"/tags/foo\">Foo</a></p>"), res)
}
func Test_renderOldContentWarning(t *testing.T) {
app := &goBlog{
cfg: createDefaultTestConfig(t),
}
_ = app.initConfig()
_ = app.initDatabase(false)
app.initComponents(false)
p := &post{
Published: "2018-01-01",
}
res := app.renderOldContentWarning(p, app.cfg.Blogs["default"])
_, err := goquery.NewDocumentFromReader(strings.NewReader(string(res)))
require.NoError(t, err)
assert.Equal(t, template.HTML("<strong class=\"p border-top border-bottom\">⚠ This entry is already over one year old. It may no longer be up to date. Opinions may have changed.</strong>"), res)
}
func Test_renderInteractions(t *testing.T) {
var err error
app := &goBlog{
cfg: createDefaultTestConfig(t),
}
app.cfg.Server.PublicAddress = "https://example.com"
_ = app.initConfig()
_ = app.initDatabase(false)
app.initComponents(false)
app.d, err = app.buildRouter()
require.NoError(t, err)
err = app.createPost(&post{
Path: "/testpost1",
})
require.NoError(t, err)
err = app.createPost(&post{
Path: "/testpost2",
Content: "[Test](/testpost1)",
Parameters: map[string][]string{
"title": {"Test-Title"},
},
})
require.NoError(t, err)
err = app.verifyMention(&mention{
Source: "https://example.com/testpost2",
Target: "https://example.com/testpost1",
})
require.NoError(t, err)
err = app.db.approveWebmentionId(1)
require.NoError(t, err)
err = app.createPost(&post{
Path: "/testpost3",
Content: "[Test](/testpost2)",
Parameters: map[string][]string{
"title": {"Test-Title"},
},
})
require.NoError(t, err)
err = app.verifyMention(&mention{
Source: "https://example.com/testpost3",
Target: "https://example.com/testpost2",
})
require.NoError(t, err)
err = app.db.approveWebmentionId(2)
require.NoError(t, err)
res := app.renderInteractions(app.cfg.Blogs["default"], "https://example.com/testpost1")
_, err = goquery.NewDocumentFromReader(strings.NewReader(string(res)))
require.NoError(t, err)
expected, err := os.ReadFile("testdata/interactionstest.html")
require.NoError(t, err)
assert.Equal(t, template.HTML(expected), res)
}
func Test_renderAuthor(t *testing.T) {
app := &goBlog{
cfg: createDefaultTestConfig(t),
}
app.cfg.User.Picture = "https://example.com/picture.jpg"
app.cfg.User.Name = "John Doe"
_ = app.initConfig()
_ = app.initDatabase(false)
app.initComponents(false)
res := app.renderAuthor()
_, err := goquery.NewDocumentFromReader(strings.NewReader(string(res)))
require.NoError(t, err)
assert.Equal(t, template.HTML("<div class=\"p-author h-card hide\"><data class=\"u-photo\" value=\"https://example.com/picture.jpg\"></data><a class=\"p-name u-url\" rel=\"me\" href=\"/\">John Doe</a></div>"), res)
}
func Test_renderTorNotice(t *testing.T) {
app := &goBlog{
cfg: createDefaultTestConfig(t),
}
_ = app.initConfig()
_ = app.initDatabase(false)
app.initComponents(false)
app.cfg.Server.Tor = true
res := app.renderTorNotice(app.cfg.Blogs["default"], true, "http://abc.onion:80/test")
_, err := goquery.NewDocumentFromReader(strings.NewReader(string(res)))
require.NoError(t, err)
assert.Equal(t, template.HTML("<p id=\"tor\">🔐 Connected via Tor.</p>"), res)
res = app.renderTorNotice(app.cfg.Blogs["default"], false, "http://abc.onion:80/test")
_, err = goquery.NewDocumentFromReader(strings.NewReader(string(res)))
require.NoError(t, err)
assert.Equal(t, template.HTML("<p id=\"tor\">🔓 <a href=\"http://abc.onion:80/test\">Connect via Tor.</a> <a href=\"https://www.torproject.org/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">What is Tor?</a></p>"), res)
}
Loading…
Cancel
Save