diff --git a/i18n/locale/en/template.json b/i18n/locale/en/template.json index 9e184a4..fe5c612 100644 --- a/i18n/locale/en/template.json +++ b/i18n/locale/en/template.json @@ -137,6 +137,7 @@ "assets/views/fragments/small-tn.jet.html:yTQShm_bVeU": "Deleted or Private", "assets/views/fragments/thumbnail-dt.jet.html:0wMo7aB_ylE": "{{ if .XRestrict == 1 }}R-18{{ else }}R-18G{{ end }}", "assets/views/fragments/thumbnail-dt.jet.html:R20Dy5iCS58": "AI", + "assets/views/index.jet.html:6103Lobv_EM": "Trending!", "assets/views/index.jet.html:8lMwxcLFwfQ": "R-18", "assets/views/index.jet.html:Awt3bDxDL0U": "View more", "assets/views/index.jet.html:D-LKBPd3-uc": "Today's illustration rankings", @@ -145,6 +146,7 @@ "assets/views/index.jet.html:Sccoo133FQI": "All", "assets/views/index.jet.html:lEVH3Ram904": "Newest works", "assets/views/index.jet.html:lv5wuj1vx98": "Latest works by followed", + "assets/views/index.jet.html:n-1FZQng9rs": "Popular tags", "assets/views/index.jet.html:n7zdClpaR18": "Recommended illustrations tagged", "assets/views/index.jet.html:s7Bp-mQhmg0": "Recommended users", "assets/views/index.jet.html:v1aAHggOM7M": "Recommended works", diff --git a/server/routes/types.go b/server/routes/types.go index c9266b6..385371a 100644 --- a/server/routes/types.go +++ b/server/routes/types.go @@ -14,9 +14,20 @@ func RenderHTML[T any](w http.ResponseWriter, r *http.Request, data T) error { } func RenderWithContentType[T any](w http.ResponseWriter, r *http.Request, contentType string, data T) error { - w.Header().Set("content-type", contentType) - w.WriteHeader(request_context.Get(r).RenderStatusCode) - return template.Render(w, template.GetTemplatingVariables(r), data) + w.Header().Set("Content-Type", contentType) + + // Render and get ETag + etag, err := template.Render(w, template.GetTemplatingVariables(r), data) + if err != nil { + return err + } + + // Set strong ETag + w.Header().Set("ETag", `"`+etag+`"`) + + w.WriteHeader(request_context.Get(r).RenderStatusCode) + + return nil } // Tutorial: adding new types in this file diff --git a/server/template/render.go b/server/template/render.go index 8d150a1..794a580 100644 --- a/server/template/render.go +++ b/server/template/render.go @@ -1,12 +1,17 @@ package template import ( + "bytes" + "encoding/base64" + "encoding/binary" "io" "log" "net/http" "reflect" "strings" + "github.com/zeebo/xxh3" + "codeberg.org/vnpower/pixivfe/v2/server/session" "codeberg.org/vnpower/pixivfe/v2/server/utils" @@ -32,7 +37,7 @@ func Init(DisableCache bool, assetsLocation string) { } } -func Render[T any](w io.Writer, variables jet.VarMap, data T) error { +func Render[T any](w io.Writer, variables jet.VarMap, data T) (string, error) { template_name, found := strings.CutPrefix(reflect.TypeFor[T]().Name(), "Data_") if !found { log.Panicf("struct name does not start with 'Data_': %s", template_name) @@ -40,12 +45,31 @@ func Render[T any](w io.Writer, variables jet.VarMap, data T) error { template, err := views.GetTemplate(template_name + ".jet.html") if err != nil { - return err + return "", err } views.Parse(template_name+".jet.html", template.String()) - return template.Execute(w, variables, data) + // Create buffer to capture rendered output + buf := &bytes.Buffer{} + + // Execute template to buffer + if err := template.Execute(buf, variables, data); err != nil { + return "", err + } + + // Generate ETag + hash := xxh3.Hash(buf.Bytes()) + hash_bytes := make([]byte, 8) + binary.LittleEndian.PutUint64(hash_bytes, uint64(hash)) + etag := template_name + ":" + base64.RawURLEncoding.EncodeToString(hash_bytes) + + // Write buffer to original writer + if _, err := io.Copy(w, buf); err != nil { + return "", err + } + + return etag, nil } func GetTemplatingVariables(r *http.Request) jet.VarMap {