about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAlan Pearce2024-04-24 18:01:15 +0200
committerAlan Pearce2024-04-24 18:01:15 +0200
commitbbfe6055609c4e7401f27d3dd4a5aa9c0a5242d6 (patch)
treee81aefa6234cd5205638a7e7e6a4890285070ecd
parentd2a1791fa01fcf4316f80fdc3be241f2c6fd4ac9 (diff)
downloadwebsite-bbfe6055609c4e7401f27d3dd4a5aa9c0a5242d6.tar.lz
website-bbfe6055609c4e7401f27d3dd4a5aa9c0a5242d6.tar.zst
website-bbfe6055609c4e7401f27d3dd4a5aa9c0a5242d6.zip
add count
-rw-r--r--cmd/build/build.go10
-rw-r--r--cmd/build/template.go87
-rw-r--r--config.toml2
-rw-r--r--templates/count.html6
4 files changed, 87 insertions, 18 deletions
diff --git a/cmd/build/build.go b/cmd/build/build.go
index 9b58095..21eedbd 100644
--- a/cmd/build/build.go
+++ b/cmd/build/build.go
@@ -78,7 +78,7 @@ func build(outDir string, config config.Config) error {
 		return errors.WithMessage(err, "could not create directory for tags")
 	}
 	slog.Debug("rendering tags list")
-	output, err := renderTags(tags, config)
+	output, err := renderTags(tags, config, "/tags")
 	if err != nil {
 		return errors.WithMessage(err, "could not render tags")
 	}
@@ -97,7 +97,7 @@ func build(outDir string, config config.Config) error {
 			return errors.WithMessage(err, "could not create directory")
 		}
 		slog.Debug("rendering tags page", "tag", tag)
-		output, err := renderListPage(tag, config, matchingPosts)
+		output, err := renderListPage(tag, config, matchingPosts, "/tags/"+tag)
 		if err != nil {
 			return errors.WithMessage(err, "could not render tag page")
 		}
@@ -116,7 +116,7 @@ func build(outDir string, config config.Config) error {
 	}
 
 	slog.Debug("rendering list page")
-	listPage, err := renderListPage("", config, posts)
+	listPage, err := renderListPage("", config, posts, "/post")
 	if err != nil {
 		return errors.WithMessage(err, "could not render list page")
 	}
@@ -143,7 +143,7 @@ func build(outDir string, config config.Config) error {
 	}
 
 	slog.Debug("rendering homepage")
-	homePage, err := renderHomepage(config, posts)
+	homePage, err := renderHomepage(config, posts, "/")
 	if err != nil {
 		return errors.WithMessage(err, "could not render homepage")
 	}
@@ -152,7 +152,7 @@ func build(outDir string, config config.Config) error {
 	}
 
 	slog.Debug("rendering 404 page")
-	notFound, err := render404(config)
+	notFound, err := render404(config, "/404.html")
 	if err != nil {
 		return errors.WithMessage(err, "could not render 404 page")
 	}
diff --git a/cmd/build/template.go b/cmd/build/template.go
index b56b7dd..32f408f 100644
--- a/cmd/build/template.go
+++ b/cmd/build/template.go
@@ -24,6 +24,7 @@ import (
 var (
 	assetsOnce sync.Once
 	css        string
+	countHTML  *goquery.Document
 	templates  map[string]*os.File = make(map[string]*os.File)
 )
 
@@ -39,7 +40,59 @@ func loadTemplate(path string) (file *os.File, err error) {
 	return
 }
 
-func layout(filename string, config config.Config, pageTitle string) (*goquery.Document, error) {
+var (
+	imgOnce     sync.Once
+	img         *goquery.Selection
+	urlTemplate *url.URL
+)
+
+type QuerySelection struct {
+	*goquery.Selection
+}
+
+type QueryDocument struct {
+	*goquery.Document
+}
+
+func NewDocumentFromReader(r io.Reader) (*QueryDocument, error) {
+	doc, err := goquery.NewDocumentFromReader(r)
+	return &QueryDocument{doc}, err
+}
+
+func (q *QueryDocument) Find(selector string) *QuerySelection {
+	return &QuerySelection{q.Document.Find(selector)}
+}
+
+func NewDocumentNoScript(r io.Reader) (*goquery.Document, error) {
+	root, err := html.ParseWithOptions(r, html.ParseOptionEnableScripting(false))
+	return goquery.NewDocumentFromNode(root), err
+}
+
+func (root QuerySelection) setImgURL(pageURL string, pageTitle string) QuerySelection {
+	clone := countHTML.Clone()
+	imgOnce.Do(func() {
+		var err error
+		img = clone.Find("img")
+		attr, _ := img.Attr("src")
+		if attr == "" {
+			panic("<img> does not have src attribute")
+		}
+		urlTemplate, err = url.Parse(attr)
+		if err != nil {
+			panic(err.Error())
+		}
+	})
+	q := urlTemplate.Query()
+	urlTemplate.RawQuery = ""
+	q.Set("p", pageURL)
+	q.Set("t", pageTitle)
+	output := urlTemplate.String() + "?" + q.Encode()
+	clone.Find("img").SetAttr("src", output)
+	root.AppendSelection(clone.Find("body").Children())
+	return root
+}
+
+func layout(filename string, config config.Config, pageTitle string, pageURL string) (*goquery.Document, error) {
 	html, err := loadTemplate(filename)
 	if err != nil {
 		return nil, err
@@ -48,13 +101,22 @@ func layout(filename string, config config.Config, pageTitle string) (*goquery.D
 	assetsOnce.Do(func() {
 		var bytes []byte
 		bytes, err = os.ReadFile("templates/style.css")
+		if err != nil {
+			return
+		}
 		css = string(bytes)
+		countFile, err := os.OpenFile("templates/count.html", os.O_RDONLY, 0)
+		if err != nil {
+			return
+		}
+		defer countFile.Close()
+		countHTML, err = NewDocumentNoScript(countFile)
 	})
 	if err != nil {
 		return nil, err
 	}
 
-	doc, err := goquery.NewDocumentFromReader(html)
+	doc, err := NewDocumentFromReader(html)
 	if err != nil {
 		return nil, err
 	}
@@ -63,17 +125,18 @@ func layout(filename string, config config.Config, pageTitle string) (*goquery.D
 	doc.Find(".title").SetText(config.Title)
 	doc.Find("title").Add(".p-name").SetText(pageTitle)
 	doc.Find("head > style").SetHtml("\n" + string(css))
+	doc.Find("body").setImgURL(pageURL, pageTitle)
 	nav := doc.Find("nav")
 	navLink := doc.Find("nav a")
 	nav.Empty()
 	for _, link := range config.Menus["main"] {
 		nav.AppendSelection(navLink.Clone().SetAttr("href", link.URL).SetText(link.Name))
 	}
-	return doc, nil
+	return doc.Document, nil
 }
 
 func renderPost(post Post, config config.Config) (r io.Reader, err error) {
-	doc, err := layout("templates/post.html", config, post.PostMatter.Title)
+	doc, err := layout("templates/post.html", config, post.PostMatter.Title, post.URL)
 	if err != nil {
 		return nil, err
 	}
@@ -96,8 +159,8 @@ func renderPost(post Post, config config.Config) (r io.Reader, err error) {
 	return renderHTML(doc), nil
 }
 
-func renderTags(tags Tags, config config.Config) (io.Reader, error) {
-	doc, err := layout("templates/tags.html", config, config.Title)
+func renderTags(tags Tags, config config.Config, url string) (io.Reader, error) {
+	doc, err := layout("templates/tags.html", config, config.Title, url)
 	if err != nil {
 		return nil, err
 	}
@@ -112,14 +175,14 @@ func renderTags(tags Tags, config config.Config) (io.Reader, error) {
 	return renderHTML(doc), nil
 }
 
-func renderListPage(tag string, config config.Config, posts []Post) (io.Reader, error) {
+func renderListPage(tag string, config config.Config, posts []Post, url string) (io.Reader, error) {
 	var title string
 	if len(tag) > 0 {
 		title = tag
 	} else {
 		title = config.Title
 	}
-	doc, err := layout("templates/list.html", config, title)
+	doc, err := layout("templates/list.html", config, title, url)
 	if err != nil {
 		return nil, err
 	}
@@ -146,12 +209,12 @@ func renderListPage(tag string, config config.Config, posts []Post) (io.Reader,
 	return renderHTML(doc), nil
 }
 
-func renderHomepage(config config.Config, posts []Post) (io.Reader, error) {
+func renderHomepage(config config.Config, posts []Post, url string) (io.Reader, error) {
 	_, index, err := getPost("content/_index.md")
 	if err != nil {
 		return nil, err
 	}
-	doc, err := layout("templates/homepage.html", config, config.Title)
+	doc, err := layout("templates/homepage.html", config, config.Title, url)
 	if err != nil {
 		return nil, err
 	}
@@ -193,8 +256,8 @@ func renderHomepage(config config.Config, posts []Post) (io.Reader, error) {
 	return renderHTML(doc), nil
 }
 
-func render404(config config.Config) (io.Reader, error) {
-	doc, err := layout("templates/404.html", config, "404 Not Found")
+func render404(config config.Config, url string) (io.Reader, error) {
+	doc, err := layout("templates/404.html", config, "404 Not Found", url)
 	if err != nil {
 		return nil, err
 	}
diff --git a/config.toml b/config.toml
index 067b829..e79653d 100644
--- a/config.toml
+++ b/config.toml
@@ -16,7 +16,7 @@ feed = true
 [extra.headers]
 cache-control = "max-age=14400"
 x-content-type-options = "nosniff"
-content-security-policy = "default-src 'none'; img-src 'self'; object-src 'none'; script-src 'self'; style-src 'unsafe-inline'; form-action 'none'; base-uri 'self'; frame-ancestors https://kagi.com;"
+content-security-policy = "default-src 'none'; img-src 'self' https://gc.zgo.at; script-src 'self https://gc.zgo.at'; style-src 'unsafe-inline'; frame-ancestors https://kagi.com; connect-src https://alanpearce-eu.goatcounter.com/count; require-trusted-types-for 'script'"
 
 [[menus.main]]
     name = "Home"
diff --git a/templates/count.html b/templates/count.html
new file mode 100644
index 0000000..737b99d
--- /dev/null
+++ b/templates/count.html
@@ -0,0 +1,6 @@
+<body>
+  <script data-goatcounter="https://alanpearce-eu.goatcounter.com/count" async src="https://gc.zgo.at/count.js"></script>
+  <noscript>
+    <img src="https://alanpearce-eu.goatcounter.com/count?p=/INSERT-PAGE-HERE" />
+  </noscript>
+</body>