summaryrefslogtreecommitdiffci
path: root/internal
diff refs
from:
to:
flip
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/blog/b2.go31
-rw-r--r--internal/blog/client.go1
-rw-r--r--internal/blog/index.go83
-rw-r--r--internal/blog/s3.go269
-rw-r--r--internal/glightbox/html_renderer.go4
-rw-r--r--internal/mailer/mailer.go16
-rw-r--r--internal/router/handlers/api-v1-blog-search.go53
-rw-r--r--internal/router/handlers/api-v1-email-is-in-verification.go3
-rw-r--r--internal/router/handlers/api-v1-email-send-verification-code.go15
-rw-r--r--internal/router/handlers/api-v1-email-verify.go7
-rw-r--r--internal/router/handlers/api-v1-like-get.go2
-rw-r--r--internal/router/handlers/api-v1-map-get.go116
-rw-r--r--internal/router/handlers/api-v1-subs-put.go7
-rw-r--r--internal/router/handlers/lang-blog-title.go31
-rw-r--r--internal/router/handlers/lang-blog.go16
-rw-r--r--internal/router/handlers/lang-map.go65
-rw-r--r--internal/router/handlers/lang-user-unsubscribe.go9
-rw-r--r--internal/router/handlers/lang-user.go9
-rw-r--r--internal/router/handlers/lang.go9
-rw-r--r--internal/router/handlers/root.go7
-rw-r--r--internal/router/router.go116
-rw-r--r--internal/tailwind/transformer.go2
-rw-r--r--internal/templatemanager/templatemanager.go6
23 files changed, 354 insertions, 523 deletions
diff --git a/internal/blog/b2.go b/internal/blog/b2.go
index f2ac890..0caa0ba 100644
--- a/internal/blog/b2.go
+++ b/internal/blog/b2.go
@@ -2,7 +2,6 @@ package blog
import (
"context"
- "encoding/json"
"fmt"
"slices"
"strings"
@@ -38,20 +37,6 @@ func NewB2Client(cfg *config.StorageConfig) (Client, error) {
return &B2Client{b2cl: b2cl, bucket: bucket, prefix: b2cfg.Prefix}, nil
}
-func (c *B2Client) GetMedleys() ([]MedleyEntry, error) {
- idxRaw, err := c.readAll(MedleysIndexFileName)
- if err != nil {
- return nil, fmt.Errorf("read %s: %w", MedleysIndexFileName, err)
- }
-
- var idx []MedleyEntry
- if err := json.Unmarshal(idxRaw, &idx); err != nil {
- return nil, fmt.Errorf("unmarshal %s: %w", MedleysIndexFileName, err)
- }
-
- return idx, nil
-}
-
func (c *B2Client) Scan(prefix string) ([]*Page, error) {
filePaths := []*Page{}
@@ -85,15 +70,17 @@ func (c *B2Client) Scan(prefix string) ([]*Page, error) {
return nil, fmt.Errorf("failed to parse published time metadata field: %w", err)
}
- link := obj.Name()
- fileName := link[strings.LastIndex(link, "/")+1 : strings.LastIndex(link, ".")]
+ linkParts := strings.Split(obj.Name(), "/")
+ nameParts := strings.Split(linkParts[len(linkParts)-1], ".")
+ fileName := strings.Join(nameParts[:len(linkParts)-1], ".")
+
tags := strings.Split(attrs.Info["tags"], ",")
slices.Sort(tags)
- lang, _ := strings.CutPrefix(link[0:strings.Index(link, "/")], c.prefix)
+ lang, _ := strings.CutPrefix(linkParts[0], c.prefix)
filePaths = append(filePaths, &Page{
- Link: link,
+ Link: obj.Name(),
FileName: fileName,
Lang: lang,
ModifiedTime: attrs.LastModified,
@@ -117,11 +104,7 @@ func (c *B2Client) Scan(prefix string) ([]*Page, error) {
}
func (c *B2Client) ReadAll(path string) ([]byte, error) {
- return c.readAll(c.prefix + path)
-}
-
-func (c *B2Client) readAll(path string) ([]byte, error) {
- obj := c.bucket.Object(path)
+ obj := c.bucket.Object(c.prefix + path)
if obj == nil {
return nil, fmt.Errorf("failed to reference object in B2 bucket")
}
diff --git a/internal/blog/client.go b/internal/blog/client.go
index 53eb0fd..56bbee4 100644
--- a/internal/blog/client.go
+++ b/internal/blog/client.go
@@ -17,7 +17,6 @@ type Page struct {
type Client interface {
Scan(prefix string) ([]*Page, error)
- GetMedleys() ([]MedleyEntry, error)
ReadAll(path string) ([]byte, error)
ReadFrontmatter(path string) (metadata *frontmatter.Metadata, markdown []byte, err error)
}
diff --git a/internal/blog/index.go b/internal/blog/index.go
index a09b59e..861a90e 100644
--- a/internal/blog/index.go
+++ b/internal/blog/index.go
@@ -1,17 +1,10 @@
package blog
-import (
- "encoding/json"
- "fmt"
- "time"
-
- "github.com/SayaAndy/saya-today-web/internal/frontmatter"
-)
+import "time"
const IndexFileName = "index.json"
-const MedleysIndexFileName = "medleys.json"
-const IndexSchemaVersion = 2
+const IndexSchemaVersion = 1
type IndexEntry struct {
Link string `json:"link"`
@@ -27,77 +20,13 @@ type IndexEntry struct {
MedleyPart int `json:"medleyPart,omitempty"`
}
-func (e IndexEntry) Metadata() *frontmatter.Metadata {
- return &frontmatter.Metadata{
- Title: e.Title,
- ShortDescription: e.ShortDescription,
- ActionDate: e.ActionDate,
- PublishedTime: e.PublishedTime,
- Thumbnail: e.Thumbnail,
- Tags: e.Tags,
- Geolocation: e.Geolocation,
- Medley: e.Medley,
- MedleyPart: e.MedleyPart,
- }
-}
-
-type IndexV2Category struct {
- GeneratedAt time.Time `json:"generatedAt"`
- Pages map[string]IndexEntry `json:"pages"`
-}
-
-type IndexV1Category struct {
+type IndexCategory struct {
GeneratedAt time.Time `json:"generatedAt"`
Pages []IndexEntry `json:"pages"`
}
type Index struct {
- SchemaVersion int `json:"schemaVersion"`
- GeneratedAt time.Time `json:"generatedAt"`
- Categories any `json:"categories"`
-}
-
-func (idx *Index) UnmarshalJSON(data []byte) error {
- var tmp struct {
- SchemaVersion int `json:"schemaVersion"`
- GeneratedAt time.Time `json:"generatedAt"`
- Categories json.RawMessage `json:"categories"`
- }
-
- if err := json.Unmarshal(data, &tmp); err != nil {
- return err
- }
-
- idx.SchemaVersion = tmp.SchemaVersion
- idx.GeneratedAt = tmp.GeneratedAt
-
- switch tmp.SchemaVersion {
- case 1:
- var categories map[string]*IndexV1Category
- if err := json.Unmarshal(tmp.Categories, &categories); err != nil {
- return fmt.Errorf("unmarshal map[string]*IndexV1Category: %w", err)
- }
- idx.Categories = &categories
- case 2:
- var categories map[string]*IndexV2Category
- if err := json.Unmarshal(tmp.Categories, &categories); err != nil {
- return fmt.Errorf("unmarshal map[string]*IndexV2Category: %w", err)
- }
- idx.Categories = &categories
- default:
- return fmt.Errorf("unsupported index version: %d", tmp.SchemaVersion)
- }
-
- return nil
-}
-
-type MedleyEntry struct {
- Codename string `json:"codename"`
- Localnames map[string]string `json:"localnames"`
- Content []string `json:"content"`
-}
-
-type MedleyPageEntry struct {
- Codename string `json:"codename"`
- Position int `json:"position"`
+ SchemaVersion int `json:"schemaVersion"`
+ GeneratedAt time.Time `json:"generatedAt"`
+ Categories map[string]IndexCategory `json:"categories"`
}
diff --git a/internal/blog/s3.go b/internal/blog/s3.go
index 3c1a932..bbd5238 100644
--- a/internal/blog/s3.go
+++ b/internal/blog/s3.go
@@ -1,22 +1,29 @@
package blog
import (
- "bytes"
"context"
"encoding/json"
+ "errors"
"fmt"
"io"
+ "log/slog"
+ "net/url"
+ "slices"
"strings"
+ "sync"
+ "time"
"github.com/SayaAndy/saya-today-web/config"
"github.com/SayaAndy/saya-today-web/internal/frontmatter"
- "github.com/SayaAndy/saya-today-web/l10n"
"github.com/aws/aws-sdk-go-v2/aws"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
+ s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
)
+const s3ScanConcurrency = 32
+
type S3Client struct {
prefix string
bucketName string
@@ -59,38 +66,39 @@ func NewS3Client(cfg *config.StorageConfig) (Client, error) {
return &S3Client{s3cfg.Prefix, s3cfg.BucketName, s3cl}, nil
}
-func (c *S3Client) GetMedleys() ([]MedleyEntry, error) {
- idxRaw, err := c.readAll(MedleysIndexFileName)
- if err != nil {
- return nil, fmt.Errorf("read %s: %w", MedleysIndexFileName, err)
+func (c *S3Client) Scan(prefix string) ([]*Page, error) {
+ pages, err := c.scanFromIndex(prefix)
+ if err == nil {
+ return pages, nil
}
- var idx []MedleyEntry
- if err := json.Unmarshal(idxRaw, &idx); err != nil {
- return nil, fmt.Errorf("unmarshal %s: %w", MedleysIndexFileName, err)
+ var nsk *s3types.NoSuchKey
+ if !errors.As(err, &nsk) {
+ return nil, err
}
- return idx, nil
+ slog.Warn("index.json missing, falling back to listing", slog.String("prefix", c.prefix))
+ return c.scanByListing(prefix)
}
-func (c *S3Client) Scan(prefix string) ([]*Page, error) {
+func (c *S3Client) scanFromIndex(prefix string) ([]*Page, error) {
out, err := c.s3cl.GetObject(context.Background(), &s3.GetObjectInput{
Bucket: aws.String(c.bucketName),
Key: aws.String(IndexFileName),
})
if err != nil {
- return nil, fmt.Errorf("get %s: %w", IndexFileName, err)
+ return nil, fmt.Errorf("get index.json: %w", err)
}
defer out.Body.Close()
raw, err := io.ReadAll(out.Body)
if err != nil {
- return nil, fmt.Errorf("read %s: %w", IndexFileName, err)
+ return nil, fmt.Errorf("read index.json: %w", err)
}
var idx Index
if err := json.Unmarshal(raw, &idx); err != nil {
- return nil, fmt.Errorf("unmarshal %s: %w", IndexFileName, err)
+ return nil, fmt.Errorf("unmarshal index.json: %w", err)
}
wantLang := ""
@@ -100,93 +108,160 @@ func (c *S3Client) Scan(prefix string) ([]*Page, error) {
fullPrefix := c.prefix + prefix
pages := make([]*Page, 0)
-
- switch idx.SchemaVersion {
- case 1:
- for catKey, cat := range *idx.Categories.(*map[string]*IndexV1Category) {
- lang, ok := strings.CutPrefix(catKey, c.prefix)
- if !ok {
+ for catKey, cat := range idx.Categories {
+ lang, ok := strings.CutPrefix(catKey, c.prefix)
+ if !ok {
+ continue
+ }
+ if wantLang != "" && wantLang != lang {
+ continue
+ }
+ for _, e := range cat.Pages {
+ if !strings.HasPrefix(e.Link, fullPrefix) {
continue
}
- if wantLang != "" && wantLang != lang {
+ linkParts := strings.Split(e.Link, "/")
+ nameParts := strings.Split(linkParts[len(linkParts)-1], ".")
+ fileName := strings.Join(nameParts[:len(nameParts)-1], ".")
+ pages = append(pages, &Page{
+ Link: e.Link,
+ FileName: fileName,
+ Lang: lang,
+ ModifiedTime: e.ModifiedTime,
+ Metadata: &frontmatter.Metadata{
+ Title: e.Title,
+ ShortDescription: e.ShortDescription,
+ ActionDate: e.ActionDate,
+ PublishedTime: e.PublishedTime,
+ Thumbnail: e.Thumbnail,
+ Tags: e.Tags,
+ Geolocation: e.Geolocation,
+ Medley: e.Medley,
+ MedleyPart: e.MedleyPart,
+ },
+ })
+ }
+ }
+ return pages, nil
+}
+
+func (c *S3Client) scanByListing(prefix string) ([]*Page, error) {
+ fullPrefix := c.prefix + prefix
+
+ type candidate struct {
+ key string
+ lastModified time.Time
+ }
+ var candidates []candidate
+
+ paginator := s3.NewListObjectsV2Paginator(c.s3cl, &s3.ListObjectsV2Input{
+ Bucket: aws.String(c.bucketName),
+ Prefix: aws.String(fullPrefix),
+ })
+ for paginator.HasMorePages() {
+ output, err := paginator.NextPage(context.Background())
+ if err != nil {
+ return nil, fmt.Errorf("list S3 objects: %w", err)
+ }
+ for _, obj := range output.Contents {
+ key := aws.ToString(obj.Key)
+ if !strings.HasSuffix(key, ".md") {
continue
}
- for _, e := range cat.Pages {
- if !strings.HasPrefix(e.Link, fullPrefix) {
- continue
+ candidates = append(candidates, candidate{key, aws.ToTime(obj.LastModified)})
+ }
+ }
+
+ pages := make([]*Page, len(candidates))
+ sem := make(chan struct{}, s3ScanConcurrency)
+ var wg sync.WaitGroup
+ var firstErr error
+ var errMu sync.Mutex
+
+ for i, cand := range candidates {
+ sem <- struct{}{}
+ wg.Go(func() {
+ defer func() { <-sem }()
+
+ head, err := c.s3cl.HeadObject(context.Background(), &s3.HeadObjectInput{
+ Bucket: aws.String(c.bucketName),
+ Key: aws.String(cand.key),
+ })
+ if err != nil {
+ errMu.Lock()
+ if firstErr == nil {
+ firstErr = fmt.Errorf("head S3 object %s: %w", cand.key, err)
}
- fileName := e.Link[strings.LastIndex(e.Link, "/")+1 : strings.LastIndex(e.Link, ".")]
- pages = append(pages, &Page{
- Link: e.Link,
- FileName: fileName,
- Lang: lang,
- ModifiedTime: e.ModifiedTime,
- Metadata: &frontmatter.Metadata{
- Title: e.Title,
- ShortDescription: e.ShortDescription,
- ActionDate: e.ActionDate,
- PublishedTime: e.PublishedTime,
- Thumbnail: e.Thumbnail,
- Tags: e.Tags,
- Geolocation: e.Geolocation,
- Medley: e.Medley,
- MedleyPart: e.MedleyPart,
- },
- })
+ errMu.Unlock()
+ return
}
- }
- case 2:
- for catKey, cat := range *idx.Categories.(*map[string]*IndexV2Category) {
- lang, ok := strings.CutPrefix(catKey, c.prefix)
- if !ok {
- continue
+
+ if head.ContentType == nil || !strings.Contains(*head.ContentType, "text/markdown") {
+ return
}
- if wantLang != "" && wantLang != lang {
- continue
+ meta := head.Metadata
+ if meta["title"] == "" {
+ return
}
- for codename, e := range cat.Pages {
- if !strings.HasPrefix(e.Link, fullPrefix) {
- continue
+
+ publishedTime, err := time.Parse(time.RFC3339, meta["published-time"])
+ if err != nil {
+ errMu.Lock()
+ if firstErr == nil {
+ firstErr = fmt.Errorf("failed to parse published time metadata field: %w", err)
}
- pages = append(pages, &Page{
- Link: e.Link,
- FileName: codename,
- Lang: lang,
- ModifiedTime: e.ModifiedTime,
- Metadata: &frontmatter.Metadata{
- Title: e.Title,
- ShortDescription: e.ShortDescription,
- ActionDate: e.ActionDate,
- PublishedTime: e.PublishedTime,
- Thumbnail: e.Thumbnail,
- Tags: e.Tags,
- Geolocation: e.Geolocation,
- Medley: e.Medley,
- MedleyPart: e.MedleyPart,
- },
- })
+ errMu.Unlock()
+ return
}
- }
+
+ linkParts := strings.Split(cand.key, "/")
+ nameParts := strings.Split(linkParts[len(linkParts)-1], ".")
+ fileName := strings.Join(nameParts[:len(nameParts)-1], ".")
+ lang, _ := strings.CutPrefix(linkParts[0], c.prefix)
+
+ tags := strings.Split(meta["tags"], ",")
+ slices.Sort(tags)
+
+ title, _ := url.QueryUnescape(meta["title"])
+ shortDescription, _ := url.QueryUnescape(meta["short-description"])
+ thumbnail, _ := url.QueryUnescape(meta["thumbnail"])
+
+ pages[i] = &Page{
+ Link: cand.key,
+ FileName: fileName,
+ Lang: lang,
+ ModifiedTime: cand.lastModified,
+ Metadata: &frontmatter.Metadata{
+ Title: title,
+ ShortDescription: shortDescription,
+ ActionDate: meta["action-date"],
+ PublishedTime: publishedTime,
+ Thumbnail: thumbnail,
+ Tags: tags,
+ Geolocation: meta["geolocation"],
+ },
+ }
+ })
}
+ wg.Wait()
- medleys, _ := c.GetMedleys()
- for _, medley := range medleys {
- for locale, localname := range medley.Localnames {
- l10n.T.SetPath(localname, true, locale, "Medleys", medley.Codename)
- }
+ if firstErr != nil {
+ return nil, firstErr
}
- return pages, nil
+ out := pages[:0]
+ for _, p := range pages {
+ if p != nil {
+ out = append(out, p)
+ }
+ }
+ return out, nil
}
func (c *S3Client) ReadAll(path string) ([]byte, error) {
- return c.readAll(c.prefix + path)
-}
-
-func (c *S3Client) readAll(path string) ([]byte, error) {
output, err := c.s3cl.GetObject(context.Background(), &s3.GetObjectInput{
Bucket: aws.String(c.bucketName),
- Key: aws.String(path),
+ Key: aws.String(c.prefix + path),
})
if err != nil {
return nil, fmt.Errorf("get S3 object: %w", err)
@@ -202,40 +277,10 @@ func (c *S3Client) readAll(path string) ([]byte, error) {
}
func (c *S3Client) ReadFrontmatter(path string) (metadata *frontmatter.Metadata, markdown []byte, err error) {
- idxRaw, err := c.readAll(IndexFileName)
- if err != nil {
- return nil, nil, fmt.Errorf("read %s: %w", IndexFileName, err)
- }
-
- var idx Index
- if err := json.Unmarshal(idxRaw, &idx); err != nil {
- return nil, nil, fmt.Errorf("unmarshal %s: %w", IndexFileName, err)
- }
-
contentBytes, err := c.ReadAll(path)
if err != nil {
return nil, nil, fmt.Errorf("failed to read file for frontmatter parsing: %w", err)
}
- switch idx.SchemaVersion {
- case 1:
- return frontmatter.ParseFrontmatter(contentBytes)
- case 2:
- fullPath := c.prefix + path
- page := (*idx.Categories.(*map[string]*IndexV2Category))[fullPath[:strings.LastIndex(fullPath, "/")]].Pages[fullPath[strings.LastIndex(fullPath, "/")+1:strings.LastIndex(fullPath, ".")]]
- metadata = page.Metadata()
-
- if !bytes.HasPrefix(contentBytes, []byte("---\n")) {
- return metadata, contentBytes, nil
- }
-
- end := bytes.Index(contentBytes[4:], []byte("\n---\n"))
- if end == -1 {
- return metadata, contentBytes, nil
- }
-
- return metadata, contentBytes[end+9:], nil
- }
-
return frontmatter.ParseFrontmatter(contentBytes)
}
diff --git a/internal/glightbox/html_renderer.go b/internal/glightbox/html_renderer.go
index 1bb606b..b2cd855 100644
--- a/internal/glightbox/html_renderer.go
+++ b/internal/glightbox/html_renderer.go
@@ -151,12 +151,12 @@ func (r *GLightboxHTMLRenderer) renderGLightbox(w util.BufWriter, source []byte,
w.WriteString(strings.ReplaceAll(`
<div class="items-center flex flex-col">
- <hr class="border-t-[0.375rem] border-dotted border-main-hard my-2 w-24 ml-auto mr-auto">
+ <hr class="border-t-3 border-dotted border-main-hard mt-1 mb-2 w-[80%] ml-auto mr-auto">
<div class="grid masonry-grid-{id}">
<div class="grid-sizer grid-sizer-{id}"></div>
`+strings.Join(elements, "\n")+`
</div>
- <hr class="border-t-[0.375rem] border-dotted border-main-hard my-2 w-24 ml-auto mr-auto">
+ <hr class="border-t-3 border-dotted border-main-hard mt-1 mb-2 w-[80%] ml-auto mr-auto">
</div>
<script>
diff --git a/internal/mailer/mailer.go b/internal/mailer/mailer.go
index 33db4f7..ffa7b30 100644
--- a/internal/mailer/mailer.go
+++ b/internal/mailer/mailer.go
@@ -16,7 +16,7 @@ import (
"github.com/SayaAndy/saya-today-web/internal/blog"
"github.com/SayaAndy/saya-today-web/internal/templatemanager"
- "github.com/SayaAndy/saya-today-web/l10n"
+ "github.com/SayaAndy/saya-today-web/locale"
"github.com/dgraph-io/ristretto/v2"
"github.com/gofiber/fiber/v2"
"github.com/wneessen/go-mail"
@@ -43,6 +43,8 @@ type Mailer struct {
hashMap map[string][]byte
hashMapMutex sync.RWMutex
+
+ l map[string]*locale.LocaleConfig
}
type SubscriptionType int
@@ -53,7 +55,7 @@ const (
Specific
)
-func NewMailer(db *sql.DB, clientHost string, mailHost string, publicName string, mailAddress string, username string, password string, salt []byte) (*Mailer, error) {
+func NewMailer(db *sql.DB, clientHost string, mailHost string, publicName string, mailAddress string, username string, password string, salt []byte, localization map[string]*locale.LocaleConfig) (*Mailer, error) {
verificationCodes, err := ristretto.NewCache(&ristretto.Config[uint64, string]{
NumCounters: 10000,
MaxCost: 1 << 20, // 1 MB
@@ -109,7 +111,7 @@ func NewMailer(db *sql.DB, clientHost string, mailHost string, publicName string
End time.Time
CodeExpiry time.Time
}, 0),
- }, nil
+ l: localization}, nil
}
func (m *Mailer) GetHash(id string) []byte {
@@ -259,9 +261,10 @@ func (m *Mailer) SendVerificationCode(userId string, address string, lang string
verificationInfo := fmt.Sprintf("%s.%s", base64.RawStdEncoding.EncodeToString([]byte(userId)), base64.RawStdEncoding.EncodeToString([]byte(address)))
m.verificationCodes.Set(verificationCode, verificationInfo, int64(len(verificationInfo)+8))
- message.Subject(l10n.T.GetPath(lang, "Mail", "VerifyEmail", "Subject").(string))
+ message.Subject(m.l[lang].Mail.VerifyEmail.Subject)
msg, err := m.tm.Render("verify-email", fiber.Map{
+ "L": m.l[lang],
"Lang": lang,
"VerificationCode": fmt.Sprintf("%X", verificationCode),
"ClientHost": m.clientHost,
@@ -496,10 +499,11 @@ rowLoop:
rand.Read(unsubscribeCodeBytes)
unsubscribeCode := binary.LittleEndian.Uint64(unsubscribeCodeBytes)
- unsubscribeFooter := strings.Replace(l10n.T.GetPath(post.Lang, "Mail", "UnsubscribeFooter").(string), "{}", fmt.Sprintf(`<a style="color: #273de1 !important;" href="https://%s/%s/user/unsubscribe?code=%X">`, m.clientHost, post.Lang, unsubscribeCode), 1)
+ unsubscribeFooter := strings.Replace(m.l[post.Lang].Mail.UnsubscribeFooter, "{}", fmt.Sprintf(`<a style="color: #273de1 !important;" href="https://%s/%s/user/unsubscribe?code=%X">`, m.clientHost, post.Lang, unsubscribeCode), 1)
unsubscribeFooter = strings.Replace(unsubscribeFooter, "{/}", "</a>", 1)
msgBody, err := m.tm.Render("new-post", fiber.Map{
+ "L": m.l[post.Lang],
"Lang": post.Lang,
"Post": post,
"ClientHost": m.clientHost,
@@ -525,7 +529,7 @@ rowLoop:
message.SetMessageID()
message.SetDate()
message.SetBulk()
- message.Subject(l10n.T.GetPath(post.Lang, "Mail", "NewPost", "Subject").(string))
+ message.Subject(m.l[post.Lang].Mail.NewPost.Subject)
message.SetBodyString(mail.TypeTextHTML, string(msgBody))
m.unsubscribeCodes.Set(unsubscribeCode, user.userId, 40)
diff --git a/internal/router/handlers/api-v1-blog-search.go b/internal/router/handlers/api-v1-blog-search.go
index b59f09a..38004a1 100644
--- a/internal/router/handlers/api-v1-blog-search.go
+++ b/internal/router/handlers/api-v1-blog-search.go
@@ -1,13 +1,13 @@
package handlers
import (
- "cmp"
"encoding/json"
"fmt"
"log/slog"
"net/url"
"regexp"
"slices"
+ "strings"
"time"
"github.com/SayaAndy/saya-today-web/internal/blog"
@@ -51,10 +51,6 @@ func (r *BlogSearchHandler) RateLimiter() *fiber.Handler {
func (r *BlogSearchHandler) Render(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
sort := c.Query("sort")
tz := c.Query("tz")
- medley := c.Query("medley")
- highlight := c.Query("highlight")
- hideTags := c.QueryBool("hideTags", false)
- hidePublishedTime := c.QueryBool("hidePublishedTime", false)
loc, err := time.LoadLocation(tz)
if err != nil {
@@ -89,26 +85,21 @@ func (r *BlogSearchHandler) Render(c *fiber.Ctx, supplements *router.Supplements
for _, page := range pages {
for _, tag := range page.Metadata.Tags {
if len(tags) == 0 || slices.Contains(tags, tag) {
- if medley == "" || medley == page.Metadata.Medley {
- 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,
- "LikeCount": supplements.ClientCache.GetLikeCount(page.FileName),
- "Liked": supplements.ClientCache.GetLikeStatus(c.IP(), page.FileName),
- "ViewCount": supplements.ClientCache.GetViewCount(page.FileName),
- "Viewed": supplements.ClientCache.GetViewStatus(c.IP(), page.FileName),
- "Medley": page.Metadata.Medley,
- "MedleyPart": page.Metadata.MedleyPart,
- "ToHighlight": page.FileName == highlight,
- })
- break
- }
+ 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,
+ "LikeCount": supplements.ClientCache.GetLikeCount(page.FileName),
+ "Liked": supplements.ClientCache.GetLikeStatus(c.IP(), page.FileName),
+ "ViewCount": supplements.ClientCache.GetViewCount(page.FileName),
+ "Viewed": supplements.ClientCache.GetViewStatus(c.IP(), page.FileName),
+ })
+ break
}
}
}
@@ -116,13 +107,13 @@ func (r *BlogSearchHandler) Render(c *fiber.Ctx, supplements *router.Supplements
slices.SortFunc(pageMeta, func(a, b fiber.Map) int {
switch sort {
case "titleAsc":
- return cmp.Compare(a["Title"].(string), b["Title"].(string))
+ return strings.Compare(a["Title"].(string), b["Title"].(string))
case "titleDesc":
- return cmp.Compare(b["Title"].(string), a["Title"].(string))
+ return strings.Compare(b["Title"].(string), a["Title"].(string))
case "actionDateAsc":
- return cmp.Compare(a["ActionDate"].(string), b["ActionDate"].(string))
+ return strings.Compare(a["ActionDate"].(string), b["ActionDate"].(string))
case "actionDateDesc":
- return cmp.Compare(b["ActionDate"].(string), a["ActionDate"].(string))
+ 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))
@@ -131,15 +122,11 @@ func (r *BlogSearchHandler) Render(c *fiber.Ctx, supplements *router.Supplements
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)
- case "medley":
- return cmp.Compare(a["MedleyPart"].(int), b["MedleyPart"].(int))
}
return 0
})
templateMap["BlogPages"] = pageMeta
- templateMap["HideTags"] = hideTags
- templateMap["HidePublishedTime"] = hidePublishedTime
return fiber.StatusOK, nil
}
diff --git a/internal/router/handlers/api-v1-email-is-in-verification.go b/internal/router/handlers/api-v1-email-is-in-verification.go
index b8ae162..4f05f48 100644
--- a/internal/router/handlers/api-v1-email-is-in-verification.go
+++ b/internal/router/handlers/api-v1-email-is-in-verification.go
@@ -6,7 +6,6 @@ import (
"strings"
"github.com/SayaAndy/saya-today-web/internal/router"
- "github.com/SayaAndy/saya-today-web/l10n"
"github.com/gofiber/fiber/v2"
)
@@ -50,7 +49,7 @@ func (r *OngoingVerificationHandler) Render(c *fiber.Ctx, supplements *router.Su
if !isAllowed {
sterileDataset["striked-end-time"] = template.HTMLAttr(fmt.Sprintf("data-striked-end-time=\"%d\"", whenAllowed.UnixMilli()))
templateMap["Status"] = "Neutral"
- templateMap["Message"] = strings.ReplaceAll(l10n.T.GetPath(lang, "UserProfile", "DelayTilVerification").(string), "{}", whenAllowed.Format("2006-01-02 15:04:05 MST"))
+ templateMap["Message"] = strings.ReplaceAll(supplements.Localization[lang].UserProfile.DelayTilVerification, "{}", whenAllowed.Format("2006-01-02 15:04:05 MST"))
} else {
templateMap["Status"] = "OK"
templateMap["Message"] = ""
diff --git a/internal/router/handlers/api-v1-email-send-verification-code.go b/internal/router/handlers/api-v1-email-send-verification-code.go
index ea8df4d..0b53efa 100644
--- a/internal/router/handlers/api-v1-email-send-verification-code.go
+++ b/internal/router/handlers/api-v1-email-send-verification-code.go
@@ -7,7 +7,6 @@ import (
"strings"
"github.com/SayaAndy/saya-today-web/internal/router"
- "github.com/SayaAndy/saya-today-web/l10n"
"github.com/gofiber/fiber/v2"
)
@@ -50,27 +49,27 @@ func (r *SendVerificationCodeHandler) Render(c *fiber.Ctx, supplements *router.S
email := c.FormValue("email")
if email == "" {
templateMap["Status"] = "Failed"
- templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "EmailEmpty").(string)
+ templateMap["Message"] = supplements.Localization[lang].UserProfile.EmailEmpty
return fiber.StatusUnprocessableEntity, nil
}
isTaken, err := supplements.Mailer.MailIsTaken(email)
if err != nil {
templateMap["Status"] = "Failed"
- templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "VerificationCodeSendingError").(string)
+ templateMap["Message"] = supplements.Localization[lang].UserProfile.VerificationCodeSendingError
slog.Error("failed to check if address is already taken", slog.String("error", err.Error()))
return fiber.StatusUnprocessableEntity, nil
}
if isTaken {
templateMap["Status"] = "Failed"
- templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "EmailTaken").(string)
+ templateMap["Message"] = supplements.Localization[lang].UserProfile.EmailTaken
return fiber.StatusUnprocessableEntity, nil
}
if isAllowed, whenAllowed, _ := supplements.Mailer.IsAllowedToRetryVerification(id); !isAllowed {
templateMap["Status"] = "Failed"
- templateMap["Message"] = strings.ReplaceAll(l10n.T.GetPath(lang, "UserProfile", "DelayTilVerification").(string), "{}", whenAllowed.Format("2006-01-02 15:04:05 MST"))
+ templateMap["Message"] = strings.ReplaceAll(supplements.Localization[lang].UserProfile.DelayTilVerification, "{}", whenAllowed.Format("2006-01-02 15:04:05 MST"))
templateMap["DataAttributes"] = map[string]any{
"striked-end-time": template.HTMLAttr(fmt.Sprintf("data-striked-end-time=\"%d\"", whenAllowed.UnixMilli())),
}
@@ -79,20 +78,20 @@ func (r *SendVerificationCodeHandler) Render(c *fiber.Ctx, supplements *router.S
if previousEmail, _, _ := supplements.Mailer.GetInfo(supplements.Mailer.GetHash(id)); previousEmail == email {
templateMap["Status"] = "Failed"
- templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "EmailAlreadyValidated").(string)
+ templateMap["Message"] = supplements.Localization[lang].UserProfile.EmailAlreadyValidated
return fiber.StatusUnprocessableEntity, nil
}
if err = supplements.Mailer.SendVerificationCode(id, email, lang); err != nil {
templateMap["Status"] = "Failed"
- templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "VerificationCodeSendingError").(string)
+ templateMap["Message"] = supplements.Localization[lang].UserProfile.VerificationCodeSendingError
slog.Error("failed to send a verification code", slog.String("error", err.Error()))
return fiber.StatusUnprocessableEntity, nil
}
_, endTime, codeExpiry := supplements.Mailer.IsAllowedToRetryVerification(id)
templateMap["Status"] = "OK"
- templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "VerificationCodeSent").(string)
+ templateMap["Message"] = supplements.Localization[lang].UserProfile.VerificationCodeSent
templateMap["DataAttributes"] = map[string]any{
"striked-end-time": template.HTMLAttr(fmt.Sprintf("data-striked-end-time=\"%d\"", endTime.UnixMilli())),
"code-expiry-time": template.HTMLAttr(fmt.Sprintf("data-code-expiry-time=\"%d\"", codeExpiry.UnixMilli())),
diff --git a/internal/router/handlers/api-v1-email-verify.go b/internal/router/handlers/api-v1-email-verify.go
index 65ad6d9..ace1870 100644
--- a/internal/router/handlers/api-v1-email-verify.go
+++ b/internal/router/handlers/api-v1-email-verify.go
@@ -5,7 +5,6 @@ import (
"log/slog"
"github.com/SayaAndy/saya-today-web/internal/router"
- "github.com/SayaAndy/saya-today-web/l10n"
"github.com/gofiber/fiber/v2"
)
@@ -47,19 +46,19 @@ func (r *VerifyCodeHandler) Render(c *fiber.Ctx, supplements *router.Supplements
if verificationCode == "" {
templateMap["Status"] = "Failed"
- templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "VerificationEmpty").(string)
+ templateMap["Message"] = supplements.Localization[lang].UserProfile.VerificationEmpty
return fiber.StatusUnprocessableEntity, nil
}
if err = supplements.Mailer.Verify(verificationCode, lang); err != nil {
slog.Warn("verification code is invalid", slog.String("verification_code", verificationCode), slog.String("error", err.Error()))
templateMap["Status"] = "Failed"
- templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "VerificationFailed").(string)
+ templateMap["Message"] = supplements.Localization[lang].UserProfile.VerificationFailed
return fiber.StatusUnprocessableEntity, nil
}
templateMap["Status"] = "OK"
- templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "VerificationSuccess").(string)
+ templateMap["Message"] = supplements.Localization[lang].UserProfile.VerificationSuccess
templateMap["DataAttributes"] = map[string]any{
"hide-verification-panel": template.HTMLAttr("data-code-expiry-time=\"true\""),
}
diff --git a/internal/router/handlers/api-v1-like-get.go b/internal/router/handlers/api-v1-like-get.go
index 9e2c856..0eaf5f6 100644
--- a/internal/router/handlers/api-v1-like-get.go
+++ b/internal/router/handlers/api-v1-like-get.go
@@ -42,7 +42,7 @@ func (r *GetLikeHandler) Render(c *fiber.Ctx, supplements *router.Supplements, l
return fiber.StatusBadRequest, fmt.Errorf("error getting path from referer: %w", err)
}
- if len(pathParts) != 3 || pathParts[1] != "blog" {
+ if len(pathParts) != 3 && pathParts[1] != "blog" {
return fiber.StatusBadRequest, fmt.Errorf("invalid path format: expected '/:lang/blog/:page', got '%s'", path)
}
diff --git a/internal/router/handlers/api-v1-map-get.go b/internal/router/handlers/api-v1-map-get.go
deleted file mode 100644
index e19c54a..0000000
--- a/internal/router/handlers/api-v1-map-get.go
+++ /dev/null
@@ -1,116 +0,0 @@
-package handlers
-
-import (
- "fmt"
- "log/slog"
- "slices"
- "strconv"
- "strings"
-
- "github.com/SayaAndy/saya-today-web/internal/blog"
- "github.com/SayaAndy/saya-today-web/internal/router"
- "github.com/gofiber/fiber/v2"
-)
-
-type GetMapHandler struct {
- router.BasicHandler
-}
-
-func init() {
- router.Routes = append(router.Routes, &GetMapHandler{})
-}
-
-func (r *GetMapHandler) Filter() (method string, path string) {
- return "GET", "/api/v1/map"
-}
-
-func (r *GetMapHandler) IsTemplated() bool {
- return false
-}
-
-func (r *GetMapHandler) TemplatesToInject() []string {
- return []string{"views/partials/global-map-widget.html"}
-}
-
-func (r *GetMapHandler) ToCache() router.CacheSetting {
- return router.ByUrlAndQuery
-}
-
-func (r *GetMapHandler) ToValidateLang() router.LangSetting {
- return router.InReferer
-}
-
-func (r *GetMapHandler) Render(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
- codename := c.Query("codename")
- zoom := c.QueryInt("zoom", 4)
- zoomPosition := c.Query("zoomPosition")
-
- pages, err := supplements.BlogClient.Scan(lang + "/")
- status := fiber.StatusOK
- if err != nil {
- slog.Error("received an error while scanning blog pages",
- slog.String("error", err.Error()),
- slog.String("lang", lang),
- )
- status = fiber.StatusPartialContent
- pages = []*blog.Page{}
- }
- slices.SortFunc(pages, func(a *blog.Page, b *blog.Page) int {
- return a.Metadata.PublishedTime.Compare(b.Metadata.PublishedTime)
- })
-
- type MapMarker struct {
- Index int `json:"Index"`
- Title string `json:"Title"`
- PageLink string `json:"PageLink"`
- Lat float64 `json:"Lat"`
- Long float64 `json:"Long"`
- AccuracyMeters int64 `json:"AccuracyMeters"`
- Thumbnail string `json:"Thumbnail"`
- ToHighlight bool `json:"ToHighlight"`
- }
-
- templateMap["MapLocationLat"] = 45.4507
- templateMap["MapLocationLong"] = 68.8319
- templateMap["MapLocationZoom"] = zoom
- templateMap["ZoomPosition"] = zoomPosition
-
- mapMarkers := make([]*MapMarker, 0, len(pages))
- for i, 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)
- }
-
- toHighlight := page.FileName == codename
- if toHighlight {
- templateMap["MapLocationLat"] = x
- templateMap["MapLocationLong"] = y
- }
-
- mapMarkers = append(mapMarkers, &MapMarker{
- Index: i,
- Title: page.Metadata.Title,
- PageLink: fmt.Sprintf("/%s/blog/%s", lang, page.FileName),
- Lat: x,
- Long: y,
- AccuracyMeters: areaError,
- Thumbnail: page.Metadata.Thumbnail,
- ToHighlight: toHighlight,
- })
- }
-
- templateMap["MapMarkers"] = mapMarkers
-
- return status, nil
-}
diff --git a/internal/router/handlers/api-v1-subs-put.go b/internal/router/handlers/api-v1-subs-put.go
index 12dc5cf..32fba7e 100644
--- a/internal/router/handlers/api-v1-subs-put.go
+++ b/internal/router/handlers/api-v1-subs-put.go
@@ -3,7 +3,6 @@ package handlers
import (
"github.com/SayaAndy/saya-today-web/internal/mailer"
"github.com/SayaAndy/saya-today-web/internal/router"
- "github.com/SayaAndy/saya-today-web/l10n"
"github.com/gofiber/fiber/v2"
)
@@ -53,18 +52,18 @@ func (r *PutSubsHandler) Render(c *fiber.Ctx, supplements *router.Supplements, l
subscriptionTypeEnum = mailer.Specific
default:
templateMap["Status"] = "Failed"
- templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "SubscribeInvalidType").(string)
+ templateMap["Message"] = supplements.Localization[lang].UserProfile.SubscribeInvalidType
return fiber.StatusUnprocessableEntity, nil
}
specificTags := c.FormValue("tags_picked")
if err = supplements.Mailer.Subscribe(supplements.Mailer.GetHash(c.IP()), subscriptionTypeEnum, specificTags); err != nil {
templateMap["Status"] = "Failed"
- templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "FailedToSubscribe").(string)
+ templateMap["Message"] = supplements.Localization[lang].UserProfile.FailedToSubscribe
return fiber.StatusUnprocessableEntity, nil
}
templateMap["Status"] = "OK"
- templateMap["Message"] = l10n.T.GetPath(lang, "UserProfile", "SubscribedSuccessfully").(string)
+ templateMap["Message"] = supplements.Localization[lang].UserProfile.SubscribedSuccessfully
return fiber.StatusOK, nil
}
diff --git a/internal/router/handlers/lang-blog-title.go b/internal/router/handlers/lang-blog-title.go
index 76dc335..49744c1 100644
--- a/internal/router/handlers/lang-blog-title.go
+++ b/internal/router/handlers/lang-blog-title.go
@@ -10,7 +10,6 @@ import (
"github.com/SayaAndy/saya-today-web/internal/blog"
"github.com/SayaAndy/saya-today-web/internal/frontmatter"
"github.com/SayaAndy/saya-today-web/internal/router"
- "github.com/SayaAndy/saya-today-web/l10n"
"github.com/gofiber/fiber/v2"
"github.com/yuin/goldmark"
)
@@ -71,15 +70,11 @@ func (r *BlogPageHandler) AddMeta(c *fiber.Ctx, supplements *router.Supplements,
if err != nil {
return nil, fmt.Errorf("failed to read frontmatter of the desired blog post: %w", err)
}
- title := metadata.Title
- if metadata.Medley != "" {
- title += " // " + l10n.T.GetPath(lang, "Medleys", metadata.Medley).(string)
- }
return []router.MetaField{
- {Property: "og:title", Content: title},
+ {Property: "og:title", Content: metadata.Title},
{Property: "og:description", Content: fmt.Sprintf("%s [%s]", metadata.ShortDescription, metadata.ActionDate)},
- {Property: "og:image", Content: fmt.Sprintf(supplements.PhotoStorage.Thumbnail560p.BaseUrl, metadata.Thumbnail)},
+ {Property: "og:image", Content: fmt.Sprintf(supplements.PhotoStorage.Thumbnail320p.BaseUrl, metadata.Thumbnail)},
{Property: "og:url", Content: fmt.Sprintf("%s/%s/blog/%s", templateMap["CanonicalEndpoint"], lang, c.Params("title"))},
{Property: "og:type", Content: "website"},
{Name: "twitter:card", Content: "summary_large_image"},
@@ -91,15 +86,11 @@ func (r *BlogPageHandler) AddLinkedData(c *fiber.Ctx, supplements *router.Supple
if err != nil {
return nil, fmt.Errorf("failed to read frontmatter of the desired blog post: %w", err)
}
- title := metadata.Title
- if metadata.Medley != "" {
- title += " // " + l10n.T.GetPath(lang, "Medleys", metadata.Medley).(string)
- }
return map[string]any{
"@context": "https://schema.org",
"@type": "Article",
- "headline": title,
+ "headline": metadata.Title,
"description": fmt.Sprintf("%s [%s]", metadata.ShortDescription, metadata.ActionDate),
"author": map[string]string{"@type": "Person", "name": "Saya Andy"},
"datePublished": metadata.PublishedTime.UTC().Format(time.RFC3339),
@@ -111,11 +102,10 @@ func (r *BlogPageHandler) RenderBody(c *fiber.Ctx, supplements *router.Supplemen
if err != nil {
return fiber.StatusBadRequest, fmt.Errorf("failed to get path from referer: %w", err)
}
- title := pathParts[2]
- metadata, parsedMarkdown, err := readBlogPost(supplements.MarkdownRenderer, supplements.BlogClient, lang+"/"+title)
+ metadata, parsedMarkdown, err := readBlogPost(supplements.MarkdownRenderer, supplements.BlogClient, lang+"/"+pathParts[2])
if err != nil {
- return fiber.StatusNotFound, fmt.Errorf("failed to find '%s' post: %w", title, err)
+ return fiber.StatusNotFound, fmt.Errorf("failed to find '%s' post: %w", pathParts[2], err)
}
geolocationParts := strings.Split(metadata.Geolocation, " ")
@@ -132,15 +122,12 @@ func (r *BlogPageHandler) RenderBody(c *fiber.Ctx, supplements *router.Supplemen
templateMap["MapLocationY"] = y
templateMap["MapLocationAreaMeters"] = areaError
templateMap["Title"] = metadata.Title
- templateMap["Codename"] = title
templateMap["ParsedMarkdown"] = template.HTML(parsedMarkdown)
templateMap["PublishedDate"] = metadata.PublishedTime.Format("2006-01-02 15:04:05 -07:00")
templateMap["ActionDate"] = metadata.ActionDate
templateMap["ShortDescription"] = metadata.ShortDescription
- templateMap["Thumbnail"] = metadata.Thumbnail
- templateMap["Medley"] = metadata.Medley
- go supplements.ClientCache.View(c.IP(), title)
+ go supplements.ClientCache.View(c.IP(), pathParts[2])
return fiber.StatusOK, nil
}
@@ -156,13 +143,7 @@ func (r *BlogPageHandler) RenderHeader(c *fiber.Ctx, supplements *router.Supplem
return fiber.StatusNotFound, fmt.Errorf("could not read '%s' for metadata: %w", path, err)
}
- pageTitle := metadata.Title
- if metadata.Medley != "" {
- pageTitle += " // " + l10n.T.GetPath(lang, "Medleys", metadata.Medley).(string)
- }
-
templateMap["Title"] = metadata.Title
- templateMap["PageTitle"] = pageTitle
return fiber.StatusOK, nil
}
diff --git a/internal/router/handlers/lang-blog.go b/internal/router/handlers/lang-blog.go
index c31c2eb..79ec9f3 100644
--- a/internal/router/handlers/lang-blog.go
+++ b/internal/router/handlers/lang-blog.go
@@ -11,7 +11,6 @@ import (
"github.com/SayaAndy/saya-today-web/internal/blog"
"github.com/SayaAndy/saya-today-web/internal/router"
- "github.com/SayaAndy/saya-today-web/l10n"
"github.com/gofiber/fiber/v2"
)
@@ -58,16 +57,18 @@ func (r *CatalogueHandler) SitemapInfo(supplements *router.Supplements) []router
func (r *CatalogueHandler) AddMeta(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (meta []router.MetaField, err error) {
return []router.MetaField{
- {Property: "og:title", Content: l10n.T.GetPath(lang, "BlogSearch", "Header").(string)},
- {Property: "og:description", Content: l10n.T.GetPath(lang, "BlogSearch", "Description").(string)},
+ {Property: "og:title", Content: supplements.Localization[lang].BlogSearch.Header},
+ {Property: "og:description", Content: supplements.Localization[lang].BlogSearch.Description},
{Property: "og:url", Content: fmt.Sprintf("%s/%s/blog", templateMap["CanonicalEndpoint"], lang)},
{Property: "og:type", Content: "website"},
}, nil
}
func (r *CatalogueHandler) RenderBody(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
- querySort := c.Query("sort", "publicationDateDesc")
- previousBlogPage := c.Query("codename")
+ querySort := c.Query("sort")
+ if querySort == "" {
+ querySort = "publicationDateDesc"
+ }
encodedQuery := c.Request().URI().QueryString()
decodedQuery, _ := url.QueryUnescape(string(encodedQuery))
@@ -86,14 +87,13 @@ func (r *CatalogueHandler) RenderBody(c *fiber.Ctx, supplements *router.Suppleme
templateMap["Tags"] = tagsArray
templateMap["QuerySort"] = querySort
templateMap["QueryTags"] = strings.Join(queryTags, ",")
- templateMap["Title"] = l10n.T.GetPath(lang, "BlogSearch", "Header").(string)
- templateMap["PreviousBlogPage"] = previousBlogPage
+ templateMap["Title"] = supplements.Localization[lang].BlogSearch.Header
return fiber.StatusOK, nil
}
func (r *CatalogueHandler) RenderHeader(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
- templateMap["Title"] = l10n.T.GetPath(lang, "BlogSearch", "Header").(string)
+ templateMap["Title"] = supplements.Localization[lang].BlogSearch.Header
return fiber.StatusOK, nil
}
diff --git a/internal/router/handlers/lang-map.go b/internal/router/handlers/lang-map.go
index 32ab35e..d452bc7 100644
--- a/internal/router/handlers/lang-map.go
+++ b/internal/router/handlers/lang-map.go
@@ -1,6 +1,13 @@
package handlers
import (
+ "fmt"
+ "log/slog"
+ "slices"
+ "strconv"
+ "strings"
+
+ "github.com/SayaAndy/saya-today-web/internal/blog"
"github.com/SayaAndy/saya-today-web/internal/router"
"github.com/gofiber/fiber/v2"
)
@@ -42,5 +49,61 @@ func (r *MapHandler) SitemapInfo(supplements *router.Supplements) []router.Sitem
}
func (r *MapHandler) Render(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
- return fiber.StatusOK, nil
+ pages, err := supplements.BlogClient.Scan(lang + "/")
+ slices.SortFunc(pages, func(a *blog.Page, b *blog.Page) int {
+ return a.Metadata.PublishedTime.Compare(b.Metadata.PublishedTime)
+ })
+ status := fiber.StatusOK
+ if err != nil {
+ slog.Error("received an error while scanning b2 pages",
+ slog.String("error", err.Error()),
+ slog.String("lang", lang),
+ )
+ status = fiber.StatusPartialContent
+ pages = []*blog.Page{}
+ }
+
+ type MapMarker struct {
+ Index int `json:"Index"`
+ 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 i, 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{
+ Index: i,
+ Title: page.Metadata.Title,
+ PageLink: fmt.Sprintf("/%s/blog/%s", lang, page.FileName),
+ Lat: x,
+ Long: y,
+ AccuracyMeters: areaError,
+ Thumbnail: page.Metadata.Thumbnail,
+ })
+ }
+
+ templateMap["MapMarkers"] = mapMarkers
+ templateMap["MapLocationLat"] = 45.4507
+ templateMap["MapLocationLong"] = 68.8319
+
+ return status, nil
}
diff --git a/internal/router/handlers/lang-user-unsubscribe.go b/internal/router/handlers/lang-user-unsubscribe.go
index e22d854..4fbd244 100644
--- a/internal/router/handlers/lang-user-unsubscribe.go
+++ b/internal/router/handlers/lang-user-unsubscribe.go
@@ -4,7 +4,6 @@ import (
"log/slog"
"github.com/SayaAndy/saya-today-web/internal/router"
- "github.com/SayaAndy/saya-today-web/l10n"
"github.com/gofiber/fiber/v2"
)
@@ -40,24 +39,24 @@ func (r *UnsubscribeHandler) Render(c *fiber.Ctx, supplements *router.Supplement
if unsubscribeCode == "" {
statusColor = "0, 0, 255"
statusEmoji = "(╭ರ_•́)"
- statusText = l10n.T.GetPath(lang, "UnsubscribePage", "UnsetCode").(string)
+ statusText = supplements.Localization[lang].UnsubscribePage.UnsetCode
status = fiber.ErrBadRequest.Code
} else if clientError, serverError := supplements.Mailer.Unsubscribe(unsubscribeCode); clientError != nil {
slog.Info("got a client error when unsubscribing", slog.String("error", clientError.Error()))
statusColor = "255, 0, 0"
statusEmoji = "(͠≖~≖ ͡ )"
- statusText = l10n.T.GetPath(lang, "UnsubscribePage", "InvalidCode").(string)
+ statusText = supplements.Localization[lang].UnsubscribePage.InvalidCode
status = fiber.ErrBadRequest.Code
} else if serverError != nil {
slog.Error("got a server error when unsubscribing", slog.String("error", serverError.Error()))
statusColor = "255, 128, 0"
statusEmoji = "( ˶°ㅁ°) !!"
- statusText = l10n.T.GetPath(lang, "UnsubscribePage", "OnServerError").(string)
+ statusText = supplements.Localization[lang].UnsubscribePage.OnServerError
status = fiber.ErrInternalServerError.Code
} else {
statusColor = "0, 255, 0"
statusEmoji = "♡⸜(˶˃ ᵕ ˂˶)⸝♡"
- statusText = l10n.T.GetPath(lang, "UnsubscribePage", "Success").(string)
+ statusText = supplements.Localization[lang].UnsubscribePage.Success
status = fiber.StatusOK
}
diff --git a/internal/router/handlers/lang-user.go b/internal/router/handlers/lang-user.go
index 085a9aa..0dda529 100644
--- a/internal/router/handlers/lang-user.go
+++ b/internal/router/handlers/lang-user.go
@@ -6,7 +6,6 @@ import (
"github.com/SayaAndy/saya-today-web/internal/mailer"
"github.com/SayaAndy/saya-today-web/internal/router"
- "github.com/SayaAndy/saya-today-web/l10n"
"github.com/gofiber/fiber/v2"
)
@@ -41,15 +40,15 @@ func (r *UserHandler) ToValidateLang() router.LangSetting {
func (r *UserHandler) AddMeta(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (meta []router.MetaField, err error) {
return []router.MetaField{
{Name: "robots", Content: "noindex,nofollow"},
- {Property: "og:title", Content: l10n.T.GetPath(lang, "UserProfile", "Header").(string)},
- {Property: "og:description", Content: l10n.T.GetPath(lang, "UserProfile", "Description").(string)},
+ {Property: "og:title", Content: supplements.Localization[lang].UserProfile.Header},
+ {Property: "og:description", Content: supplements.Localization[lang].UserProfile.Description},
{Property: "og:url", Content: fmt.Sprintf("%s/%s/user", templateMap["CanonicalEndpoint"], lang)},
{Property: "og:type", Content: "website"},
}, nil
}
func (r *UserHandler) RenderBody(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
- templateMap["Title"] = l10n.T.GetPath(lang, "UserProfile", "Header").(string)
+ templateMap["Title"] = supplements.Localization[lang].UserProfile.Header
email, _, err := supplements.Mailer.GetInfo(supplements.Mailer.GetHash(c.IP()))
if err != nil {
@@ -84,6 +83,6 @@ func (r *UserHandler) RenderBody(c *fiber.Ctx, supplements *router.Supplements,
}
func (r *UserHandler) RenderHeader(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
- templateMap["Title"] = l10n.T.GetPath(lang, "UserProfile", "Header").(string)
+ templateMap["Title"] = supplements.Localization[lang].UserProfile.Header
return fiber.StatusOK, nil
}
diff --git a/internal/router/handlers/lang.go b/internal/router/handlers/lang.go
index cc0b33b..25b5036 100644
--- a/internal/router/handlers/lang.go
+++ b/internal/router/handlers/lang.go
@@ -6,7 +6,6 @@ import (
"time"
"github.com/SayaAndy/saya-today-web/internal/router"
- "github.com/SayaAndy/saya-today-web/l10n"
"github.com/gofiber/fiber/v2"
)
@@ -52,8 +51,8 @@ func (r *HomeHandler) SitemapInfo(supplements *router.Supplements) []router.Site
func (r *HomeHandler) AddMeta(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (meta []router.MetaField, err error) {
return []router.MetaField{
- {Property: "og:title", Content: l10n.T.GetPath(lang, "HomePage", "Header").(string)},
- {Property: "og:description", Content: l10n.T.GetPath(lang, "HomePage", "HomePageDescription").(string)},
+ {Property: "og:title", Content: supplements.Localization[lang].HomePage.Header},
+ {Property: "og:description", Content: supplements.Localization[lang].HomePage.HomePageDescription},
{Property: "og:image", Content: fmt.Sprintf(
supplements.PhotoStorage.HomePageGifs.BaseUrl,
supplements.PhotoStorage.HomePageGifs.Indexes[rand.Int()%len(supplements.PhotoStorage.HomePageGifs.Indexes)],
@@ -64,7 +63,7 @@ func (r *HomeHandler) AddMeta(c *fiber.Ctx, supplements *router.Supplements, lan
}
func (r *HomeHandler) RenderBody(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
- templateMap["Title"] = l10n.T.GetPath(lang, "HomePage", "Header").(string)
+ templateMap["Title"] = supplements.Localization[lang].HomePage.Header
templateMap["FilledHeartCount"] = uint(40)
templateMap["OutlineHeartCount"] = uint(40)
templateMap["GifUrl"] = fmt.Sprintf(
@@ -76,6 +75,6 @@ func (r *HomeHandler) RenderBody(c *fiber.Ctx, supplements *router.Supplements,
}
func (r *HomeHandler) RenderHeader(c *fiber.Ctx, supplements *router.Supplements, lang string, templateMap fiber.Map) (statusCode int, err error) {
- templateMap["Title"] = l10n.T.GetPath(lang, "HomePage", "Header").(string)
+ templateMap["Title"] = supplements.Localization[lang].HomePage.Header
return fiber.StatusOK, nil
}
diff --git a/internal/router/handlers/root.go b/internal/router/handlers/root.go
index 4fc4e31..6a011e4 100644
--- a/internal/router/handlers/root.go
+++ b/internal/router/handlers/root.go
@@ -47,8 +47,11 @@ func (r *RootHandler) AddMeta(c *fiber.Ctx, supplements *router.Supplements, lan
{Property: "og:url", Content: fmt.Sprint(templateMap["CanonicalEndpoint"]) + "/"},
{Property: "og:type", Content: "website"},
}
- for _, field := range supplements.Meta {
- meta = append(meta, router.MetaField{Name: field.Name, Content: field.Value})
+ if supplements.Meta.GoogleSiteVerification != "" {
+ meta = append(meta, router.MetaField{Name: "google-site-verification", Content: supplements.Meta.GoogleSiteVerification})
+ }
+ if supplements.Meta.YandexVerification != "" {
+ meta = append(meta, router.MetaField{Name: "yandex-verification", Content: supplements.Meta.YandexVerification})
}
return meta, nil
}
diff --git a/internal/router/router.go b/internal/router/router.go
index a11a934..65e35a4 100644
--- a/internal/router/router.go
+++ b/internal/router/router.go
@@ -23,6 +23,7 @@ import (
"github.com/SayaAndy/saya-today-web/internal/mailer"
"github.com/SayaAndy/saya-today-web/internal/tailwind"
"github.com/SayaAndy/saya-today-web/internal/templatemanager"
+ "github.com/SayaAndy/saya-today-web/locale"
"github.com/dgraph-io/ristretto/v2"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/compress"
@@ -94,6 +95,7 @@ type Route interface {
type Supplements struct {
DB *sql.DB
BlogClient blog.Client
+ Localization map[string]*locale.LocaleConfig
AvailableLanguages []config.AvailableLanguageConfig
ClientCache *ClientCache
PageCache *ristretto.Cache[string, []byte]
@@ -102,9 +104,9 @@ type Supplements struct {
BlogTrigger *blogtrigger.BlogTriggerScheduler
TemplateManager *templatemanager.TemplateManager
MarkdownRenderer goldmark.Markdown
- Meta []config.MetaConfig
+ Meta config.MetaConfig
PhotoStorage config.PhotoStorageConfig
- StaticStorage config.StaticStorageConfig
+ StaticStorage config.PhotoTypeConfig
}
type Router struct {
@@ -147,7 +149,16 @@ func NewRouter(cfg *config.Config) (*Router, error) {
supplements.BlogClient, err = blog.NewClientMap[cfg.BlogPages.Storage.Type](&cfg.BlogPages.Storage)
if err != nil {
- return nil, fmt.Errorf("fail to initialize blog client: type %s: %w", cfg.BlogPages.Storage.Type, err)
+ return nil, fmt.Errorf("fail to initialize b2 client: %w", err)
+ }
+
+ supplements.Localization = make(map[string]*locale.LocaleConfig, len(cfg.AvailableLanguages))
+ for _, lang := range cfg.AvailableLanguages {
+ localeCfg, err := locale.InitConfig(cfg.LocalePath + lang.LocFile)
+ if err != nil {
+ return nil, fmt.Errorf("fail to initialize a locale: %w", err)
+ }
+ supplements.Localization[lang.Name] = localeCfg
}
supplements.MarkdownRenderer = goldmark.New(
@@ -189,7 +200,7 @@ func NewRouter(cfg *config.Config) (*Router, error) {
}
supplements.Mailer, err = mailer.NewMailer(supplements.DB, cfg.Mail.ClientHost, cfg.Mail.MailHost,
- cfg.Mail.PublicName, cfg.Mail.MailAddress, cfg.Mail.Username, cfg.Mail.Password, []byte(cfg.Mail.Salt))
+ cfg.Mail.PublicName, cfg.Mail.MailAddress, cfg.Mail.Username, cfg.Mail.Password, []byte(cfg.Mail.Salt), supplements.Localization)
if err != nil {
return nil, fmt.Errorf("fail to initialize mailer: %w", err)
}
@@ -303,12 +314,13 @@ func (r *Router) InitRoutes() (err error) {
}
defaultMap := fiber.Map{
- "Lang": lang,
- "Path": trimmedPath,
- "QueryString": queryString,
- "CanonicalEndpoint": r.canonicalEndpoint,
- "StaticStorage": r.supplements.StaticStorage,
- "PhotoStorage": r.supplements.PhotoStorage,
+ "L": r.supplements.Localization[lang],
+ "Lang": lang,
+ "Path": trimmedPath,
+ "QueryString": queryString,
+ "CanonicalEndpoint": r.canonicalEndpoint,
+ "StaticStorageBaseUrl": r.supplements.StaticStorage.BaseUrl,
+ "ThumbnailBaseUrl": r.supplements.PhotoStorage.Thumbnail320p.BaseUrl,
}
statusCode, err := currentRoute.Render(c, r.supplements, lang, defaultMap)
@@ -382,11 +394,6 @@ func (r *Router) Listen() error {
case "unix":
unixConfig := r.endpoint.Config.(*config.UnixConfig)
endpoint, _ := strings.CutPrefix(unixConfig.Path, "unix://")
-
- if err := os.Remove(endpoint); err != nil && !errors.Is(err, os.ErrNotExist) {
- return fmt.Errorf("error while cleaning up existing unix socket: %w", err)
- }
-
ln, err := net.Listen("unix", endpoint)
if err != nil {
return fmt.Errorf("error while initializing unix listener: %w", err)
@@ -439,8 +446,8 @@ func (r *Router) generalPage(c *fiber.Ctx, route Route, lang string) error {
path := c.Path()
method := c.Method()
trimmedPath := strings.Trim(path, "/")
- queryString := c.Request().URI().QueryString()
cacheKey := ""
+ queryString := c.Request().URI().QueryString()
switch route.ToCache() {
case ByUrlOnly:
@@ -455,12 +462,12 @@ func (r *Router) generalPage(c *fiber.Ctx, route Route, lang string) error {
}
valueMap := fiber.Map{
- "Lang": lang,
- "Path": trimmedPath,
- "QueryString": queryString,
- "CanonicalEndpoint": r.canonicalEndpoint,
- "StaticStorage": r.supplements.StaticStorage,
- "PhotoStorage": r.supplements.PhotoStorage,
+ "L": r.supplements.Localization[lang],
+ "Lang": lang,
+ "QueryString": queryString,
+ "CanonicalEndpoint": r.canonicalEndpoint,
+ "StaticStorageBaseUrl": r.supplements.StaticStorage.BaseUrl,
+ "ThumbnailBaseUrl": r.supplements.PhotoStorage.Thumbnail320p.BaseUrl,
}
var err error
@@ -477,52 +484,10 @@ func (r *Router) generalPage(c *fiber.Ctx, route Route, lang string) error {
valueMap["LinkedData"] = template.JS(ldBytes)
}
- syntheticReferer := path
- if len(queryString) > 0 {
- syntheticReferer += "?" + string(queryString)
- }
- c.Request().Header.Set("Referer", syntheticReferer)
-
- parts := []struct {
- name string
- key string
- render func(c *fiber.Ctx, supplements *Supplements, lang string, templateMap fiber.Map) (int, error)
- }{
- {"top-embeds", "RenderedTopEmbeds", route.RenderTopEmbeds},
- {"header", "RenderedHeader", route.RenderHeader},
- {"body", "RenderedBody", route.RenderBody},
- {"footer", "RenderedFooter", route.RenderFooter},
- {"bottom-embeds", "RenderedBottomEmbeds", route.RenderBottomEmbeds},
- }
-
- for _, p := range parts {
- statusCode, err := p.render(c, r.supplements, lang, valueMap)
- if err != nil {
- slog.Error("failed to render segment for full page",
- slog.String("path", path),
- slog.String("segment", p.name),
- slog.String("error", err.Error()),
- )
- c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
- return c.Status(statusCode).SendString(err.Error())
- }
- segContent, err := r.supplements.TemplateManager.Render("general-page-"+p.name, valueMap, route.TemplatesToInject()...)
- if err != nil {
- slog.Error("failed to render segment template for full page",
- slog.String("path", path),
- slog.String("segment", p.name),
- slog.String("error", err.Error()),
- )
- c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
- return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate segment")
- }
- valueMap[p.key] = template.HTML(segContent)
- }
-
content, err := r.supplements.TemplateManager.Render("general-page", valueMap)
if err != nil {
- slog.Warn("failed to generate full page", slog.String("path", path), slog.String("error", err.Error()))
- return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate full page")
+ slog.Warn("failed to generate div", slog.String("path", path), slog.String("error", err.Error()))
+ return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div")
}
go r.supplements.PageCache.SetWithTTL(cacheKey, content, int64(len(content)), route.CacheDuration())
@@ -563,12 +528,11 @@ func (r *Router) generalPageSegment(c *fiber.Ctx, part string) error {
cacheKey := ""
trimmedPath := strings.Trim(path, "/")
- requestQuery := string(c.Request().URI().QueryString())
switch route.ToCache() {
case ByUrlOnly:
cacheKey = fmt.Sprintf("%s.%s.%s", method, part, trimmedPath)
case ByUrlAndQuery:
- cacheKey = fmt.Sprintf("%s.%s.%s.%s.%s", method, part, trimmedPath, queryString, requestQuery)
+ cacheKey = fmt.Sprintf("%s.%s.%s.%s", method, part, trimmedPath, queryString)
}
if route.ToCache() != Disabled {
@@ -580,12 +544,13 @@ func (r *Router) generalPageSegment(c *fiber.Ctx, part string) error {
var statusCode int
defaultMap := fiber.Map{
- "Lang": lang,
- "Path": strings.Trim(path, "/"),
- "QueryString": queryString,
- "CanonicalEndpoint": r.canonicalEndpoint,
- "StaticStorage": r.supplements.StaticStorage,
- "PhotoStorage": r.supplements.PhotoStorage,
+ "L": r.supplements.Localization[lang],
+ "Lang": lang,
+ "Path": strings.Trim(path, "/"),
+ "QueryString": queryString,
+ "CanonicalEndpoint": r.canonicalEndpoint,
+ "StaticStorageBaseUrl": r.supplements.StaticStorage.BaseUrl,
+ "ThumbnailBaseUrl": r.supplements.PhotoStorage.Thumbnail320p.BaseUrl,
}
switch part {
@@ -677,7 +642,8 @@ func (r *Router) getAndValidateLang(c *fiber.Ctx, langSetting LangSetting, defau
return lang, nil
}
}
- return "", fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("lang value is invalid: '%s' is not considered an available language", lang))
+ c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
+ return "", c.Status(fiber.ErrBadRequest.Code).SendString(fmt.Sprintf("lang value is invalid: '%s' is not considered an available language", lang))
}
func GetPathFromReferer(c *fiber.Ctx) (path string, pathParts []string, queryString string, err error) {
diff --git a/internal/tailwind/transformer.go b/internal/tailwind/transformer.go
index f90489f..c9da9df 100644
--- a/internal/tailwind/transformer.go
+++ b/internal/tailwind/transformer.go
@@ -45,7 +45,7 @@ func (t *TailwindTransformer) Transform(node *ast.Document, reader text.Reader,
node.SetAttribute([]byte("class"), []byte("text-base/[2] font-gentium tracking-[.0125rem]"))
case *ast.Blockquote:
- node.SetAttribute([]byte("class"), []byte("border-l-[0.125rem] border-main-medium bg-paper bg-background-dark p-1 mb-2 italic font-thin"))
+ node.SetAttribute([]byte("class"), []byte("border-l-2 border-main-medium bg-paper bg-background-dark p-1 mb-2 italic font-thin"))
case *ast.CodeSpan:
node.SetAttribute([]byte("class"), []byte("bg-paper bg-background-dark"))
diff --git a/internal/templatemanager/templatemanager.go b/internal/templatemanager/templatemanager.go
index ff1214a..9d14e15 100644
--- a/internal/templatemanager/templatemanager.go
+++ b/internal/templatemanager/templatemanager.go
@@ -8,8 +8,6 @@ import (
"path/filepath"
"strings"
"time"
-
- "github.com/SayaAndy/saya-today-web/l10n"
)
type TemplateManager struct {
@@ -40,10 +38,6 @@ var templateFuncMap = template.FuncMap{
"fdiv": func(a, b int) float64 {
return float64(a) / float64(b)
},
- "l": func(path ...any) any {
- return l10n.T.GetPath(path...)
- },
- "join": strings.Join,
}
func NewTemplateManager(templates ...TemplateManagerTemplates) (*TemplateManager, error) {