27 files changed, 79 insertions, 498 deletions
diff --git a/config/config.go b/config/config.go index f57b5cd..7438fe1 100644 --- a/config/config.go +++ b/config/config.go @@ -11,7 +11,6 @@ import ( type Config struct { LogLevel slog.Level `json:"LogLevel" yaml:"logLevel" validate:"required"` BlogPages BlogPagesConfig `json:"BlogPages" yaml:"blogPages" validate:"required"` - FactGiver FactGiverConfig `json:"FactGiver" yaml:"factGiver" validate:"required"` LocalePath string `json:"LocalePath" yaml:"localePath" validate:"required,filepath"` AvailableLanguages []AvailableLanguageConfig `json:"AvailableLanguages" yaml:"availableLanguages" validate:"required"` Auth AuthConfig `json:"Auth" yaml:"auth" validate:"required"` @@ -34,11 +33,6 @@ type B2Config struct { ApplicationKey string `json:"ApplicationKey" yaml:"applicationKey"` } -type FactGiverConfig struct { - Storage StorageConfig `json:"Storage" yaml:"storage" validate:"required"` - FactsFileName string `json:"FactsFileName" yaml:"factsFileName" validate:"required"` -} - type AvailableLanguageConfig struct { Name string `json:"Name" yaml:"name" validate:"required"` Alt string `json:"Alt" yaml:"alt"` diff --git a/config/config.local.yaml b/config/config.local.yaml index 6d0abf3..0b48e47 100644 --- a/config/config.local.yaml +++ b/config/config.local.yaml @@ -8,16 +8,6 @@ blogPages: prefix: 'stage-' keyID: '${B2_KEY_ID}' applicationKey: '${B2_APPLICATION_KEY}' -factGiver: - storage: - type: b2 - config: - bucketName: sayana-pages - region: eu-central-003 - prefix: '' - keyID: '${B2_KEY_ID}' - applicationKey: '${B2_APPLICATION_KEY}' - factsFileName: 'facts-*.txt' localePath: ./locale/ availableLanguages: - name: ru diff --git a/config/config.prod.yaml b/config/config.prod.yaml index 38981a8..e1294c0 100644 --- a/config/config.prod.yaml +++ b/config/config.prod.yaml @@ -8,16 +8,6 @@ blogPages: prefix: 'prod-' keyID: '${B2_KEY_ID}' applicationKey: '${B2_APPLICATION_KEY}' -factGiver: - storage: - type: b2 - config: - bucketName: sayana-pages - region: eu-central-003 - prefix: '' - keyID: '${B2_KEY_ID}' - applicationKey: '${B2_APPLICATION_KEY}' - factsFileName: 'facts-*.txt' localePath: ./locale/ availableLanguages: - name: ru diff --git a/config/config.stage.yaml b/config/config.stage.yaml index b05edaf..30233c1 100644 --- a/config/config.stage.yaml +++ b/config/config.stage.yaml @@ -8,16 +8,6 @@ blogPages: prefix: 'stage-' keyID: '${B2_KEY_ID}' applicationKey: '${B2_APPLICATION_KEY}' -factGiver: - storage: - type: b2 - config: - bucketName: sayana-pages - region: eu-central-003 - prefix: '' - keyID: '${B2_KEY_ID}' - applicationKey: '${B2_APPLICATION_KEY}' - factsFileName: 'facts-*.txt' localePath: ./locale/ availableLanguages: - name: ru diff --git a/internal/factgiver/factgiver.go b/internal/factgiver/factgiver.go deleted file mode 100644 index 6f77403..0000000 --- a/internal/factgiver/factgiver.go +++ /dev/null @@ -1,67 +0,0 @@ -package factgiver - -import ( - "fmt" - "math/rand" - "regexp" - "strings" - "time" - - "github.com/SayaAndy/saya-today-web/config" - "github.com/SayaAndy/saya-today-web/internal/b2" -) - -type FactGiver struct { - b2Client *b2.B2Client - cache map[string][]string - langs []string - factsFileName string - nlRe *regexp.Regexp - randGen *rand.Rand -} - -func NewFactGiver(cfg *config.FactGiverConfig, langs []string) (*FactGiver, error) { - b2Client, err := b2.NewB2Client(&cfg.Storage.Config) - if err != nil { - return nil, fmt.Errorf("fail to init b2 client for a new fact giver: %s", err.Error()) - } - - factGiver := &FactGiver{ - b2Client: b2Client, - cache: make(map[string][]string, len(langs)), - langs: langs, - factsFileName: cfg.FactsFileName, - nlRe: regexp.MustCompile(`\r?\n`), - randGen: rand.New(rand.NewSource(time.Now().UnixNano())), - } - if err = factGiver.initCache(); err != nil { - return nil, fmt.Errorf("fail to init cache for a new fact giver: %s", err.Error()) - } - - return factGiver, nil -} - -func (g *FactGiver) Give(lang string) [3]string { - factSlice := make([]string, len(g.cache[lang])) - copy(factSlice, g.cache[lang]) - g.randGen.Shuffle(len(factSlice), func(i, j int) { - factSlice[i], factSlice[j] = factSlice[j], factSlice[i] - }) - return [3]string{factSlice[0], factSlice[1], factSlice[2]} -} - -func (g *FactGiver) initCache() error { - for _, lang := range g.langs { - localFacts := strings.Replace(g.factsFileName, "*", lang, 1) - factsContentBytes, err := g.b2Client.ReadAll(localFacts) - if err != nil { - return fmt.Errorf("fail to read '%s' facts file: %s", lang, err.Error()) - } - factsContent := string(factsContentBytes) - g.cache[lang] = g.nlRe.Split(factsContent, -1) - if g.cache[lang][len(g.cache[lang])-1] == "" { - g.cache[lang] = g.cache[lang][:len(g.cache[lang])-1] - } - } - return nil -} diff --git a/internal/frontmatter/parser.go b/internal/frontmatter/parser.go index 83312dc..1a521c0 100644 --- a/internal/frontmatter/parser.go +++ b/internal/frontmatter/parser.go @@ -16,8 +16,6 @@ type Metadata struct { Thumbnail string `yaml:"thumbnail"` Tags []string `yaml:"tags"` Geolocation string `yaml:"geolocation"` - Medley string `yaml:"medley"` - MedleyPart int `yaml:"medleyPart"` } func ParseFrontmatter(content []byte) (metadata *Metadata, markdown []byte, err error) { diff --git a/internal/router/api-v1-blog-search.go b/internal/router/api-v1-blog-search.go index 69be651..41f2dec 100644 --- a/internal/router/api-v1-blog-search.go +++ b/internal/router/api-v1-blog-search.go @@ -36,9 +36,8 @@ func Api_V1_BlogSearch(l map[string]*locale.LocaleConfig, langs []string, b2Clie slog.Warn("unable to parse a client timezone, defaulting to UTC", slog.String("error", err.Error()), slog.String("tz", tz)) } - cacheKey := "blog-search." + lang + ".pages-list" var pages []*b2.BlogPage - if pagesBytes, ok := PCache.Get(cacheKey); pagesBytes != nil || ok { + if pagesBytes, ok := PCache.Get("blog-search.pages-list"); pagesBytes != nil || ok { json.Unmarshal(pagesBytes, &pages) } else { pages, err = b2Client.Scan(lang + "/") @@ -47,7 +46,7 @@ func Api_V1_BlogSearch(l map[string]*locale.LocaleConfig, langs []string, b2Clie return c.Status(fiber.ErrInternalServerError.Code).SendString(fmt.Sprintf("failed to scan pages for '%s' lang: %v", lang, err)) } pagesBytes, _ := json.Marshal(pages) - PCache.SetWithTTL(cacheKey, pagesBytes, int64(len(pagesBytes)), 5*time.Minute) + PCache.SetWithTTL("blog-search.pages-list", pagesBytes, int64(len(pagesBytes)), 5*time.Minute) } encodedQuery := c.Request().URI().QueryString() @@ -79,7 +78,6 @@ func Api_V1_BlogSearch(l map[string]*locale.LocaleConfig, langs []string, b2Clie "Thumbnail": page.Metadata.Thumbnail, "Tags": page.Metadata.Tags, "LikeCount": CCache.GetLikeCount(page.FileName), - "ViewCount": CCache.GetViewCount(page.FileName), }) break } diff --git a/internal/router/api-v1-general-page-body.go b/internal/router/api-v1-general-page-body.go index b2fa268..cdb558a 100644 --- a/internal/router/api-v1-general-page-body.go +++ b/internal/router/api-v1-general-page-body.go @@ -4,7 +4,6 @@ import ( "fmt" "html/template" "log/slog" - "math/rand" "net/url" "regexp" "slices" @@ -12,14 +11,11 @@ import ( "time" "github.com/SayaAndy/saya-today-web/internal/b2" - "github.com/SayaAndy/saya-today-web/internal/factgiver" "github.com/SayaAndy/saya-today-web/locale" "github.com/gofiber/fiber/v2" "github.com/yuin/goldmark" ) -var FactGiver *factgiver.FactGiver - func init() { tm.Add("general-page-body", "views/partials/general-page-body.html") } @@ -46,7 +42,7 @@ func Api_V1_GeneralPage_Body(l map[string]*locale.LocaleConfig, langs []string, } pathParts := strings.Split(strings.Trim(path, "/"), "/") - if len(pathParts) == 0 { + if len(pathParts) < 2 { return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'") } @@ -138,15 +134,6 @@ func Api_V1_GeneralPage_Body(l map[string]*locale.LocaleConfig, langs []string, values["ParsedMarkdown"] = template.HTML(parsedMarkdown) additionalTemplates = append(additionalTemplates, "views/pages/blog-page.html") - - go CCache.View(c.IP(), pathParts[2]) - } else if len(pathParts) == 1 { - values["Title"] = l[lang].HomePage.Header - values["FilledHeartCount"] = uint(40) - values["OutlineHeartCount"] = uint(40) - values["GifName"] = fmt.Sprintf("otter-%d.gif", rand.Int()%3+1) - values["FunFacts"] = FactGiver.Give(lang) - additionalTemplates = append(additionalTemplates, "views/pages/home-page.html") } content, err := tm.Render("general-page-body", values, additionalTemplates...) diff --git a/internal/router/api-v1-general-page-bottom-embeds.go b/internal/router/api-v1-general-page-bottom-embeds.go index c4d9543..df766a7 100644 --- a/internal/router/api-v1-general-page-bottom-embeds.go +++ b/internal/router/api-v1-general-page-bottom-embeds.go @@ -38,7 +38,7 @@ func Api_V1_GeneralPage_BottomEmbeds(l map[string]*locale.LocaleConfig, langs [] } pathParts := strings.Split(strings.Trim(path, "/"), "/") - if len(pathParts) == 0 { + if len(pathParts) < 2 { return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'") } @@ -58,8 +58,6 @@ func Api_V1_GeneralPage_BottomEmbeds(l map[string]*locale.LocaleConfig, langs [] additionalTemplates = append(additionalTemplates, "views/pages/blog-catalogue.html") } else if len(pathParts) == 3 && pathParts[1] == "blog" { additionalTemplates = append(additionalTemplates, "views/pages/blog-page.html") - } else if len(pathParts) == 1 { - additionalTemplates = append(additionalTemplates, "views/pages/home-page.html") } content, err := tm.Render("general-page-bottom-embeds", values, additionalTemplates...) diff --git a/internal/router/api-v1-general-page-footer.go b/internal/router/api-v1-general-page-footer.go index 003f54c..98da6bb 100644 --- a/internal/router/api-v1-general-page-footer.go +++ b/internal/router/api-v1-general-page-footer.go @@ -38,7 +38,7 @@ func Api_V1_GeneralPage_Footer(l map[string]*locale.LocaleConfig, langs []string } pathParts := strings.Split(strings.Trim(path, "/"), "/") - if len(pathParts) == 0 { + if len(pathParts) < 2 { return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'") } @@ -58,8 +58,6 @@ func Api_V1_GeneralPage_Footer(l map[string]*locale.LocaleConfig, langs []string additionalTemplates = append(additionalTemplates, "views/pages/blog-catalogue.html") } else if len(pathParts) == 3 && pathParts[1] == "blog" { additionalTemplates = append(additionalTemplates, "views/pages/blog-page.html") - } else if len(pathParts) == 1 { - additionalTemplates = append(additionalTemplates, "views/pages/home-page.html") } content, err := tm.Render("general-page-footer", values, additionalTemplates...) diff --git a/internal/router/api-v1-general-page-header.go b/internal/router/api-v1-general-page-header.go index 80228f7..1d65966 100644 --- a/internal/router/api-v1-general-page-header.go +++ b/internal/router/api-v1-general-page-header.go @@ -39,7 +39,7 @@ func Api_V1_GeneralPage_Header(l map[string]*locale.LocaleConfig, langs []string } pathParts := strings.Split(strings.Trim(path, "/"), "/") - if len(pathParts) == 0 { + if len(pathParts) < 2 { return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'") } @@ -67,9 +67,6 @@ func Api_V1_GeneralPage_Header(l map[string]*locale.LocaleConfig, langs []string values["PublishedDate"] = metadata.PublishedTime.Format("2006-01-02 15:04:05 -07:00") values["ActionDate"] = metadata.ActionDate additionalTemplates = append(additionalTemplates, "views/pages/blog-page.html") - } else if len(pathParts) == 1 { - values["Title"] = l[lang].HomePage.Header - additionalTemplates = append(additionalTemplates, "views/pages/home-page.html") } content, err := tm.Render("general-page-header", values, additionalTemplates...) diff --git a/internal/router/api-v1-general-page-top-embeds.go b/internal/router/api-v1-general-page-top-embeds.go index 78021b2..b26675f 100644 --- a/internal/router/api-v1-general-page-top-embeds.go +++ b/internal/router/api-v1-general-page-top-embeds.go @@ -38,7 +38,7 @@ func Api_V1_GeneralPage_TopEmbeds(l map[string]*locale.LocaleConfig, langs []str } pathParts := strings.Split(strings.Trim(path, "/"), "/") - if len(pathParts) == 0 { + if len(pathParts) < 2 { return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'") } @@ -58,8 +58,6 @@ func Api_V1_GeneralPage_TopEmbeds(l map[string]*locale.LocaleConfig, langs []str additionalTemplates = append(additionalTemplates, "views/pages/blog-catalogue.html") } else if len(pathParts) == 3 && pathParts[1] == "blog" { additionalTemplates = append(additionalTemplates, "views/pages/blog-page.html") - } else if len(pathParts) == 1 { - additionalTemplates = append(additionalTemplates, "views/pages/home-page.html") } content, err := tm.Render("general-page-top-embeds", values, additionalTemplates...) diff --git a/internal/router/api-v1-general-page.go b/internal/router/api-v1-general-page.go index d0ea559..8e1387b 100644 --- a/internal/router/api-v1-general-page.go +++ b/internal/router/api-v1-general-page.go @@ -20,7 +20,7 @@ func Api_V1_GeneralPage(l map[string]*locale.LocaleConfig, langs []string) func( path := c.Path() pathParts := strings.Split(strings.Trim(path, "/"), "/") - if len(pathParts) == 0 { + if len(pathParts) < 2 { return c.Status(fiber.ErrBadRequest.Code).SendString("url path is invalid: expect format '/{lang}/...'") } diff --git a/internal/router/client-cache.go b/internal/router/client-cache.go index d607cdd..e448fb3 100644 --- a/internal/router/client-cache.go +++ b/internal/router/client-cache.go @@ -16,7 +16,6 @@ type ClientCache struct { hashMapMutex sync.RWMutex likePageMap map[string]map[string]struct{} - viewPageMap map[string]map[string]struct{} pageMutexMap map[string]*sync.RWMutex pageMutexMapMutex sync.Mutex @@ -39,7 +38,6 @@ func NewClientCache(db *sql.DB, salt []byte) (*ClientCache, error) { } likePageMap := make(map[string]map[string]struct{}) - viewPageMap := make(map[string]map[string]struct{}) pageMutexMap := make(map[string]*sync.RWMutex) for rows.Next() { @@ -52,32 +50,9 @@ func NewClientCache(db *sql.DB, salt []byte) (*ClientCache, error) { userIdString := base64.RawStdEncoding.EncodeToString(userId) if _, ok := likePageMap[pageRef]; !ok { likePageMap[pageRef] = make(map[string]struct{}) - viewPageMap[pageRef] = make(map[string]struct{}) pageMutexMap[pageRef] = &sync.RWMutex{} } likePageMap[pageRef][userIdString] = struct{}{} - viewPageMap[pageRef][userIdString] = struct{}{} - } - - rows, err = tx.Query("select * from blog_views;") - if err != nil { - tx.Rollback() - return nil, fmt.Errorf("fail to query db for blog_views to fill cache: %w", err) - } - - for rows.Next() { - var pageRef string - var userId []byte - if err = rows.Scan(&pageRef, &userId); err != nil { - tx.Rollback() - return nil, fmt.Errorf("fail scanning blog_views to fill cache: %w", err) - } - userIdString := base64.RawStdEncoding.EncodeToString(userId) - if _, ok := viewPageMap[pageRef]; !ok { - viewPageMap[pageRef] = make(map[string]struct{}) - pageMutexMap[pageRef] = &sync.RWMutex{} - } - viewPageMap[pageRef][userIdString] = struct{}{} } if err = tx.Commit(); err != nil { @@ -87,7 +62,6 @@ func NewClientCache(db *sql.DB, salt []byte) (*ClientCache, error) { return &ClientCache{ hashMap: make(map[string]string), likePageMap: likePageMap, - viewPageMap: viewPageMap, pageMutexMap: pageMutexMap, salt: salt, db: db, @@ -100,14 +74,51 @@ func (c *ClientCache) Close() error { return fmt.Errorf("fail to init transaction with db to dump cache: %w", err) } - if err = batchSave(tx, "blog_likes", c.likePageMap); err != nil { + if _, err = tx.Exec("delete from blog_likes;"); err != nil { tx.Rollback() - return fmt.Errorf("fail to save blog_likes: %s", err) + return fmt.Errorf("fail to truncate table blog_likes: %w", err) } - if err = batchSave(tx, "blog_views", c.viewPageMap); err != nil { - tx.Rollback() - return fmt.Errorf("fail to save blog_views: %s", err) + userIdBytes := make(map[string][]byte) + + sqlStatement := fmt.Sprintf(` + INSERT OR IGNORE INTO blog_likes (page_ref, user_id) + VALUES %s(?, ?); + `, strings.Repeat("(?, ?), ", 99)) + sqlStatementVars := make([]any, 0, 200) + + for pageRef, userSet := range c.likePageMap { + for userId := range userSet { + if _, ok := userIdBytes[userId]; !ok { + userIdBytes[userId], err = base64.RawStdEncoding.DecodeString(userId) + if err != nil { + slog.Warn("couldn't parse one of user hashes into bytes back", slog.String("hash", userId), slog.String("error", err.Error())) + continue + } + } + + sqlStatementVars = append(sqlStatementVars, any(pageRef), any(userIdBytes[userId])) + if len(sqlStatementVars) < 200 { + continue + } + + if _, err := tx.Exec(sqlStatement, sqlStatementVars...); err != nil { + slog.Warn("couldn't insert blog like pairs into db", slog.String("error", err.Error())) + } + + sqlStatementVars = make([]any, 0, 200) + } + } + + if len(sqlStatementVars) > 0 { + sqlStatement = fmt.Sprintf(` + INSERT OR IGNORE INTO blog_likes (page_ref, user_id) + VALUES %s(?, ?); + `, strings.Repeat("(?, ?), ", len(sqlStatementVars)/2-1)) + + if _, err := tx.Exec(sqlStatement, sqlStatementVars...); err != nil { + slog.Warn("couldn't insert blog like pairs into db", slog.String("error", err.Error())) + } } return tx.Commit() @@ -211,94 +222,3 @@ func (c *ClientCache) LikeOff(id string, page string) (alreadyUnliked bool) { delete(c.likePageMap[page], hash) return false } - -func (c *ClientCache) GetViewStatus(id string, page string) bool { - page = strings.Clone(page) - - mutex := c.getPageMutex(page) - mutex.RLock() - defer mutex.RUnlock() - - if _, ok := c.viewPageMap[page]; !ok { - return false - } - _, ok := c.viewPageMap[page][c.GetHash(id)] - return ok -} - -func (c *ClientCache) GetViewCount(page string) int { - page = strings.Clone(page) - - mutex := c.getPageMutex(page) - mutex.RLock() - defer mutex.RUnlock() - - if userSet, ok := c.viewPageMap[page]; ok { - return len(userSet) - } - return 0 -} - -func (c *ClientCache) View(id string, page string) { - page = strings.Clone(page) - hash := c.GetHash(id) - - mutex := c.getPageMutex(page) - mutex.Lock() - defer mutex.Unlock() - - if _, ok := c.viewPageMap[page]; !ok { - c.viewPageMap[page] = make(map[string]struct{}) - } - c.viewPageMap[page][hash] = struct{}{} -} - -func batchSave(tx *sql.Tx, table string, pageMap map[string]map[string]struct{}) (err error) { - if _, err = tx.Exec(fmt.Sprintf("delete from %s;", table)); err != nil { - return fmt.Errorf("fail to truncate table %s: %w", table, err) - } - - userIdBytes := make(map[string][]byte) - - sqlStatement := fmt.Sprintf(` - INSERT OR IGNORE INTO %s (page_ref, user_id) - VALUES %s(?, ?); - `, table, strings.Repeat("(?, ?), ", 99)) - sqlStatementVars := make([]any, 0, 200) - - for pageRef, userSet := range pageMap { - for userId := range userSet { - if _, ok := userIdBytes[userId]; !ok { - userIdBytes[userId], err = base64.RawStdEncoding.DecodeString(userId) - if err != nil { - slog.Warn("couldn't parse one of user hashes into bytes back", slog.String("hash", userId), slog.String("error", err.Error())) - continue - } - } - - sqlStatementVars = append(sqlStatementVars, any(pageRef), any(userIdBytes[userId])) - if len(sqlStatementVars) < 200 { - continue - } - - if _, err := tx.Exec(sqlStatement, sqlStatementVars...); err != nil { - slog.Warn("couldn't insert blog stat pairs into db", slog.String("table", table), slog.String("error", err.Error())) - } - - sqlStatementVars = make([]any, 0, 200) - } - } - - if len(sqlStatementVars) > 0 { - sqlStatement = fmt.Sprintf(` - INSERT OR IGNORE INTO %s (page_ref, user_id) - VALUES %s(?, ?); - `, table, strings.Repeat("(?, ?), ", len(sqlStatementVars)/2-1)) - - if _, err := tx.Exec(sqlStatement, sqlStatementVars...); err != nil { - slog.Warn("couldn't insert blog stat pairs into db", slog.String("table", table), slog.String("error", err.Error())) - } - } - - return nil -} diff --git a/internal/templatemanager/templatemanager.go b/internal/templatemanager/templatemanager.go index 4bf9a49..d8adab6 100644 --- a/internal/templatemanager/templatemanager.go +++ b/internal/templatemanager/templatemanager.go @@ -22,22 +22,13 @@ type TemplateManagerTemplates struct { Files []string } -var templateFuncMap = template.FuncMap{ - "contains": strings.Contains, - "iterate": func(count uint) []uint { - items := make([]uint, count) - for i := range count { - items[i] = i - } - return items - }, -} - func NewTemplateManager(templates ...TemplateManagerTemplates) (*TemplateManager, error) { templateMap := make(map[string]templateManagerRender) for _, tmplStruct := range templates { - tmpl := template.New("").Funcs(templateFuncMap) + tmpl := template.New("").Funcs(template.FuncMap{ + "contains": strings.Contains, + }) tmpl, err := tmpl.ParseFiles(tmplStruct.Files...) if err != nil { return nil, err @@ -84,7 +75,9 @@ func (tm *TemplateManager) Add(name string, files ...string) error { return fmt.Errorf("you can't add template without any files") } - tmpl := template.New("").Funcs(templateFuncMap) + tmpl := template.New("").Funcs(template.FuncMap{ + "contains": strings.Contains, + }) tmpl, err := tmpl.ParseFiles(files...) if err != nil { diff --git a/locale/localization.en.yaml b/locale/localization.en.yaml index 1b3654b..f5ee6c9 100644 --- a/locale/localization.en.yaml +++ b/locale/localization.en.yaml @@ -9,16 +9,3 @@ BlogSearch: ChooseAllTags: 'Choose All' GlobalMap: Header: 'Global Map' -HomePage: - Header: 'Home Page' - Contacts: 'Contacts' - DidYouKnowThat: 'Did you know, that...' - SidebarDescription: 'Describing the Sidebar' - HomePageDescription: 'Home Page -- where we are right now!' - BlogSearchDescription: 'Blog Search. You can find my travel notes there and, maybe, something else!' - MarkerMapDescription: 'Map with markers of my travel notes -- a convinient way to find an interesting region and find related posts!' - ThemeSwitchDescription: 'Theme Palette lets you change the theme of the site -- there are 2 dark and 2 bright themes currently!' - Hymn1: 'We welcome you to Sayasite!' - Hymn2: "We've been expecting you!" - Hymn3: 'You bring such joy in Sayasite!' - Hymn4: 'No matter where you roam, know our love is true!~' diff --git a/locale/localization.go b/locale/localization.go index 088783f..56b6462 100644 --- a/locale/localization.go +++ b/locale/localization.go @@ -11,7 +11,6 @@ type LocaleConfig struct { TagsLabel string `yaml:"TagsLabel" json:"TagsLabel"` BlogSearch BlogSearchConfig `yaml:"BlogSearch" json:"BlogSearch"` GlobalMap GlobalMapConfig `yaml:"GlobalMap" json:"GlobalMap"` - HomePage HomePageConfig `yaml:"HomePage" json:"HomePage"` } type BlogSearchConfig struct { @@ -28,21 +27,6 @@ type GlobalMapConfig struct { Header string `yaml:"Header" json:"Header"` } -type HomePageConfig struct { - Header string `yaml:"Header" json:"Header"` - Contacts string `yaml:"Contacts" json:"Contacts"` - DidYouKnowThat string `yaml:"DidYouKnowThat" json:"DidYouKnowThat"` - SidebarDescription string `yaml:"SidebarDescription" json:"SidebarDescription"` - HomePageDescription string `yaml:"HomePageDescription" json:"HomePageDescription"` - BlogSearchDescription string `yaml:"BlogSearchDescription" json:"BlogSearchDescription"` - MarkerMapDescription string `yaml:"MarkerMapDescription" json:"MarkerMapDescription"` - ThemeSwitchDescription string `yaml:"ThemeSwitchDescription" json:"ThemeSwitchDescription"` - Hymn1 string `yaml:"Hymn1" json:"Hymn1"` - Hymn2 string `yaml:"Hymn2" json:"Hymn2"` - Hymn3 string `yaml:"Hymn3" json:"Hymn3"` - Hymn4 string `yaml:"Hymn4" json:"Hymn4"` -} - func LoadConfig(path string, config *LocaleConfig) error { fileBytes, err := os.ReadFile(path) if err != nil { diff --git a/locale/localization.ru.yaml b/locale/localization.ru.yaml index 9d7084f..c236d1e 100644 --- a/locale/localization.ru.yaml +++ b/locale/localization.ru.yaml @@ -9,16 +9,3 @@ BlogSearch: ChooseAllTags: 'Выбрать все' GlobalMap: Header: 'Глобальная карта' -HomePage: - Header: 'Домашняя страница' - Contacts: 'Контакты' - DidYouKnowThat: 'А вы знали, что...' - SidebarDescription: 'Поясняем за сайдбар' - HomePageDescription: 'Домашняя страница -- где мы сейчас и находимся!' - BlogSearchDescription: 'Поиск по блогу. Там размещены мои путевые заметки и, может, что-то ещё!' - MarkerMapDescription: 'Карта с маркерами моих путевых заметок, -- удобный способ найти интересный регион и перейти на заинтересовавшие посты!' - ThemeSwitchDescription: 'Позволяет поменять тему сайта -- есть 2 светлые и 2 тёмные темы!' - Hymn1: 'Приветствуем Вас на Саясайте!' - Hymn2: 'Мы Вас столько ждали!' - Hymn3: 'Столько радости от Вас на Саясайте!' - Hymn4: 'Где бы Вы не были, знайте, -- наша любовь крепка!' @@ -11,14 +11,13 @@ import ( "github.com/SayaAndy/saya-today-web/config" "github.com/SayaAndy/saya-today-web/internal/b2" - "github.com/SayaAndy/saya-today-web/internal/factgiver" "github.com/SayaAndy/saya-today-web/internal/lightgallery" "github.com/SayaAndy/saya-today-web/internal/router" "github.com/SayaAndy/saya-today-web/internal/tailwind" "github.com/SayaAndy/saya-today-web/locale" "github.com/dgraph-io/ristretto/v2" "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v2/middleware/redirect" "github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4/database/sqlite3" "github.com/yuin/goldmark" @@ -108,10 +107,12 @@ func main() { ProxyHeader: "X-Forwarded-For", }) - app.Use(cors.New(cors.Config{ - AllowOrigins: "https://f003.backblazeb2.com", - AllowMethods: "GET,POST,OPTIONS", - AllowHeaders: "Origin, Content-Type, Accept", + app.Use(redirect.New(redirect.Config{ + Rules: map[string]string{ + "/ru": "/", + "/en": "/", + }, + StatusCode: 301, })) router.CCache, err = router.NewClientCache(db, []byte(cfg.Auth.Salt)) @@ -130,14 +131,7 @@ func main() { os.Exit(1) } - router.FactGiver, err = factgiver.NewFactGiver(&cfg.FactGiver, availableLanguages) - if err != nil { - slog.Error("fail to initialize fact giver", slog.String("error", err.Error())) - os.Exit(1) - } - app.Get("/", router.Root(cfg.AvailableLanguages)) - app.Get("/:lang<len(2)>", router.Api_V1_GeneralPage(localization, availableLanguages)) app.Get("/:lang/map", router.Lang_Map(localization, availableLanguages, b2Client)) app.Get("/:lang/blog", router.Api_V1_GeneralPage(localization, availableLanguages)) app.Get("/:lang/blog/:title", router.Api_V1_GeneralPage(localization, availableLanguages)) diff --git a/static/input.css b/static/input.css index 739bea7..261aeb3 100644 --- a/static/input.css +++ b/static/input.css @@ -10,7 +10,6 @@ --main-medium-color: #8a9d8a; --main-light-color: #a5b8a5; --background-dark-color: #ffffdd; - --background-medium-color: var(--background-dark-color); --background-light-color: #fffff0; --sidebar-text-color: var(--background-dark-color); --sidebar-stroke-color: var(--main-dark-color); @@ -32,7 +31,6 @@ --main-medium-color: #9bb665; --main-light-color: #bbd095; --background-dark-color: #f5eee2; - --background-medium-color: var(--background-dark-color); --background-light-color: #fff9ef; --sidebar-text-color: var(--main-dark-color); --sidebar-stroke-color: var(--background-dark-color); @@ -54,7 +52,6 @@ --main-medium-color: var(--color-sky-200); --main-light-color: var(--color-sky-300); --background-dark-color: #13131b; - --background-medium-color: var(--background-dark-color); --background-light-color: #222230; --sidebar-text-color: var(--main-dark-color); --sidebar-stroke-color: var(--background-light-color); @@ -76,7 +73,6 @@ --main-medium-color: #f6e4c5; --main-light-color: #e8d4b8; --background-dark-color: #413b34; - --background-medium-color: #62584f; --background-light-color: #6d6258; --sidebar-text-color: var(--main-dark-color); --sidebar-stroke-color: var(--background-light-color); @@ -619,22 +615,6 @@ body[data-theme~="ram"] .leaflet-tile { } } -@media (min-aspect-ratio: 0.6/1) { - .ar-gt-0\.6\:flex-3\/5 { - flex: 60%; - } - - .ar-gt-0\.6\:flex-2\/5 { - flex: 40%; - } -} - -@media (max-aspect-ratio: 0.6/1) { - .ar-lt-0\.6\:flex-col { - flex-direction: column; - } -} - @media (max-aspect-ratio: 0.4/1) { .ar-lt-0\.4\:w-\[100vw\] { width: 100vw; @@ -645,14 +625,6 @@ body[data-theme~="ram"] .leaflet-tile { } } -.home-page-heart { - position: absolute; - color: var(--color-pink-700); - user-select: none; - pointer-events: none; - font-family: serif; -} - .night-star { color: var(--color-yellow-100); animation-name: blink; diff --git a/views/index.html b/views/index.html index b52df94..2b8c937 100644 --- a/views/index.html +++ b/views/index.html @@ -106,7 +106,7 @@ <p class="font-patua mx-auto text-[2.5vmax] mt-[0.8vmin] mb-[0.8vmin]">saya.today</p> <div class="menu-inner-body grow bg-background-light flex flex-adaptive mx-auto opacity-100"> {{- range .AvailableLanguages }} - <img onclick="location.href='/{{ .Name }}';" class="object-scale-down cursor-pointer flex-1 rounded-[20%] language-icon mx-auto my-auto relative" src="{{ .Flag }}" alt="{{ .Alt }}"> + <img onclick="location.href='/{{ .Name }}/blog';" class="object-scale-down cursor-pointer flex-1 rounded-[20%] language-icon mx-auto my-auto relative" src="{{ .Flag }}" alt="{{ .Alt }}"> {{- end }} </div> <p class="font-spectral mx-auto text-[1.5vmax] text-secondary mt-[0.8vmin] mb-[0.8vmin]">Choose your language</p> diff --git a/views/layouts/general-page.html b/views/layouts/general-page.html index 60608f4..cb85a09 100644 --- a/views/layouts/general-page.html +++ b/views/layouts/general-page.html @@ -7,9 +7,9 @@ <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Spectral:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,800;1,200;1,300;1,400;1,500;1,600;1,700;1,800&family=Patua+One&display=swap" rel="stylesheet"> - <link rel="stylesheet" href="https://f003.backblazeb2.com/file/sayana-static/libs/lightgallery/2.8.3/css/lightgallery.min.css"> - <link rel="stylesheet" href="https://f003.backblazeb2.com/file/sayana-static/libs/lightgallery/2.8.3/css/lg-zoom.min.css"> - <link rel="stylesheet" href="https://f003.backblazeb2.com/file/sayana-static/libs/lightgallery/2.8.3/css/lg-thumbnail.min.css"> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/lightgallery/2.8.3/css/lightgallery.min.css"> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/lightgallery/2.8.3/css/lg-zoom.min.css"> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/lightgallery/2.8.3/css/lg-thumbnail.min.css"> <link rel="stylesheet" href="https://f003.backblazeb2.com/file/sayana-static/libs/leaflet/1.9.4/leaflet.css"> <link href="/output.css" rel="stylesheet"> </head> @@ -166,7 +166,7 @@ saya.today </div> <div class="text-[3vw] ar-lt-1.1:text-[5vh] text-stroke-(--sidebar-stroke-color) text-stroke-[0.1vw] flex flex-col ar-lt-1.1:flex-row gap-0 relative z-20"> - <i onclick="changeUrl('/{{ .Lang }}');" class="fas fa-home w-[5vw] ar-lt-1.1:w-[8vh] h-[5vw] ar-lt-1.1:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i> + <i onclick="location.href='/{{ .Lang }}';" class="fas fa-home w-[5vw] ar-lt-1.1:w-[8vh] h-[5vw] ar-lt-1.1:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i> <i onclick="changeUrl('/{{ .Lang }}/blog');" class="fas fa-search w-[5vw] ar-lt-1.1:w-[8vh] h-[5vw] ar-lt-1.1:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i> <i onclick="location.href='/{{ .Lang }}/map';" class="fas fa-map w-[5vw] ar-lt-1.1:w-[8vh] h-[5vw] ar-lt-1.1:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i> <i onclick="toggleThemeWheel();" id="theme-button" class="fas fa-swatchbook w-[5vw] ar-lt-1.1:w-[8vh] h-[5vw] ar-lt-1.1:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i> diff --git a/views/pages/blog-page.html b/views/pages/blog-page.html index 863b417..b121cc6 100644 --- a/views/pages/blog-page.html +++ b/views/pages/blog-page.html @@ -1,7 +1,7 @@ {{ define "top-embeds" }} <script> - let galleryMap = new Map(); - let map; + var galleryMap = new Map(); + var map; window.addEventListener('popout', (e) => { map.remove(); @@ -9,8 +9,6 @@ g.destroy(); }); galleryMap.clear(); - map = null; - galleryMap = null; }, {once: true}); </script> {{ end }} @@ -89,8 +87,7 @@ window.addEventListener('resize', resizeCb); document.addEventListener('popout', (e) => { - window.removeEventListener('resize', resizeCb); - galleryResizeTimeout = null; + window.removeEventListener('resize', resizeCb, {once: true}); }); </script> {{ end }}
\ No newline at end of file diff --git a/views/pages/global-map.html b/views/pages/global-map.html index a844e06..d6e7281 100644 --- a/views/pages/global-map.html +++ b/views/pages/global-map.html @@ -45,7 +45,7 @@ saya.today </div> <div class="text-[3vw] ar-lt-1.1:text-[5vh] text-stroke-(--sidebar-stroke-color) text-stroke-[0.1vw] flex flex-col ar-lt-1.1:flex-row gap-0 relative z-20"> - <i onclick="location.href='/{{ .Lang }}';" class="fas fa-home w-[5vw] ar-lt-1.1:w-[8vh] h-[5vw] ar-lt-1.1:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i> + <i onclick="location.href='/';" class="fas fa-home w-[5vw] ar-lt-1.1:w-[8vh] h-[5vw] ar-lt-1.1:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i> <i onclick="location.href='/{{ .Lang }}/blog';" class="fas fa-search w-[5vw] ar-lt-1.1:w-[8vh] h-[5vw] ar-lt-1.1:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i> <i onclick="location.href='/{{ .Lang }}/map';" class="fas fa-map w-[5vw] ar-lt-1.1:w-[8vh] h-[5vw] ar-lt-1.1:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i> <i onclick="toggleThemeWheel();" id="theme-button" class="fas fa-swatchbook w-[5vw] ar-lt-1.1:w-[8vh] h-[5vw] ar-lt-1.1:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i> diff --git a/views/pages/home-page.html b/views/pages/home-page.html deleted file mode 100644 index 6fd716b..0000000 --- a/views/pages/home-page.html +++ /dev/null @@ -1,121 +0,0 @@ -{{ define "body" }} -<div class="bg-background-light flex flex-row ar-lt-0.6:flex-col w-full h-full inset-shadow overflow-y-auto text-[1vmax]/[2] tracking-[0.04vmin]"> - <div class="ar-gt-0.6:flex-3/5 justify-center content-start p-[5%]"> - <div class="flex flex-col justify-start items-start bg-background-dark mb-[2vmax]"> - <p class="text-[3vmax]/[2] tracking-[0.2vmin] uppercase w-full font-spectral italic text-left - border-l-[0.4vmax] border-solid border-(--background-dark-color) - bg-gradient-to-r from-(--background-dark-color) to-(--background-light-color)">{{ .L.HomePage.Hymn1 }}</p> - <hr class="w-full border-t-[0.2vmax] border-dotted border-main-dark mb-[0.8vmin]"> - <p class="text-[2vmax]/[2] -indent-[2vmax] ml-[2vmax] p-[0.4vmax]">{{ .L.HomePage.Hymn2 }}</p> - <p class="text-[2vmax]/[2] -indent-[2vmax] ml-[2vmax] p-[0.4vmax]">{{ .L.HomePage.Hymn3 }}</p> - <p class="text-[2vmax]/[2] -indent-[2vmax] ml-[2vmax] p-[0.4vmax]">{{ .L.HomePage.Hymn4 }}</p> - </div> - - <div class="flex flex-col justify-center items-center"> - <p class="text-[2vmax] w-full font-spectral italic text-left - border-l-[0.4vmax] border-solid border-(--background-dark-color) - bg-gradient-to-r from-(--background-dark-color) to-(--background-light-color)">{{ .L.HomePage.DidYouKnowThat }}</p> - <hr class="w-full border-t-[0.2vmax] border-dotted border-main-dark mb-[0.8vmin]"> - <ul class="w-full text-left list-[circle]"> - <li class="ml-[2vmax] mb-[0.8vmin]">{{ index .FunFacts 0 }}</li> - <li class="ml-[2vmax] mb-[0.8vmin]">{{ index .FunFacts 1 }}</li> - <li class="ml-[2vmax]">{{ index .FunFacts 2 }}</li> - </ul> - </div> - </div> - - <div class="ar-gt-0.6:flex-2/5 justify-center content-start p-[5%]"> - <div class="flex flex-col justify-center items-start mb-[2vmax]"> - <p class="text-[2vmax] w-full font-spectral italic text-left - border-l-[0.4vmax] border-solid border-(--background-dark-color) - bg-gradient-to-r from-(--background-dark-color) to-(--background-light-color)">{{ .L.HomePage.Contacts }}</p> - <a href="https://t.me/EarlInisMona"> - <i class="fa-brands fa-telegram w-[1vmax] h-[1vmax] mr-[0.4vmax]"></i> - <span class="font-bold">Telegram:</span> - <span>@EarlInisMona</span> - </a> - <a href="https://discord.com/users/356057270951346176"> - <i class="fa-brands fa-discord w-[1vmax] h-[1vmax] mr-[0.4vmax]"></i> - <span class="font-bold">Discord:</span> - <span>montferrat</span> - </a> - <a href="mailto:saya.andy@posteo.com"> - <i class="fas fa-envelope w-[1vmax] h-[1vmax] mr-[0.4vmax]"></i> - <span class="font-bold">E-mail:</span> - <span>saya.andy@posteo.com</span> - </a> - </div> - - <div class="flex relative aspect-square justify-center content-center mb-[2vmax]"> - {{- range $_ := iterate .FilledHeartCount }} - <div class="home-page-heart z-50">♥</div> - {{- end }} - {{- range $_ := iterate .OutlineHeartCount }} - <div class="home-page-heart z-50">♡</div> - {{- end }} - <img src="https://f003.backblazeb2.com/file/sayana-static/home-page-gifs/{{ .GifName }}" - class="home-page-gif z-49 -rotate-12 max-w-[70%] max-h-[70%] border-[1vmin] border-(--background-dark-color) border-inset"> - </div> - - <script> - function shuffleHearts() { - vmin = calculateVmin(1); - document.querySelectorAll('.home-page-heart').forEach((e) => { - let top, left; - do { - top = Math.floor(rand() * 90) + 5; - left = Math.floor(rand() * 90) + 5; - } while (top > 30 && top < 70 && left > 30 && left < 70); - const opacity = rand() * 0.7 + 0.2; - const size = rand() * 3.2 + 0.8; - e.style["top"] = `calc(${top}% - ${size}vmin / 2)`; - e.style["left"] = `calc(${left}% - ${size}vmin / 2)`; - e.style["opacity"] = `${opacity}`; - e.style["font-size"] = `${size}vmin`; - }); - } - - let shuffleHeartsResizeTimeout; - function shuffleHeartsResizeCb() { - clearTimeout(shuffleHeartsResizeTimeout); - shuffleHeartsResizeTimeout = setTimeout(shuffleHearts, 300); - } - window.addEventListener('resize', shuffleHeartsResizeCb); - - document.addEventListener('popout', (e) => { - window.removeEventListener('resize', shuffleHeartsResizeCb); - shuffleHeartsResizeCb = null; - }); - </script> - - <script> - document.addEventListener('htmx:afterRequest', (e) => { - if (e.detail.xhr.status == 404 || e.detail.successful != true) { - return console.error(e); - } - if (e.detail.target.id == 'general-page-body') { - shuffleHearts(); - } - }, {once: true}); - </script> - - <div class="flex flex-col justify-center items-center"> - <p class="text-[2vmax] w-full font-spectral italic text-left - border-l-[0.4vmax] border-solid border-(--background-dark-color) - bg-gradient-to-r from-(--background-dark-color) to-(--background-light-color)">{{ .L.HomePage.SidebarDescription }}</p> - <hr class="w-full border-t-[0.2vmax] border-dotted border-main-dark mb-[0.8vmin]"> - <div class="w-full grid grid-cols-[1fr_4fr] content-center items-center - [&>*:nth-child(4n+2)]:bg-(--background-medium-color) [&>*:nth-child(4n+3)]:bg-(--background-medium-color)" style="container-type: inline-size;"> - <i class="fas fa-home w-full h-full text-[10cqi] content-center text-center text-main-dark"></i> - <p class="p-[0.4vmax]">{{ .L.HomePage.HomePageDescription }}</p> - <i class="fas fa-search w-full h-full text-[10cqi] content-center text-center text-main-dark"></i> - <p class="p-[0.4vmax]">{{ .L.HomePage.BlogSearchDescription }}</p> - <i class="fas fa-map w-full h-full text-[10cqi] content-center text-center text-main-dark"></i> - <p class="p-[0.4vmax]">{{ .L.HomePage.MarkerMapDescription }}</p> - <i class="fas fa-swatchbook w-full h-full text-[10cqi] content-center text-center text-main-dark"></i> - <p class="p-[0.4vmax]">{{ .L.HomePage.ThemeSwitchDescription }}</p> - </div> - </div> - </div> -</div> -{{ end }} diff --git a/views/partials/catalogue-blog-cards.html b/views/partials/catalogue-blog-cards.html index 37b3f57..06d99d5 100644 --- a/views/partials/catalogue-blog-cards.html +++ b/views/partials/catalogue-blog-cards.html @@ -8,17 +8,14 @@ <span class="font-extrabold">{{ .Title }}</span> <span class="font-extrabold select-none text-secondary">//</span> <span class="italic text-secondary">{{ .ActionDate }}</span> + <span class="font-extrabold select-none text-secondary">//</span> + <i class="fas fa-thumbs-up w-[1vmax] h-[1vmax]"></i> + <span class="italic text-secondary">{{ .LikeCount }}</span> </a> + <div hx-trigger="click from:#user_search_bar" hx-get="/endpoint1" hx-target="#users-list"></div> <p class="flex-3/12 grow-0 text-[1vmax] italic text-secondary text-right">{{ .PublishedTime }}</p> </div> <p class="text-[1vmax] text-main-dark">{{ .ShortDescription }}</p> - <p class="text-[1vmax] text-secondary italic"> - <i class="fas fa-thumbs-up w-[1vmax] h-[1vmax]"></i> - <span>{{ .LikeCount }}</span> - <span class="font-extrabold select-none">//</span> - <i class="fas fa-eye w-[1vmax] h-[1vmax]"></i> - <span>{{ .ViewCount }}</span> - </p> <p class="text-[1vmax] text-secondary">{{ $.L.TagsLabel }} {{ template "catalogue-blog-card-tags.html" . }}</p> </div> </div> diff --git a/views/partials/general-page-header.html b/views/partials/general-page-header.html index 6cb87b7..63803bd 100644 --- a/views/partials/general-page-header.html +++ b/views/partials/general-page-header.html @@ -1,8 +1,8 @@ <div {{ if .PublishedDate }} hx-get="/api/v1/tz" hx-vals='js:{timestamp: "{{ .PublishedDate }}", tz: clientTimeZone}' hx-target="#published-date" hx-swap="innerHTML" hx-trigger="load" {{ end }} class="flex flex-row mb-[0.4vmin]"> <i onclick="changeUrl('/../', true);" class="fas fa-circle-left hover:bg-main-dark hover:bg-opacity-10 cursor-pointer transition-colors duration-300 rounded-md text-[2vmax] mt-auto mb-auto px-[0.8vmin]"></i> - <p class="text-[2.5vmin] font-spectral italic font-extrabold select-none mt-auto pl-[0.8vmin] pr-[1.2vmin]">//</p> - <p class="text-[2.5vmin] font-spectral italic text-left mt-auto">{{ .Title }}</p> - <p id="published-date" class="grow text-[1.75vmin] font-spectral text-secondary text-right mt-auto">{{ .PublishedDate }}</p> + <p class="text-[2vmax] font-spectral italic font-extrabold select-none mt-auto pl-[0.8vmin] pr-[1.2vmin]">//</p> + <p class="text-[2vmax] font-spectral italic text-left mt-auto">{{ .Title }}</p> + <p id="published-date" class="grow text-[1.5vmax] font-spectral text-secondary text-right mt-auto">{{ .PublishedDate }}</p> </div> {{ block "header" . }}{{ end }} |