summaryrefslogtreecommitdiff
path: root/internal
diff refs
from:
to:
flip
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/factgiver/factgiver.go14
-rw-r--r--internal/glightbox/block.go32
-rw-r--r--internal/glightbox/extension.go (renamed from internal/lightgallery/extension.go)14
-rw-r--r--internal/glightbox/html_renderer.go186
-rw-r--r--internal/glightbox/parser.go (renamed from internal/lightgallery/parser.go)42
-rw-r--r--internal/lightgallery/block.go31
-rw-r--r--internal/lightgallery/html_renderer.go145
-rw-r--r--internal/router/api-v1-blog-search.go13
-rw-r--r--internal/router/api-v1-general-page-body.go28
-rw-r--r--internal/router/api-v1-general-page-bottom-embeds.go25
-rw-r--r--internal/router/api-v1-general-page-footer.go25
-rw-r--r--internal/router/api-v1-general-page-header.go26
-rw-r--r--internal/router/api-v1-general-page-top-embeds.go25
-rw-r--r--internal/router/api-v1-general-page.go23
-rw-r--r--internal/router/lang-blog-title.go13
-rw-r--r--internal/router/lang-blog.go12
-rw-r--r--internal/router/lang-map.go13
-rw-r--r--internal/tailwind/link_renderer.go49
-rw-r--r--internal/tailwind/transformer.go25
-rw-r--r--internal/templatemanager/templatemanager.go4
20 files changed, 463 insertions, 282 deletions
diff --git a/internal/factgiver/factgiver.go b/internal/factgiver/factgiver.go
index 6f77403..5b418ee 100644
--- a/internal/factgiver/factgiver.go
+++ b/internal/factgiver/factgiver.go
@@ -14,13 +14,13 @@ import (
type FactGiver struct {
b2Client *b2.B2Client
cache map[string][]string
- langs []string
+ langs []config.AvailableLanguageConfig
factsFileName string
nlRe *regexp.Regexp
randGen *rand.Rand
}
-func NewFactGiver(cfg *config.FactGiverConfig, langs []string) (*FactGiver, error) {
+func NewFactGiver(cfg *config.FactGiverConfig, langs []config.AvailableLanguageConfig) (*FactGiver, error) {
b2Client, err := b2.NewB2Client(&cfg.Storage.Config)
if err != nil {
return nil, fmt.Errorf("fail to init b2 client for a new fact giver: %s", err.Error())
@@ -52,15 +52,15 @@ func (g *FactGiver) Give(lang string) [3]string {
func (g *FactGiver) initCache() error {
for _, lang := range g.langs {
- localFacts := strings.Replace(g.factsFileName, "*", lang, 1)
+ localFacts := strings.Replace(g.factsFileName, "*", lang.Name, 1)
factsContentBytes, err := g.b2Client.ReadAll(localFacts)
if err != nil {
- return fmt.Errorf("fail to read '%s' facts file: %s", lang, err.Error())
+ return fmt.Errorf("fail to read '%s' facts file: %s", lang.Name, err.Error())
}
factsContent := string(factsContentBytes)
- g.cache[lang] = g.nlRe.Split(factsContent, -1)
- if g.cache[lang][len(g.cache[lang])-1] == "" {
- g.cache[lang] = g.cache[lang][:len(g.cache[lang])-1]
+ g.cache[lang.Name] = g.nlRe.Split(factsContent, -1)
+ if g.cache[lang.Name][len(g.cache[lang.Name])-1] == "" {
+ g.cache[lang.Name] = g.cache[lang.Name][:len(g.cache[lang.Name])-1]
}
}
return nil
diff --git a/internal/glightbox/block.go b/internal/glightbox/block.go
new file mode 100644
index 0000000..e53e67b
--- /dev/null
+++ b/internal/glightbox/block.go
@@ -0,0 +1,32 @@
+package glightbox
+
+import (
+ "time"
+
+ "github.com/yuin/goldmark/ast"
+)
+
+// GLightboxBlock represents a light gallery block in the AST
+type GLightboxBlock struct {
+ ast.BaseBlock
+ Images []GLightboxImage
+ Location *time.Location
+}
+
+type GLightboxImage struct {
+ URL string
+ Tags []string
+ Caption []byte
+}
+
+var KindGLightboxBlock = ast.NewNodeKind("GLightboxBlock")
+
+// Dump implements ast.Node.Dump
+func (n *GLightboxBlock) Dump(source []byte, level int) {
+ ast.DumpHelper(n, source, level, nil, nil)
+}
+
+// Kind implements ast.Node.Kind
+func (n *GLightboxBlock) Kind() ast.NodeKind {
+ return KindGLightboxBlock
+}
diff --git a/internal/lightgallery/extension.go b/internal/glightbox/extension.go
index 761dc05..7fd17b1 100644
--- a/internal/lightgallery/extension.go
+++ b/internal/glightbox/extension.go
@@ -1,4 +1,4 @@
-package lightgallery
+package glightbox
import (
"github.com/yuin/goldmark"
@@ -8,21 +8,21 @@ import (
)
// Extension that combines parser and renderer
-type LightGalleryExtension struct{}
+type GLightboxExtension struct{}
-func NewLightGalleryExtension() goldmark.Extender {
- return &LightGalleryExtension{}
+func NewGLightboxExtension() goldmark.Extender {
+ return &GLightboxExtension{}
}
-func (e *LightGalleryExtension) Extend(m goldmark.Markdown) {
+func (e *GLightboxExtension) Extend(m goldmark.Markdown) {
m.Parser().AddOptions(
parser.WithBlockParsers(
- util.Prioritized(NewLightGalleryParser(), 500),
+ util.Prioritized(NewGLightboxParser(), 500),
),
)
m.Renderer().AddOptions(
renderer.WithNodeRenderers(
- util.Prioritized(NewLightGalleryHTMLRenderer(), 500),
+ util.Prioritized(NewGLightboxHTMLRenderer(), 500),
),
)
}
diff --git a/internal/glightbox/html_renderer.go b/internal/glightbox/html_renderer.go
new file mode 100644
index 0000000..7022793
--- /dev/null
+++ b/internal/glightbox/html_renderer.go
@@ -0,0 +1,186 @@
+package glightbox
+
+import (
+ "bytes"
+ "fmt"
+ "math/rand"
+ "regexp"
+ "strings"
+ "time"
+
+ "github.com/SayaAndy/saya-today-web/internal/tailwind"
+ "github.com/yuin/goldmark"
+ "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/renderer"
+ "github.com/yuin/goldmark/renderer/html"
+ "github.com/yuin/goldmark/util"
+)
+
+type GLightboxHTMLRenderer struct {
+ html.Config
+ md goldmark.Markdown
+ anchorMatchRe *regexp.Regexp
+}
+
+func NewGLightboxHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
+ r := &GLightboxHTMLRenderer{
+ Config: html.NewConfig(),
+ md: goldmark.New(
+ goldmark.WithParserOptions(
+ parser.WithAutoHeadingID(),
+ parser.WithAttribute(),
+ ),
+ goldmark.WithRenderer(
+ renderer.NewRenderer(
+ renderer.WithNodeRenderers(
+ util.Prioritized(tailwind.NewCustomLinkRenderer(
+ html.WithUnsafe(), html.WithHardWraps(), html.WithXHTML(),
+ ), 50),
+ util.Prioritized(html.NewRenderer(
+ html.WithUnsafe(), html.WithHardWraps(), html.WithXHTML(),
+ ), 100),
+ ),
+ ),
+ ),
+ ),
+ }
+ for _, opt := range opts {
+ opt.SetHTMLOption(&r.Config)
+ }
+ r.anchorMatchRe = regexp.MustCompile(`(?s)<\s*a(\s+[^<]*)(href\s*=\s*["'].*?["'])\s*([^<]*)>(.*?)<\s*\/\s*a\s*>`)
+ return r
+}
+
+func (r *GLightboxHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+ reg.Register(KindGLightboxBlock, r.renderGLightbox)
+}
+
+func (r *GLightboxHTMLRenderer) renderGLightbox(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
+ if entering {
+ gallery := n.(*GLightboxBlock)
+
+ if len(gallery.Images) == 0 {
+ return ast.WalkContinue, nil
+ }
+
+ galleryID := generateDivId(8)
+
+ var elements []string
+ for i, img := range gallery.Images {
+ imageUrlSegments := strings.Split(img.URL, ".")
+ imageUrlWithoutExt := strings.Join(imageUrlSegments[:len(imageUrlSegments)-1], ".")
+ imageUrlParts := strings.Split(imageUrlWithoutExt, "/")
+ imageNameParts := strings.Split(imageUrlParts[len(imageUrlParts)-1], "-")
+
+ var captionBuf bytes.Buffer
+ captionHTML := img.Caption
+ if err := r.md.Convert(img.Caption, &captionBuf); err == nil {
+ captionHTML = captionBuf.Bytes()
+ captionHTML = bytes.TrimPrefix(captionHTML, []byte("<p>"))
+ captionHTML = bytes.TrimSuffix(captionHTML, []byte("</p>\n"))
+ captionHTML = bytes.TrimSuffix(captionHTML, []byte("</p>"))
+ }
+
+ dayDate, _ := time.Parse("20060102 150405", imageNameParts[len(imageNameParts)-2]+" "+imageNameParts[len(imageNameParts)-1])
+ dayDate = dayDate.In(gallery.Location)
+
+ glightboxDescId := ""
+ if len(captionHTML) != 0 {
+ glightboxDescId = fmt.Sprintf("glightbox-desc-%s-%d", galleryID, i)
+ }
+
+ dataDescriptionAttribute := ""
+ if glightboxDescId != "" {
+ dataDescriptionAttribute = fmt.Sprintf("data-description=\".%s\"", glightboxDescId)
+ }
+
+ tagClassList := make([]string, 0, len(img.Tags))
+ for _, tag := range img.Tags {
+ switch tag {
+ case "2x":
+ tagClassList = append(tagClassList, "grid-item-2x")
+ }
+ }
+
+ if len(img.Caption) > 0 {
+ tagClassList = append(tagClassList, "grid-tooltip")
+ }
+
+ anchorlessCaptionHTML := r.anchorMatchRe.ReplaceAll(captionHTML, []byte("<span class=\"linklike\" $1 $3>$4</span>"))
+
+ elements = append(elements, fmt.Sprintf(`
+ <a href="https://f003.backblazeb2.com/file/sayana-photos/full/%s" class="glightbox grid-item %s grid-item-%s p-1"
+ data-gallery="gallery" data-title="%s" %s>
+ <picture>
+ <source media="(width < 800px)" srcset="https://f003.backblazeb2.com/file/sayana-photos/webp-320p/%s.webp" />
+ <source media="(width < 2400px)" srcset="https://f003.backblazeb2.com/file/sayana-photos/webp-560p/%s.webp" />
+ <source media="(width < 3200px)" srcset="https://f003.backblazeb2.com/file/sayana-photos/webp-800p/%s.webp" />
+ <source media="(width < 4000px)" srcset="https://f003.backblazeb2.com/file/sayana-photos/webp-1200p/%s.webp" />
+ <source media="(width >= 4000px)" srcset="https://f003.backblazeb2.com/file/sayana-photos/webp-1600p/%s.webp" />
+ <img src="https://f003.backblazeb2.com/file/sayana-photos/webp-560p/%s.webp" />
+ </picture>
+ <span class="grid-tooltip-text"><p>%s</p></span>
+ <span class="grid-item-index">%d</span>
+ </a>`, img.URL, strings.Join(tagClassList, " "), galleryID, dayDate.Format("2006-01-02 15:04:05 -07:00"), dataDescriptionAttribute, imageUrlWithoutExt, imageUrlWithoutExt, imageUrlWithoutExt, imageUrlWithoutExt, imageUrlWithoutExt, imageUrlWithoutExt, anchorlessCaptionHTML, i+1))
+
+ if glightboxDescId != "" {
+ elements = append(elements, fmt.Sprintf(`
+ <div class="glightbox-desc rounded-4 %s">
+ <p>%s</p>
+ </div>`, glightboxDescId, captionHTML))
+ }
+ }
+
+ w.WriteString(fmt.Sprintf(`
+<div class="justify-content-center display-block">
+ <hr class="border-t-3 border-dotted border-main-hard mt-1 mb-2 w-[80%%] ml-auto mr-auto">
+ <div class="grid masonry-grid-%s mx-auto">
+ <div class="grid-sizer grid-sizer-%s"></div>
+ %s
+ </div>
+ <hr class="border-t-3 border-dotted border-main-hard mt-1 mb-2 w-[80%%] ml-auto mr-auto">
+</div>`, galleryID, galleryID, strings.Join(elements, "\n")))
+
+ w.WriteString(fmt.Sprintf(`
+<script>
+ var msnry_%s = new Masonry('.masonry-grid-%s', {
+ itemSelector: '.grid-item-%s',
+ columnWidth: '.grid-sizer-%s',
+ percentPosition: false,
+ fitWidth: true
+ });
+
+ var imgLoad_%s_timer;
+ var imgLoad_%s = imagesLoaded('.masonry-grid-%s');
+
+ function initMasonryLayout_%s(tm) {
+ clearTimeout(imgLoad_%s_timer);
+ imgLoad_%s_timer = setTimeout(() => msnry_%s.layout(), 100);
+ }
+
+ imgLoad_%s.on('progress', initMasonryLayout_%s);
+
+ window.addEventListener('resize', initMasonryLayout_%s);
+
+ document.addEventListener('popout', (e) => {
+ window.removeEventListener('resize', initMasonryLayout_%s);
+ imgLoad_%s.off('progress', initMasonryLayout_%s);
+ initMasonryLayout_%s = null;
+ imgLoad_%s = null;
+ }, {once: true});
+</script>`, galleryID, galleryID, galleryID, galleryID, galleryID, galleryID, galleryID, galleryID, galleryID, galleryID, galleryID, galleryID, galleryID, galleryID, galleryID, galleryID, galleryID, galleryID, galleryID))
+ }
+
+ return ast.WalkContinue, nil
+}
+
+func generateDivId(length int) string {
+ const charset = "abcdefghijklmnopqrstuvwxyz"
+ seededRand := rand.New(rand.NewSource(time.Now().UnixNano())) // Seed with current time
+ b := make([]byte, length)
+ for i := range b {
+ b[i] = charset[seededRand.Intn(len(charset))]
+ }
+ return string(b)
+}
diff --git a/internal/lightgallery/parser.go b/internal/glightbox/parser.go
index daec797..6780090 100644
--- a/internal/lightgallery/parser.go
+++ b/internal/glightbox/parser.go
@@ -1,9 +1,10 @@
-package lightgallery
+package glightbox
import (
"bytes"
"log/slog"
"regexp"
+ "strings"
"time"
"github.com/yuin/goldmark/ast"
@@ -11,17 +12,17 @@ import (
"github.com/yuin/goldmark/text"
)
-type LightGalleryParser struct{}
+type GLightboxParser struct{}
-func NewLightGalleryParser() parser.BlockParser {
- return &LightGalleryParser{}
+func NewGLightboxParser() parser.BlockParser {
+ return &GLightboxParser{}
}
-func (p *LightGalleryParser) Trigger() []byte {
+func (p *GLightboxParser) Trigger() []byte {
return []byte{'{'}
}
-func (p *LightGalleryParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) {
+func (p *GLightboxParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) {
line, _ := reader.PeekLine()
if !bytes.HasPrefix(line, []byte("{Gallery:")) {
@@ -43,10 +44,10 @@ func (p *LightGalleryParser) Open(parent ast.Node, reader text.Reader, pc parser
return nil, parser.NoChildren
}
- return &LightGalleryBlock{Location: loc}, parser.NoChildren
+ return &GLightboxBlock{Location: loc}, parser.NoChildren
}
-func (p *LightGalleryParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
+func (p *GLightboxParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
line, segment := reader.PeekLine()
if len(line) == 0 || segment.Len() == 0 {
return parser.Continue | parser.NoChildren
@@ -58,31 +59,42 @@ func (p *LightGalleryParser) Continue(node ast.Node, reader text.Reader, pc pars
return parser.Close
}
- gallery := node.(*LightGalleryBlock)
+ gallery := node.(*GLightboxBlock)
- parts := bytes.SplitN(trimmed, []byte{'|'}, 2)
+ parts := bytes.SplitN(trimmed, []byte{'|'}, 3)
url := bytes.TrimSpace(parts[0])
caption := make([]byte, 0)
- if len(parts) > 1 {
+ tagsRaw := ""
+ if len(parts) == 2 {
caption = bytes.TrimSpace(parts[1])
}
+ if len(parts) >= 3 {
+ tagsRaw = string(parts[1])
+ caption = bytes.TrimSpace(parts[2])
+ }
+
+ tags := strings.Split(tagsRaw, ",")
+ for i := range tags {
+ tags[i] = strings.TrimSpace(tags[i])
+ }
- gallery.Images = append(gallery.Images, LightGalleryImage{
+ gallery.Images = append(gallery.Images, GLightboxImage{
URL: string(url),
+ Tags: tags,
Caption: caption,
})
return parser.Continue | parser.NoChildren
}
-func (p *LightGalleryParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {
+func (p *GLightboxParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {
}
-func (p *LightGalleryParser) CanInterruptParagraph() bool {
+func (p *GLightboxParser) CanInterruptParagraph() bool {
return true
}
-func (p *LightGalleryParser) CanAcceptIndentedLine() bool {
+func (p *GLightboxParser) CanAcceptIndentedLine() bool {
return false
}
diff --git a/internal/lightgallery/block.go b/internal/lightgallery/block.go
deleted file mode 100644
index 8eb7c9e..0000000
--- a/internal/lightgallery/block.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package lightgallery
-
-import (
- "time"
-
- "github.com/yuin/goldmark/ast"
-)
-
-// LightGalleryBlock represents a light gallery block in the AST
-type LightGalleryBlock struct {
- ast.BaseBlock
- Images []LightGalleryImage
- Location *time.Location
-}
-
-type LightGalleryImage struct {
- URL string
- Caption []byte
-}
-
-var KindLightGalleryBlock = ast.NewNodeKind("LightGalleryBlock")
-
-// Dump implements ast.Node.Dump
-func (n *LightGalleryBlock) Dump(source []byte, level int) {
- ast.DumpHelper(n, source, level, nil, nil)
-}
-
-// Kind implements ast.Node.Kind
-func (n *LightGalleryBlock) Kind() ast.NodeKind {
- return KindLightGalleryBlock
-}
diff --git a/internal/lightgallery/html_renderer.go b/internal/lightgallery/html_renderer.go
deleted file mode 100644
index c2f785e..0000000
--- a/internal/lightgallery/html_renderer.go
+++ /dev/null
@@ -1,145 +0,0 @@
-package lightgallery
-
-import (
- "bytes"
- "fmt"
- "math/rand"
- "strings"
- "time"
-
- "github.com/yuin/goldmark"
- "github.com/yuin/goldmark/ast"
- "github.com/yuin/goldmark/renderer"
- "github.com/yuin/goldmark/renderer/html"
- "github.com/yuin/goldmark/util"
-)
-
-type LightGalleryHTMLRenderer struct {
- html.Config
- md goldmark.Markdown
-}
-
-func NewLightGalleryHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
- r := &LightGalleryHTMLRenderer{
- Config: html.NewConfig(),
- md: goldmark.New(
- goldmark.WithRendererOptions(
- html.WithHardWraps(),
- html.WithXHTML(),
- ),
- ),
- }
- for _, opt := range opts {
- opt.SetHTMLOption(&r.Config)
- }
- return r
-}
-
-func (r *LightGalleryHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
- reg.Register(KindLightGalleryBlock, r.renderLightGallery)
-}
-
-func (r *LightGalleryHTMLRenderer) renderLightGallery(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
- if entering {
- gallery := n.(*LightGalleryBlock)
-
- if len(gallery.Images) == 0 {
- return ast.WalkContinue, nil
- }
-
- galleryID := generateDivId(8)
-
- w.WriteString(fmt.Sprintf(`
-<div class="justify-content-center display-block m-1">
- <hr class="border-t-3 border-dotted border-main-dark mt-1 mb-2 w-[80%%] ml-auto mr-auto">
- <div id="lg-%s" class="inline-gallery-container relative ml-auto mr-auto"></div>
- <hr class="border-t-3 border-dotted border-main-dark mt-1 mb-2 w-[80%%] ml-auto mr-auto">
-</div>`, galleryID))
-
- var dynamicElements []string
- for _, img := range gallery.Images {
- imageUrlSegments := strings.Split(img.URL, ".")
- imageUrlWithoutExt := strings.Join(imageUrlSegments[:len(imageUrlSegments)-1], ".")
- imageUrlParts := strings.Split(imageUrlWithoutExt, "/")
- imageNameParts := strings.Split(imageUrlParts[len(imageUrlParts)-1], "-")
-
- var captionBuf bytes.Buffer
- captionHTML := img.Caption
- if err := r.md.Convert(img.Caption, &captionBuf); err == nil {
- captionHTML = captionBuf.Bytes()
- captionHTML = bytes.TrimPrefix(captionHTML, []byte("<p>"))
- captionHTML = bytes.TrimSuffix(captionHTML, []byte("</p>\n"))
- captionHTML = bytes.TrimSuffix(captionHTML, []byte("</p>"))
- }
-
- dayDate, _ := time.Parse("20060102 150405", imageNameParts[len(imageNameParts)-2]+" "+imageNameParts[len(imageNameParts)-1])
- dayDate = dayDate.In(gallery.Location)
- dynamicElements = append(dynamicElements, fmt.Sprintf(`{
- src:
- "https://f003.backblazeb2.com/file/sayana-photos/full/%s",
- downloadUrl:
- "https://f003.backblazeb2.com/file/sayana-photos/full/%s",
- alt: "%s",
- responsive: "https://f003.backblazeb2.com/file/sayana-photos/webp-320p/%s.webp 384, https://f003.backblazeb2.com/file/sayana-photos/webp-560p/%s.webp 672, https://f003.backblazeb2.com/file/sayana-photos/webp-800p/%s.webp 960, https://f003.backblazeb2.com/file/sayana-photos/webp-1200p/%s.webp 1440, https://f003.backblazeb2.com/file/sayana-photos/webp-1600p/%s.webp 1920",
- thumb:
- "https://f003.backblazeb2.com/file/sayana-photos/webp-320p/%s.webp",
- subHtml: `+"`"+`<div class="flex flex-row light-gallery-captions">
- <p class="grow !text-base/[1] text-left font-spectral text-main-dark">%s</p>
- <p class="!text-base/[1] text-right font-spectral text-secondary">%s</p>
- </div>`+"`"+`
- }`, img.URL, img.URL, util.EscapeHTML(captionHTML), imageUrlWithoutExt, imageUrlWithoutExt, imageUrlWithoutExt, imageUrlWithoutExt, imageUrlWithoutExt, imageUrlWithoutExt, captionHTML, dayDate.Format("2006-01-02 15:04:05 -07:00")))
- }
-
- w.WriteString(fmt.Sprintf(`
-<script>
-function createLightGallery%s() {
- const $lgContainer = document.getElementById('lg-%s');
- const config = {
- container: $lgContainer,
- dynamic: true,
- dynamicEl: [%s],
- width: "100%%",
- height: "50vmin",
- hash: false,
- closable: false,
- showMaximizeIcon: true,
- appendSubHtmlTo: ".lg-sub-html",
- isMobile: () => false,
- slideDelay: 0,
- plugins: [lgZoom, lgThumbnail],
- thumbWidth: calculateVmin(10),
- thumbHeight: "10vmin",
- thumbMargin: 4
- };
- const inlineGallery = lightGallery($lgContainer, config);
-
- setTimeout(() => {
- inlineGallery.openGallery();
- }, 200);
-
- galleryMap.set(inlineGallery, createLightGallery%s);
-}
-
-document.addEventListener('htmx:afterRequest', (e) => {
- if (e.detail.xhr.status == 404 || e.detail.successful != true) {
- return console.error(e);
- }
- if (e.detail.target.id == 'general-page-body') {
- createLightGallery%s();
- }
-}, {once: true});
-</script>`, galleryID, galleryID, strings.Join(dynamicElements, ","), galleryID, galleryID))
- }
-
- return ast.WalkContinue, nil
-}
-
-func generateDivId(length int) string {
- const charset = "abcdefghijklmnopqrstuvwxyz"
- seededRand := rand.New(rand.NewSource(time.Now().UnixNano())) // Seed with current time
- b := make([]byte, length)
- for i := range b {
- b[i] = charset[seededRand.Intn(len(charset))]
- }
- return string(b)
-}
diff --git a/internal/router/api-v1-blog-search.go b/internal/router/api-v1-blog-search.go
index 69be651..ab5425c 100644
--- a/internal/router/api-v1-blog-search.go
+++ b/internal/router/api-v1-blog-search.go
@@ -10,6 +10,7 @@ import (
"strings"
"time"
+ "github.com/SayaAndy/saya-today-web/config"
"github.com/SayaAndy/saya-today-web/internal/b2"
"github.com/SayaAndy/saya-today-web/locale"
"github.com/gofiber/fiber/v2"
@@ -19,17 +20,21 @@ func init() {
tm.Add("catalogue-blog-cards", "views/partials/catalogue-blog-cards.html", "views/partials/catalogue-blog-card-tags.html")
}
-func Api_V1_BlogSearch(l map[string]*locale.LocaleConfig, langs []string, b2Client *b2.B2Client) func(c *fiber.Ctx) error {
+func Api_V1_BlogSearch(l map[string]*locale.LocaleConfig, langs []config.AvailableLanguageConfig, b2Client *b2.B2Client) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
sort := c.Query("sort")
lang := c.Query("lang")
tz := c.Query("tz")
- if !slices.Contains(langs, lang) {
- c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
- return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language", lang))
+ for _, availableLang := range langs {
+ if availableLang.Name == lang {
+ goto langIsAvailable
+ }
}
+ c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
+ return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language", lang))
+ langIsAvailable:
loc, err := time.LoadLocation(tz)
if err != nil {
loc = time.UTC
diff --git a/internal/router/api-v1-general-page-body.go b/internal/router/api-v1-general-page-body.go
index b2fa268..fe21957 100644
--- a/internal/router/api-v1-general-page-body.go
+++ b/internal/router/api-v1-general-page-body.go
@@ -11,6 +11,7 @@ import (
"strings"
"time"
+ "github.com/SayaAndy/saya-today-web/config"
"github.com/SayaAndy/saya-today-web/internal/b2"
"github.com/SayaAndy/saya-today-web/internal/factgiver"
"github.com/SayaAndy/saya-today-web/locale"
@@ -24,7 +25,7 @@ func init() {
tm.Add("general-page-body", "views/partials/general-page-body.html")
}
-func Api_V1_GeneralPage_Body(l map[string]*locale.LocaleConfig, langs []string, b2Client *b2.B2Client, md goldmark.Markdown) func(c *fiber.Ctx) error {
+func Api_V1_GeneralPage_Body(l map[string]*locale.LocaleConfig, langs []config.AvailableLanguageConfig, b2Client *b2.B2Client, md goldmark.Markdown) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
@@ -45,19 +46,26 @@ func Api_V1_GeneralPage_Body(l map[string]*locale.LocaleConfig, langs []string,
return c.Status(fiber.StatusOK).Type("html").Send(val)
}
+ lang := ""
pathParts := strings.Split(strings.Trim(path, "/"), "/")
- if len(pathParts) == 0 {
- return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'")
+ if len(pathParts) == 1 && pathParts[0] == "" {
+ pathParts = []string{}
}
-
- lang := pathParts[0]
- if !slices.Contains(langs, lang) {
- return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language... yet??", lang))
+ if len(pathParts) > 0 {
+ lang = pathParts[0]
+ for _, availableLang := range langs {
+ if availableLang.Name == lang {
+ goto langIsAvailable
+ }
+ }
+ return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'")
}
+ langIsAvailable:
values := fiber.Map{
"L": l[lang],
"Lang": lang,
+ "Path": strings.Trim(path, "/"),
"QueryString": string(c.Request().URI().QueryString()),
}
var additionalTemplates []string
@@ -136,6 +144,8 @@ func Api_V1_GeneralPage_Body(l map[string]*locale.LocaleConfig, langs []string,
values["MapLocationAreaMeters"] = areaError
values["Title"] = metadata.Title
values["ParsedMarkdown"] = template.HTML(parsedMarkdown)
+ values["PublishedDate"] = metadata.PublishedTime.Format("2006-01-02 15:04:05 -07:00")
+ values["ActionDate"] = metadata.ActionDate
additionalTemplates = append(additionalTemplates, "views/pages/blog-page.html")
@@ -147,6 +157,10 @@ func Api_V1_GeneralPage_Body(l map[string]*locale.LocaleConfig, langs []string,
values["GifName"] = fmt.Sprintf("otter-%d.gif", rand.Int()%3+1)
values["FunFacts"] = FactGiver.Give(lang)
additionalTemplates = append(additionalTemplates, "views/pages/home-page.html")
+ } else if len(pathParts) == 0 {
+ values["Title"] = "Choose Your Language"
+ values["AvailableLanguages"] = langs
+ additionalTemplates = append(additionalTemplates, "views/pages/language-pick.html")
}
content, err := tm.Render("general-page-body", values, additionalTemplates...)
diff --git a/internal/router/api-v1-general-page-bottom-embeds.go b/internal/router/api-v1-general-page-bottom-embeds.go
index c4d9543..0a2030f 100644
--- a/internal/router/api-v1-general-page-bottom-embeds.go
+++ b/internal/router/api-v1-general-page-bottom-embeds.go
@@ -4,10 +4,10 @@ import (
"fmt"
"log/slog"
"net/url"
- "slices"
"strings"
"time"
+ "github.com/SayaAndy/saya-today-web/config"
"github.com/SayaAndy/saya-today-web/internal/b2"
"github.com/SayaAndy/saya-today-web/locale"
"github.com/gofiber/fiber/v2"
@@ -17,7 +17,7 @@ func init() {
tm.Add("general-page-bottom-embeds", "views/partials/general-page-bottom-embeds.html")
}
-func Api_V1_GeneralPage_BottomEmbeds(l map[string]*locale.LocaleConfig, langs []string, b2Client *b2.B2Client) func(c *fiber.Ctx) error {
+func Api_V1_GeneralPage_BottomEmbeds(l map[string]*locale.LocaleConfig, langs []config.AvailableLanguageConfig, b2Client *b2.B2Client) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
@@ -37,19 +37,26 @@ func Api_V1_GeneralPage_BottomEmbeds(l map[string]*locale.LocaleConfig, langs []
return c.Status(fiber.StatusOK).Type("html").Send(val)
}
+ lang := ""
pathParts := strings.Split(strings.Trim(path, "/"), "/")
- if len(pathParts) == 0 {
- return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'")
+ if len(pathParts) == 1 && pathParts[0] == "" {
+ pathParts = []string{}
}
-
- lang := pathParts[0]
- if !slices.Contains(langs, lang) {
- return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language... yet??", lang))
+ if len(pathParts) > 0 {
+ lang = pathParts[0]
+ for _, availableLang := range langs {
+ if availableLang.Name == lang {
+ goto langIsAvailable
+ }
+ }
+ return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'")
}
+ langIsAvailable:
values := fiber.Map{
"L": l[lang],
"Lang": lang,
+ "Path": strings.Trim(path, "/"),
"QueryString": string(c.Request().URI().QueryString()),
}
var additionalTemplates []string
@@ -60,6 +67,8 @@ func Api_V1_GeneralPage_BottomEmbeds(l map[string]*locale.LocaleConfig, langs []
additionalTemplates = append(additionalTemplates, "views/pages/blog-page.html")
} else if len(pathParts) == 1 {
additionalTemplates = append(additionalTemplates, "views/pages/home-page.html")
+ } else if len(pathParts) == 0 {
+ additionalTemplates = append(additionalTemplates, "views/pages/language-pick.html")
}
content, err := tm.Render("general-page-bottom-embeds", values, additionalTemplates...)
diff --git a/internal/router/api-v1-general-page-footer.go b/internal/router/api-v1-general-page-footer.go
index 003f54c..2030b3b 100644
--- a/internal/router/api-v1-general-page-footer.go
+++ b/internal/router/api-v1-general-page-footer.go
@@ -4,10 +4,10 @@ import (
"fmt"
"log/slog"
"net/url"
- "slices"
"strings"
"time"
+ "github.com/SayaAndy/saya-today-web/config"
"github.com/SayaAndy/saya-today-web/locale"
"github.com/gofiber/fiber/v2"
)
@@ -16,7 +16,7 @@ func init() {
tm.Add("general-page-footer", "views/partials/general-page-footer.html")
}
-func Api_V1_GeneralPage_Footer(l map[string]*locale.LocaleConfig, langs []string) func(c *fiber.Ctx) error {
+func Api_V1_GeneralPage_Footer(l map[string]*locale.LocaleConfig, langs []config.AvailableLanguageConfig) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
@@ -37,19 +37,26 @@ func Api_V1_GeneralPage_Footer(l map[string]*locale.LocaleConfig, langs []string
return c.Status(fiber.StatusOK).Type("html").Send(val)
}
+ lang := ""
pathParts := strings.Split(strings.Trim(path, "/"), "/")
- if len(pathParts) == 0 {
- return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'")
+ if len(pathParts) == 1 && pathParts[0] == "" {
+ pathParts = []string{}
}
-
- lang := pathParts[0]
- if !slices.Contains(langs, lang) {
- return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language... yet??", lang))
+ if len(pathParts) > 0 {
+ lang = pathParts[0]
+ for _, availableLang := range langs {
+ if availableLang.Name == lang {
+ goto langIsAvailable
+ }
+ }
+ return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'")
}
+ langIsAvailable:
values := fiber.Map{
"L": l[lang],
"Lang": lang,
+ "Path": strings.Trim(path, "/"),
"QueryString": string(c.Request().URI().QueryString()),
}
var additionalTemplates []string
@@ -60,6 +67,8 @@ func Api_V1_GeneralPage_Footer(l map[string]*locale.LocaleConfig, langs []string
additionalTemplates = append(additionalTemplates, "views/pages/blog-page.html")
} else if len(pathParts) == 1 {
additionalTemplates = append(additionalTemplates, "views/pages/home-page.html")
+ } else if len(pathParts) == 0 {
+ additionalTemplates = append(additionalTemplates, "views/pages/language-pick.html")
}
content, err := tm.Render("general-page-footer", values, additionalTemplates...)
diff --git a/internal/router/api-v1-general-page-header.go b/internal/router/api-v1-general-page-header.go
index 80228f7..a2c832b 100644
--- a/internal/router/api-v1-general-page-header.go
+++ b/internal/router/api-v1-general-page-header.go
@@ -4,10 +4,10 @@ import (
"fmt"
"log/slog"
"net/url"
- "slices"
"strings"
"time"
+ "github.com/SayaAndy/saya-today-web/config"
"github.com/SayaAndy/saya-today-web/internal/b2"
"github.com/SayaAndy/saya-today-web/locale"
"github.com/gofiber/fiber/v2"
@@ -17,7 +17,7 @@ func init() {
tm.Add("general-page-header", "views/partials/general-page-header.html")
}
-func Api_V1_GeneralPage_Header(l map[string]*locale.LocaleConfig, langs []string, b2Client *b2.B2Client) func(c *fiber.Ctx) error {
+func Api_V1_GeneralPage_Header(l map[string]*locale.LocaleConfig, langs []config.AvailableLanguageConfig, b2Client *b2.B2Client) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
@@ -38,19 +38,26 @@ func Api_V1_GeneralPage_Header(l map[string]*locale.LocaleConfig, langs []string
return c.Status(fiber.StatusOK).Type("html").Send(val)
}
+ lang := ""
pathParts := strings.Split(strings.Trim(path, "/"), "/")
- if len(pathParts) == 0 {
- return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'")
+ if len(pathParts) == 1 && pathParts[0] == "" {
+ pathParts = []string{}
}
-
- lang := pathParts[0]
- if !slices.Contains(langs, lang) {
- return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language... yet??", lang))
+ if len(pathParts) > 0 {
+ lang = pathParts[0]
+ for _, availableLang := range langs {
+ if availableLang.Name == lang {
+ goto langIsAvailable
+ }
+ }
+ return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'")
}
+ langIsAvailable:
values := fiber.Map{
"L": l[lang],
"Lang": lang,
+ "Path": strings.Trim(path, "/"),
"QueryString": string(c.Request().URI().QueryString()),
}
var additionalTemplates []string
@@ -70,6 +77,9 @@ func Api_V1_GeneralPage_Header(l map[string]*locale.LocaleConfig, langs []string
} else if len(pathParts) == 1 {
values["Title"] = l[lang].HomePage.Header
additionalTemplates = append(additionalTemplates, "views/pages/home-page.html")
+ } else if len(pathParts) == 0 {
+ values["Title"] = "Choose Your Language"
+ additionalTemplates = append(additionalTemplates, "views/pages/language-pick.html")
}
content, err := tm.Render("general-page-header", values, additionalTemplates...)
diff --git a/internal/router/api-v1-general-page-top-embeds.go b/internal/router/api-v1-general-page-top-embeds.go
index 78021b2..544e3a4 100644
--- a/internal/router/api-v1-general-page-top-embeds.go
+++ b/internal/router/api-v1-general-page-top-embeds.go
@@ -4,10 +4,10 @@ import (
"fmt"
"log/slog"
"net/url"
- "slices"
"strings"
"time"
+ "github.com/SayaAndy/saya-today-web/config"
"github.com/SayaAndy/saya-today-web/locale"
"github.com/gofiber/fiber/v2"
)
@@ -16,7 +16,7 @@ func init() {
tm.Add("general-page-top-embeds", "views/partials/general-page-top-embeds.html")
}
-func Api_V1_GeneralPage_TopEmbeds(l map[string]*locale.LocaleConfig, langs []string) func(c *fiber.Ctx) error {
+func Api_V1_GeneralPage_TopEmbeds(l map[string]*locale.LocaleConfig, langs []config.AvailableLanguageConfig) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
@@ -37,19 +37,26 @@ func Api_V1_GeneralPage_TopEmbeds(l map[string]*locale.LocaleConfig, langs []str
return c.Status(fiber.StatusOK).Type("html").Send(val)
}
+ lang := ""
pathParts := strings.Split(strings.Trim(path, "/"), "/")
- if len(pathParts) == 0 {
- return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'")
+ if len(pathParts) == 1 && pathParts[0] == "" {
+ pathParts = []string{}
}
-
- lang := pathParts[0]
- if !slices.Contains(langs, lang) {
- return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language... yet??", lang))
+ if len(pathParts) > 0 {
+ lang = pathParts[0]
+ for _, availableLang := range langs {
+ if availableLang.Name == lang {
+ goto langIsAvailable
+ }
+ }
+ return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'")
}
+ langIsAvailable:
values := fiber.Map{
"L": l[lang],
"Lang": lang,
+ "Path": strings.Trim(path, "/"),
"QueryString": string(c.Request().URI().QueryString()),
}
var additionalTemplates []string
@@ -60,6 +67,8 @@ func Api_V1_GeneralPage_TopEmbeds(l map[string]*locale.LocaleConfig, langs []str
additionalTemplates = append(additionalTemplates, "views/pages/blog-page.html")
} else if len(pathParts) == 1 {
additionalTemplates = append(additionalTemplates, "views/pages/home-page.html")
+ } else if len(pathParts) == 0 {
+ additionalTemplates = append(additionalTemplates, "views/pages/language-pick.html")
}
content, err := tm.Render("general-page-top-embeds", values, additionalTemplates...)
diff --git a/internal/router/api-v1-general-page.go b/internal/router/api-v1-general-page.go
index d0ea559..a77fc5e 100644
--- a/internal/router/api-v1-general-page.go
+++ b/internal/router/api-v1-general-page.go
@@ -1,11 +1,10 @@
package router
import (
- "fmt"
"log/slog"
- "slices"
"strings"
+ "github.com/SayaAndy/saya-today-web/config"
"github.com/SayaAndy/saya-today-web/locale"
"github.com/gofiber/fiber/v2"
)
@@ -14,21 +13,27 @@ func init() {
tm.Add("general-page", "views/layouts/general-page.html")
}
-func Api_V1_GeneralPage(l map[string]*locale.LocaleConfig, langs []string) func(c *fiber.Ctx) error {
+func Api_V1_GeneralPage(l map[string]*locale.LocaleConfig, langs []config.AvailableLanguageConfig) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
path := c.Path()
+ lang := ""
pathParts := strings.Split(strings.Trim(path, "/"), "/")
- if len(pathParts) == 0 {
- return c.Status(fiber.ErrBadRequest.Code).SendString("url path is invalid: expect format '/{lang}/...'")
+ if len(pathParts) == 1 && pathParts[0] == "" {
+ pathParts = []string{}
}
-
- lang := pathParts[0]
- if !slices.Contains(langs, lang) {
- return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language... yet??", lang))
+ if len(pathParts) > 0 {
+ lang = pathParts[0]
+ for _, availableLang := range langs {
+ if availableLang.Name == lang {
+ goto langIsAvailable
+ }
+ }
+ return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'")
}
+ langIsAvailable:
cacheKey := "general-page." + lang
if val, ok := PCache.Get(cacheKey); val != nil && ok {
c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
diff --git a/internal/router/lang-blog-title.go b/internal/router/lang-blog-title.go
index d6ca917..6e80671 100644
--- a/internal/router/lang-blog-title.go
+++ b/internal/router/lang-blog-title.go
@@ -5,10 +5,10 @@ import (
"fmt"
"html/template"
"log/slog"
- "slices"
"strconv"
"strings"
+ "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/locale"
@@ -20,17 +20,20 @@ func init() {
tm.Add("blog-page", "views/layouts/general-page.html", "views/pages/blog-page.html")
}
-func Lang_Blog_Title(l map[string]*locale.LocaleConfig, langs []string, b2Client *b2.B2Client, md goldmark.Markdown) func(c *fiber.Ctx) error {
+func Lang_Blog_Title(l map[string]*locale.LocaleConfig, langs []config.AvailableLanguageConfig, b2Client *b2.B2Client, md goldmark.Markdown) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
ip := c.IP()
slog.Debug("client entering blog page", slog.String("ip", ip), slog.String("page", c.Path()))
lang := c.Params("lang")
- if !slices.Contains(langs, lang) {
- c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
- return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language... yet??", lang))
+ for _, availableLang := range langs {
+ if availableLang.Name == lang {
+ goto langIsAvailable
+ }
}
+ return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language", lang))
+ langIsAvailable:
metadata, parsedMarkdown, err := readBlogPost(md, b2Client, lang+"/"+c.Params("title"))
if err != nil {
c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
diff --git a/internal/router/lang-blog.go b/internal/router/lang-blog.go
index 38a6597..960ac3d 100644
--- a/internal/router/lang-blog.go
+++ b/internal/router/lang-blog.go
@@ -8,6 +8,7 @@ import (
"slices"
"strings"
+ "github.com/SayaAndy/saya-today-web/config"
"github.com/SayaAndy/saya-today-web/internal/b2"
"github.com/SayaAndy/saya-today-web/locale"
"github.com/gofiber/fiber/v2"
@@ -17,14 +18,17 @@ func init() {
tm.Add("blog-catalogue", "views/layouts/general-page.html", "views/pages/blog-catalogue.html")
}
-func Lang_Blog(l map[string]*locale.LocaleConfig, langs []string, b2Client *b2.B2Client) func(c *fiber.Ctx) error {
+func Lang_Blog(l map[string]*locale.LocaleConfig, langs []config.AvailableLanguageConfig, b2Client *b2.B2Client) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
lang := c.Params("lang")
- if !slices.Contains(langs, lang) {
- c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
- return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language... yet??", lang))
+ for _, availableLang := range langs {
+ if availableLang.Name == lang {
+ goto langIsAvailable
+ }
}
+ return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language", lang))
+ langIsAvailable:
querySort := c.Query("sort")
if querySort == "" {
querySort = "publicationDateDesc"
diff --git a/internal/router/lang-map.go b/internal/router/lang-map.go
index e94d756..a744f02 100644
--- a/internal/router/lang-map.go
+++ b/internal/router/lang-map.go
@@ -3,10 +3,10 @@ package router
import (
"fmt"
"log/slog"
- "slices"
"strconv"
"strings"
+ "github.com/SayaAndy/saya-today-web/config"
"github.com/SayaAndy/saya-today-web/internal/b2"
"github.com/SayaAndy/saya-today-web/locale"
"github.com/gofiber/fiber/v2"
@@ -16,14 +16,17 @@ func init() {
tm.Add("global-map", "views/pages/global-map.html")
}
-func Lang_Map(l map[string]*locale.LocaleConfig, langs []string, b2Client *b2.B2Client) func(c *fiber.Ctx) error {
+func Lang_Map(l map[string]*locale.LocaleConfig, langs []config.AvailableLanguageConfig, b2Client *b2.B2Client) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
lang := c.Params("lang")
- if !slices.Contains(langs, lang) {
- c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
- return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language... yet??", lang))
+ for _, availableLang := range langs {
+ if availableLang.Name == lang {
+ goto langIsAvailable
+ }
}
+ return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server does not support '%s' language", lang))
+ langIsAvailable:
pages, err := b2Client.Scan(lang + "/")
status := fiber.StatusOK
if err != nil {
diff --git a/internal/tailwind/link_renderer.go b/internal/tailwind/link_renderer.go
new file mode 100644
index 0000000..ff3d437
--- /dev/null
+++ b/internal/tailwind/link_renderer.go
@@ -0,0 +1,49 @@
+package tailwind
+
+import (
+ "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/renderer"
+ "github.com/yuin/goldmark/renderer/html"
+ "github.com/yuin/goldmark/util"
+)
+
+type CustomLinkRenderer struct {
+ html.Config
+}
+
+func NewCustomLinkRenderer(opts ...html.Option) renderer.NodeRenderer {
+ r := &CustomLinkRenderer{
+ Config: html.NewConfig(),
+ }
+ for _, opt := range opts {
+ opt.SetHTMLOption(&r.Config)
+ }
+ return r
+}
+
+func (r *CustomLinkRenderer) renderLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
+ n := node.(*ast.Link)
+ if entering {
+ _, _ = w.WriteString("<a href=\"")
+ if r.Unsafe || !html.IsDangerousURL(n.Destination) {
+ _, _ = w.Write(util.EscapeHTML(util.URLEscape(n.Destination, true)))
+ }
+ _, _ = w.WriteString(`"`)
+ if n.Title != nil {
+ _, _ = w.WriteString(` title="`)
+ _, _ = w.Write(util.EscapeHTML(n.Title))
+ _, _ = w.WriteString(`"`)
+ }
+ if n.Attributes() != nil {
+ html.RenderAttributes(w, n, nil)
+ }
+ _, _ = w.WriteString(">")
+ } else {
+ _, _ = w.WriteString("</a>")
+ }
+ return ast.WalkContinue, nil
+}
+
+func (r *CustomLinkRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+ reg.Register(ast.KindLink, r.renderLink)
+}
diff --git a/internal/tailwind/transformer.go b/internal/tailwind/transformer.go
index f932533..c53ded1 100644
--- a/internal/tailwind/transformer.go
+++ b/internal/tailwind/transformer.go
@@ -1,6 +1,9 @@
package tailwind
import (
+ "bytes"
+ "fmt"
+
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/text"
@@ -17,19 +20,19 @@ func (t *TailwindTransformer) Transform(node *ast.Document, reader text.Reader,
switch node := n.(type) {
case *ast.Heading:
classes := map[int]string{
- 1: "font-patua text-4xl font-bold text-main-dark mb-3 tracking-[.0125rem]",
- 2: "font-spectral text-3xl font-bold text-main-dark mb-1 tracking-[.0125rem]",
- 3: "font-spectral text-2xl font-medium text-main-dark mb-0.8 tracking-[.0125rem]",
- 4: "font-spectral text-xl font-medium text-main-medium mb-0.5 tracking-[.0125rem]",
- 5: "font-spectral text-base font-medium text-main-medium mb-0.5 italic tracking-[.0125rem]",
- 6: "font-spectral text-base font-medium text-secondary mb-0.5",
+ 1: "font-andika text-4xl font-bold text-main-hard mb-3 tracking-[.0125rem]",
+ 2: "font-andika text-3xl font-bold text-main-hard mb-1 tracking-[.0125rem]",
+ 3: "font-andika text-2xl font-medium text-main-hard mb-0.8 tracking-[.0125rem]",
+ 4: "font-andika text-xl font-medium text-main-medium mb-0.5 tracking-[.0125rem]",
+ 5: "font-andika text-base font-medium text-main-medium mb-0.5 italic tracking-[.0125rem]",
+ 6: "font-andika text-base font-medium text-secondary mb-0.5",
}
if class, ok := classes[node.Level]; ok {
node.SetAttribute([]byte("class"), []byte(class))
}
case *ast.Paragraph:
- node.SetAttribute([]byte("class"), []byte("text-base/[2] font-spectral tracking-[.0125rem] -indent-8 ml-4 mb-8"))
+ node.SetAttribute([]byte("class"), []byte("text-base/[2] font-andika tracking-[.0125rem] -indent-8 ml-4 mb-8"))
case *ast.List:
if node.IsOrdered() {
@@ -39,7 +42,7 @@ func (t *TailwindTransformer) Transform(node *ast.Document, reader text.Reader,
}
case *ast.ListItem:
- node.SetAttribute([]byte("class"), []byte("text-base/[2] font-spectral tracking-[.0125rem]"))
+ node.SetAttribute([]byte("class"), []byte("text-base/[2] font-andika tracking-[.0125rem]"))
case *ast.Blockquote:
node.SetAttribute([]byte("class"), []byte("border-l-2 border-main-medium bg-background-dark p-1 mb-2 italic"))
@@ -51,7 +54,11 @@ func (t *TailwindTransformer) Transform(node *ast.Document, reader text.Reader,
node.SetAttribute([]byte("class"), []byte("bg-background-dark p-1 rounded-lg overflow-x-auto mb-4"))
case *ast.Link:
- node.SetAttribute([]byte("class"), []byte("text-secondary hover:text-main-dark underline"))
+ if bytes.HasPrefix(node.Destination, []byte{'.', '/'}) {
+ onclickAttr := fmt.Appendf(make([]byte, 0, 21+len(node.Destination)), "return changeUrl('%s');", node.Destination)
+ node.SetAttribute([]byte("onclick"), onclickAttr)
+ }
+ node.SetAttribute([]byte("class"), []byte("text-secondary hover:text-main-hard underline"))
case *ast.Image:
node.SetAttribute([]byte("class"), []byte("max-w-full h-auto rounded-lg shadow-lg mb-4"))
diff --git a/internal/templatemanager/templatemanager.go b/internal/templatemanager/templatemanager.go
index 4bf9a49..eebc297 100644
--- a/internal/templatemanager/templatemanager.go
+++ b/internal/templatemanager/templatemanager.go
@@ -37,7 +37,7 @@ func NewTemplateManager(templates ...TemplateManagerTemplates) (*TemplateManager
templateMap := make(map[string]templateManagerRender)
for _, tmplStruct := range templates {
- tmpl := template.New("").Funcs(templateFuncMap)
+ tmpl := template.New(tmplStruct.Name).Funcs(templateFuncMap)
tmpl, err := tmpl.ParseFiles(tmplStruct.Files...)
if err != nil {
return nil, err
@@ -84,7 +84,7 @@ func (tm *TemplateManager) Add(name string, files ...string) error {
return fmt.Errorf("you can't add template without any files")
}
- tmpl := template.New("").Funcs(templateFuncMap)
+ tmpl := template.New(name).Funcs(templateFuncMap)
tmpl, err := tmpl.ParseFiles(files...)
if err != nil {