diff --git a/.air.toml b/.air.toml index 7f0fe43..319bac0 100644 --- a/.air.toml +++ b/.air.toml @@ -12,7 +12,7 @@ tmp_dir = "tmp" exclude_regex = ["_test.go", "_templ.go"] exclude_unchanged = false follow_symlink = false - full_bin = "" + full_bin = "dlv exec ./tmp/main --listen=127.0.0.1:2345 --headless=true --api-version=2 --accept-multiclient --continue --log --" include_dir = [] include_ext = ["go", "tpl", "tmpl", "templ", "html", "css"] kill_delay = "0s" @@ -20,7 +20,6 @@ tmp_dir = "tmp" poll = false poll_interval = 0 rerun = false - rerun_delay = 500 send_interrupt = false stop_on_error = true @@ -28,6 +27,7 @@ tmp_dir = "tmp" enabled = true proxy_port = 8888 app_port = 8080 + app_start_timeout = 50000000 [color] app = "" diff --git a/.vscode/launch.json b/.vscode/launch.json index 0760044..cf8b117 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,6 +9,14 @@ "program": "${workspaceFolder}/cmd/schreifuchs-ch/main.go", "cwd": "${workspaceFolder}", "showLog": true + }, + { + "name": "Attach to Air", + "type": "go", + "request": "attach", + "mode": "remote", + "port": 2345, + "host": "127.0.0.1" } ] } diff --git a/go.mod b/go.mod index c0385f5..6098909 100644 --- a/go.mod +++ b/go.mod @@ -13,9 +13,11 @@ require ( github.com/cli/browser v1.3.0 // indirect github.com/fatih/color v1.16.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/klauspost/compress v1.18.4 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/natefinch/atomic v1.0.1 // indirect + github.com/studio-b12/gowebdav v0.12.0 // indirect golang.org/x/mod v0.26.0 // indirect golang.org/x/net v0.42.0 // indirect golang.org/x/sync v0.16.0 // indirect diff --git a/go.sum b/go.sum index e7e087a..f3ebc4e 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 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/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= +github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -27,6 +29,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/studio-b12/gowebdav v0.12.0 h1:kFRtQECt8jmVAvA6RHBz3geXUGJHUZA6/IKpOVUs5kM= +github.com/studio-b12/gowebdav v0.12.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE= golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= diff --git a/internal/components/images/uid.go b/internal/components/images/uid.go new file mode 100644 index 0000000..cdcf990 --- /dev/null +++ b/internal/components/images/uid.go @@ -0,0 +1,47 @@ +package images + +import ( + "bytes" + "compress/flate" + "encoding/base64" + "io" +) + +func UIDFromPath(path string) (uid string, err error) { + var b bytes.Buffer + // NewWriter with NoDict (nil) creates a raw DEFLATE compressor + zw, _ := flate.NewWriter(&b, flate.BestCompression) + + _, err = zw.Write([]byte(path)) + if err != nil { + return + } + err = zw.Close() // Essential to flush the final bits + if err != nil { + return + } + + return base64.RawURLEncoding.EncodeToString(b.Bytes()), nil +} + +func PathFromUID(uid string) (string, error) { + // 1. Decode the Base64 string back to compressed bytes + compressedBytes, err := base64.RawURLEncoding.DecodeString(uid) + if err != nil { + return "", err + } + + // 2. Create a flate reader (NoDict/nil since you used nil in the writer) + // flate.NewReader returns an io.ReadCloser + reader := flate.NewReader(bytes.NewReader(compressedBytes)) + defer reader.Close() + + // 3. Read the decompressed data into a buffer + var out bytes.Buffer + _, err = io.Copy(&out, reader) + if err != nil { + return "", err + } + + return out.String(), nil +} diff --git a/internal/components/page/controller.go b/internal/components/page/controller.go index 6fb9017..b0b8520 100644 --- a/internal/components/page/controller.go +++ b/internal/components/page/controller.go @@ -1,6 +1,10 @@ package page -import "context" +import ( + "context" + "log/slog" + "strings" +) type PageHeader struct { UID string @@ -13,29 +17,41 @@ type Page struct { Content string } -func (s *Service) GetPages() (pages []PageHeader, err error) { - pages = []PageHeader{ - { - UID: "asdf", - Title: "asdfAasdf!", - CoverImageUID: "500-1000", - }, +func (s *Service) GetPages(ctx context.Context) (pages []PageHeader, err error) { + paths, err := s.fs.ReadDir("/") + if err != nil { + return + } + + for _, path := range paths { + if strings.HasPrefix(path.Name(), ".") || // hidden files + strings.HasPrefix(path.Name(), "_") { // hidden pages + continue + } + page, err := s.getPageHeaders(ctx, path.Name()) + if err != nil { // just ignore missing pages + slog.Error("could not open path", "path", path.Name(), "err", err) + continue + } + pages = append(pages, page) } return } func (s *Service) GetPage(ctx context.Context, uid string) (page Page, err error) { + p, err := s.getPageHeaders(ctx, uid) + slog.Debug("got page", "page", p, "err", err) content := `

LOL

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed mollis ligula vel nisl pellentesque aliquet. Maecenas eu congue tortor. Nunc mattis ligula vel augue viverra cursus. Suspendisse potenti. Vivamus in risus vitae mi molestie feugiat. Cras sapien tortor, elementum ut tempus vel, commodo at dui. In hac habitasse platea dictumst. Integer nec ullamcorper purus, tincidunt elementum felis. Duis hendrerit tempor vestibulum. Nunc ut finibus tellus. Donec eu lacus sit amet sem suscipit imperdiet. Quisque cursus vulputate sodales. In sit amet nisl a est lacinia sagittis.

- +

LOL

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed mollis ligula vel nisl pellentesque aliquet. Maecenas eu congue tortor. Nunc mattis ligula vel augue viverra cursus. Suspendisse potenti. Vivamus in risus vitae mi molestie feugiat. Cras sapien tortor, elementum ut tempus vel, commodo at dui. In hac habitasse platea dictumst. Integer nec ullamcorper purus, tincidunt elementum felis. Duis hendrerit tempor vestibulum. Nunc ut finibus tellus. Donec eu lacus sit amet sem suscipit imperdiet. Quisque cursus vulputate sodales. In sit amet nisl a est lacinia sagittis.

- +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed mollis ligula vel nisl pellentesque aliquet. Maecenas eu congue tortor. Nunc mattis ligula vel augue viverra cursus. Suspendisse potenti. Vivamus in risus vitae mi molestie feugiat. Cras sapien tortor, elementum ut tempus vel, commodo at dui. In hac habitasse platea dictumst. Integer nec ullamcorper purus, tincidunt elementum felis. Duis hendrerit tempor vestibulum. Nunc ut finibus tellus. Donec eu lacus sit amet sem suscipit imperdiet. Quisque cursus vulputate sodales. In sit amet nisl a est lacinia sagittis.

@@ -43,6 +59,25 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed mollis ligula vel n

LOL

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed mollis ligula vel nisl pellentesque aliquet. Maecenas eu congue tortor. Nunc mattis ligula vel augue viverra cursus. Suspendisse potenti. Vivamus in risus vitae mi molestie feugiat. Cras sapien tortor, elementum ut tempus vel, commodo at dui. In hac habitasse platea dictumst. Integer nec ullamcorper purus, tincidunt elementum felis. Duis hendrerit tempor vestibulum. Nunc ut finibus tellus. Donec eu lacus sit amet sem suscipit imperdiet. Quisque cursus vulputate sodales. In sit amet nisl a est lacinia sagittis.

+

LOL

+

+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed mollis ligula vel nisl pellentesque aliquet. Maecenas eu congue tortor. Nunc mattis ligula vel augue viverra cursus. Suspendisse potenti. Vivamus in risus vitae mi molestie feugiat. Cras sapien tortor, elementum ut tempus vel, commodo at dui. In hac habitasse platea dictumst. Integer nec ullamcorper purus, tincidunt elementum felis. Duis hendrerit tempor vestibulum. Nunc ut finibus tellus. Donec eu lacus sit amet sem suscipit imperdiet. Quisque cursus vulputate sodales. In sit amet nisl a est lacinia sagittis.

+ + + +

+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed mollis ligula vel nisl pellentesque aliquet. Maecenas eu congue tortor. Nunc mattis ligula vel augue viverra cursus. Suspendisse potenti. Vivamus in risus vitae mi molestie feugiat. Cras sapien tortor, elementum ut tempus vel, commodo at dui. In hac habitasse platea dictumst. Integer nec ullamcorper purus, tincidunt elementum felis. Duis hendrerit tempor vestibulum. Nunc ut finibus tellus. Donec eu lacus sit amet sem suscipit imperdiet. Quisque cursus vulputate sodales. In sit amet nisl a est lacinia sagittis.

+ +

LOL

+

+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed mollis ligula vel nisl pellentesque aliquet. Maecenas eu congue tortor. Nunc mattis ligula vel augue viverra cursus. Suspendisse potenti. Vivamus in risus vitae mi molestie feugiat. Cras sapien tortor, elementum ut tempus vel, commodo at dui. In hac habitasse platea dictumst. Integer nec ullamcorper purus, tincidunt elementum felis. Duis hendrerit tempor vestibulum. Nunc ut finibus tellus. Donec eu lacus sit amet sem suscipit imperdiet. Quisque cursus vulputate sodales. In sit amet nisl a est lacinia sagittis.

+ + + +

+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed mollis ligula vel nisl pellentesque aliquet. Maecenas eu congue tortor. Nunc mattis ligula vel augue viverra cursus. Suspendisse potenti. Vivamus in risus vitae mi molestie feugiat. Cras sapien tortor, elementum ut tempus vel, commodo at dui. In hac habitasse platea dictumst. Integer nec ullamcorper purus, tincidunt elementum felis. Duis hendrerit tempor vestibulum. Nunc ut finibus tellus. Donec eu lacus sit amet sem suscipit imperdiet. Quisque cursus vulputate sodales. In sit amet nisl a est lacinia sagittis.

+ + ` page = Page{ PageHeader: PageHeader{ diff --git a/internal/components/page/page.go b/internal/components/page/page.go new file mode 100644 index 0000000..245b0e7 --- /dev/null +++ b/internal/components/page/page.go @@ -0,0 +1,88 @@ +package page + +import ( + "bytes" + "compress/flate" + "context" + "encoding/base64" + "errors" + "fmt" + "log/slog" + "regexp" + "strings" + + "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/components/images" + "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/components/translate" + "github.com/studio-b12/gowebdav" +) + +var titleRGX = regexp.MustCompile(`(?m)^#\s+(.+)$`) + +var ( + ErrMalFormedInput = errors.New("mal formed input") + ErrNotFound = errors.New("not found") +) + +func (s *Service) getPageHeaders(ctx context.Context, uid string) (page PageHeader, err error) { + info, err := s.fs.ReadDir(uid) + if gowebdav.IsErrNotFound(err) { // try private + err = ErrNotFound + } + if err != nil { + return + } + + lang := translate.Language(ctx) + + var coverImage *gowebdav.File + var contentMD *gowebdav.File + + for _, f := range info { + file := f.(gowebdav.File) + + if strings.HasPrefix(file.ContentType(), "image/") { + coverImage = &file + } + + parts := strings.Split(file.Name(), ".") + + // fallback content + if contentMD == nil && len(parts) == 2 && parts[1] == "md" { + contentMD = &file + } + // language content + if len(parts) == 3 && parts[1] == lang && parts[2] == "md" { + contentMD = &file + } + } + + slog.Debug("loaded page infos", "cover", coverImage, "content", contentMD) + + content, err := s.fs.Read(contentMD.Path()) + if err != nil { + return + } + + title := titleRGX.FindStringSubmatch(string(content)) + if len(title) < 2 { + err = fmt.Errorf("%w: no matches for title", ErrMalFormedInput) + return + } + + page.UID = uid + page.Title = title[1] + page.CoverImageUID, err = images.UIDFromPath(coverImage.Path()) + + return +} + +func getImageUID(path string) string { + var b bytes.Buffer + // NewWriter with NoDict (nil) creates a raw DEFLATE compressor + zw, _ := flate.NewWriter(&b, flate.BestCompression) + + zw.Write([]byte(path)) + zw.Close() // Essential to flush the final bits + + return base64.RawURLEncoding.EncodeToString(b.Bytes()) +} diff --git a/internal/components/page/resource.go b/internal/components/page/resource.go index 75055ea..63f2bc6 100644 --- a/internal/components/page/resource.go +++ b/internal/components/page/resource.go @@ -1,7 +1,23 @@ package page -type Service struct{} +import ( + "log/slog" -func New() *Service { - return &Service{} + "github.com/studio-b12/gowebdav" +) + +type Service struct { + fs *gowebdav.Client +} + +func New(fs *gowebdav.Client) *Service { + info, err := fs.ReadDir("/") + if err != nil { + slog.Error("could not read dir", "err", err) + } + + slog.Info("file info", "info", info) + return &Service{ + fs: fs, + } } diff --git a/internal/handlers/handlehome/controller.go b/internal/handlers/handlehome/controller.go index 569b78a..87d3210 100644 --- a/internal/handlers/handlehome/controller.go +++ b/internal/handlers/handlehome/controller.go @@ -8,7 +8,7 @@ import ( ) func (h *Handler) home(w http.ResponseWriter, r *http.Request) { - pageHeaders, err := h.src.GetPages() + pageHeaders, err := h.src.GetPages(r.Context()) if err != nil { w.WriteHeader(http.StatusInternalServerError) return diff --git a/internal/handlers/handlehome/resource.go b/internal/handlers/handlehome/resource.go index 7222737..6bb5371 100644 --- a/internal/handlers/handlehome/resource.go +++ b/internal/handlers/handlehome/resource.go @@ -22,6 +22,6 @@ func New(mux *http.ServeMux, srv pageService) *Handler { } type pageService interface { - GetPages() ([]page.PageHeader, error) + GetPages(ctx context.Context) ([]page.PageHeader, error) GetPage(ctx context.Context, uid string) (page.Page, error) } diff --git a/internal/server/routes.go b/internal/server/routes.go index f9bab52..b393a3c 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -3,6 +3,7 @@ package server import ( "log/slog" "net/http" + "os" "time" "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/components/page" @@ -10,6 +11,7 @@ import ( "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/handlers/handlehome" "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/templer" "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/web" + "github.com/studio-b12/gowebdav" ) func (s *Server) RegisterRoutes() (h http.Handler) { @@ -24,17 +26,26 @@ func (s *Server) RegisterRoutes() (h http.Handler) { return h } -func registerOther() http.Handler { +func registerOther() (h http.Handler) { mux := http.NewServeMux() - pager := page.New() + webdavClient := gowebdav.NewClient("https://cloud.schreifuchs.ch/remote.php/dav/files/test/website", "test", os.Getenv("TEST_PW")) + if err := webdavClient.Connect(); err != nil { + slog.Error("could not connect webdavClient", "err", err, "pw", os.Getenv("TEST_PW")) + } + + pager := page.New(webdavClient) _ = handlehome.New(mux, pager) mux.Handle("/", http.FileServer(http.FS(web.GetStaticFS()))) translator := translate.New() - return translator.Middleware(mux) + + h = mux + h = translator.Middleware(h) + h = cacheMiddleware(time.Second * 45)(h) + return h } func (s *Server) loggingMiddleware(next http.Handler) http.Handler { diff --git a/internal/server/static.go b/internal/server/static.go index df4bf2e..66fa38f 100644 --- a/internal/server/static.go +++ b/internal/server/static.go @@ -1,9 +1,11 @@ package server import ( + "fmt" "io/fs" "log/slog" "net/http" + "time" "git.schreifuchs.ch/schreifuchs/schreifuchs.ch/web" ) @@ -13,7 +15,7 @@ func registerStatic(mux *http.ServeMux) { fileSystem := web.GetStaticFS() // we still use go's fileServer to avoid unnecessary implementation - fileServer := http.FileServer(http.FS(fileSystem)) + fileServer := cacheMiddleware(time.Hour * 24)(http.FileServer(http.FS(fileSystem))) err := fs.WalkDir(fileSystem, ".", func(path string, d fs.DirEntry, err error) error { if err != nil { @@ -36,3 +38,13 @@ func registerStatic(mux *http.ServeMux) { panic(err) } } + +// cacheMiddleware adds Cache-Control headers to the response. +func cacheMiddleware(maxAge time.Duration) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%.0f", maxAge.Seconds())) + next.ServeHTTP(w, r) + }) + } +} diff --git a/tmp/build-errors.log b/tmp/build-errors.log index 32ee03e..189b41c 100644 --- a/tmp/build-errors.log +++ b/tmp/build-errors.log @@ -1 +1 @@ -exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file +exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file diff --git a/tmp/main b/tmp/main index 36898cd..f3d2c13 100755 Binary files a/tmp/main and b/tmp/main differ diff --git a/web/components/imagetile.templ b/web/components/imagetile.templ index 2e1099f..9a5d6e4 100644 --- a/web/components/imagetile.templ +++ b/web/components/imagetile.templ @@ -4,7 +4,7 @@ templ ImageTile(src string) {
diff --git a/web/components/imagetile_templ.go b/web/components/imagetile_templ.go index 56bd20b..fe33d59 100644 --- a/web/components/imagetile_templ.go +++ b/web/components/imagetile_templ.go @@ -42,7 +42,7 @@ func ImageTile(src string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" loading=\"lazy\" class=\"row-start-1 col-start-1 pointer-events-none w-full h-96 object-cover\">
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" fetchpriority=\"high\" class=\"row-start-1 col-start-1 pointer-events-none w-full h-96 object-cover\">
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/web/layouts/base.templ b/web/layouts/base.templ index fe24fe3..21ca704 100644 --- a/web/layouts/base.templ +++ b/web/layouts/base.templ @@ -12,14 +12,15 @@ templ Base(title string) { { title } - - + + + - +
- schreifuchs.ch + schreifuchs.ch