From ea1f83df820baa14dcd904d581ab2bb0976b172d Mon Sep 17 00:00:00 2001 From: SayaAndy Date: Mon, 11 Aug 2025 16:52:48 +0700 Subject: feat: redo blog catalogue with same layout as blog-page feat: implement localization feat: filters and sort in blog catalogue feat: one-page search feat: ram theme refactor: generate blog catalogue and blog page out of one layout refactor: have one layout for both mobile & desktop (aspect ratio dependable) refactor: use own template manager to use templates separately --- config/config.go | 16 ++- config/config.yaml | 17 ++- internal/templatemanager/templatemanager.go | 57 ++++++++ locale/localization.en.yaml | 9 ++ locale/localization.go | 50 +++++++ locale/localization.ru.yaml | 9 ++ main.go | 215 +++++++++++++++++++++++----- static/input.css | 150 ++++++++++++++----- views/layouts/blog-main.html | 147 ------------------- views/layouts/blog-page.html | 194 ------------------------- views/layouts/general-page.html | 82 +++++++++++ views/pages/blog-catalogue.html | 140 ++++++++++++++++++ views/pages/blog-page.html | 70 +++++++++ views/partials/catalogue-blog-cards.html | 16 +++ 14 files changed, 749 insertions(+), 423 deletions(-) create mode 100644 internal/templatemanager/templatemanager.go create mode 100644 locale/localization.en.yaml create mode 100644 locale/localization.go create mode 100644 locale/localization.ru.yaml delete mode 100644 views/layouts/blog-main.html delete mode 100644 views/layouts/blog-page.html create mode 100644 views/layouts/general-page.html create mode 100644 views/pages/blog-catalogue.html create mode 100644 views/pages/blog-page.html create mode 100644 views/partials/catalogue-blog-cards.html diff --git a/config/config.go b/config/config.go index 67c0c4d..8c0321e 100644 --- a/config/config.go +++ b/config/config.go @@ -9,13 +9,14 @@ import ( ) type Config struct { - LogLevel slog.Level `json:"LogLevel" yaml:"logLevel" validate:"required"` - BlogPages BlogPagesConfig `json:"BlogPages" yaml:"blogPages" validate:"required"` + LogLevel slog.Level `json:"LogLevel" yaml:"logLevel" validate:"required"` + BlogPages BlogPagesConfig `json:"BlogPages" yaml:"blogPages" validate:"required"` + LocalePath string `json:"LocalePath" yaml:"localePath" validate:"required,filepath"` + AvailableLanguages []AvailableLanguageConfig `json:"AvailableLanguages" yaml:"availableLanguages" validate:"required"` } type BlogPagesConfig struct { - Storage StorageConfig `json:"Storage" yaml:"storage" validate:"required"` - AvailableLanguages []AvailableLanguageConfig `json:"AvailableLanguages" yaml:"availableLanguages" validate:"required"` + Storage StorageConfig `json:"Storage" yaml:"storage" validate:"required"` } type StorageConfig struct { @@ -32,9 +33,10 @@ type B2Config struct { } type AvailableLanguageConfig struct { - Name string `json:"Name" yaml:"name" validate:"required"` - Alt string `json:"Alt" yaml:"alt"` - Flag string `json:"Flag" yaml:"flag" validate:"url"` + Name string `json:"Name" yaml:"name" validate:"required"` + Alt string `json:"Alt" yaml:"alt"` + Flag string `json:"Flag" yaml:"flag" validate:"url"` + LocFile string `json:"LocFile" yaml:"locFile" validate:"required,filepath"` } func LoadConfig(path string, config *Config) error { diff --git a/config/config.yaml b/config/config.yaml index 908bfe2..dac7b9a 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -8,10 +8,13 @@ blogPages: prefix: '' keyID: '${B2_KEY_ID}' applicationKey: '${B2_APPLICATION_KEY}' - availableLanguages: - - name: ru - alt: Русский - flag: https://f003.backblazeb2.com/file/sayana-static/flags/ru.svg - - name: en - alt: English - flag: https://f003.backblazeb2.com/file/sayana-static/flags/gb.svg +localePath: ./locale/ +availableLanguages: + - name: ru + alt: Русский + flag: https://f003.backblazeb2.com/file/sayana-static/flags/ru.svg + locFile: localization.ru.yaml + - name: en + alt: English + flag: https://f003.backblazeb2.com/file/sayana-static/flags/gb.svg + locFile: localization.en.yaml diff --git a/internal/templatemanager/templatemanager.go b/internal/templatemanager/templatemanager.go new file mode 100644 index 0000000..5effcb3 --- /dev/null +++ b/internal/templatemanager/templatemanager.go @@ -0,0 +1,57 @@ +package templatemanager + +import ( + "bytes" + "fmt" + "html/template" + "path/filepath" + "strings" +) + +type TemplateManager struct { + templates map[string]templateManagerRender +} + +type templateManagerRender struct { + Main string + Tmpl *template.Template +} + +type TemplateManagerTemplates struct { + Name string + Files []string +} + +func NewTemplateManager(templates []TemplateManagerTemplates) (*TemplateManager, error) { + templateMap := make(map[string]templateManagerRender) + + for _, tmplStruct := range templates { + tmpl := template.New("").Funcs(template.FuncMap{ + "contains": strings.Contains, + }) + tmpl, err := tmpl.ParseFiles(tmplStruct.Files...) + if err != nil { + return nil, err + } + templateMap[tmplStruct.Name] = templateManagerRender{ + Main: filepath.Base(tmplStruct.Files[0]), + Tmpl: tmpl, + } + + } + + return &TemplateManager{ + templates: templateMap, + }, nil +} + +func (tm *TemplateManager) Render(name string, data interface{}) ([]byte, error) { + tmpl, exists := tm.templates[name] + if !exists { + return nil, fmt.Errorf("template %s is not found", name) + } + + var buf bytes.Buffer + err := tmpl.Tmpl.ExecuteTemplate(&buf, tmpl.Main, data) + return buf.Bytes(), err +} diff --git a/locale/localization.en.yaml b/locale/localization.en.yaml new file mode 100644 index 0000000..879fce8 --- /dev/null +++ b/locale/localization.en.yaml @@ -0,0 +1,9 @@ +TagsLabel: 'Tags:' +BlogSearch: + Header: 'Blog Search' + TagsHeader: 'Tags' + OrderByHeader: 'Order by...' + TitleOrdered: 'Title' + ActionDateOrdered: 'Action Date' + PublicationDateOrdered: 'Publication Date' + ChooseAllTags: 'Choose All' diff --git a/locale/localization.go b/locale/localization.go new file mode 100644 index 0000000..d5c6b48 --- /dev/null +++ b/locale/localization.go @@ -0,0 +1,50 @@ +package locale + +import ( + "os" + + "github.com/go-playground/validator/v10" + "gopkg.in/yaml.v3" +) + +type LocaleConfig struct { + TagsLabel string `yaml:"TagsLabel" json:"TagsLabel"` + BlogSearch BlogSearchConfig `yaml:"BlogSearch" json:"BlogSearch"` +} + +type BlogSearchConfig struct { + Header string `yaml:"Header" json:"Header"` + TagsHeader string `yaml:"TagsHeader" json:"TagsHeader"` + OrderByHeader string `yaml:"OrderByHeader" json:"OrderByHeader"` + TitleOrdered string `yaml:"TitleOrdered" json:"TitleOrdered"` + ActionDateOrdered string `yaml:"ActionDateOrdered" json:"ActionDateOrdered"` + PublicationDateOrdered string `yaml:"PublicationDateOrdered" json:"PublicationDateOrdered"` + ChooseAllTags string `yaml:"ChooseAllTags" json:"ChooseAllTags"` +} + +func LoadConfig(path string, config *LocaleConfig) error { + fileBytes, err := os.ReadFile(path) + if err != nil { + return err + } + + if err = yaml.Unmarshal(fileBytes, config); err != nil { + return err + } + + return nil +} + +func InitConfig(path string) (*LocaleConfig, error) { + config := &LocaleConfig{} + if err := LoadConfig(path, config); err != nil { + return nil, err + } + + validate := validator.New(validator.WithRequiredStructEnabled()) + if err := validate.Struct(config); err != nil { + return nil, err + } + + return config, nil +} diff --git a/locale/localization.ru.yaml b/locale/localization.ru.yaml new file mode 100644 index 0000000..786665d --- /dev/null +++ b/locale/localization.ru.yaml @@ -0,0 +1,9 @@ +TagsLabel: 'Тэги:' +BlogSearch: + Header: 'Поиск по блогу' + TagsHeader: 'Тэги' + OrderByHeader: 'Упорядочить по...' + TitleOrdered: 'названию' + ActionDateOrdered: 'дате действия' + PublicationDateOrdered: 'времени публикации' + ChooseAllTags: 'Выбрать все' diff --git a/main.go b/main.go index 303fadd..6a36f59 100644 --- a/main.go +++ b/main.go @@ -7,19 +7,23 @@ import ( "html/template" "log" "log/slog" + "net/url" "os" + "regexp" "slices" "strconv" "strings" + "time" "github.com/SayaAndy/saya-today-web/config" "github.com/SayaAndy/saya-today-web/internal/b2" "github.com/SayaAndy/saya-today-web/internal/frontmatter" "github.com/SayaAndy/saya-today-web/internal/lightgallery" "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/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/redirect" - "github.com/gofiber/template/html/v2" "github.com/yuin/goldmark" "github.com/yuin/goldmark/parser" gmhtml "github.com/yuin/goldmark/renderer/html" @@ -41,6 +45,7 @@ var ( b2Client *b2.B2Client configPath = flag.String("c", "config.yaml", "Path to the configuration file (in YAML format)") availableLanguages = make([]string, 0) + localization = make(map[string]*locale.LocaleConfig) ) func main() { @@ -63,15 +68,28 @@ func main() { os.Exit(1) } - for _, lang := range cfg.BlogPages.AvailableLanguages { + for _, lang := range cfg.AvailableLanguages { + localeCfg, err := locale.InitConfig(cfg.LocalePath + lang.LocFile) + if err != nil { + slog.Warn("fail to initialize a locale", slog.String("locale", lang.Name), slog.String("error", err.Error())) + continue + } availableLanguages = append(availableLanguages, lang.Name) + localization[lang.Name] = localeCfg } - engine := html.New("./views", ".html") - - app := fiber.New(fiber.Config{ - Views: engine, + tm, err := templatemanager.NewTemplateManager([]templatemanager.TemplateManagerTemplates{ + {Name: "blog-page", Files: []string{"views/layouts/general-page.html", "views/pages/blog-page.html"}}, + {Name: "blog-catalogue", Files: []string{"views/layouts/general-page.html", "views/pages/blog-catalogue.html"}}, + {Name: "index", Files: []string{"views/index.html"}}, + {Name: "catalogue-blog-cards", Files: []string{"views/partials/catalogue-blog-cards.html"}}, }) + if err != nil { + slog.Error("fail to initialize template manager", slog.String("error", err.Error())) + os.Exit(1) + } + + app := fiber.New(fiber.Config{}) app.Use(redirect.New(redirect.Config{ Rules: map[string]string{ @@ -81,9 +99,16 @@ func main() { })) app.Get("/", func(c *fiber.Ctx) error { - return c.Status(fiber.StatusOK).Render("index", fiber.Map{ - "AvailableLanguages": cfg.BlogPages.AvailableLanguages, + content, err := tm.Render("index", fiber.Map{ + "AvailableLanguages": cfg.AvailableLanguages, }) + if err != nil { + slog.Warn("failed to generate page", slog.String("page", "/"), slog.String("error", err.Error())) + c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) + return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate page") + } + + return c.Type("html").Send(content) }) app.Get("/blog/:lang", func(c *fiber.Ctx) error { @@ -93,6 +118,26 @@ func main() { return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language... yet??", lang)) } + querySort := c.Query("sort") + if querySort == "" { + querySort = "titleAsc" + } + + encodedQuery := c.Request().URI().QueryString() + re, err := regexp.Compile(`tags\[\]=([\w]+)`) + if err != nil { + slog.Warn("failed to generate regex for tags gathering", slog.String("error", err.Error())) + c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) + return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate regex for tags gathering") + } + decodedQuery, err := url.QueryUnescape(string(encodedQuery)) + matches := re.FindAllStringSubmatch(decodedQuery, -1) + + queryTags := make([]string, 0, len(matches)) + for _, match := range matches { + queryTags = append(queryTags, string(match[1])) + } + pages, err := b2Client.Scan(lang + "/") status := fiber.StatusOK if err != nil { @@ -100,24 +145,36 @@ func main() { pages = []*b2.BlogPage{} } - pageMeta := make([]map[string]string, 0, len(pages)) + tagsSet := make(map[string]struct{}) for _, page := range pages { slog.Debug("enlist page for catalogue", slog.Any("page", page), slog.String("endpoint", "/")) - pageMeta = append(pageMeta, map[string]string{ - "Link": page.Link, - "Title": page.Metadata.Title, - "PublishedTime": page.Metadata.PublishedTime.Format("2006-01-02 15:04:05 -0700"), - "ActionDate": page.Metadata.ActionDate, - "ShortDescription": page.Metadata.ShortDescription, - "Thumbnail": page.Metadata.Thumbnail, - "Tags": strings.Join(page.Metadata.Tags, ", "), - }) - } - - return c.Status(status).Render("layouts/blog-main", fiber.Map{ + for _, tag := range page.Metadata.Tags { + tagsSet[tag] = struct{}{} + } + } + + tagsArray := make([]string, 0, len(tagsSet)) + for tag := range tagsSet { + tagsArray = append(tagsArray, tag) + } + slices.Sort(tagsArray) + + content, err := tm.Render("blog-catalogue", fiber.Map{ + "QuerySort": querySort, + "QueryTags": strings.Join(queryTags, ","), + "Tags": tagsArray, + "Lang": lang, + "L": localization[lang], "PublishedYear": "2025", - "BlogPages": pageMeta, + "Title": localization[lang].BlogSearch.Header, }) + if err != nil { + slog.Warn("failed to generate page", slog.String("page", "/blog/"+lang), slog.String("error", err.Error())) + c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) + return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate page") + } + + return c.Type("html").Status(status).Send(content) }) app.Get("/blog/:lang/:title", func(c *fiber.Ctx) error { @@ -127,7 +184,7 @@ func main() { return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language... yet??", lang)) } - metadata, parsedMarkdownDesktop, parsedMarkdownMobile, err := readBlogPost(lang + "/" + c.Params("title")) + metadata, parsedMarkdown, err := readBlogPost(lang + "/" + c.Params("title")) if err != nil { c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("failed to find '%s' post", c.Params("title"))) @@ -143,16 +200,106 @@ func main() { areaError = geolocationParts[2] } - return c.Render("layouts/blog-page", fiber.Map{ + content, err := tm.Render("blog-page", fiber.Map{ "Title": metadata.Title, "PublishedDate": metadata.PublishedTime.Format("2006-01-02 15:04:05 -07:00"), "PublishedYear": strconv.Itoa(metadata.PublishedTime.Year()), - "ParsedMarkdownDesktop": template.HTML(parsedMarkdownDesktop), - "ParsedMarkdownMobile": template.HTML(parsedMarkdownMobile), + "ParsedMarkdown": template.HTML(parsedMarkdown), "MapLocationX": x, "MapLocationY": y, "MapLocationAreaMeters": areaError, + "Lang": lang, + }) + if err != nil { + slog.Warn("failed to generate page", slog.String("page", "/blog/"+lang+c.Params("title")), slog.String("error", err.Error())) + c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) + return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate page") + } + + return c.Type("html").Send(content) + }) + + app.Get("/api/v1/blog-search", func(c *fiber.Ctx) error { + sort := c.Query("sort") + lang := c.Query("lang") + + if !slices.Contains(availableLanguages, lang) { + c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) + return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language", lang)) + } + + pages, err := b2Client.Scan(lang + "/") + if err != nil { + c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) + return c.Status(fiber.ErrInternalServerError.Code).SendString(fmt.Sprintf("failed to scan pages for '%s' lang: %v", lang, err)) + } + + encodedQuery := c.Request().URI().QueryString() + re, err := regexp.Compile(`tags\[\]=([\w]+)`) + if err != nil { + slog.Warn("failed to generate regex for tags gathering", slog.String("error", err.Error())) + c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) + return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate regex for tags gathering") + } + decodedQuery, err := url.QueryUnescape(string(encodedQuery)) + matches := re.FindAllStringSubmatch(decodedQuery, -1) + + tags := make([]string, 0, len(matches)) + for _, match := range matches { + tags = append(tags, string(match[1])) + } + + pageMeta := make([]map[string]string, 0, len(pages)) + for _, page := range pages { + for _, tag := range page.Metadata.Tags { + if len(tags) == 0 || slices.Contains(tags, tag) { + pageMeta = append(pageMeta, map[string]string{ + "Link": page.Link, + "Title": page.Metadata.Title, + "PublishedTime": page.Metadata.PublishedTime.Format("2006-01-02 15:04:05 -0700"), + "ActionDate": page.Metadata.ActionDate, + "ShortDescription": page.Metadata.ShortDescription, + "Thumbnail": page.Metadata.Thumbnail, + "Tags": strings.Join(page.Metadata.Tags, ", "), + }) + break + } + } + } + + slices.SortFunc(pageMeta, func(a, b map[string]string) int { + switch sort { + case "titleAsc": + return strings.Compare(a["Title"], b["Title"]) + case "titleDesc": + return strings.Compare(b["Title"], a["Title"]) + case "actionDateAsc": + return strings.Compare(a["ActionDate"], b["ActionDate"]) + case "actionDateDesc": + return strings.Compare(b["ActionDate"], a["ActionDate"]) + case "publicationDateAsc": + publishedTimeA, _ := time.Parse(time.RFC3339, a["PublishedTime"]) + publishedTimeB, _ := time.Parse(time.RFC3339, b["PublishedTime"]) + return publishedTimeA.Compare(publishedTimeB) + case "publicationDateDesc": + publishedTimeA, _ := time.Parse(time.RFC3339, a["PublishedTime"]) + publishedTimeB, _ := time.Parse(time.RFC3339, b["PublishedTime"]) + return publishedTimeB.Compare(publishedTimeA) + } + return 0 }) + + content, err := tm.Render("catalogue-blog-cards", fiber.Map{ + "BlogPages": pageMeta, + "L": localization[lang], + }) + if err != nil { + slog.Warn("failed to generate div", slog.String("page", "/blog/"+lang), slog.String("error", err.Error())) + c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) + return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div") + } + + return c.Type("html").Send(content) }) app.Static("/", "./static") @@ -160,20 +307,16 @@ func main() { log.Fatal(app.Listen(":3000")) } -func readBlogPost(sourceName string) (metadata *frontmatter.Metadata, desktopBody string, mobileBody string, err error) { +func readBlogPost(sourceName string) (metadata *frontmatter.Metadata, html string, err error) { metadata, markdown, err := b2Client.ReadFrontmatter(sourceName + ".md") if err != nil { - return nil, "", "", fmt.Errorf("failed to read a frontmatter file: %w", err) + return nil, "", fmt.Errorf("failed to read a frontmatter file: %w", err) } - var bufDesktop bytes.Buffer - if err := md.Convert(markdown, &bufDesktop); err != nil { - return nil, "", "", fmt.Errorf("convert source context from md to html (desktop version): %w", err) - } - var bufMobile bytes.Buffer - if err := md.Convert(markdown, &bufMobile); err != nil { - return nil, "", "", fmt.Errorf("convert source context from md to html (mobile version): %w", err) + var buf bytes.Buffer + if err := md.Convert(markdown, &buf); err != nil { + return nil, "", fmt.Errorf("convert source context from md to html: %w", err) } - return metadata, bufDesktop.String(), bufMobile.String(), nil + return metadata, buf.String(), nil } diff --git a/static/input.css b/static/input.css index edecc19..8776b25 100644 --- a/static/input.css +++ b/static/input.css @@ -30,7 +30,8 @@ --interlocked-hexagons-background-size: 10vh; --squares-and-triangles-background: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22%234a5139%22%2F%3E%3Cpath%20fill%3D%22%23b2b285%22%20d%3D%22M0%200v6.77L6.72%200zm8.91%200L0%208.96v3.86L12.83%200zm6.11%200L0%2015.06v3.87L18.9%200zm6.11%200L0%2021.12v3.92L25%200zM25%200l25%2024.99v-3.87L28.87%200zm25%2024.99%2025%2024.98L100%2025%2075%200zm-50%20.05v3.87l21.13%2021.12H25l-25-25zm25%2024.99h3.87L50%2028.9v-3.87zm0%200L0%2075l25%2025%2025-24.99zM50%2075l25-24.98h-3.87L50%2071.14v3.87zm0%200v3.87L71.13%20100H75L50%2075.01zm25%2025h3.87L100%2078.88v-3.87zm25-24.99v-3.87L78.87%2050.03H75L100%2075zM31.1%200%2050%2018.93v-3.87L34.98%200h-3.87zm6.07%200L50%2012.82V8.96L41.04%200zm6.1%200L50%206.77V0h-6.72zM25%202.19%202.14%2025.04H25zm0%2022.85v22.8l22.81-22.8zM75%203.92l21.13%2021.12L75%2046.16%2053.87%2025.04zm0%202.19L56.06%2025.04l18.89%2018.88%2018.94-18.88L75%206.1zm0%203.92%2015.07%2015L75%2040.06%2059.98%2025.04zm0%202.18L62.17%2025.04%2075%2037.86l12.83-12.82L75%2012.2zm0%203.82%208.96%209L75%2033.95%2066.04%2025zm0%202.24-6.72%206.77L75%2031.76l6.77-6.72h-.05zM0%2031.09v3.87l15.02%2015.07h3.87zm50%200L31.1%2050.03h3.88L50%2034.96V31.1zM0%2037.2v3.87l8.91%208.96h3.92zm50%200L37.17%2050.03h3.87L50%2041.07zM0%2043.3v6.73h6.72zm50%200-6.72%206.73H50zm0%206.73v6.71l6.72-6.71zm8.96%200L50%2058.93v3.92l12.83-12.82zm6.06%200L50%2065.03v3.88l18.94-18.88zm16.09%200L100%2068.9v-3.87l-15.02-15zm6.11%200L100%2062.85v-3.92l-8.91-8.9zm6.06%200%206.72%206.71v-6.71zM75%2052.2%2052.19%2075H75zM75%2075v22.8L97.81%2075zM25%2053.9l21.13%2021.12L25%2096.13%203.87%2075.01zm0%202.2L6.1%2074.95l18.95%2018.88%2018.9-18.88L25%2056.08zm0%203.9%2015.02%2015.01L25%2090.03%209.98%2075zm0%202.19L12.17%2075%2025%2087.84%2037.83%2075%2025%2062.2zm0%203.87L33.96%2075%2025%2083.97%2016.04%2075zm0%202.23-6.72%206.72L25%2081.73l6.72-6.72L25%2068.3zm25%2012.83v3.87L65.02%20100h3.87zm50%200L81.1%20100h3.88L100%2084.99zm-50%206.06v3.86l8.96%208.96h3.87zm50%200L87.22%20100h3.87l8.91-8.96zm-50%206.1V100h6.72zm50%200L93.28%20100H100z%22%2F%3E%3C%2Fsvg%3E"); --squares-and-triangles-background-repeat: repeat; - --squares-and-triangles-background-size: 10; + --squares-and-triangles-background-width-coef: 10; + --squares-and-triangles-background-height-coef: 10; } [data-theme="lettuce"] { @@ -47,7 +48,8 @@ --interlocked-hexagons-background-size: 10vh; --squares-and-triangles-background: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22%23bbd095%22%2F%3E%3Cpath%20fill%3D%22%23f5ede1%22%20d%3D%22M0%200v6.77L6.72%200zm8.91%200L0%208.96v3.86L12.83%200zm6.11%200L0%2015.06v3.87L18.9%200zm6.11%200L0%2021.12v3.92L25%200zM25%200l25%2024.99v-3.87L28.87%200zm25%2024.99%2025%2024.98L100%2025%2075%200zm-50%20.05v3.87l21.13%2021.12H25l-25-25zm25%2024.99h3.87L50%2028.9v-3.87zm0%200L0%2075l25%2025%2025-24.99zM50%2075l25-24.98h-3.87L50%2071.14v3.87zm0%200v3.87L71.13%20100H75L50%2075.01zm25%2025h3.87L100%2078.88v-3.87zm25-24.99v-3.87L78.87%2050.03H75L100%2075zM31.1%200%2050%2018.93v-3.87L34.98%200h-3.87zm6.07%200L50%2012.82V8.96L41.04%200zm6.1%200L50%206.77V0h-6.72zM25%202.19%202.14%2025.04H25zm0%2022.85v22.8l22.81-22.8zM75%203.92l21.13%2021.12L75%2046.16%2053.87%2025.04zm0%202.19L56.06%2025.04l18.89%2018.88%2018.94-18.88L75%206.1zm0%203.92%2015.07%2015L75%2040.06%2059.98%2025.04zm0%202.18L62.17%2025.04%2075%2037.86l12.83-12.82L75%2012.2zm0%203.82%208.96%209L75%2033.95%2066.04%2025zm0%202.24-6.72%206.77L75%2031.76l6.77-6.72h-.05zM0%2031.09v3.87l15.02%2015.07h3.87zm50%200L31.1%2050.03h3.88L50%2034.96V31.1zM0%2037.2v3.87l8.91%208.96h3.92zm50%200L37.17%2050.03h3.87L50%2041.07zM0%2043.3v6.73h6.72zm50%200-6.72%206.73H50zm0%206.73v6.71l6.72-6.71zm8.96%200L50%2058.93v3.92l12.83-12.82zm6.06%200L50%2065.03v3.88l18.94-18.88zm16.09%200L100%2068.9v-3.87l-15.02-15zm6.11%200L100%2062.85v-3.92l-8.91-8.9zm6.06%200%206.72%206.71v-6.71zM75%2052.2%2052.19%2075H75zM75%2075v22.8L97.81%2075zM25%2053.9l21.13%2021.12L25%2096.13%203.87%2075.01zm0%202.2L6.1%2074.95l18.95%2018.88%2018.9-18.88L25%2056.08zm0%203.9%2015.02%2015.01L25%2090.03%209.98%2075zm0%202.19L12.17%2075%2025%2087.84%2037.83%2075%2025%2062.2zm0%203.87L33.96%2075%2025%2083.97%2016.04%2075zm0%202.23-6.72%206.72L25%2081.73l6.72-6.72L25%2068.3zm25%2012.83v3.87L65.02%20100h3.87zm50%200L81.1%20100h3.88L100%2084.99zm-50%206.06v3.86l8.96%208.96h3.87zm50%200L87.22%20100h3.87l8.91-8.96zm-50%206.1V100h6.72zm50%200L93.28%20100H100z%22%2F%3E%3C%2Fsvg%3E"); --squares-and-triangles-background-repeat: repeat; - --squares-and-triangles-background-size: 10; + --squares-and-triangles-background-width-coef: 10; + --squares-and-triangles-background-height-coef: 10; } [data-theme="night"] { @@ -64,7 +66,26 @@ --interlocked-hexagons-background-size: 100vw 100vh; --squares-and-triangles-background: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2260%22%20height%3D%2260%22%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22%2313131b%22%2F%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%23363645%22%20stroke-width%3D%22.5%22%20d%3D%22M10%2050H0m60-40H50m0%2040v10M10%200v10m21%2030v20m0-60v20m9%2010h20M0%2030h20m0%2010h20V20H20v10zM10%2010h40v40H10zM0%200h60v60H0z%22%2F%3E%3C%2Fsvg%3E"); --squares-and-triangles-background-repeat: repeat; - --squares-and-triangles-background-size: 5; + --squares-and-triangles-background-width-coef: 5; + --squares-and-triangles-background-height-coef: 5; +} + +[data-theme="ram"] { + --main-dark-color: #fefad8; + --main-medium-color: #f6e4c5; + --main-light-color: #e8d4b8; + --background-dark-color: #413b34; + --background-light-color: #6d6258; + --sidebar-text-color: var(--main-dark-color); + --sidebar-stroke-color: var(--background-light-color); + --secondary-color: var(--main-light-color); + --interlocked-hexagons-background: url('https://f003.backblazeb2.com/file/sayana-static/themes/ram/ram-background.webp'); + --interlocked-hexagons-background-repeat: repeat; + --interlocked-hexagons-background-size: auto; + --squares-and-triangles-background: url('https://f003.backblazeb2.com/file/sayana-static/themes/ram/ram-sidebar.webp'); + --squares-and-triangles-background-repeat: repeat; + --squares-and-triangles-background-width-coef: 5; + --squares-and-triangles-background-height-coef: 15; } .bg-interlocked-hexagons { @@ -127,6 +148,12 @@ background-size: calc(var(--squares-and-triangles-background-size) * 1vw); } +.bg-sidebar { + background-image: var(--squares-and-triangles-background); + background-repeat: var(--squares-and-triangles-background-repeat); + background-size: calc(var(--squares-and-triangles-background-width-coef) * 1vw) calc(var(--squares-and-triangles-background-height-coef) * 1vw); +} + body:has(.lg-container:not([class~="lg-inline"])) .desktop-sidebar-custom { display: none !important; } @@ -165,10 +192,6 @@ body:has(.lg-container:not([class~="lg-inline"])) .mobile-topbar-custom { font-size: 4vw; } -.content-square { - width: 100vh; -} - .border-left-custom { width: 0.5vw; border-right: 0.3vh dashed var(--main-dark-color); @@ -186,12 +209,12 @@ body:has(.lg-container:not([class~="lg-inline"])) .mobile-topbar-custom { inset 0vh -0.5vh rgba(75, 81, 58, 0.5); } -.desktop { - display: flex; +.text-stroke-\(--sidebar-stroke-color\) { + -webkit-text-stroke-color: var(--sidebar-stroke-color); } -.mobile { - display: none; +.text-stroke-\[0\.1vw\] { + -webkit-text-stroke-width: 0.1vw; } .lg-backdrop { @@ -313,25 +336,43 @@ body:has(.lg-container:not([class~="lg-inline"])) .mobile-topbar-custom { } } -/* Mobile responsive styles */ -@media (max-aspect-ratio: 1.1/1) { - .desktop { - display: none; +@media (min-aspect-ratio: 0.8/1) { + .ar-gt-0\.8\:mr-\[1\.2vmin\] { + margin-right: 1.2vmin; } - .mobile { - display: flex; + .ar-gt-0\.8\:w-fit { + width: fit-content; } - .inline-gallery-container { - width: 100%; + .ar-gt-0\.8\:min-w-fit { + min-width: fit-content; } - .desktop-sidebar-custom { - width: 90%; - margin-left: 5vw; - height: 10vh; - background-size: 20vh; + .ar-gt-0\.8\:min-w-\[10vh\] { + min-width: 10vh; + } + + .ar-gt-0\.8\:max-w-\[20vh\] { + max-width: 20vh; + } + + .ar-gt-0\.8\:max-w-\[100vh\] { + max-width: 100vh; + } + + .ar-gt-0\.8\:items-center { + align-items: center; + } +} + +@media (max-aspect-ratio: 0.8/1) { + .bg-sidebar { + background-size: calc(var(--squares-and-triangles-background-width-coef) * 1.6vh) calc(var(--squares-and-triangles-background-height-coef) * 1.6vh); + } + + .inline-gallery-container { + width: 100%; } .logo-custom { @@ -355,20 +396,65 @@ body:has(.lg-container:not([class~="lg-inline"])) .mobile-topbar-custom { background-size: 10vh; } - .border-left-custom { - width: 1%; - border-right: 0.3vw dashed var(--main-dark-color); + .ar-lt-0\.8\:grid { + display: grid; } - .border-right-custom { - width: 1.5%; - border-left: 0.5vw dotted var(--main-dark-color); + .ar-lt-0\.8\:flex-col { + flex-direction: column; + } + + .ar-lt-0\.8\:flex-row { + flex-direction: row; + } + + .ar-lt-0\.8\:h-\[8vh\] { + height: 8vh; + } + + .ar-lt-0\.8\:h-\[10vh\] { + height: 10vh; + } + + .ar-lt-0\.8\:h-\[90vh\] { + height: 90vh; + } + + .ar-lt-0\.8\:w-\[8vh\] { + width: 8vh; + } + + .ar-lt-0\.8\:w-\[90vw\] { + width: 90vw; + } + + .ar-lt-0\.8\:w-screen { + width: 100vw; + } + + .ar-lt-0\.8\:max-h-\[80\%\] { + max-height: 80%; + } + + .ar-lt-0\.8\:text-\[5vh\] { + font-size: 5vh; + } + + .ar-lt-0\.8\:ml-\[5vw\] { + margin-left: 5vw; + } + + .ar-lt-0\.8\:hidden { + display: none; } } @media (max-aspect-ratio: 0.4/1) { - .desktop-sidebar-custom { - width: 100%; + .ar-lt-0\.4\:w-\[100vw\] { + width: 100vw; + } + + .ar-lt-0\.4\:ml-0 { margin-left: 0; } } diff --git a/views/layouts/blog-main.html b/views/layouts/blog-main.html deleted file mode 100644 index 1b8e149..0000000 --- a/views/layouts/blog-main.html +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - SAYA TODAY // Заглавная - - - - - - - - - - -
*
-
*
-
*
-
*
-
*
-
*
-
*
-
*
-
*
-
*
-
*
-
*
-
*
-
*
-
*
-
*
-
*
-
*
-
*
-
*
-
*
-
*
-
*
-
*
-
*
- - - - - - - - diff --git a/views/layouts/blog-page.html b/views/layouts/blog-page.html deleted file mode 100644 index 46fe9f4..0000000 --- a/views/layouts/blog-page.html +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - SAYA TODAY // {{ .Title }} - - - - -{{- if .MapLocationX }} - -{{- end }} - - - - - - - - - - - - -
-
- saya.today -
-
- - - - -
-
- -
-
- -
-
- -

//

-

{{ .Title }}

-

{{ .PublishedDate }}

-
-
- -
- {{ .ParsedMarkdownDesktop }} - {{- if .MapLocationX }} -
-
-
- {{- end }} -
- -
-

- {{ .Title }} © {{ .PublishedYear }} by Saya Andy is licensed under CC BY-SA 4.0 -

- - - -
- -
-
- -
-
- -
-
- - - - -
-
- demo.saya.today -
-
- -
-
- -
-
- -

//

-

{{ .Title }}

-

{{ .PublishedDate }}

-
-
- -
- {{ .ParsedMarkdownMobile }} - {{- if .MapLocationX }} -
-
-
- {{- end }} -
- -
-

- {{ .Title }} © {{ .PublishedYear }} by Saya Andy is licensed under CC BY-SA 4.0 -

- - - -
- -
-
- -
-
- - - - -{{- if .MapLocationX }} - - - -{{- end }} - - - diff --git a/views/layouts/general-page.html b/views/layouts/general-page.html new file mode 100644 index 0000000..d33aa76 --- /dev/null +++ b/views/layouts/general-page.html @@ -0,0 +1,82 @@ + + + + + + SAYA TODAY // {{ .Title }} + + + + + {{ block "header" . }}{{ end }} + + + + + + + +
+
+ saya.today +
+
+ + + + +
+
+ +
+
+ +
+
+ +

//

+

{{ .Title }}

+ {{- if .PublishedDate }} +

{{ .PublishedDate }}

+ {{- end }} +
+
+ + {{ block "body" . }}{{ end }} + +
+

+ All the photos on saya.today are licensed under CC BY-SA 4.0 by Saya Andy © {{ .PublishedYear }} +

+ + + +
+ +
+
+ +
+
+ + {{ block "footer" . }}{{ end }} + + + diff --git a/views/pages/blog-catalogue.html b/views/pages/blog-catalogue.html new file mode 100644 index 0000000..6fe1c82 --- /dev/null +++ b/views/pages/blog-catalogue.html @@ -0,0 +1,140 @@ +{{ define "body" }} +
+
+
+ {{ .L.BlogSearch.TagsHeader }} +
+
+ +
+ {{- range .Tags }} +
+ +
+ {{- end }} +
+
+
+ {{ .L.BlogSearch.OrderByHeader }} +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+ +
+
+ +
+ +
+ Loading... +
+
+{{ end }} + +{{ define "footer" }} + + + + + + +{{ end }} diff --git a/views/pages/blog-page.html b/views/pages/blog-page.html new file mode 100644 index 0000000..5f001d6 --- /dev/null +++ b/views/pages/blog-page.html @@ -0,0 +1,70 @@ +{{ define "header" }} + + + +{{- if .MapLocationX }} + +{{- end }} + + +{{ end }} + +{{ define "body" }} +
+ {{ .ParsedMarkdown }} +{{- if .MapLocationX }} +
+
+
+{{- end }} +
+{{ end }} + +{{ define "footer" }} + + + +{{- if .MapLocationX }} + + + +{{- end }} +{{ end }} \ No newline at end of file diff --git a/views/partials/catalogue-blog-cards.html b/views/partials/catalogue-blog-cards.html new file mode 100644 index 0000000..7cb48f0 --- /dev/null +++ b/views/partials/catalogue-blog-cards.html @@ -0,0 +1,16 @@ +{{- range .BlogPages }} +
+
+ +
+
+ {{ .Title }} + // + {{ .ActionDate }} +

{{ .PublishedTime }}

+
+

{{ .ShortDescription }}

+

{{ .L.TagsLabel }} {{ .Tags }}

+
+
+{{- end }} \ No newline at end of file -- cgit v1.3.1+13