| -rw-r--r-- | .github/workflows/build-and-deploy.yml | 37 | ||||
| -rw-r--r-- | .gitignore | 4 | ||||
| -rw-r--r-- | Dockerfile | 1 | ||||
| -rw-r--r-- | config/config.go | 16 | ||||
| -rw-r--r-- | config/config.yaml | 17 | ||||
| -rw-r--r-- | deploy/inventory.yml | 28 | ||||
| -rw-r--r-- | deploy/playbook.yml | 29 | ||||
| -rw-r--r-- | internal/b2/client.go | 12 | ||||
| -rw-r--r-- | internal/templatemanager/templatemanager.go | 57 | ||||
| -rw-r--r-- | locale/localization.en.yaml | 9 | ||||
| -rw-r--r-- | locale/localization.go | 50 | ||||
| -rw-r--r-- | locale/localization.ru.yaml | 9 | ||||
| -rw-r--r-- | main.go | 252 | ||||
| -rw-r--r-- | static/input.css | 158 | ||||
| -rw-r--r-- | views/index.html | 60 | ||||
| -rw-r--r-- | views/layouts/blog-page.html | 194 | ||||
| -rw-r--r-- | views/layouts/general-page.html | 82 | ||||
| -rw-r--r-- | views/pages/blog-catalogue.html | 142 | ||||
| -rw-r--r-- | views/pages/blog-page.html | 70 | ||||
| -rw-r--r-- | views/partials/catalogue-blog-cards.html | 18 |
20 files changed, 351 insertions, 894 deletions
diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index ac5fa1f..57cfc96 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -8,8 +8,6 @@ jobs: if: github.repository == 'SayaAndy/saya-today-web' runs-on: ubuntu-latest environment: Production - permissions: - packages: write steps: - name: Checkout uses: actions/checkout@v4 @@ -20,17 +18,17 @@ jobs: output: static/output.css params: "--minify" - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v1 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v1 - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@v1 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GHCR_TOKEN }} - name: Build and push - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v2 with: context: . file: ./Dockerfile @@ -47,31 +45,22 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - - name: Set up SSH connection for pl.saya.today - run: | - mkdir -p ~/.ssh - (cat <<EOF - ${{ secrets.SVC_GITHUB_PK }} - EOF - ) > ~/.ssh/id_ed25519 - chmod 600 ~/.ssh/id_ed25519 - sed -i 's/\r$//' ~/.ssh/id_ed25519 - ssh-keyscan -H pl.saya.today >> ~/.ssh/known_hosts - - - name: Test SSH connection + - name: Set up SSH private key run: | - ssh -o ConnectTimeout=10 -i ~/.ssh/id_ed25519 svc_github@pl.saya.today "echo 'SSH connection successful'" - + echo "${{ secrets.SVC_GITHUB_PK }}" > deploy/private_key.pem + chmod 600 deploy/private_key.pem - name: Install Ansible shell: bash run: | sudo apt update sudo apt install -y ansible - - name: Run Ansible playbook env: ANSIBLE_HOST_KEY_CHECKING: False working-directory: ./deploy - run: | - ansible-playbook -i inventory.yml -l stage playbook.yml --private-key ~/.ssh/id_ed25519 -u svc_github -e saya_today_web_b2_key_id=${{ secrets.B2_KEY_ID }} -e saya_today_web_b2_application_key=${{ secrets.B2_APPLICATION_KEY }} -e saya_today_web_tag=${{ github.ref_name }} + run: > + ansible-playbook -i inventory.yml -l stage playbook.yml + --private-key private_key.pem + -e saya_today_web.b2.key_id=${{ secrets.B2_KEY_ID }} + -e saya_today_web.b2.application_key=${{ secrets.B2_APPLICATION_KEY }} + -e saya_today_web.tag=${{ github.ref_name }} @@ -36,9 +36,7 @@ config*.json /main /sayana-demo -/static/output.css +/static/input.css /.vscode .envrc - -/.ansible @@ -13,7 +13,6 @@ WORKDIR /app COPY --from=build-stage /builddir/sayana-demo /app/sayana-demo COPY --from=build-stage /builddir/static /app/static COPY --from=build-stage /builddir/views /app/views -COPY --from=build-stage /builddir/locale/*.yaml /app/locale/ COPY --from=build-stage /builddir/config/config.yaml /app/config.yaml ENTRYPOINT [ "/app/sayana-demo", "-c", "/app/config.yaml" ] diff --git a/config/config.go b/config/config.go index 8c0321e..67c0c4d 100644 --- a/config/config.go +++ b/config/config.go @@ -9,14 +9,13 @@ import ( ) type Config struct { - 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"` + LogLevel slog.Level `json:"LogLevel" yaml:"logLevel" validate:"required"` + BlogPages BlogPagesConfig `json:"BlogPages" yaml:"blogPages" validate:"required"` } type BlogPagesConfig struct { - Storage StorageConfig `json:"Storage" yaml:"storage" validate:"required"` + Storage StorageConfig `json:"Storage" yaml:"storage" validate:"required"` + AvailableLanguages []AvailableLanguageConfig `json:"AvailableLanguages" yaml:"availableLanguages" validate:"required"` } type StorageConfig struct { @@ -33,10 +32,9 @@ 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"` - LocFile string `json:"LocFile" yaml:"locFile" validate:"required,filepath"` + Name string `json:"Name" yaml:"name" validate:"required"` + Alt string `json:"Alt" yaml:"alt"` + Flag string `json:"Flag" yaml:"flag" validate:"url"` } func LoadConfig(path string, config *Config) error { diff --git a/config/config.yaml b/config/config.yaml index dac7b9a..908bfe2 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -8,13 +8,10 @@ blogPages: prefix: '' keyID: '${B2_KEY_ID}' applicationKey: '${B2_APPLICATION_KEY}' -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 + 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 diff --git a/deploy/inventory.yml b/deploy/inventory.yml index 3c1220d..d3c325f 100644 --- a/deploy/inventory.yml +++ b/deploy/inventory.yml @@ -1,15 +1,15 @@ -all: - children: - prod: - hosts: - pl.saya.today: - saya_today_web_name: sayana-web - saya_today_web_hostname: sayana-web - saya_today_web_ipv4_address: 172.16.0.18 +prod: + hosts: + pl.saya.today: + saya_today_web: + name: sayana-web + hostname: sayana-web + ipv4_address: 172.16.0.18 - stage: - hosts: - pl.saya.today: - saya_today_web_name: sayana-demo - saya_today_web_hostname: sayana-demo - saya_today_web_ipv4_address: 172.16.0.17 +stage: + hosts: + pl.saya.today: + saya_today_web: + name: sayana-demo + hostname: sayana-demo + ipv4_address: 172.16.0.17 diff --git a/deploy/playbook.yml b/deploy/playbook.yml index 773bb61..571ed2b 100644 --- a/deploy/playbook.yml +++ b/deploy/playbook.yml @@ -1,21 +1,20 @@ --- - name: Deploy saya-today-web Docker Container - hosts: all remote_user: svc_github tasks: - - name: Deploy saya-today-web Docker Container - community.docker.docker_container: - name: "{{ saya_today_web_name }}" - hostname: "{{ saya_today_web_hostname }}" - image: "ghcr.io/sayaandy/saya-today-web:{{ saya_today_web_tag }}" - env: - B2_KEY_ID: "{{ saya_today_web_b2_key_id }}" - B2_APPLICATION_KEY: "{{ saya_today_web_b2_application_key }}" - network_mode: caddy - networks: - - name: caddy - ipv4_address: "{{ saya_today_web_ipv4_address }}" - restart_policy: unless-stopped - no_log: true + - name: Deploy saya-today-web Docker Container + community.docker.docker_container: + name: "{{ saya_today_web.name }}" + hostname: "{{ saya_today_web.hostname }}" + image: "ghcr.io/sayaandy/saya-today-web:{{ saya_today_web.tag }}" + env: + B2_KEY_ID: "{{ saya_today_web.b2.key_id }}" + B2_APPLICATION_KEY: "{{ saya_today_web.b2.application_key }}" + network_mode: caddy + networks: + - name: caddy + ipv4_address: "{{ saya_today_web.ipv4_address }}" + restart_policy: unless-stopped + no_log: true ... diff --git a/internal/b2/client.go b/internal/b2/client.go index 5322b84..f2b6cad 100644 --- a/internal/b2/client.go +++ b/internal/b2/client.go @@ -33,14 +33,13 @@ func NewB2Client(cfg *config.B2Config) (*B2Client, error) { type BlogPage struct { Link string - FileName string Metadata *frontmatter.Metadata } -func (c *B2Client) Scan(prefix string) ([]*BlogPage, error) { +func (c *B2Client) Scan() ([]*BlogPage, error) { filePaths := []*BlogPage{} - iter := c.bucket.List(context.Background(), b2.ListPrefix(c.prefix+prefix)) + iter := c.bucket.List(context.Background(), b2.ListPrefix(c.prefix)) for iter.Next() { obj := iter.Object() @@ -70,13 +69,10 @@ func (c *B2Client) Scan(prefix string) ([]*BlogPage, error) { return nil, fmt.Errorf("failed to parse published time metadata field: %w", err) } - linkParts := strings.Split(obj.Name(), "/") - nameParts := strings.Split(linkParts[len(linkParts)-1], ".") - fileName := strings.Join(nameParts[:len(linkParts)-1], ".") + linkParts := strings.Split(obj.Name(), ".") filePaths = append(filePaths, &BlogPage{ - Link: obj.Name(), - FileName: fileName, + Link: strings.Join(linkParts[:len(linkParts)-1], "."), Metadata: &frontmatter.Metadata{ Title: attrs.Info["title"], ShortDescription: attrs.Info["short-description"], diff --git a/internal/templatemanager/templatemanager.go b/internal/templatemanager/templatemanager.go deleted file mode 100644 index 5effcb3..0000000 --- a/internal/templatemanager/templatemanager.go +++ /dev/null @@ -1,57 +0,0 @@ -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 deleted file mode 100644 index 879fce8..0000000 --- a/locale/localization.en.yaml +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index d5c6b48..0000000 --- a/locale/localization.go +++ /dev/null @@ -1,50 +0,0 @@ -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 deleted file mode 100644 index 786665d..0000000 --- a/locale/localization.ru.yaml +++ /dev/null @@ -1,9 +0,0 @@ -TagsLabel: 'Тэги:' -BlogSearch: - Header: 'Поиск по блогу' - TagsHeader: 'Тэги' - OrderByHeader: 'Упорядочить по...' - TitleOrdered: 'названию' - ActionDateOrdered: 'дате действия' - PublicationDateOrdered: 'времени публикации' - ChooseAllTags: 'Выбрать все' @@ -7,23 +7,17 @@ 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" @@ -42,10 +36,8 @@ var ( gmhtml.WithXHTML(), ), ) - 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) + b2Client *b2.B2Client + configPath = flag.String("c", "config.yaml", "Path to the configuration file (in YAML format)") ) func main() { @@ -68,131 +60,42 @@ func main() { os.Exit(1) } - 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") - 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"}}, + app := fiber.New(fiber.Config{ + Views: engine, }) - 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{ - "/ru": "/", - "/en": "/", - }, - StatusCode: 301, - })) app.Get("/", func(c *fiber.Ctx) error { - 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("/:lang/blog", func(c *fiber.Ctx) error { - lang := c.Params("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... 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 + "/") + pages, err := b2Client.Scan() status := fiber.StatusOK if err != nil { status = fiber.StatusPartialContent pages = []*b2.BlogPage{} } - tagsMap := make(map[string]int) + pageMeta := make([]map[string]string, 0, len(pages)) for _, page := range pages { slog.Debug("enlist page for catalogue", slog.Any("page", page), slog.String("endpoint", "/")) - for _, tag := range page.Metadata.Tags { - tagsMap[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, ", "), + }) } - type Tag struct { - Name string `json:"Name" yaml:"name"` - Count int `json:"Count" yaml:"count"` - } - - tagsArray := make([]Tag, 0, len(tagsMap)) - for tag, count := range tagsMap { - tagsArray = append(tagsArray, Tag{tag, count}) - } - slices.SortFunc(tagsArray, func(a Tag, b Tag) int { - return strings.Compare(a.Name, b.Name) - }) - - content, err := tm.Render("blog-catalogue", fiber.Map{ - "QuerySort": querySort, - "QueryTags": strings.Join(queryTags, ","), - "Tags": tagsArray, - "Lang": lang, - "L": localization[lang], + return c.Status(status).Render("index", fiber.Map{ "PublishedYear": "2025", - "Title": localization[lang].BlogSearch.Header, + "BlogPages": pageMeta, }) - if err != nil { - slog.Warn("failed to generate page", slog.String("page", "/"+lang+"/blog"), slog.String("error", err.Error())) - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate page") - } - - return c.Type("html").Status(status).Send(content) }) - app.Get("/:lang/blog/:title", func(c *fiber.Ctx) error { - lang := c.Params("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... yet??", lang)) - } - - metadata, parsedMarkdown, err := readBlogPost(lang + "/" + c.Params("title")) + app.Get("/blog/:lang/:title", func(c *fiber.Ctx) error { + metadata, parsedMarkdownDesktop, parsedMarkdownMobile, err := readBlogPost(c.Params("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"))) @@ -208,107 +111,16 @@ func main() { areaError = geolocationParts[2] } - content, err := tm.Render("blog-page", fiber.Map{ + return c.Render("layouts/blog-page", fiber.Map{ "Title": metadata.Title, "PublishedDate": metadata.PublishedTime.Format("2006-01-02 15:04:05 -07:00"), "PublishedYear": strconv.Itoa(metadata.PublishedTime.Year()), - "ParsedMarkdown": template.HTML(parsedMarkdown), + "ParsedMarkdownDesktop": template.HTML(parsedMarkdownDesktop), + "ParsedMarkdownMobile": template.HTML(parsedMarkdownMobile), "MapLocationX": x, "MapLocationY": y, "MapLocationAreaMeters": areaError, - "Lang": lang, }) - if err != nil { - slog.Warn("failed to generate page", slog.String("page", "/"+lang+"/blog/"+c.Params("title")), slog.String("error", err.Error())) - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate page") - } - - return c.Type("html").Send(content) - }) - - 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, - "ArticleLink": "/" + lang + "/blog/" + page.FileName, - "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("2006-01-02 15:04:05 -0700", a["PublishedTime"]) - publishedTimeB, _ := time.Parse("2006-01-02 15:04:05 -0700", b["PublishedTime"]) - return publishedTimeA.Compare(publishedTimeB) - case "publicationDateDesc": - publishedTimeA, _ := time.Parse("2006-01-02 15:04:05 -0700", a["PublishedTime"]) - publishedTimeB, _ := time.Parse("2006-01-02 15:04:05 -0700", 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", "/"+lang+"/blog"), slog.String("error", err.Error())) - c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) - return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div") - } - - return c.Type("html").Send(content) }) app.Static("/", "./static") @@ -316,16 +128,20 @@ func main() { log.Fatal(app.Listen(":3000")) } -func readBlogPost(sourceName string) (metadata *frontmatter.Metadata, html string, err error) { +func readBlogPost(sourceName string) (metadata *frontmatter.Metadata, desktopBody string, mobileBody 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 buf bytes.Buffer - if err := md.Convert(markdown, &buf); err != nil { - return nil, "", fmt.Errorf("convert source context from md to html: %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) } - return metadata, buf.String(), nil + return metadata, bufDesktop.String(), bufMobile.String(), nil } diff --git a/static/input.css b/static/input.css index 69cfa91..edecc19 100644 --- a/static/input.css +++ b/static/input.css @@ -30,8 +30,7 @@ --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-width-coef: 10; - --squares-and-triangles-background-height-coef: 10; + --squares-and-triangles-background-size: 10; } [data-theme="lettuce"] { @@ -48,8 +47,7 @@ --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-width-coef: 10; - --squares-and-triangles-background-height-coef: 10; + --squares-and-triangles-background-size: 10; } [data-theme="night"] { @@ -66,26 +64,7 @@ --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-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; + --squares-and-triangles-background-size: 5; } .bg-interlocked-hexagons { @@ -148,12 +127,6 @@ 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; } @@ -192,6 +165,10 @@ 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); @@ -209,12 +186,12 @@ body:has(.lg-container:not([class~="lg-inline"])) .mobile-topbar-custom { inset 0vh -0.5vh rgba(75, 81, 58, 0.5); } -.text-stroke-\(--sidebar-stroke-color\) { - -webkit-text-stroke-color: var(--sidebar-stroke-color); +.desktop { + display: flex; } -.text-stroke-\[0\.1vw\] { - -webkit-text-stroke-width: 0.1vw; +.mobile { + display: none; } .lg-backdrop { @@ -336,45 +313,27 @@ body:has(.lg-container:not([class~="lg-inline"])) .mobile-topbar-custom { } } -@media (min-aspect-ratio: 0.8/1) { - .ar-gt-0\.8\:mr-\[1\.2vmin\] { - margin-right: 1.2vmin; - } - - .ar-gt-0\.8\:w-fit { - width: fit-content; - } - - .ar-gt-0\.8\:min-w-fit { - min-width: fit-content; - } - - .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; +/* Mobile responsive styles */ +@media (max-aspect-ratio: 1.1/1) { + .desktop { + display: none; } -} -@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); + .mobile { + display: flex; } .inline-gallery-container { width: 100%; } + .desktop-sidebar-custom { + width: 90%; + margin-left: 5vw; + height: 10vh; + background-size: 20vh; + } + .logo-custom { display: none; } @@ -396,73 +355,20 @@ body:has(.lg-container:not([class~="lg-inline"])) .mobile-topbar-custom { background-size: 10vh; } - .ar-lt-0\.8\:grid { - display: grid; - } - - .ar-lt-0\.8\:flex-col { - flex-direction: column; + .border-left-custom { + width: 1%; + border-right: 0.3vw dashed var(--main-dark-color); } - .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-\[30vh\] { - height: 30vh; - } - - .ar-lt-0\.8\:h-\[90vh\] { - height: 90vh; - } - - .ar-lt-0\.8\:w-\[8vh\] { - width: 8vh; - } - - .ar-lt-0\.8\:w-\[80\%\] { - width: 80%; - } - - .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; + .border-right-custom { + width: 1.5%; + border-left: 0.5vw dotted var(--main-dark-color); } } @media (max-aspect-ratio: 0.4/1) { - .ar-lt-0\.4\:w-\[100vw\] { - width: 100vw; - } - - .ar-lt-0\.4\:ml-0 { + .desktop-sidebar-custom { + width: 100%; margin-left: 0; } } diff --git a/views/index.html b/views/index.html index 2b8c937..731def6 100644 --- a/views/index.html +++ b/views/index.html @@ -12,45 +12,33 @@ <style> .menu-outer-body { border-radius: 10%; - width: 40vh; - height: 30vh; + width: 60vw; + height: 40vh; } .menu-inner-body { width: 85%; } - .flex-adaptive { - flex-direction: row; - } - - .language-icon { - max-width: calc(80% / {{ .AvailableLanguages | len }}); - max-height: 80%; - } - - @media (max-aspect-ratio: 0.8/1) { + @media (max-aspect-ratio: 1.2/1) { .menu-outer-body { border-radius: 5%; } - .menu-outer-body { - width: min(40vh, 100vw); - height: 40vh; - border-radius: 0; + .menu-inner-body { + width: 92.5%; } + } - .language-icon { - max-width: 40%; - max-height: calc(80% / {{ .AvailableLanguages | len }}); + @media (max-aspect-ratio: 0.8/1) { + .menu-outer-body { + width: 100vw; + height: 80vh; + border-radius: 0; } .menu-inner-body { - width: 92.5%; - } - - .flex-adaptive { - flex-direction: column; + width: 100%; } } </style> @@ -103,13 +91,27 @@ </script> <div class="menu-outer-body p-4 z-1 bg-background-dark flex flex-col mx-auto my-auto opacity-70"> - <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 }}/blog';" class="object-scale-down cursor-pointer flex-1 rounded-[20%] language-icon mx-auto my-auto relative" src="{{ .Flag }}" alt="{{ .Alt }}"> + <p class="font-patua mx-auto text-[2.5vmax] mt-[0.8vmin] mb-[1.6vmin]">saya.today</p> + <div class="menu-inner-body grow bg-background-light flex flex-col mx-auto mb-[1.6vmin] overflow-y-auto opacity-100"> + {{- range .BlogPages }} + <div class="m-2 flex flex-row justify-center justify-items-center gap-4"> + <img onclick="location.href='/blog/{{ .Link }}';" style="flex-grow: 0;" class="flex-1 cursor-pointer object-cover rounded-4xl select-none w-[4vmax] h-[4vmax]" src="https://f003.backblazeb2.com/file/sayana-photos/thumbnails/{{ .Thumbnail }}.webp"> + <div class="flex-5 justify-self-center flex-col border-r-1 border-dashed border-opacity-50 border-main-medium"> + <a href="/blog/{{ .Link }}" class="block font-extrabold">{{ .Title }}</a> + <a href="/blog/{{ .Link }}" class="block italic text-[1vmax] text-secondary">{{ .ActionDate }}</a> + <p>{{ .ShortDescription }}</p> + </div> + <div class="flex-3 justify-self-center flex flex-col text-right"> + <p class="grow text-[1vmax]"> + Тэги: {{ .Tags }} + </p> + <p class="italic text-secondary text-[1vmax]"> + {{ .PublishedTime }} + </p> + </div> + </div> {{- end }} </div> - <p class="font-spectral mx-auto text-[1.5vmax] text-secondary mt-[0.8vmin] mb-[0.8vmin]">Choose your language</p> </div> <script> diff --git a/views/layouts/blog-page.html b/views/layouts/blog-page.html new file mode 100644 index 0000000..08a6f93 --- /dev/null +++ b/views/layouts/blog-page.html @@ -0,0 +1,194 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>SAYA TODAY // {{ .Title }}</title> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.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"> +{{- if .MapLocationX }} + <link rel="stylesheet" href="https://f003.backblazeb2.com/file/sayana-static/libs/leaflet/1.9.4/leaflet.css"> +{{- end }} + <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 href="/output.css" rel="stylesheet"> + + <style> + .map-container .leaflet-container { + width: 100% !important; + height: 100% !important; + } + </style> +</head> + +<body data-theme="night" class="font-spectral text-main-dark overflow-hidden bg-interlocked-hexagons h-screen flex"> + + <script> + function switchTheme(theme) { + if (theme == "" || typeof theme == "undefined") { + const currentTheme = document.body.getAttribute('data-theme'); + switch (currentTheme) { + case "olive": theme = "lettuce"; break; + case "lettuce": theme = "night"; break; + case "night": theme = "olive"; break; + } + } + localStorage.setItem('theme', theme); + document.body.setAttribute('data-theme', theme); + } + + const savedTheme = localStorage.getItem('theme') || 'night'; + switchTheme(savedTheme); + </script> + + <div class="desktop-sidebar-custom sticky bg-squares-and-triangles bg-repeat flex-col items-center shadow-2xl z-20 h-screen desktop"> + <div class="logo-custom text-sidebar font-patua font-extrabold text-center relative z-20"> + saya.today + </div> + <div class="flex flex-col gap-0 relative z-20"> + <i onclick="location.href='/';" class="fas fa-home nav-icon-custom text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex justify-center items-center rounded-lg"></i> + <i class="fas fa-search nav-icon-custom text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex justify-center items-center rounded-lg"></i> + <i class="fas fa-images nav-icon-custom text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex justify-center items-center rounded-lg"></i> + <i onclick="switchTheme('')" class="fas fa-swatchbook nav-icon-custom text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex justify-center items-center rounded-lg"></i> + </div> + </div> + + <div class="bg-background-dark z-10 h-screen desktop"> + <div class="border-left-custom bg-background-dark"></div> + + <div class="content-square flex flex-col bg-background-dark relative mt-[1.6vmin] mx-[1.6vmin]"> + <div class="flex flex-row mb-[0.4vmin]"> + <i onclick="location.href='/';" 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-[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 class="grow text-[1.5vmax] font-spectral text-secondary text-right mt-auto">{{ .PublishedDate }}</p> + </div> + <hr class="border-t-4 border-dotted border-main-dark mb-[0.8vmin]"> + + <div class="bg-background-light flex flex-col flex-1 overflow-y-auto inset-shadow p-[2.4vmin]"> + {{ .ParsedMarkdownDesktop }} + {{- if .MapLocationX }} + <hr class="border-t-4 border-dotted border-main-dark mt-[0.8vmin] mb-[0.8vmin] w-[80%] ml-auto mr-auto"> + <div id="location-map-desktop" class="relative p-[0.8vmin] flex-none ml-auto mr-auto w-[60%] h-[40vh]"></div> + <hr class="border-t-4 border-dotted border-main-dark mt-[0.8vmin] mb-[0.8vmin] w-[80%] ml-auto mr-auto"> + {{- end }} + </div> + + <div class="flex flex-row mt-[0.8vmin] mb-[0.4vmin]"> + <p class="grow text-[0.8vmax]/[0.9] font-spectral italic text-right text-secondary mr-2"> + <a href="">{{ .Title }}</a> © {{ .PublishedYear }} by <a href="https://t.me/EarlInisMona">Saya Andy</a> is licensed under <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a> + </p> + <img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" alt="" style="max-width:1.2vh; max-height:1.2vh;"> + <img src="https://mirrors.creativecommons.org/presskit/icons/by.svg" alt="" style="max-width:1.2vh; max-height:1.2vh;"> + <img src="https://mirrors.creativecommons.org/presskit/icons/sa.svg" alt="" style="max-width:1.2vh; max-height:1.2vh;"> + </div> + + <hr class="border-t-4 border-dotted border-main-dark mb-[0.8vmin]"> + </div> + + <div class="border-right-custom bg-background-dark shadow-2xl"></div> + </div> + + <div class="mobile-topbar-custom fixed bg-squares-and-triangles bg-repeat flex-row items-center shadow-2xl w-[90vw] z-20 mobile"> + <div class="flex flex-row gap-0 relative z-20"> + <i onclick="location.href='/';" class="fas fa-home nav-icon-custom text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex justify-center items-center rounded-lg"></i> + <i class="fas fa-search nav-icon-custom text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex justify-center items-center rounded-lg"></i> + <i class="fas fa-images nav-icon-custom text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex justify-center items-center rounded-lg"></i> + <i onclick="switchTheme('')" class="fas fa-swatchbook nav-icon-custom text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex justify-center items-center rounded-lg"></i> + </div> + <div class="logo-custom text-background-dark font-patua font-extrabold text-center relative grow z-20"> + demo.saya.today + </div> + </div> + + <div class="bg-background-dark flex-row z-10 w-[90vw] h-[92vh] mobile ml-[5vw] mt-[8vh]"> + <div class="border-left-custom bg-background-dark"></div> + + <div class="content-square flex flex-col bg-background-dark relative mt-[1.6vmin] mx-[1.6vmin]"> + <div class="flex flex-row mb-[0.4vmin]"> + <i onclick="location.href='/';" 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-[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 class="grow text-[1.5vmax] font-spectral text-secondary text-right mt-auto">{{ .PublishedDate }}</p> + </div> + <hr class="border-t-4 border-dotted border-main-dark mb-[0.8vmin]"> + + <div class="bg-background-light flex flex-col overflow-y-auto inset-shadow p-[2.4vmin] max-h-[80%]"> + {{ .ParsedMarkdownMobile }} + {{- if .MapLocationX }} + <hr class="border-t-4 border-dotted border-main-dark mt-[0.8vmin] mb-[0.8vmin] w-[80%] ml-auto mr-auto"> + <div id="location-map-mobile" class="relative p-[0.8vmin] flex-none ml-auto mr-auto w-[100%] h-[40vh]"></div> + <hr class="border-t-4 border-dotted border-main-dark mt-[0.8vmin] mb-[0.8vmin] w-[80%] ml-auto mr-auto"> + {{- end }} + </div> + + <div class="flex flex-row mt-[0.8vmin] mb-[0.4vmin]"> + <p class="grow text-[0.8vmax]/[0.9] font-spectral italic text-right text-secondary mr-2"> + <a href="">{{ .Title }}</a> © {{ .PublishedYear }} by <a href="https://t.me/EarlInisMona">Saya Andy</a> is licensed under <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a> + </p> + <img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" alt="" style="max-width:1.2vh; max-height:1.2vh;"> + <img src="https://mirrors.creativecommons.org/presskit/icons/by.svg" alt="" style="max-width:1.2vh; max-height:1.2vh;"> + <img src="https://mirrors.creativecommons.org/presskit/icons/sa.svg" alt="" style="max-width:1.2vh; max-height:1.2vh;"> + </div> + + <hr class="border-t-4 border-dotted border-main-dark mb-[0.8vmin]"> + </div> + + <div class="border-right-custom bg-background-dark shadow-2xl"></div> + </div> + + <script src="https://f003.backblazeb2.com/file/sayana-static/libs/lightgallery/2.8.3/lightgallery.min.js"></script> + <script src="https://f003.backblazeb2.com/file/sayana-static/libs/lightgallery/2.8.3/plugins/lg-zoom.min.js"></script> + <script src="https://f003.backblazeb2.com/file/sayana-static/libs/lightgallery/2.8.3/plugins/lg-thumbnail.min.js"></script> +{{- if .MapLocationX }} + <script src="https://f003.backblazeb2.com/file/sayana-static/libs/leaflet/1.9.4/leaflet.js"></script> + + <script> + function initLocationMap(id, x, y, relativeErrorMeters = 0) { + var map = L.map(id).setView([x, y], 13); + + L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 19, + attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>' + }).addTo(map); + + if (relativeErrorMeters != 0) { + var circle = L.circle([x, y], { + color: '#8a9d8a', + fillColor: '#8a9d8a', + fillOpacity: 0.15, + radius: relativeErrorMeters + }).addTo(map); + } + + var marker = L.marker([x, y]).addTo(map); + } + + document.addEventListener('DOMContentLoaded', () => {initLocationMap( + 'location-map-desktop', + {{ .MapLocationX }}, + {{ .MapLocationY }}, + {{ .MapLocationAreaMeters }} + )}); + + document.addEventListener('DOMContentLoaded', () => {initLocationMap( + 'location-map-mobile', + {{ .MapLocationX }}, + {{ .MapLocationY }}, + {{ .MapLocationAreaMeters }} + )}); + + window.addEventListener('resize', function() { + setTimeout(() => { + if (window.location-map-desktop) window.location-map-desktop.invalidateSize(); + if (window.location-map-mobile) window.location-map-mobile.invalidateSize(); + }, 100); + }); + </script> +{{- end }} + +</body> +</html> diff --git a/views/layouts/general-page.html b/views/layouts/general-page.html deleted file mode 100644 index 121a5cd..0000000 --- a/views/layouts/general-page.html +++ /dev/null @@ -1,82 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>SAYA TODAY // {{ .Title }}</title> - <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"> - <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"> - {{ block "header" . }}{{ end }} - <link href="/output.css" rel="stylesheet"> -</head> - -<body data-theme="night" class="font-spectral text-main-dark overflow-hidden bg-interlocked-hexagons h-screen flex flex-row ar-lt-0.8:flex-col"> - - <script> - function switchTheme(theme) { - if (theme == "" || typeof theme == "undefined") { - const currentTheme = document.body.getAttribute('data-theme'); - switch (currentTheme) { - case "olive": theme = "lettuce"; break; - case "lettuce": theme = "night"; break; - case "night": theme = "ram"; break; - case "ram": theme = "olive"; break; - } - } - localStorage.setItem('theme', theme); - document.body.setAttribute('data-theme', theme); - } - - const savedTheme = localStorage.getItem('theme') || 'night'; - switchTheme(savedTheme); - </script> - - <div class="bg-sidebar flex sticky flex-col ar-gt-0.8:items-center shadow-2xl z-20 w-[5vw] ar-lt-0.8:w-[90vw] ar-lt-0.4:w-[100vw] h-screen ar-lt-0.8:h-[8vh] ar-lt-0.8:ml-[5vw] ar-lt-0.4:ml-0"> - <div class="logo-custom ar-lt-0.8:hidden text-sidebar font-patua font-extrabold text-center relative z-20"> - saya.today - </div> - <div class="text-[3vw] ar-lt-0.8:text-[5vh] text-stroke-(--sidebar-stroke-color) text-stroke-[0.1vw] flex flex-col ar-lt-0.8:flex-row gap-0 relative z-20"> - <i onclick="location.href='/';" class="fas fa-home w-[5vw] ar-lt-0.8:w-[8vh] h-[5vw] ar-lt-0.8: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-0.8:w-[8vh] h-[5vw] ar-lt-0.8:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i> - <!-- <i class="fas fa-images w-[5vw] ar-lt-0.8:w-[8vh] h-[5vw] ar-lt-0.8:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i> --> - <i onclick="switchTheme('')" class="fas fa-swatchbook w-[5vw] ar-lt-0.8:w-[8vh] h-[5vw] ar-lt-0.8:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i> - </div> - </div> - - <div class="bg-background-dark flex z-10 ar-lt-0.8:w-[90vw] ar-lt-0.4:w-[100vw] h-screen ar-lt-0.8:ml-[5vw] ar-lt-0.4:ml-0"> - <div class="pl-[0.5vw] bg-background-dark border-r-[0.4vmin] border-dashed border-main-dark"></div> - - <div class="ar-gt-0.8:min-w-fit ar-gt-0.8:max-w-[100vh] flex flex-col bg-background-dark relative pt-[1.6vmin] ml-[1.6vmin] pr-[1.6vmin]"> - <div class="flex flex-row mb-[0.4vmin]"> - <i onclick="location.pathname += location.pathname.endsWith('/') ? '../' : '/../';" 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-[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> - {{- if .PublishedDate }} - <p class="grow text-[1.5vmax] font-spectral text-secondary text-right mt-auto">{{ .PublishedDate }}</p> - {{- end }} - </div> - <hr class="border-t-4 border-dotted border-main-dark mb-[0.8vmin]"> - - {{ block "body" . }}{{ end }} - - <div class="flex flex-row mt-[0.8vmin] mb-[0.4vmin]"> - <p class="grow text-[0.8vmax]/[0.9] font-spectral italic text-right text-secondary mr-2"> - All the photos on <a href="https://saya.today/">saya.today</a> are licensed under <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a> by <a href="https://t.me/EarlInisMona">Saya Andy</a> © {{ .PublishedYear }} - </p> - <img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" alt="" style="max-width:1.2vh; max-height:1.2vh;"> - <img src="https://mirrors.creativecommons.org/presskit/icons/by.svg" alt="" style="max-width:1.2vh; max-height:1.2vh;"> - <img src="https://mirrors.creativecommons.org/presskit/icons/sa.svg" alt="" style="max-width:1.2vh; max-height:1.2vh;"> - </div> - - <hr class="border-t-4 border-dotted border-main-dark mb-[0.8vmin]"> - </div> - - <div class="pr-[0.5vw] bg-background-dark shadow-2xl border-l-[0.4vmin] border-dotted border-main-dark"></div> - </div> - - {{ block "footer" . }}{{ end }} - -</body> -</html> diff --git a/views/pages/blog-catalogue.html b/views/pages/blog-catalogue.html deleted file mode 100644 index 4cda19d..0000000 --- a/views/pages/blog-catalogue.html +++ /dev/null @@ -1,142 +0,0 @@ -{{ define "body" }} -<div class="flex flex-row ar-lt-0.8:flex-col max-h-[calc(100%-6vmax)] ar-lt-0.8:max-h-[80%] flex-1 text-[1vmax]"> - <form hx-get="/api/v1/blog-search" hx-vals='{"lang": "{{ .Lang }}"}' hx-target=".blog-cards" hx-swap="innerHTML" - class="tags-list ar-gt-0.8:min-w-[10vh] ar-gt-0.8:max-w-[20vh] ar-gt-0.8:w-fit ar-lt-0.8:w-[100%] flex shrink-0 flex-col overflow-y-auto ar-gt-0.8:mr-[1.2vmin]"> - <fieldset> - <legend class="font-bold w-[100%] text-center">{{ .L.BlogSearch.TagsHeader }}</legend> - <div class="flex flex-col ar-lt-0.8:grid grid-cols-5 grid-flow-row-dense gap-[5%]"> - <div> - <label class="font-bold"><input type="checkbox" id="tagsAllCheckbox" onclick="selectAll();"> {{ .L.BlogSearch.ChooseAllTags }}</label> - </div> - {{- range .Tags }} - <div> - <label><input type="checkbox" id="tag{{ .Name }}Checkbox" name="tags[]" value="{{ .Name }}" onclick="contextAll();" {{ if contains $.QueryTags .Name }}checked{{ end }}> {{ .Name }} <span class="text-secondary italic">{{ .Count }}</span></label> - </div> - {{- end }} - </div> - </fieldset> - <fieldset class="mt-[0.8vmin]"> - <legend class="font-bold w-[100%] text-center">{{ .L.BlogSearch.OrderByHeader }}</legend> - <div class="flex flex-col ar-lt-0.8:grid grid-cols-3 grid-rows-2 grid-flow-col gap-[5%]"> - <div> - <label><input type="radio" id="sortTitleAsc" name="sort" value="titleAsc" {{ if eq .QuerySort "titleAsc" }}checked{{ end }}> {{ .L.BlogSearch.TitleOrdered }} <i class="fas fa-arrow-down-a-z"></i></label> - </div> - <div> - <label><input type="radio" id="sortTitleDesc" name="sort" value="titleDesc" {{ if eq .QuerySort "titleDesc" }}checked{{ end }}> {{ .L.BlogSearch.TitleOrdered }} <i class="fas fa-arrow-down-z-a"></i></label> - </div> - <div> - <label><input type="radio" id="sortActionDateAsc" name="sort" value="actionDateAsc" {{ if eq .QuerySort "actionDateAsc" }}checked{{ end }}> {{ .L.BlogSearch.ActionDateOrdered }} <i class="fas fa-arrow-down-1-9"></i></label> - </div> - <div> - <label><input type="radio" id="sortActionDateDesc" name="sort" value="actionDateDesc" {{ if eq .QuerySort "actionDateDesc" }}checked{{ end }}> {{ .L.BlogSearch.ActionDateOrdered }} <i class="fas fa-arrow-down-9-1"></i></label> - </div> - <div> - <label><input type="radio" id="sortPublicationDateAsc" name="sort" value="publicationDateAsc" {{ if eq .QuerySort "publicationDateAsc" }}checked{{ end }}> {{ .L.BlogSearch.PublicationDateOrdered }} <i class="fas fa-arrow-down-1-9"></i></label> - </div> - <div> - <label><input type="radio" id="sortPublicationDateDesc" name="sort" value="publicationDateDesc" {{ if eq .QuerySort "publicationDateDesc" }}checked{{ end }}> {{ .L.BlogSearch.PublicationDateOrdered }} <i class="fas fa-arrow-down-9-1"></i></label> - </div> - </div> - </fieldset> - <div class="flex flex-row bg-background-light my-[2vmin] p-[0.4vmin] rounded-[0.4vmin] justify-items-center"> - <i onclick="defaultSearchParams(); cleanHrefParams();" class="fas fa-rotate-left flex-1 text-center text-[1.5vmax] hover:bg-background-dark cursor-pointer transition-colors duration-300"></i> - <div class="w-[0.5vmin] bg-background-dark grow-0"></div> - <button class="flex-1" type="submit"> - <i onclick="setHrefParams();" class="fas fa-magnifying-glass text-center text-[1.5vmax] hover:bg-background-dark cursor-pointer transition-colors duration-300"></i> - </button> - </div> - </form> - - <div class="w-[0.5vh] border-r-[0.3vh] border-dotted border-main-dark bg-background-dark"></div> - - <div hx-get="/api/v1/blog-search" hx-vals='{"lang": "{{ .Lang }}"}' hx-target="this" hx-swap="innerHTML" hx-trigger="load" hx-include=".tags-list" - class="blog-cards grow flex flex-col overflow-y-auto bg-background-light inset-shadow p-[2.4vmin]"> - Loading... - </div> -</div> -{{ end }} - -{{ define "footer" }} -<script src="https://f003.backblazeb2.com/file/sayana-static/libs/htmx/2.0.6/htmx.min.js"></script> - -<script> - function selectAll() { - const tagsContainers = document.querySelectorAll('form.tags-list'); - tagsContainers.forEach(tagsContainer => { - const selectAllCheckbox = tagsContainer.querySelector('#tagsAllCheckbox'); - const checkboxes = tagsContainer.querySelectorAll('input[type="checkbox"]:not([id="tagsAllCheckbox"])'); - checkboxes.forEach(checkbox => { - checkbox.checked = selectAllCheckbox.checked; - }); - }); - } - - function contextAll() { - const tagsContainers = document.querySelectorAll('form.tags-list'); - tagsContainers.forEach(tagsContainer => { - const selectAllCheckbox = tagsContainer.querySelector('#tagsAllCheckbox'); - const checkboxes = tagsContainer.querySelectorAll('input[type="checkbox"]:not([id="tagsAllCheckbox"])'); - var areAllChecked = true; - checkboxes.forEach(checkbox => { - if (!checkbox.checked) areAllChecked = false; - }); - selectAllCheckbox.checked = areAllChecked; - }); - } - - function defaultSearchParams() { - const tagsContainers = document.querySelectorAll('form.tags-list'); - tagsContainers.forEach(tagsContainer => { - const checkboxes = tagsContainer.querySelectorAll('input[type="checkbox"]'); - checkboxes.forEach(checkbox => { - checkbox.checked = true; - }); - const sortRadio = tagsContainer.querySelector('#sortTitleAsc'); - sortRadio.checked = true; - }); - } - - function cleanHrefParams() { - const url = new URL(window.location.href); - url.search = ''; - window.history.pushState({}, '', url.toString()); - } - - function setHrefParams() { - const url = new URL(window.location.href); - url.search = ''; - - const tagsContainers = document.querySelectorAll('form.tags-list'); - tagsContainers.forEach(tagsContainer => { - const checkboxes = tagsContainer.querySelectorAll('input[type="checkbox"]'); - checkboxes.forEach(checkbox => { - if (checkbox.checked) - url.searchParams.append('tags[]', checkbox.value); - }); - const sortRadio = tagsContainer.querySelector('input[type="radio"]:checked'); - url.searchParams.set('sort', sortRadio.value); - }); - - window.history.pushState({}, '', url.toString()); - } - - document.addEventListener('DOMContentLoaded', contextAll); -</script> - -<script> - const tagsContainers = document.querySelectorAll('form.tags-list'); - tagsContainers.forEach(tagsContainer => { - const checkboxes = tagsContainer.querySelectorAll('input[type="checkbox"]'); - anyChecked = false; - checkboxes.forEach(checkbox => { - anyChecked = checkbox.checked ? true : anyChecked; - }); - if (!anyChecked) { - const selectAllCheckbox = tagsContainer.querySelector('#tagsAllCheckbox'); - selectAllCheckbox.checked = true; - selectAll(); - } - }); -</script> - -{{ end }} diff --git a/views/pages/blog-page.html b/views/pages/blog-page.html deleted file mode 100644 index 5f52c2f..0000000 --- a/views/pages/blog-page.html +++ /dev/null @@ -1,70 +0,0 @@ -{{ define "header" }} -<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"> -{{- if .MapLocationX }} -<link rel="stylesheet" href="https://f003.backblazeb2.com/file/sayana-static/libs/leaflet/1.9.4/leaflet.css"> -{{- end }} - -<style> - .map-container .leaflet-container { - width: 100% !important; - height: 100% !important; - } -</style> -{{ end }} - -{{ define "body" }} -<div class="bg-background-light flex flex-col grow overflow-y-auto inset-shadow p-[2.4vmin] ar-lt-0.8:max-h-[80%]"> - {{ .ParsedMarkdown }} -{{- if .MapLocationX }} - <hr class="border-t-4 border-dotted border-main-dark mt-[0.8vmin] mb-[0.8vmin] w-[80%] ml-auto mr-auto"> - <div id="location-map" class="relative p-[0.8vmin] flex-none ml-auto mr-auto w-[60%] ar-lt-0.8:w-[80%] h-[40vh] ar-lt-0.8:h-[30vh]"></div> - <hr class="border-t-4 border-dotted border-main-dark mt-[0.8vmin] mb-[0.8vmin] w-[80%] ml-auto mr-auto"> -{{- end }} -</div> -{{ end }} - -{{ define "footer" }} -<script src="https://f003.backblazeb2.com/file/sayana-static/libs/lightgallery/2.8.3/lightgallery.min.js"></script> -<script src="https://f003.backblazeb2.com/file/sayana-static/libs/lightgallery/2.8.3/plugins/lg-zoom.min.js"></script> -<script src="https://f003.backblazeb2.com/file/sayana-static/libs/lightgallery/2.8.3/plugins/lg-thumbnail.min.js"></script> -{{- if .MapLocationX }} -<script src="https://f003.backblazeb2.com/file/sayana-static/libs/leaflet/1.9.4/leaflet.js"></script> - -<script> - function initLocationMap(id, x, y, relativeErrorMeters = 0) { - var map = L.map(id).setView([x, y], 13); - - L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { - maxZoom: 19, - attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>' - }).addTo(map); - - if (relativeErrorMeters != 0) { - var circle = L.circle([x, y], { - color: '#8a9d8a', - fillColor: '#8a9d8a', - fillOpacity: 0.15, - radius: relativeErrorMeters - }).addTo(map); - } - - var marker = L.marker([x, y]).addTo(map); - } - - document.addEventListener('DOMContentLoaded', () => {initLocationMap( - 'location-map', - {{ .MapLocationX }}, - {{ .MapLocationY }}, - {{ .MapLocationAreaMeters }} - )}); - - window.addEventListener('resize', function() { - setTimeout(() => { - if (window.location-map) window.location-map.invalidateSize(); - }, 100); - }); -</script> -{{- end }} -{{ end }}
\ No newline at end of file diff --git a/views/partials/catalogue-blog-cards.html b/views/partials/catalogue-blog-cards.html deleted file mode 100644 index a9e4010..0000000 --- a/views/partials/catalogue-blog-cards.html +++ /dev/null @@ -1,18 +0,0 @@ -{{- range .BlogPages }} -<hr class="first-of-type:hidden border-t-[0.3vh] my-[0.8vmin] border-dotted border-main-dark w-[80%] mx-auto"> -<div class="m-[0.4vmin] flex flex-row justify-center justify-items-center"> - <img onclick="location.href='{{ .ArticleLink }}';" class="cursor-pointer object-cover rounded-[10%] select-none w-[10vmin] h-[10vmin] aspect-square my-[0.4vmin] mr-[0.8vmin]" src="https://f003.backblazeb2.com/file/sayana-photos/thumbnails/{{ .Thumbnail }}.webp"> - <div class="grow my-[0.4vmin] mx-[0.8vmin] flex flex-col justify-center"> - <div class="flex flex-row gap-[5%] max-w-[100%]"> - <a href="{{ .ArticleLink }}" class="flex-9/12 grow-0 block text-[1vmax] cursor-pointer"> - <span class="font-extrabold">{{ .Title }}</span> - <span class="font-extrabold select-none text-secondary">//</span> - <span class="italic text-secondary">{{ .ActionDate }}</span> - </a> - <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">{{ .L.TagsLabel }} {{ .Tags }}</p> - </div> -</div> -{{- end }}
\ No newline at end of file |