Improve blogstats and load table using JS (because it's slow)

This commit is contained in:
Jan-Lukas Else 2021-05-09 09:08:31 +02:00
parent 026ae9469a
commit 59e491ee0a
11 changed files with 114 additions and 61 deletions

View File

@ -3,6 +3,7 @@ package main
import (
"fmt"
"io"
"log"
"net/http"
"sort"
"strings"
@ -26,7 +27,8 @@ func serveBlogroll(w http.ResponseWriter, r *http.Request) {
return getBlogrollOutlines(c)
})
if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError)
log.Println("Failed to get outlines:", err.Error())
serveError(w, r, "", http.StatusInternalServerError)
return
}
if appConfig.Cache != nil && appConfig.Cache.Enable {
@ -54,7 +56,8 @@ func serveBlogrollExport(w http.ResponseWriter, r *http.Request) {
return getBlogrollOutlines(c)
})
if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError)
log.Println("Failed to get outlines:", err.Error())
serveError(w, r, "", http.StatusInternalServerError)
return
}
if appConfig.Cache != nil && appConfig.Cache.Enable {

View File

@ -8,6 +8,18 @@ import (
)
func serveBlogStats(w http.ResponseWriter, r *http.Request) {
blog := r.Context().Value(blogContextKey).(string)
canonical := blogPath(blog) + appConfig.Blogs[blog].BlogStats.Path
render(w, r, templateBlogStats, &renderData{
BlogString: blog,
Canonical: canonical,
Data: map[string]interface{}{
"TableUrl": canonical + ".table.html",
},
})
}
func serveBlogStatsTable(w http.ResponseWriter, r *http.Request) {
blog := r.Context().Value(blogContextKey).(string)
// Start timing
t := servertiming.FromContext(r.Context()).NewMetric("sq").Start()
@ -17,6 +29,7 @@ func serveBlogStats(w http.ResponseWriter, r *http.Request) {
status: statusPublished,
}
query, params := buildPostsQuery(prq)
query = "select path, mdtext(content) as content, published, substr(published, 1, 4) as year, substr(published, 6, 2) as month from (" + query + ")"
postCount := "coalesce(count(distinct path), 0) as postcount"
charCount := "coalesce(sum(coalesce(length(distinct content), 0)), 0)"
wordCount := "coalesce(sum(wordcount(distinct content)), 0) as wordcount"
@ -36,7 +49,7 @@ func serveBlogStats(w http.ResponseWriter, r *http.Request) {
return
}
// Count posts per year
rows, err := appDbQuery("select *, "+wordsPerPost+" from (select substr(published, 1, 4) as year, "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published != '' group by year order by year desc)", params...)
rows, err := appDbQuery("select *, "+wordsPerPost+" from (select year, "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published != '' group by year order by year desc)", params...)
if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError)
return
@ -66,7 +79,7 @@ func serveBlogStats(w http.ResponseWriter, r *http.Request) {
months := map[string][]statsTableType{}
month := statsTableType{}
for _, year := range years {
rows, err = appDbQuery("select *, "+wordsPerPost+" from (select substr(published, 6, 2) as month, "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published != '' and substr(published, 1, 4) = @year group by month order by month desc)", append(params, sql.Named("year", year.Name))...)
rows, err = appDbQuery("select *, "+wordsPerPost+" from (select month, "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published != '' and year = @year group by month order by month desc)", append(params, sql.Named("year", year.Name))...)
if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError)
return
@ -83,9 +96,8 @@ func serveBlogStats(w http.ResponseWriter, r *http.Request) {
// Stop timing
t.Stop()
// Render
render(w, r, templateBlogStats, &renderData{
render(w, r, templateBlogStatsTable, &renderData{
BlogString: blog,
Canonical: blogPath(blog) + appConfig.Blogs[blog].BlogStats.Path,
Data: map[string]interface{}{
"total": total,
"years": years,

View File

@ -25,6 +25,9 @@ func initDatabase() (err error) {
if err := c.RegisterFunc("wordcount", wordCount, true); err != nil {
return err
}
if err := c.RegisterFunc("mdtext", renderText, true); err != nil {
return err
}
return nil
},
})

View File

@ -478,7 +478,12 @@ func buildDynamicRouter() (*chi.Mux, error) {
// Stats
if blogConfig.BlogStats != nil && blogConfig.BlogStats.Enabled {
statsPath := blogPath + blogConfig.BlogStats.Path
r.With(privateModeHandler...).With(cacheMiddleware, sbm).Get(statsPath, serveBlogStats)
r.Group(func(r chi.Router) {
r.Use(privateModeHandler...)
r.Use(cacheMiddleware, sbm)
r.Get(statsPath, serveBlogStats)
r.Get(statsPath+".table.html", serveBlogStatsTable)
})
}
// Date archives

View File

@ -4,6 +4,7 @@ import (
"bytes"
"strings"
"github.com/PuerkitoBio/goquery"
kemoji "github.com/kyokomi/emoji/v2"
"github.com/yuin/goldmark"
emoji "github.com/yuin/goldmark-emoji"
@ -54,6 +55,18 @@ func renderMarkdown(source string, absoluteLinks bool) (rendered []byte, err err
return buffer.Bytes(), err
}
func renderText(s string) string {
h, err := renderMarkdown(s, false)
if err != nil {
return ""
}
d, err := goquery.NewDocumentFromReader(bytes.NewReader(h))
if err != nil {
return ""
}
return d.Text()
}
// Extensions etc...
// All emojis from emoji lib

View File

@ -35,6 +35,7 @@ const (
templateLogin = "login"
templateStaticHome = "statichome"
templateBlogStats = "blogstats"
templateBlogStatsTable = "blogstatstable"
templateComment = "comment"
templateCaptcha = "captcha"
templateCommentsAdmin = "commentsadmin"

View File

@ -1,11 +1,22 @@
(function () {
Array.from(document.getElementsByClassName('statsyear')).forEach(element => {
element.addEventListener('click', function () {
Array.from(document.getElementsByClassName('statsmonth')).forEach(c => {
if (element.dataset.year == c.dataset.year) {
c.classList.contains('hide') ? c.classList.remove('hide') : c.classList.add('hide')
}
let loadingEl = document.getElementById('loading')
let tableUrl = loadingEl.dataset.table
let tableReq = new XMLHttpRequest()
tableReq.open('GET', tableUrl)
tableReq.onload = function() {
if (tableReq.status == 200) {
loadingEl.outerHTML = tableReq.responseText
Array.from(document.getElementsByClassName('statsyear')).forEach(element => {
element.addEventListener('click', function () {
Array.from(document.getElementsByClassName('statsmonth')).forEach(c => {
if (element.dataset.year == c.dataset.year) {
c.classList.contains('hide') ? c.classList.remove('hide') : c.classList.add('hide')
}
})
})
})
})
})
}
}
tableReq.send()
})()

View File

@ -6,52 +6,7 @@
<main>
{{ with .Blog.BlogStats.Title }}<h1>{{ . }}</h1>{{ end }}
{{ with .Blog.BlogStats.Description }}{{ md . }}{{ end }}
<table>
<thead>
<tr>
<th class="tal">{{ string .Blog.Lang "year" }}</th>
<th class="tar">{{ string .Blog.Lang "posts" }}</th>
<th class="tar">~{{ string .Blog.Lang "chars" }}</th>
<th class="tar">~{{ string .Blog.Lang "words" }}</th>
<th class="tar">~{{ string .Blog.Lang "wordsperpost" }}</th>
</tr>
</thead>
<tbody>
{{ $months := .Data.months }}
{{ range $year := .Data.years }}
<tr class="statsyear" data-year="{{ $year.Name }}">
<td class="tal">{{ $year.Name }}</td>
<td class="tar">{{ $year.Posts }}</td>
<td class="tar">{{ $year.Chars }}</td>
<td class="tar">{{ $year.Words }}</td>
<td class="tar">{{ $year.WordsPerPost }}</td>
</tr>
{{ range $month := (index $months $year.Name) }}
<tr class="statsmonth hide" data-year="{{ $year.Name }}">
<td class="tal">{{ $year.Name }}-{{ $month.Name }}</td>
<td class="tar">{{ $month.Posts }}</td>
<td class="tar">{{ $month.Chars }}</td>
<td class="tar">{{ $month.Words }}</td>
<td class="tar">{{ $month.WordsPerPost }}</td>
</tr>
{{ end }}
{{ end }}
<tr>
<td class="tal">{{ string .Blog.Lang "withoutdate" }}</td>
<td class="tar">{{ .Data.withoutdate.Posts }}</td>
<td class="tar">{{ .Data.withoutdate.Chars }}</td>
<td class="tar">{{ .Data.withoutdate.Words }}</td>
<td class="tar">{{ .Data.withoutdate.WordsPerPost }}</td>
</tr>
<tr>
<td class="tal"><b>{{ string .Blog.Lang "total" }}</b></td>
<td class="tar">{{ .Data.total.Posts }}</td>
<td class="tar">{{ .Data.total.Chars }}</td>
<td class="tar">{{ .Data.total.Words }}</td>
<td class="tar">{{ .Data.total.WordsPerPost }}</td>
</tr>
</tbody>
</table>
<p id="loading" data-table="{{.Data.TableUrl}}">{{ string .Blog.Lang "loading" }}</p>
<script defer src="{{ asset "js/blogstats.js" }}"></script>
</main>
{{ end }}

View File

@ -0,0 +1,48 @@
{{ define "blogstatstable" }}
<table>
<thead>
<tr>
<th class="tal">{{ string .Blog.Lang "year" }}</th>
<th class="tar">{{ string .Blog.Lang "posts" }}</th>
<th class="tar">~{{ string .Blog.Lang "chars" }}</th>
<th class="tar">~{{ string .Blog.Lang "words" }}</th>
<th class="tar">~{{ string .Blog.Lang "wordsperpost" }}</th>
</tr>
</thead>
<tbody>
{{ $months := .Data.months }}
{{ range $year := .Data.years }}
<tr class="statsyear" data-year="{{ $year.Name }}">
<td class="tal">{{ $year.Name }}</td>
<td class="tar">{{ $year.Posts }}</td>
<td class="tar">{{ $year.Chars }}</td>
<td class="tar">{{ $year.Words }}</td>
<td class="tar">{{ $year.WordsPerPost }}</td>
</tr>
{{ range $month := (index $months $year.Name) }}
<tr class="statsmonth hide" data-year="{{ $year.Name }}">
<td class="tal">{{ $year.Name }}-{{ $month.Name }}</td>
<td class="tar">{{ $month.Posts }}</td>
<td class="tar">{{ $month.Chars }}</td>
<td class="tar">{{ $month.Words }}</td>
<td class="tar">{{ $month.WordsPerPost }}</td>
</tr>
{{ end }}
{{ end }}
<tr>
<td class="tal">{{ string .Blog.Lang "withoutdate" }}</td>
<td class="tar">{{ .Data.withoutdate.Posts }}</td>
<td class="tar">{{ .Data.withoutdate.Chars }}</td>
<td class="tar">{{ .Data.withoutdate.Words }}</td>
<td class="tar">{{ .Data.withoutdate.WordsPerPost }}</td>
</tr>
<tr>
<td class="tal"><b>{{ string .Blog.Lang "total" }}</b></td>
<td class="tar">{{ .Data.total.Posts }}</td>
<td class="tar">{{ .Data.total.Chars }}</td>
<td class="tar">{{ .Data.total.Words }}</td>
<td class="tar">{{ .Data.total.WordsPerPost }}</td>
</tr>
</tbody>
</table>
{{ end }}

View File

@ -12,6 +12,7 @@ editor: "Editor"
interactions: "Interaktionen & Kommentare"
interactionslabel: "Hast du eine Antwort hierzu veröffentlicht? Füge hier die URL ein."
likeof: "Gefällt mir von"
loading: "Laden..."
next: "Weiter"
noposts: "Hier sind keine Posts."
oldcontent: "⚠️ Dieser Eintrag ist bereits über ein Jahr alt. Er ist möglicherweise nicht mehr aktuell. Meinungen können sich geändert haben."

View File

@ -19,6 +19,7 @@ indieauth: "IndieAuth"
interactions: "Interactions & Comments"
interactionslabel: "Have you published a response to this? Paste the URL here."
likeof: "Like of"
loading: "Loading..."
login: "Login"
logout: "Logout"
nameopt: "Name (optional)"