Diffstat (limited to 'internal')
| -rw-r--r-- | internal/b2/client.go | 1 | ||||
| -rw-r--r-- | internal/frontmatter/parser.go | 1 | ||||
| -rw-r--r-- | internal/lightgallery/html_renderer.go | 31 | ||||
| -rw-r--r-- | internal/router/api-v1-blog-search.go | 112 | ||||
| -rw-r--r-- | internal/router/api-v1-tz.go | 37 | ||||
| -rw-r--r-- | internal/router/lang-blog-title.go | 79 | ||||
| -rw-r--r-- | internal/router/lang-blog.go | 93 | ||||
| -rw-r--r-- | internal/router/lang-map.go | 85 | ||||
| -rw-r--r-- | internal/router/root.go | 27 | ||||
| -rw-r--r-- | internal/router/tm.go | 18 | ||||
| -rw-r--r-- | internal/templatemanager/templatemanager.go | 24 |
11 files changed, 25 insertions, 483 deletions
diff --git a/internal/b2/client.go b/internal/b2/client.go index 7b745a8..5322b84 100644 --- a/internal/b2/client.go +++ b/internal/b2/client.go @@ -84,7 +84,6 @@ func (c *B2Client) Scan(prefix string) ([]*BlogPage, error) { PublishedTime: publishedTime, Thumbnail: attrs.Info["thumbnail"], Tags: strings.Split(attrs.Info["tags"], ","), - Geolocation: attrs.Info["geolocation"], }, }) } diff --git a/internal/frontmatter/parser.go b/internal/frontmatter/parser.go index 1a521c0..3c00a7f 100644 --- a/internal/frontmatter/parser.go +++ b/internal/frontmatter/parser.go @@ -16,6 +16,7 @@ type Metadata struct { Thumbnail string `yaml:"thumbnail"` Tags []string `yaml:"tags"` Geolocation string `yaml:"geolocation"` + Timezone string `yaml:"timezone"` } func ParseFrontmatter(content []byte) (metadata *Metadata, markdown []byte, err error) { diff --git a/internal/lightgallery/html_renderer.go b/internal/lightgallery/html_renderer.go index e20e9ec..850cd95 100644 --- a/internal/lightgallery/html_renderer.go +++ b/internal/lightgallery/html_renderer.go @@ -79,19 +79,22 @@ func (r *LightGalleryHTMLRenderer) renderLightGallery(w util.BufWriter, source [ downloadUrl: "https://f003.backblazeb2.com/file/sayana-photos/full/%s", alt: "%s", - responsive: "https://f003.backblazeb2.com/file/sayana-photos/webp-320p/%s.webp 384, https://f003.backblazeb2.com/file/sayana-photos/webp-560p/%s.webp 672, https://f003.backblazeb2.com/file/sayana-photos/webp-800p/%s.webp 960, https://f003.backblazeb2.com/file/sayana-photos/webp-1200p/%s.webp 1440, https://f003.backblazeb2.com/file/sayana-photos/webp-1600p/%s.webp 1920", + sources: [{ + srcset: "https://f003.backblazeb2.com/file/sayana-photos/thumbnails/%s.webp", + media: "(max-width: 800px)" + }], thumb: - "https://f003.backblazeb2.com/file/sayana-photos/webp-320p/%s.webp", + "https://f003.backblazeb2.com/file/sayana-photos/thumbnails/%s.webp", subHtml: `+"`"+`<div class="flex flex-row light-gallery-captions"> - <p class="grow !text-[1vmax]/[1] text-left font-spectral text-main-dark">%s</p> - <p class="!text-[1vmax]/[1] text-right font-spectral text-secondary">%s</p> + <p class="grow !text-[1vmax]/[0.9] text-left font-spectral text-main-dark">%s</p> + <p class="!text-[1vmax]/[0.9] text-right font-spectral text-secondary">%s</p> </div>`+"`"+` - }`, img.URL, img.URL, util.EscapeHTML(captionHTML), imageUrlWithoutExt, imageUrlWithoutExt, imageUrlWithoutExt, imageUrlWithoutExt, imageUrlWithoutExt, imageUrlWithoutExt, captionHTML, dayDate.Format("2006-01-02 15:04:05 -07:00"))) + }`, img.URL, img.URL, util.EscapeHTML(captionHTML), imageUrlWithoutExt, imageUrlWithoutExt, captionHTML, dayDate.Format("2006-01-02 15:04:05 -07:00"))) } w.WriteString(fmt.Sprintf(` <script> -function createLightGallery%s() { +function createLightLibrary%s() { const $lgContainer = document.getElementById('lg-%s'); const inlineGallery = lightGallery($lgContainer, { container: $lgContainer, @@ -106,7 +109,7 @@ function createLightGallery%s() { isMobile: () => false, slideDelay: 0, plugins: [lgZoom, lgThumbnail], - thumbWidth: calculateVmin(10), + thumbWidth: 160, thumbHeight: "10vmin", thumbMargin: 4 }); @@ -116,8 +119,18 @@ function createLightGallery%s() { }, 200); } -document.addEventListener('DOMContentLoaded', createLightGallery%s); -</script>`, galleryID, galleryID, strings.Join(dynamicElements, ","), galleryID)) +document.addEventListener('DOMContentLoaded', createLightLibrary%s); + +let resizeTimer%s; +window.addEventListener("resize", () => { + clearTimeout(resizeTimer%s); + resizeTimer%s = setTimeout(() => { + const $lgContainer = document.getElementById('lg-%s'); + $lgContainer.innerHTML = ""; + createLightLibrary%s(); + }, 1000); +}); +</script>`, galleryID, galleryID, strings.Join(dynamicElements, ","), galleryID, galleryID, galleryID, galleryID, galleryID, galleryID)) } return ast.WalkContinue, nil diff --git a/internal/router/api-v1-blog-search.go b/internal/router/api-v1-blog-search.go deleted file mode 100644 index 51ace85..0000000 --- a/internal/router/api-v1-blog-search.go +++ /dev/null @@ -1,112 +0,0 @@ -package router - -import ( - "fmt" - "log/slog" - "net/url" - "regexp" - "slices" - "strings" - "time" - - "github.com/SayaAndy/saya-today-web/internal/b2" - "github.com/SayaAndy/saya-today-web/locale" - "github.com/gofiber/fiber/v2" -) - -func init() { - tm.Add("catalogue-blog-cards", "views/partials/catalogue-blog-cards.html", "views/partials/catalogue-blog-card-tags.html") -} - -func Api_V1_BlogSearch(l map[string]*locale.LocaleConfig, langs []string, b2Client *b2.B2Client) func(c *fiber.Ctx) error { - return func(c *fiber.Ctx) error { - sort := c.Query("sort") - lang := c.Query("lang") - tz := c.Query("tz") - - if !slices.Contains(langs, lang) { - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language", lang)) - } - - loc, err := time.LoadLocation(tz) - if err != nil { - loc = time.UTC - slog.Warn("unable to parse a client timezone, defaulting to UTC", slog.String("error", err.Error()), slog.String("tz", tz)) - } - - pages, err := b2Client.Scan(lang + "/") - if err != nil { - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(fiber.ErrInternalServerError.Code).SendString(fmt.Sprintf("failed to scan pages for '%s' lang: %v", lang, err)) - } - - encodedQuery := c.Request().URI().QueryString() - re, err := regexp.Compile(`tags\[\]=([\w]+)`) - if err != nil { - slog.Warn("failed to generate regex for tags gathering", slog.String("error", err.Error())) - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate regex for tags gathering") - } - decodedQuery, err := url.QueryUnescape(string(encodedQuery)) - matches := re.FindAllStringSubmatch(decodedQuery, -1) - - tags := make([]string, 0, len(matches)) - for _, match := range matches { - tags = append(tags, string(match[1])) - } - - pageMeta := make([]fiber.Map, 0, len(pages)) - for _, page := range pages { - for _, tag := range page.Metadata.Tags { - if len(tags) == 0 || slices.Contains(tags, tag) { - pageMeta = append(pageMeta, fiber.Map{ - "Link": page.Link, - "ArticleLink": "/" + lang + "/blog/" + page.FileName, - "Title": page.Metadata.Title, - "PublishedTime": page.Metadata.PublishedTime.In(loc).Format("2006-01-02 15:04:05 -07:00"), - "ActionDate": page.Metadata.ActionDate, - "ShortDescription": page.Metadata.ShortDescription, - "Thumbnail": page.Metadata.Thumbnail, - "Tags": page.Metadata.Tags, - }) - break - } - } - } - - slices.SortFunc(pageMeta, func(a, b fiber.Map) int { - switch sort { - case "titleAsc": - return strings.Compare(a["Title"].(string), b["Title"].(string)) - case "titleDesc": - return strings.Compare(b["Title"].(string), a["Title"].(string)) - case "actionDateAsc": - return strings.Compare(a["ActionDate"].(string), b["ActionDate"].(string)) - case "actionDateDesc": - return strings.Compare(b["ActionDate"].(string), a["ActionDate"].(string)) - case "publicationDateAsc": - publishedTimeA, _ := time.Parse("2006-01-02 15:04:05 -07:00", a["PublishedTime"].(string)) - publishedTimeB, _ := time.Parse("2006-01-02 15:04:05 -07:00", b["PublishedTime"].(string)) - return publishedTimeA.Compare(publishedTimeB) - case "publicationDateDesc": - publishedTimeA, _ := time.Parse("2006-01-02 15:04:05 -07:00", a["PublishedTime"].(string)) - publishedTimeB, _ := time.Parse("2006-01-02 15:04:05 -07:00", b["PublishedTime"].(string)) - return publishedTimeB.Compare(publishedTimeA) - } - return 0 - }) - - content, err := tm.Render("catalogue-blog-cards", fiber.Map{ - "BlogPages": pageMeta, - "L": l[lang], - }) - if err != nil { - slog.Warn("failed to generate div", slog.String("page", "/"+lang+"/blog"), slog.String("error", err.Error())) - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div") - } - - return c.Type("html").Send(content) - } -} diff --git a/internal/router/api-v1-tz.go b/internal/router/api-v1-tz.go deleted file mode 100644 index 4e4e462..0000000 --- a/internal/router/api-v1-tz.go +++ /dev/null @@ -1,37 +0,0 @@ -package router - -import ( - "log/slog" - "time" - - "github.com/gofiber/fiber/v2" -) - -func Api_V1_TZ() func(c *fiber.Ctx) error { - return func(c *fiber.Ctx) error { - timestampString := c.Query("timestamp") - tz := c.Query("tz") - - loc, err := time.LoadLocation(tz) - if err != nil { - loc = time.UTC - slog.Warn("unable to parse a client timezone, defaulting to UTC", slog.String("error", err.Error()), slog.String("tz", tz)) - } - - var format string - var timestamp time.Time - for _, format = range []string{"2006-01-02 15:04:05 -07:00", time.RFC3339} { - if timestamp, err = time.Parse(format, timestampString); err == nil { - break - } - } - - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - - if err != nil { - return c.Status(fiber.StatusBadRequest).SendString("invalid timestamp format") - } - - return c.Status(fiber.StatusOK).SendString(timestamp.In(loc).Format(format)) - } -} diff --git a/internal/router/lang-blog-title.go b/internal/router/lang-blog-title.go deleted file mode 100644 index b7b5d7e..0000000 --- a/internal/router/lang-blog-title.go +++ /dev/null @@ -1,79 +0,0 @@ -package router - -import ( - "bytes" - "fmt" - "html/template" - "log/slog" - "slices" - "strconv" - "strings" - - "github.com/SayaAndy/saya-today-web/internal/b2" - "github.com/SayaAndy/saya-today-web/internal/frontmatter" - "github.com/SayaAndy/saya-today-web/locale" - "github.com/gofiber/fiber/v2" - "github.com/yuin/goldmark" -) - -func init() { - tm.Add("blog-page", "views/layouts/general-page.html", "views/pages/blog-page.html") -} - -func Lang_Blog_Title(l map[string]*locale.LocaleConfig, langs []string, b2Client *b2.B2Client, md goldmark.Markdown) func(c *fiber.Ctx) error { - return func(c *fiber.Ctx) error { - lang := c.Params("lang") - if !slices.Contains(langs, lang) { - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language... yet??", lang)) - } - - metadata, parsedMarkdown, err := readBlogPost(md, b2Client, lang+"/"+c.Params("title")) - if err != nil { - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("failed to find '%s' post", c.Params("title"))) - } - - geolocationParts := strings.Split(metadata.Geolocation, " ") - var x, y, areaError string - if len(geolocationParts) >= 2 { - x = geolocationParts[0] - y = geolocationParts[1] - } - if len(geolocationParts) >= 3 { - areaError = geolocationParts[2] - } - - content, err := tm.Render("blog-page", fiber.Map{ - "Title": metadata.Title, - "PublishedDate": metadata.PublishedTime.Format("2006-01-02 15:04:05 -07:00"), - "PublishedYear": strconv.Itoa(metadata.PublishedTime.Year()), - "ParsedMarkdown": template.HTML(parsedMarkdown), - "MapLocationX": x, - "MapLocationY": y, - "MapLocationAreaMeters": areaError, - "Lang": lang, - }) - if err != nil { - slog.Warn("failed to generate page", slog.String("page", "/"+lang+"/blog/"+c.Params("title")), slog.String("error", err.Error())) - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate page") - } - - return c.Type("html").Send(content) - } -} - -func readBlogPost(md goldmark.Markdown, b2Client *b2.B2Client, sourceName string) (metadata *frontmatter.Metadata, html string, err error) { - metadata, markdown, err := b2Client.ReadFrontmatter(sourceName + ".md") - if err != nil { - return nil, "", fmt.Errorf("failed to read a frontmatter file: %w", err) - } - - var buf bytes.Buffer - if err := md.Convert(markdown, &buf); err != nil { - return nil, "", fmt.Errorf("convert source context from md to html: %w", err) - } - - return metadata, buf.String(), nil -} diff --git a/internal/router/lang-blog.go b/internal/router/lang-blog.go deleted file mode 100644 index 650c4b1..0000000 --- a/internal/router/lang-blog.go +++ /dev/null @@ -1,93 +0,0 @@ -package router - -import ( - "fmt" - "log/slog" - "net/url" - "regexp" - "slices" - "strings" - - "github.com/SayaAndy/saya-today-web/internal/b2" - "github.com/SayaAndy/saya-today-web/locale" - "github.com/gofiber/fiber/v2" -) - -func init() { - tm.Add("blog-catalogue", "views/layouts/general-page.html", "views/pages/blog-catalogue.html") -} - -func Lang_Blog(l map[string]*locale.LocaleConfig, langs []string, b2Client *b2.B2Client) func(c *fiber.Ctx) error { - return func(c *fiber.Ctx) error { - lang := c.Params("lang") - if !slices.Contains(langs, lang) { - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language... yet??", lang)) - } - - querySort := c.Query("sort") - if querySort == "" { - querySort = "publicationDateDesc" - } - - encodedQuery := c.Request().URI().QueryString() - re, err := regexp.Compile(`tags\[\]=([\w]+)`) - if err != nil { - slog.Warn("failed to generate regex for tags gathering", slog.String("error", err.Error())) - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate regex for tags gathering") - } - decodedQuery, err := url.QueryUnescape(string(encodedQuery)) - matches := re.FindAllStringSubmatch(decodedQuery, -1) - - queryTags := make([]string, 0, len(matches)) - for _, match := range matches { - queryTags = append(queryTags, string(match[1])) - } - - pages, err := b2Client.Scan(lang + "/") - status := fiber.StatusOK - if err != nil { - status = fiber.StatusPartialContent - pages = []*b2.BlogPage{} - } - - tagsMap := make(map[string]int) - for _, page := range pages { - slog.Debug("enlist page for catalogue", slog.Any("page", page), slog.String("endpoint", "/"+lang+"/blog")) - for _, tag := range page.Metadata.Tags { - tagsMap[tag]++ - } - } - - type Tag struct { - Name string `json:"Name" yaml:"name"` - Count int `json:"Count" yaml:"count"` - } - - tagsArray := make([]Tag, 0, len(tagsMap)) - for tag, count := range tagsMap { - tagsArray = append(tagsArray, Tag{tag, count}) - } - slices.SortFunc(tagsArray, func(a Tag, b Tag) int { - return strings.Compare(a.Name, b.Name) - }) - - content, err := tm.Render("blog-catalogue", fiber.Map{ - "QuerySort": querySort, - "QueryTags": strings.Join(queryTags, ","), - "Tags": tagsArray, - "Lang": lang, - "L": l[lang], - "PublishedYear": "2025", - "Title": l[lang].BlogSearch.Header, - }) - if err != nil { - slog.Warn("failed to generate page", slog.String("page", "/"+lang+"/blog"), slog.String("error", err.Error())) - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate page") - } - - return c.Type("html").Status(status).Send(content) - } -} diff --git a/internal/router/lang-map.go b/internal/router/lang-map.go deleted file mode 100644 index e94d756..0000000 --- a/internal/router/lang-map.go +++ /dev/null @@ -1,85 +0,0 @@ -package router - -import ( - "fmt" - "log/slog" - "slices" - "strconv" - "strings" - - "github.com/SayaAndy/saya-today-web/internal/b2" - "github.com/SayaAndy/saya-today-web/locale" - "github.com/gofiber/fiber/v2" -) - -func init() { - tm.Add("global-map", "views/pages/global-map.html") -} - -func Lang_Map(l map[string]*locale.LocaleConfig, langs []string, b2Client *b2.B2Client) func(c *fiber.Ctx) error { - return func(c *fiber.Ctx) error { - lang := c.Params("lang") - if !slices.Contains(langs, lang) { - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language... yet??", lang)) - } - - pages, err := b2Client.Scan(lang + "/") - status := fiber.StatusOK - if err != nil { - status = fiber.StatusPartialContent - pages = []*b2.BlogPage{} - } - - type MapMarker struct { - Title string `json:"Title"` - PageLink string `json:"PageLink"` - Lat float64 `json:"Lat"` - Long float64 `json:"Long"` - AccuracyMeters int64 `json:"AccuracyMeters"` - Thumbnail string `json:"Thumbnail"` - } - - mapMarkers := make([]*MapMarker, 0, len(pages)) - for _, page := range pages { - geolocationParts := strings.Split(page.Metadata.Geolocation, " ") - if len(geolocationParts) < 2 { - continue - } - - var x, y float64 - var areaError int64 - if len(geolocationParts) >= 2 { - x, _ = strconv.ParseFloat(geolocationParts[0], 64) - y, _ = strconv.ParseFloat(geolocationParts[1], 64) - } - if len(geolocationParts) >= 3 { - areaError, _ = strconv.ParseInt(geolocationParts[2], 10, 64) - } - - mapMarkers = append(mapMarkers, &MapMarker{ - Title: page.Metadata.Title, - PageLink: fmt.Sprintf("/%s/blog/%s", lang, page.FileName), - Lat: x, - Long: y, - AccuracyMeters: areaError, - Thumbnail: page.Metadata.Thumbnail, - }) - } - - content, err := tm.Render("global-map", fiber.Map{ - "Lang": lang, - "L": l[lang], - "MapLocationLat": 45.4507, - "MapLocationLong": 68.8319, - "MapMarkers": mapMarkers, - }) - if err != nil { - slog.Warn("failed to generate page", slog.String("page", "/"+lang+"/map"), slog.String("error", err.Error())) - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate page") - } - - return c.Type("html").Status(status).Send(content) - } -} diff --git a/internal/router/root.go b/internal/router/root.go deleted file mode 100644 index f4a0692..0000000 --- a/internal/router/root.go +++ /dev/null @@ -1,27 +0,0 @@ -package router - -import ( - "log/slog" - - "github.com/SayaAndy/saya-today-web/config" - "github.com/gofiber/fiber/v2" -) - -func init() { - tm.Add("index", "views/index.html") -} - -func Root(localeCfg []config.AvailableLanguageConfig) func(c *fiber.Ctx) error { - return func(c *fiber.Ctx) error { - content, err := tm.Render("index", fiber.Map{ - "AvailableLanguages": localeCfg, - }) - if err != nil { - slog.Warn("failed to generate page", slog.String("page", "/"), slog.String("error", err.Error())) - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate page") - } - - return c.Type("html").Send(content) - } -} diff --git a/internal/router/tm.go b/internal/router/tm.go deleted file mode 100644 index e8095ee..0000000 --- a/internal/router/tm.go +++ /dev/null @@ -1,18 +0,0 @@ -package router - -import ( - "log/slog" - "os" - - "github.com/SayaAndy/saya-today-web/internal/templatemanager" -) - -var tm = assert(templatemanager.NewTemplateManager()) - -func assert[T any](t T, err error) T { - if err != nil { - slog.Error("fail to initialize template manager", slog.String("error", err.Error())) - os.Exit(1) - } - return t -} diff --git a/internal/templatemanager/templatemanager.go b/internal/templatemanager/templatemanager.go index f89d0c8..5effcb3 100644 --- a/internal/templatemanager/templatemanager.go +++ b/internal/templatemanager/templatemanager.go @@ -22,7 +22,7 @@ type TemplateManagerTemplates struct { Files []string } -func NewTemplateManager(templates ...TemplateManagerTemplates) (*TemplateManager, error) { +func NewTemplateManager(templates []TemplateManagerTemplates) (*TemplateManager, error) { templateMap := make(map[string]templateManagerRender) for _, tmplStruct := range templates { @@ -37,6 +37,7 @@ func NewTemplateManager(templates ...TemplateManagerTemplates) (*TemplateManager Main: filepath.Base(tmplStruct.Files[0]), Tmpl: tmpl, } + } return &TemplateManager{ @@ -54,24 +55,3 @@ func (tm *TemplateManager) Render(name string, data interface{}) ([]byte, error) err := tmpl.Tmpl.ExecuteTemplate(&buf, tmpl.Main, data) return buf.Bytes(), err } - -func (tm *TemplateManager) Add(name string, files ...string) error { - if len(files) == 0 { - return fmt.Errorf("you can't add template without any files") - } - - tmpl := template.New("").Funcs(template.FuncMap{ - "contains": strings.Contains, - }) - - tmpl, err := tmpl.ParseFiles(files...) - if err != nil { - return fmt.Errorf("failed to add template into manager: %w", err) - } - - tm.templates[name] = templateManagerRender{ - Main: filepath.Base(files[0]), - Tmpl: tmpl, - } - return nil -} |