summaryrefslogtreecommitdiff
path: root/internal
diff refs
from: back
to: back
| flip
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/factgiver/factgiver.go67
-rw-r--r--internal/frontmatter/parser.go2
-rw-r--r--internal/glightbox/block.go32
-rw-r--r--internal/glightbox/html_renderer.go188
-rw-r--r--internal/lightgallery/block.go31
-rw-r--r--internal/lightgallery/extension.go (renamed from internal/glightbox/extension.go)14
-rw-r--r--internal/lightgallery/html_renderer.go137
-rw-r--r--internal/lightgallery/parser.go (renamed from internal/glightbox/parser.go)42
-rw-r--r--internal/router/api-v1-blog-search.go19
-rw-r--r--internal/router/api-v1-general-page-body.go164
-rw-r--r--internal/router/api-v1-general-page-bottom-embeds.go75
-rw-r--r--internal/router/api-v1-general-page-footer.go75
-rw-r--r--internal/router/api-v1-general-page-header.go85
-rw-r--r--internal/router/api-v1-general-page-top-embeds.go75
-rw-r--r--internal/router/api-v1-general-page.go52
-rw-r--r--internal/router/api-v1-like.go15
-rw-r--r--internal/router/client-cache.go254
-rw-r--r--internal/router/lang-blog-title.go1
-rw-r--r--internal/router/lang-blog.go3
-rw-r--r--internal/router/page-cache.go5
-rw-r--r--internal/tailwind/link_renderer.go49
-rw-r--r--internal/tailwind/transformer.go33
-rw-r--r--internal/templatemanager/templatemanager.go38
23 files changed, 245 insertions, 1211 deletions
diff --git a/internal/factgiver/factgiver.go b/internal/factgiver/factgiver.go
deleted file mode 100644
index 6f77403..0000000
--- a/internal/factgiver/factgiver.go
+++ /dev/null
@@ -1,67 +0,0 @@
-package factgiver
-
-import (
- "fmt"
- "math/rand"
- "regexp"
- "strings"
- "time"
-
- "github.com/SayaAndy/saya-today-web/config"
- "github.com/SayaAndy/saya-today-web/internal/b2"
-)
-
-type FactGiver struct {
- b2Client *b2.B2Client
- cache map[string][]string
- langs []string
- factsFileName string
- nlRe *regexp.Regexp
- randGen *rand.Rand
-}
-
-func NewFactGiver(cfg *config.FactGiverConfig, langs []string) (*FactGiver, error) {
- b2Client, err := b2.NewB2Client(&cfg.Storage.Config)
- if err != nil {
- return nil, fmt.Errorf("fail to init b2 client for a new fact giver: %s", err.Error())
- }
-
- factGiver := &FactGiver{
- b2Client: b2Client,
- cache: make(map[string][]string, len(langs)),
- langs: langs,
- factsFileName: cfg.FactsFileName,
- nlRe: regexp.MustCompile(`\r?\n`),
- randGen: rand.New(rand.NewSource(time.Now().UnixNano())),
- }
- if err = factGiver.initCache(); err != nil {
- return nil, fmt.Errorf("fail to init cache for a new fact giver: %s", err.Error())
- }
-
- return factGiver, nil
-}
-
-func (g *FactGiver) Give(lang string) [3]string {
- factSlice := make([]string, len(g.cache[lang]))
- copy(factSlice, g.cache[lang])
- g.randGen.Shuffle(len(factSlice), func(i, j int) {
- factSlice[i], factSlice[j] = factSlice[j], factSlice[i]
- })
- return [3]string{factSlice[0], factSlice[1], factSlice[2]}
-}
-
-func (g *FactGiver) initCache() error {
- for _, lang := range g.langs {
- localFacts := strings.Replace(g.factsFileName, "*", lang, 1)
- factsContentBytes, err := g.b2Client.ReadAll(localFacts)
- if err != nil {
- return fmt.Errorf("fail to read '%s' facts file: %s", lang, err.Error())
- }
- factsContent := string(factsContentBytes)
- g.cache[lang] = g.nlRe.Split(factsContent, -1)
- if g.cache[lang][len(g.cache[lang])-1] == "" {
- g.cache[lang] = g.cache[lang][:len(g.cache[lang])-1]
- }
- }
- return nil
-}
diff --git a/internal/frontmatter/parser.go b/internal/frontmatter/parser.go
index 83312dc..1a521c0 100644
--- a/internal/frontmatter/parser.go
+++ b/internal/frontmatter/parser.go
@@ -16,8 +16,6 @@ type Metadata struct {
Thumbnail string `yaml:"thumbnail"`
Tags []string `yaml:"tags"`
Geolocation string `yaml:"geolocation"`
- Medley string `yaml:"medley"`
- MedleyPart int `yaml:"medleyPart"`
}
func ParseFrontmatter(content []byte) (metadata *Metadata, markdown []byte, err error) {
diff --git a/internal/glightbox/block.go b/internal/glightbox/block.go
deleted file mode 100644
index e53e67b..0000000
--- a/internal/glightbox/block.go
+++ /dev/null
@@ -1,32 +0,0 @@
-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/glightbox/html_renderer.go b/internal/glightbox/html_renderer.go
deleted file mode 100644
index 44fbd4c..0000000
--- a/internal/glightbox/html_renderer.go
+++ /dev/null
@@ -1,188 +0,0 @@
-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-%s grid-item %s grid-item-%s p-1"
- data-gallery="gallery-%s" data-title="%s" %s>
- <picture>
- <source media="(width < 640px)" srcset="https://f003.backblazeb2.com/file/sayana-photos/webp-320p/%s.webp" />
- <source media="(width < 1120px)" srcset="https://f003.backblazeb2.com/file/sayana-photos/webp-560p/%s.webp" />
- <source media="(width < 1600px)" srcset="https://f003.backblazeb2.com/file/sayana-photos/webp-800p/%s.webp" />
- <source media="(width < 2400px)" srcset="https://f003.backblazeb2.com/file/sayana-photos/webp-1200p/%s.webp" />
- <source media="(width >= 2400px)" srcset="https://f003.backblazeb2.com/file/sayana-photos/webp-1600p/%s.webp" />
- <img src="https://f003.backblazeb2.com/file/sayana-photos/webp-800p/%s.webp" />
- </picture>
- <span class="grid-tooltip-text"><p>%s</p></span>
- <span class="grid-item-index">%d</span>
- </a>`, img.URL, galleryID, strings.Join(tagClassList, " "), galleryID, 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 m-1">
- <hr class="border-t-3 border-dotted border-main-dark 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-dark mt-1 mb-2 w-[80%%] ml-auto mr-auto">
-</div>`, galleryID, galleryID, strings.Join(elements, "\n")))
-
- w.WriteString(fmt.Sprintf(`
-<script>
- var lightbox_%s = GLightbox({
- selector: '.glightbox-%s',
- moreLength: 0
- });
-
- var msnry_%s = new Masonry('.masonry-grid-%s', {
- itemSelector: '.grid-item-%s',
- columnWidth: '.grid-sizer-%s',
- percentPosition: true,
- horizontalOrder: true
- });
-
- var imgLoad_%s_timer;
- var imgLoad_%s = imagesLoaded('.masonry-grid-%s');
-
- function initMasonryLayout_%s() {
- clearTimeout(imgLoad_%s_timer);
- imgLoad_%s_timer = setTimeout(() => msnry_%s.layout(), 500);
- }
-
- imgLoad_%s.on('progress', initMasonryLayout_%s);
-
- document.addEventListener('popout', (e) => {
- imgLoad_%s.off('progress', initMasonryLayout_%s);
- initMasonryLayout_%s = null;
- imgLoad_%s = null;
- });
-</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/block.go b/internal/lightgallery/block.go
new file mode 100644
index 0000000..8eb7c9e
--- /dev/null
+++ b/internal/lightgallery/block.go
@@ -0,0 +1,31 @@
+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/glightbox/extension.go b/internal/lightgallery/extension.go
index 7fd17b1..761dc05 100644
--- a/internal/glightbox/extension.go
+++ b/internal/lightgallery/extension.go
@@ -1,4 +1,4 @@
-package glightbox
+package lightgallery
import (
"github.com/yuin/goldmark"
@@ -8,21 +8,21 @@ import (
)
// Extension that combines parser and renderer
-type GLightboxExtension struct{}
+type LightGalleryExtension struct{}
-func NewGLightboxExtension() goldmark.Extender {
- return &GLightboxExtension{}
+func NewLightGalleryExtension() goldmark.Extender {
+ return &LightGalleryExtension{}
}
-func (e *GLightboxExtension) Extend(m goldmark.Markdown) {
+func (e *LightGalleryExtension) Extend(m goldmark.Markdown) {
m.Parser().AddOptions(
parser.WithBlockParsers(
- util.Prioritized(NewGLightboxParser(), 500),
+ util.Prioritized(NewLightGalleryParser(), 500),
),
)
m.Renderer().AddOptions(
renderer.WithNodeRenderers(
- util.Prioritized(NewGLightboxHTMLRenderer(), 500),
+ util.Prioritized(NewLightGalleryHTMLRenderer(), 500),
),
)
}
diff --git a/internal/lightgallery/html_renderer.go b/internal/lightgallery/html_renderer.go
new file mode 100644
index 0000000..17a6f2b
--- /dev/null
+++ b/internal/lightgallery/html_renderer.go
@@ -0,0 +1,137 @@
+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-2">
+ <hr class="border-t-4 border-dotted border-main-dark mt-[0.8vmin] mb-[0.8vmin] w-[80%%] ml-auto mr-auto">
+ <div id="lg-%s" class="inline-gallery-container relative ml-auto mr-auto"></div>
+ <hr class="border-t-4 border-dotted border-main-dark mt-[0.8vmin] mb-[0.8vmin] 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], ".")
+ imageNameParts := strings.Split(imageUrlWithoutExt, "-")
+
+ 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-[1vmax]/[1] text-left font-spectral text-main-dark">%s</p>
+ <p class="!text-[1vmax]/[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('DOMContentLoaded', createLightGallery%s);
+</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/glightbox/parser.go b/internal/lightgallery/parser.go
index 6780090..daec797 100644
--- a/internal/glightbox/parser.go
+++ b/internal/lightgallery/parser.go
@@ -1,10 +1,9 @@
-package glightbox
+package lightgallery
import (
"bytes"
"log/slog"
"regexp"
- "strings"
"time"
"github.com/yuin/goldmark/ast"
@@ -12,17 +11,17 @@ import (
"github.com/yuin/goldmark/text"
)
-type GLightboxParser struct{}
+type LightGalleryParser struct{}
-func NewGLightboxParser() parser.BlockParser {
- return &GLightboxParser{}
+func NewLightGalleryParser() parser.BlockParser {
+ return &LightGalleryParser{}
}
-func (p *GLightboxParser) Trigger() []byte {
+func (p *LightGalleryParser) Trigger() []byte {
return []byte{'{'}
}
-func (p *GLightboxParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) {
+func (p *LightGalleryParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) {
line, _ := reader.PeekLine()
if !bytes.HasPrefix(line, []byte("{Gallery:")) {
@@ -44,10 +43,10 @@ func (p *GLightboxParser) Open(parent ast.Node, reader text.Reader, pc parser.Co
return nil, parser.NoChildren
}
- return &GLightboxBlock{Location: loc}, parser.NoChildren
+ return &LightGalleryBlock{Location: loc}, parser.NoChildren
}
-func (p *GLightboxParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
+func (p *LightGalleryParser) 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
@@ -59,42 +58,31 @@ func (p *GLightboxParser) Continue(node ast.Node, reader text.Reader, pc parser.
return parser.Close
}
- gallery := node.(*GLightboxBlock)
+ gallery := node.(*LightGalleryBlock)
- parts := bytes.SplitN(trimmed, []byte{'|'}, 3)
+ parts := bytes.SplitN(trimmed, []byte{'|'}, 2)
url := bytes.TrimSpace(parts[0])
caption := make([]byte, 0)
- tagsRaw := ""
- if len(parts) == 2 {
+ if len(parts) > 1 {
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, GLightboxImage{
+ gallery.Images = append(gallery.Images, LightGalleryImage{
URL: string(url),
- Tags: tags,
Caption: caption,
})
return parser.Continue | parser.NoChildren
}
-func (p *GLightboxParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {
+func (p *LightGalleryParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {
}
-func (p *GLightboxParser) CanInterruptParagraph() bool {
+func (p *LightGalleryParser) CanInterruptParagraph() bool {
return true
}
-func (p *GLightboxParser) CanAcceptIndentedLine() bool {
+func (p *LightGalleryParser) CanAcceptIndentedLine() bool {
return false
}
diff --git a/internal/router/api-v1-blog-search.go b/internal/router/api-v1-blog-search.go
index 69be651..a809783 100644
--- a/internal/router/api-v1-blog-search.go
+++ b/internal/router/api-v1-blog-search.go
@@ -1,7 +1,6 @@
package router
import (
- "encoding/json"
"fmt"
"log/slog"
"net/url"
@@ -36,18 +35,10 @@ func Api_V1_BlogSearch(l map[string]*locale.LocaleConfig, langs []string, b2Clie
slog.Warn("unable to parse a client timezone, defaulting to UTC", slog.String("error", err.Error()), slog.String("tz", tz))
}
- cacheKey := "blog-search." + lang + ".pages-list"
- var pages []*b2.BlogPage
- if pagesBytes, ok := PCache.Get(cacheKey); pagesBytes != nil || ok {
- json.Unmarshal(pagesBytes, &pages)
- } else {
- 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))
- }
- pagesBytes, _ := json.Marshal(pages)
- PCache.SetWithTTL(cacheKey, pagesBytes, int64(len(pagesBytes)), 5*time.Minute)
+ 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()
@@ -78,8 +69,6 @@ func Api_V1_BlogSearch(l map[string]*locale.LocaleConfig, langs []string, b2Clie
"ShortDescription": page.Metadata.ShortDescription,
"Thumbnail": page.Metadata.Thumbnail,
"Tags": page.Metadata.Tags,
- "LikeCount": CCache.GetLikeCount(page.FileName),
- "ViewCount": CCache.GetViewCount(page.FileName),
})
break
}
diff --git a/internal/router/api-v1-general-page-body.go b/internal/router/api-v1-general-page-body.go
deleted file mode 100644
index c1a4a9f..0000000
--- a/internal/router/api-v1-general-page-body.go
+++ /dev/null
@@ -1,164 +0,0 @@
-package router
-
-import (
- "fmt"
- "html/template"
- "log/slog"
- "math/rand"
- "net/url"
- "regexp"
- "slices"
- "strings"
- "time"
-
- "github.com/SayaAndy/saya-today-web/internal/b2"
- "github.com/SayaAndy/saya-today-web/internal/factgiver"
- "github.com/SayaAndy/saya-today-web/locale"
- "github.com/gofiber/fiber/v2"
- "github.com/yuin/goldmark"
-)
-
-var FactGiver *factgiver.FactGiver
-
-func init() {
- tm.Add("general-page-body", "views/partials/general-page-body.html")
-}
-
-func Api_V1_GeneralPage_Body(l map[string]*locale.LocaleConfig, langs []string, b2Client *b2.B2Client, md goldmark.Markdown) func(c *fiber.Ctx) error {
- return func(c *fiber.Ctx) error {
- c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
-
- referer := c.Get("Referer", "")
- if referer == "" {
- return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is empty")
- }
- urlStruct, err := url.ParseRequestURI(referer)
- if err != nil {
- return c.Status(fiber.ErrBadRequest.Code).SendString(fmt.Sprintf("'Referer' header is invalid: %s", err.Error()))
- }
-
- path := urlStruct.EscapedPath()
-
- cacheKey := fmt.Sprintf("body.%s", path)
- if val, ok := PCache.Get(cacheKey); val != nil && ok {
- c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
- return c.Status(fiber.StatusOK).Type("html").Send(val)
- }
-
- pathParts := strings.Split(strings.Trim(path, "/"), "/")
- if len(pathParts) == 0 {
- return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'")
- }
-
- 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))
- }
-
- values := fiber.Map{
- "L": l[lang],
- "Lang": lang,
- "QueryString": string(c.Request().URI().QueryString()),
- }
- var additionalTemplates []string
-
- if len(pathParts) == 2 && pathParts[1] == "blog" {
- querySort := c.Query("sort")
- if querySort == "" {
- querySort = "publicationDateDesc"
- }
-
- 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()))
- return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate regex for tags gathering")
- }
- decodedQuery, _ := 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 + "/")
- if err != nil {
- slog.Warn("failed to scan pages via b2", slog.String("error", err.Error()))
- return c.Status(fiber.ErrInternalServerError.Code).SendString(fmt.Sprintf("failed to scan pages via b2: %s", slog.String("error", err.Error())))
- }
-
- tagsMap := make(map[string]int)
- for _, page := range pages {
- for _, tag := range page.Metadata.Tags {
- tagsMap[tag]++
- }
- }
- slog.Debug("enlist pages for catalogue", slog.Int("tag_count", len(tagsMap)), slog.Int("page_count", len(pages)), slog.String("path", c.Path()))
-
- 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)
- })
-
- values["Tags"] = tagsArray
- values["QuerySort"] = querySort
- values["QueryTags"] = strings.Join(queryTags, ",")
- values["Title"] = l[lang].BlogSearch.Header
-
- additionalTemplates = append(additionalTemplates, "views/pages/blog-catalogue.html")
- } else if len(pathParts) == 3 && pathParts[1] == "blog" {
- metadata, parsedMarkdown, err := readBlogPost(md, b2Client, lang+"/"+pathParts[2])
- if err != nil {
- return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("failed to find '%s' post", pathParts[2]))
- }
-
- geolocationParts := strings.Split(metadata.Geolocation, " ")
- var x, y, areaError string
- if len(geolocationParts) >= 2 {
- x = geolocationParts[0]
- y = geolocationParts[1]
- }
- if len(geolocationParts) >= 3 {
- areaError = geolocationParts[2]
- }
-
- values["MapLocationX"] = x
- values["MapLocationY"] = y
- 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")
-
- go CCache.View(c.IP(), pathParts[2])
- } else if len(pathParts) == 1 {
- values["Title"] = l[lang].HomePage.Header
- values["FilledHeartCount"] = uint(40)
- values["OutlineHeartCount"] = uint(40)
- values["GifName"] = fmt.Sprintf("otter-%d.gif", rand.Int()%3+1)
- values["FunFacts"] = FactGiver.Give(lang)
- additionalTemplates = append(additionalTemplates, "views/pages/home-page.html")
- }
-
- content, err := tm.Render("general-page-body", values, additionalTemplates...)
- if err != nil {
- slog.Warn("failed to generate div", slog.String("path", path), slog.String("error", err.Error()))
- return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div")
- }
-
- go PCache.SetWithTTL(cacheKey, content, int64(len(content)), 5*time.Minute)
- c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
- return c.Status(fiber.StatusOK).Type("html").Send(content)
- }
-}
diff --git a/internal/router/api-v1-general-page-bottom-embeds.go b/internal/router/api-v1-general-page-bottom-embeds.go
deleted file mode 100644
index c4d9543..0000000
--- a/internal/router/api-v1-general-page-bottom-embeds.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package router
-
-import (
- "fmt"
- "log/slog"
- "net/url"
- "slices"
- "strings"
- "time"
-
- "github.com/SayaAndy/saya-today-web/internal/b2"
- "github.com/SayaAndy/saya-today-web/locale"
- "github.com/gofiber/fiber/v2"
-)
-
-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 {
- return func(c *fiber.Ctx) error {
- c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
-
- referer := c.Get("Referer", "")
- if referer == "" {
- return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is empty")
- }
- urlStruct, err := url.ParseRequestURI(referer)
- if err != nil {
- return c.Status(fiber.ErrBadRequest.Code).SendString(fmt.Sprintf("'Referer' header is invalid: %s", err.Error()))
- }
-
- path := urlStruct.EscapedPath()
- cacheKey := fmt.Sprintf("bottom-embeds.%s", path)
- if val, ok := PCache.Get(cacheKey); val != nil && ok {
- c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
- return c.Status(fiber.StatusOK).Type("html").Send(val)
- }
-
- pathParts := strings.Split(strings.Trim(path, "/"), "/")
- if len(pathParts) == 0 {
- return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'")
- }
-
- 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))
- }
-
- values := fiber.Map{
- "L": l[lang],
- "Lang": lang,
- "QueryString": string(c.Request().URI().QueryString()),
- }
- var additionalTemplates []string
-
- if len(pathParts) == 2 && pathParts[1] == "blog" {
- additionalTemplates = append(additionalTemplates, "views/pages/blog-catalogue.html")
- } else if len(pathParts) == 3 && pathParts[1] == "blog" {
- additionalTemplates = append(additionalTemplates, "views/pages/blog-page.html")
- } else if len(pathParts) == 1 {
- additionalTemplates = append(additionalTemplates, "views/pages/home-page.html")
- }
-
- content, err := tm.Render("general-page-bottom-embeds", values, additionalTemplates...)
- if err != nil {
- slog.Warn("failed to generate div", slog.String("path", path), slog.String("error", err.Error()))
- return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div")
- }
-
- go PCache.SetWithTTL(cacheKey, content, int64(len(content)), 5*time.Minute)
- c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
- return c.Status(fiber.StatusOK).Type("html").Send(content)
- }
-}
diff --git a/internal/router/api-v1-general-page-footer.go b/internal/router/api-v1-general-page-footer.go
deleted file mode 100644
index 003f54c..0000000
--- a/internal/router/api-v1-general-page-footer.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package router
-
-import (
- "fmt"
- "log/slog"
- "net/url"
- "slices"
- "strings"
- "time"
-
- "github.com/SayaAndy/saya-today-web/locale"
- "github.com/gofiber/fiber/v2"
-)
-
-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 {
- return func(c *fiber.Ctx) error {
- c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
-
- referer := c.Get("Referer", "")
- if referer == "" {
- return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is empty")
- }
- urlStruct, err := url.ParseRequestURI(referer)
- if err != nil {
- return c.Status(fiber.ErrBadRequest.Code).SendString(fmt.Sprintf("'Referer' header is invalid: %s", err.Error()))
- }
-
- path := urlStruct.EscapedPath()
-
- cacheKey := fmt.Sprintf("footer.%s", path)
- if val, ok := PCache.Get(cacheKey); val != nil && ok {
- c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
- return c.Status(fiber.StatusOK).Type("html").Send(val)
- }
-
- pathParts := strings.Split(strings.Trim(path, "/"), "/")
- if len(pathParts) == 0 {
- return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'")
- }
-
- 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))
- }
-
- values := fiber.Map{
- "L": l[lang],
- "Lang": lang,
- "QueryString": string(c.Request().URI().QueryString()),
- }
- var additionalTemplates []string
-
- if len(pathParts) == 2 && pathParts[1] == "blog" {
- additionalTemplates = append(additionalTemplates, "views/pages/blog-catalogue.html")
- } else if len(pathParts) == 3 && pathParts[1] == "blog" {
- additionalTemplates = append(additionalTemplates, "views/pages/blog-page.html")
- } else if len(pathParts) == 1 {
- additionalTemplates = append(additionalTemplates, "views/pages/home-page.html")
- }
-
- content, err := tm.Render("general-page-footer", values, additionalTemplates...)
- if err != nil {
- slog.Warn("failed to generate div", slog.String("path", path), slog.String("error", err.Error()))
- return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div")
- }
-
- go PCache.SetWithTTL(cacheKey, content, int64(len(content)), 5*time.Minute)
- c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
- return c.Status(fiber.StatusOK).Type("html").Send(content)
- }
-}
diff --git a/internal/router/api-v1-general-page-header.go b/internal/router/api-v1-general-page-header.go
deleted file mode 100644
index 80228f7..0000000
--- a/internal/router/api-v1-general-page-header.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package router
-
-import (
- "fmt"
- "log/slog"
- "net/url"
- "slices"
- "strings"
- "time"
-
- "github.com/SayaAndy/saya-today-web/internal/b2"
- "github.com/SayaAndy/saya-today-web/locale"
- "github.com/gofiber/fiber/v2"
-)
-
-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 {
- return func(c *fiber.Ctx) error {
- c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
-
- referer := c.Get("Referer", "")
- if referer == "" {
- return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is empty")
- }
- urlStruct, err := url.ParseRequestURI(referer)
- if err != nil {
- return c.Status(fiber.ErrBadRequest.Code).SendString(fmt.Sprintf("'Referer' header is invalid: %s", err.Error()))
- }
-
- path := urlStruct.EscapedPath()
-
- cacheKey := fmt.Sprintf("header.%s", path)
- if val, ok := PCache.Get(cacheKey); val != nil && ok {
- c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
- return c.Status(fiber.StatusOK).Type("html").Send(val)
- }
-
- pathParts := strings.Split(strings.Trim(path, "/"), "/")
- if len(pathParts) == 0 {
- return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'")
- }
-
- 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))
- }
-
- values := fiber.Map{
- "L": l[lang],
- "Lang": lang,
- "QueryString": string(c.Request().URI().QueryString()),
- }
- var additionalTemplates []string
-
- if len(pathParts) == 2 && pathParts[1] == "blog" {
- values["Title"] = l[lang].BlogSearch.Header
- additionalTemplates = append(additionalTemplates, "views/pages/blog-catalogue.html")
- } else if len(pathParts) == 3 && pathParts[1] == "blog" {
- metadata, _, err := b2Client.ReadFrontmatter(lang + "/" + pathParts[2] + ".md")
- if err != nil {
- return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("could not read '%s' for content", path))
- }
- values["Title"] = metadata.Title
- values["PublishedDate"] = metadata.PublishedTime.Format("2006-01-02 15:04:05 -07:00")
- values["ActionDate"] = metadata.ActionDate
- additionalTemplates = append(additionalTemplates, "views/pages/blog-page.html")
- } else if len(pathParts) == 1 {
- values["Title"] = l[lang].HomePage.Header
- additionalTemplates = append(additionalTemplates, "views/pages/home-page.html")
- }
-
- content, err := tm.Render("general-page-header", values, additionalTemplates...)
- if err != nil {
- slog.Warn("failed to generate div", slog.String("path", path), slog.String("error", err.Error()))
- return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div")
- }
-
- go PCache.SetWithTTL(cacheKey, content, int64(len(content)), 5*time.Minute)
- c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
- return c.Status(fiber.StatusOK).Type("html").Send(content)
- }
-}
diff --git a/internal/router/api-v1-general-page-top-embeds.go b/internal/router/api-v1-general-page-top-embeds.go
deleted file mode 100644
index 78021b2..0000000
--- a/internal/router/api-v1-general-page-top-embeds.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package router
-
-import (
- "fmt"
- "log/slog"
- "net/url"
- "slices"
- "strings"
- "time"
-
- "github.com/SayaAndy/saya-today-web/locale"
- "github.com/gofiber/fiber/v2"
-)
-
-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 {
- return func(c *fiber.Ctx) error {
- c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
-
- referer := c.Get("Referer", "")
- if referer == "" {
- return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is empty")
- }
- urlStruct, err := url.ParseRequestURI(referer)
- if err != nil {
- return c.Status(fiber.ErrBadRequest.Code).SendString(fmt.Sprintf("'Referer' header is invalid: %s", err.Error()))
- }
-
- path := urlStruct.EscapedPath()
-
- cacheKey := fmt.Sprintf("top-embeds.%s", path)
- if val, ok := PCache.Get(cacheKey); val != nil && ok {
- c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
- return c.Status(fiber.StatusOK).Type("html").Send(val)
- }
-
- pathParts := strings.Split(strings.Trim(path, "/"), "/")
- if len(pathParts) == 0 {
- return c.Status(fiber.ErrBadRequest.Code).SendString("'Referer' header is invalid: expect format '/{lang}/...'")
- }
-
- 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))
- }
-
- values := fiber.Map{
- "L": l[lang],
- "Lang": lang,
- "QueryString": string(c.Request().URI().QueryString()),
- }
- var additionalTemplates []string
-
- if len(pathParts) == 2 && pathParts[1] == "blog" {
- additionalTemplates = append(additionalTemplates, "views/pages/blog-catalogue.html")
- } else if len(pathParts) == 3 && pathParts[1] == "blog" {
- additionalTemplates = append(additionalTemplates, "views/pages/blog-page.html")
- } else if len(pathParts) == 1 {
- additionalTemplates = append(additionalTemplates, "views/pages/home-page.html")
- }
-
- content, err := tm.Render("general-page-top-embeds", values, additionalTemplates...)
- if err != nil {
- slog.Warn("failed to generate div", slog.String("path", path), slog.String("error", err.Error()))
- return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div")
- }
-
- go PCache.SetWithTTL(cacheKey, content, int64(len(content)), 5*time.Minute)
- c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
- return c.Status(fiber.StatusOK).Type("html").Send(content)
- }
-}
diff --git a/internal/router/api-v1-general-page.go b/internal/router/api-v1-general-page.go
deleted file mode 100644
index d0ea559..0000000
--- a/internal/router/api-v1-general-page.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package router
-
-import (
- "fmt"
- "log/slog"
- "slices"
- "strings"
-
- "github.com/SayaAndy/saya-today-web/locale"
- "github.com/gofiber/fiber/v2"
-)
-
-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 {
- return func(c *fiber.Ctx) error {
- c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
- path := c.Path()
-
- pathParts := strings.Split(strings.Trim(path, "/"), "/")
- if len(pathParts) == 0 {
- return c.Status(fiber.ErrBadRequest.Code).SendString("url path is invalid: expect format '/{lang}/...'")
- }
-
- 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))
- }
-
- cacheKey := "general-page." + lang
- if val, ok := PCache.Get(cacheKey); val != nil && ok {
- c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
- return c.Status(fiber.StatusOK).Type("html").Send(val)
- }
-
- content, err := tm.Render("general-page", fiber.Map{
- "L": l[lang],
- "Lang": lang,
- "QueryString": string(c.Request().URI().QueryString()),
- })
- if err != nil {
- slog.Warn("failed to generate div", slog.String("path", path), slog.String("error", err.Error()))
- return c.Status(fiber.ErrInternalServerError.Code).SendString("failed to generate div")
- }
-
- go PCache.Set(cacheKey, content, int64(len(content)))
- c.Set(fiber.HeaderContentType, fiber.MIMETextHTMLCharsetUTF8)
- return c.Status(fiber.StatusOK).Type("html").Send(content)
- }
-}
diff --git a/internal/router/api-v1-like.go b/internal/router/api-v1-like.go
index 327dafa..1732c77 100644
--- a/internal/router/api-v1-like.go
+++ b/internal/router/api-v1-like.go
@@ -8,7 +8,6 @@ import (
"strings"
"github.com/SayaAndy/saya-today-web/internal/b2"
- "github.com/SayaAndy/saya-today-web/locale"
"github.com/gofiber/fiber/v2"
)
@@ -16,7 +15,7 @@ func init() {
tm.Add("blog-page-like-button", "views/partials/blog-page-like-button.html")
}
-func Api_V1_Like_Put(l map[string]*locale.LocaleConfig, b2 *b2.B2Client) func(c *fiber.Ctx) error {
+func Api_V1_Like_Put(b2 *b2.B2Client) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
@@ -42,12 +41,12 @@ func Api_V1_Like_Put(l map[string]*locale.LocaleConfig, b2 *b2.B2Client) func(c
return c.Status(fiber.ErrNotFound.Code).SendString(fmt.Sprintf("server did not find '%s' article", pageLink))
}
+ ip := c.IP()
newLikeStatus, err := strconv.ParseBool(c.FormValue("like", "true"))
if err != nil {
return c.Status(fiber.ErrBadRequest.Code).SendString("invalid 'like' value")
}
- ip := c.IP()
if newLikeStatus {
CCache.LikeOn(ip, page)
} else {
@@ -57,9 +56,7 @@ func Api_V1_Like_Put(l map[string]*locale.LocaleConfig, b2 *b2.B2Client) func(c
slog.Debug("someone pressed the like button!", slog.String("ip", ip), slog.String("page", page), slog.String("new_like_status", fmt.Sprint(newLikeStatus)))
if c.Get("HX-Request", "false") == "true" {
content, err := tm.Render("blog-page-like-button", fiber.Map{
- "L": l[lang],
- "Liked": newLikeStatus,
- "LikedCount": CCache.GetLikeCount(page),
+ "Liked": newLikeStatus,
})
if err != nil {
slog.Warn("failed to generate div", slog.String("path", path), slog.String("error", err.Error()))
@@ -73,7 +70,7 @@ func Api_V1_Like_Put(l map[string]*locale.LocaleConfig, b2 *b2.B2Client) func(c
}
}
-func Api_V1_Like_Get(l map[string]*locale.LocaleConfig, b2 *b2.B2Client) func(c *fiber.Ctx) error {
+func Api_V1_Like_Get(b2 *b2.B2Client) func(c *fiber.Ctx) error {
return func(c *fiber.Ctx) error {
c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
@@ -105,9 +102,7 @@ func Api_V1_Like_Get(l map[string]*locale.LocaleConfig, b2 *b2.B2Client) func(c
slog.Debug("someone requested the like status!", slog.String("ip", ip), slog.String("page", page), slog.Bool("like_status", likeStatus))
if c.Get("HX-Request", "false") == "true" {
content, err := tm.Render("blog-page-like-button", fiber.Map{
- "L": l[lang],
- "Liked": likeStatus,
- "LikedCount": CCache.GetLikeCount(page),
+ "Liked": likeStatus,
})
if err != nil {
slog.Warn("failed to generate div", slog.String("path", path), slog.String("error", err.Error()))
diff --git a/internal/router/client-cache.go b/internal/router/client-cache.go
index d607cdd..3104a66 100644
--- a/internal/router/client-cache.go
+++ b/internal/router/client-cache.go
@@ -1,11 +1,8 @@
package router
import (
- "database/sql"
"encoding/base64"
- "fmt"
"log/slog"
- "strings"
"sync"
"golang.org/x/crypto/argon2"
@@ -13,117 +10,35 @@ import (
type ClientCache struct {
hashMap map[string]string
- hashMapMutex sync.RWMutex
-
- likePageMap map[string]map[string]struct{}
- viewPageMap map[string]map[string]struct{}
- pageMutexMap map[string]*sync.RWMutex
- pageMutexMapMutex sync.Mutex
-
- salt []byte
- db *sql.DB
+ mutexLikeMap map[string]*sync.Mutex
+ mutexHashMap map[string]*sync.Mutex
+ likePageMap map[string]map[string]struct{}
+ salt []byte
}
var CCache *ClientCache
-func NewClientCache(db *sql.DB, salt []byte) (*ClientCache, error) {
- tx, err := db.Begin()
- if err != nil {
- return nil, fmt.Errorf("fail to init transaction with db to fill cache: %w", err)
- }
-
- rows, err := tx.Query("select * from blog_likes;")
- if err != nil {
- tx.Rollback()
- return nil, fmt.Errorf("fail to query db for blog_likes to fill cache: %w", err)
- }
-
- likePageMap := make(map[string]map[string]struct{})
- viewPageMap := make(map[string]map[string]struct{})
- pageMutexMap := make(map[string]*sync.RWMutex)
-
- for rows.Next() {
- var pageRef string
- var userId []byte
- if err = rows.Scan(&pageRef, &userId); err != nil {
- tx.Rollback()
- return nil, fmt.Errorf("fail scanning blog_likes to fill cache: %w", err)
- }
- userIdString := base64.RawStdEncoding.EncodeToString(userId)
- if _, ok := likePageMap[pageRef]; !ok {
- likePageMap[pageRef] = make(map[string]struct{})
- viewPageMap[pageRef] = make(map[string]struct{})
- pageMutexMap[pageRef] = &sync.RWMutex{}
- }
- likePageMap[pageRef][userIdString] = struct{}{}
- viewPageMap[pageRef][userIdString] = struct{}{}
- }
-
- rows, err = tx.Query("select * from blog_views;")
- if err != nil {
- tx.Rollback()
- return nil, fmt.Errorf("fail to query db for blog_views to fill cache: %w", err)
- }
-
- for rows.Next() {
- var pageRef string
- var userId []byte
- if err = rows.Scan(&pageRef, &userId); err != nil {
- tx.Rollback()
- return nil, fmt.Errorf("fail scanning blog_views to fill cache: %w", err)
- }
- userIdString := base64.RawStdEncoding.EncodeToString(userId)
- if _, ok := viewPageMap[pageRef]; !ok {
- viewPageMap[pageRef] = make(map[string]struct{})
- pageMutexMap[pageRef] = &sync.RWMutex{}
- }
- viewPageMap[pageRef][userIdString] = struct{}{}
- }
-
- if err = tx.Commit(); err != nil {
- return nil, fmt.Errorf("fail to commit transaction in db: %w", err)
- }
-
+func NewClientCache(salt []byte) *ClientCache {
return &ClientCache{
hashMap: make(map[string]string),
- likePageMap: likePageMap,
- viewPageMap: viewPageMap,
- pageMutexMap: pageMutexMap,
+ mutexLikeMap: make(map[string]*sync.Mutex),
+ mutexHashMap: make(map[string]*sync.Mutex),
+ likePageMap: make(map[string]map[string]struct{}),
salt: salt,
- db: db,
- }, nil
-}
-
-func (c *ClientCache) Close() error {
- tx, err := c.db.Begin()
- if err != nil {
- return fmt.Errorf("fail to init transaction with db to dump cache: %w", err)
- }
-
- if err = batchSave(tx, "blog_likes", c.likePageMap); err != nil {
- tx.Rollback()
- return fmt.Errorf("fail to save blog_likes: %s", err)
}
-
- if err = batchSave(tx, "blog_views", c.viewPageMap); err != nil {
- tx.Rollback()
- return fmt.Errorf("fail to save blog_views: %s", err)
- }
-
- return tx.Commit()
}
func (c *ClientCache) GetHash(id string) string {
- c.hashMapMutex.RLock()
if val, ok := c.hashMap[id]; ok {
- c.hashMapMutex.RUnlock()
slog.Debug("gave an old hash", slog.String("hash", val))
return val
}
- c.hashMapMutex.RUnlock()
- c.hashMapMutex.Lock()
- defer c.hashMapMutex.Unlock()
+ if _, ok := c.mutexHashMap[id]; !ok {
+ c.mutexHashMap[id] = &sync.Mutex{}
+ }
+ c.mutexHashMap[id].Lock()
+ defer c.mutexHashMap[id].Unlock()
if val, ok := c.hashMap[id]; ok {
slog.Debug("gave a newly generated hash", slog.String("hash", val))
@@ -135,25 +50,7 @@ func (c *ClientCache) GetHash(id string) string {
return c.hashMap[id]
}
-func (c *ClientCache) getPageMutex(page string) *sync.RWMutex {
- c.pageMutexMapMutex.Lock()
- defer c.pageMutexMapMutex.Unlock()
-
- if mutex, ok := c.pageMutexMap[page]; ok {
- return mutex
- }
-
- c.pageMutexMap[page] = &sync.RWMutex{}
- return c.pageMutexMap[page]
-}
-
func (c *ClientCache) GetLikeStatus(id string, page string) bool {
- page = strings.Clone(page)
-
- mutex := c.getPageMutex(page)
- mutex.RLock()
- defer mutex.RUnlock()
-
if _, ok := c.likePageMap[page]; !ok {
return false
}
@@ -161,27 +58,15 @@ func (c *ClientCache) GetLikeStatus(id string, page string) bool {
return ok
}
-func (c *ClientCache) GetLikeCount(page string) int {
- page = strings.Clone(page)
-
- mutex := c.getPageMutex(page)
- mutex.RLock()
- defer mutex.RUnlock()
-
- if userSet, ok := c.likePageMap[page]; ok {
- return len(userSet)
+func (c *ClientCache) LikeOn(id string, page string) (alreadyLiked bool) {
+ if _, ok := c.mutexLikeMap[id]; !ok {
+ c.mutexLikeMap[id] = &sync.Mutex{}
}
- return 0
-}
+ c.mutexLikeMap[id].Lock()
+ defer c.mutexLikeMap[id].Unlock()
-func (c *ClientCache) LikeOn(id string, page string) (alreadyLiked bool) {
- page = strings.Clone(page)
hash := c.GetHash(id)
- mutex := c.getPageMutex(page)
- mutex.Lock()
- defer mutex.Unlock()
-
if userSet, ok := c.likePageMap[page]; ok {
_, alreadyLiked = userSet[hash]
c.likePageMap[page][hash] = struct{}{}
@@ -194,16 +79,16 @@ func (c *ClientCache) LikeOn(id string, page string) (alreadyLiked bool) {
}
func (c *ClientCache) LikeOff(id string, page string) (alreadyUnliked bool) {
- page = strings.Clone(page)
- hash := c.GetHash(id)
-
- mutex := c.getPageMutex(page)
- mutex.Lock()
- defer mutex.Unlock()
+ if _, ok := c.mutexLikeMap[id]; !ok {
+ c.mutexLikeMap[id] = &sync.Mutex{}
+ }
+ c.mutexLikeMap[id].Lock()
+ defer c.mutexLikeMap[id].Unlock()
if _, ok := c.likePageMap[page]; !ok {
return true
}
+ hash := c.GetHash(id)
if _, ok := c.likePageMap[page][hash]; !ok {
return true
}
@@ -211,94 +96,3 @@ func (c *ClientCache) LikeOff(id string, page string) (alreadyUnliked bool) {
delete(c.likePageMap[page], hash)
return false
}
-
-func (c *ClientCache) GetViewStatus(id string, page string) bool {
- page = strings.Clone(page)
-
- mutex := c.getPageMutex(page)
- mutex.RLock()
- defer mutex.RUnlock()
-
- if _, ok := c.viewPageMap[page]; !ok {
- return false
- }
- _, ok := c.viewPageMap[page][c.GetHash(id)]
- return ok
-}
-
-func (c *ClientCache) GetViewCount(page string) int {
- page = strings.Clone(page)
-
- mutex := c.getPageMutex(page)
- mutex.RLock()
- defer mutex.RUnlock()
-
- if userSet, ok := c.viewPageMap[page]; ok {
- return len(userSet)
- }
- return 0
-}
-
-func (c *ClientCache) View(id string, page string) {
- page = strings.Clone(page)
- hash := c.GetHash(id)
-
- mutex := c.getPageMutex(page)
- mutex.Lock()
- defer mutex.Unlock()
-
- if _, ok := c.viewPageMap[page]; !ok {
- c.viewPageMap[page] = make(map[string]struct{})
- }
- c.viewPageMap[page][hash] = struct{}{}
-}
-
-func batchSave(tx *sql.Tx, table string, pageMap map[string]map[string]struct{}) (err error) {
- if _, err = tx.Exec(fmt.Sprintf("delete from %s;", table)); err != nil {
- return fmt.Errorf("fail to truncate table %s: %w", table, err)
- }
-
- userIdBytes := make(map[string][]byte)
-
- sqlStatement := fmt.Sprintf(`
- INSERT OR IGNORE INTO %s (page_ref, user_id)
- VALUES %s(?, ?);
- `, table, strings.Repeat("(?, ?), ", 99))
- sqlStatementVars := make([]any, 0, 200)
-
- for pageRef, userSet := range pageMap {
- for userId := range userSet {
- if _, ok := userIdBytes[userId]; !ok {
- userIdBytes[userId], err = base64.RawStdEncoding.DecodeString(userId)
- if err != nil {
- slog.Warn("couldn't parse one of user hashes into bytes back", slog.String("hash", userId), slog.String("error", err.Error()))
- continue
- }
- }
-
- sqlStatementVars = append(sqlStatementVars, any(pageRef), any(userIdBytes[userId]))
- if len(sqlStatementVars) < 200 {
- continue
- }
-
- if _, err := tx.Exec(sqlStatement, sqlStatementVars...); err != nil {
- slog.Warn("couldn't insert blog stat pairs into db", slog.String("table", table), slog.String("error", err.Error()))
- }
-
- sqlStatementVars = make([]any, 0, 200)
- }
- }
-
- if len(sqlStatementVars) > 0 {
- sqlStatement = fmt.Sprintf(`
- INSERT OR IGNORE INTO %s (page_ref, user_id)
- VALUES %s(?, ?);
- `, table, strings.Repeat("(?, ?), ", len(sqlStatementVars)/2-1))
-
- if _, err := tx.Exec(sqlStatement, sqlStatementVars...); err != nil {
- slog.Warn("couldn't insert blog stat pairs into db", slog.String("table", table), slog.String("error", err.Error()))
- }
- }
-
- return nil
-}
diff --git a/internal/router/lang-blog-title.go b/internal/router/lang-blog-title.go
index d6ca917..bafb0d2 100644
--- a/internal/router/lang-blog-title.go
+++ b/internal/router/lang-blog-title.go
@@ -56,7 +56,6 @@ func Lang_Blog_Title(l map[string]*locale.LocaleConfig, langs []string, b2Client
"MapLocationY": y,
"MapLocationAreaMeters": areaError,
"Lang": lang,
- "QueryString": string(c.Request().URI().QueryString()),
})
if err != nil {
slog.Warn("failed to generate page", slog.String("page", "/"+lang+"/blog/"+c.Params("title")), slog.String("error", err.Error()))
diff --git a/internal/router/lang-blog.go b/internal/router/lang-blog.go
index 38a6597..e5220c6 100644
--- a/internal/router/lang-blog.go
+++ b/internal/router/lang-blog.go
@@ -76,7 +76,6 @@ func Lang_Blog(l map[string]*locale.LocaleConfig, langs []string, b2Client *b2.B
content, err := tm.Render("blog-catalogue", fiber.Map{
"QuerySort": querySort,
"QueryTags": strings.Join(queryTags, ","),
- "QueryString": string(c.Request().URI().QueryString()),
"Tags": tagsArray,
"Lang": lang,
"L": l[lang],
@@ -84,7 +83,7 @@ func Lang_Blog(l map[string]*locale.LocaleConfig, langs []string, b2Client *b2.B
"Title": l[lang].BlogSearch.Header,
})
if err != nil {
- slog.Warn("failed to generate page", slog.String("path", c.Path()), slog.String("error", err.Error()))
+ 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")
}
diff --git a/internal/router/page-cache.go b/internal/router/page-cache.go
deleted file mode 100644
index 01e4792..0000000
--- a/internal/router/page-cache.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package router
-
-import "github.com/dgraph-io/ristretto/v2"
-
-var PCache *ristretto.Cache[string, []byte]
diff --git a/internal/tailwind/link_renderer.go b/internal/tailwind/link_renderer.go
deleted file mode 100644
index ff3d437..0000000
--- a/internal/tailwind/link_renderer.go
+++ /dev/null
@@ -1,49 +0,0 @@
-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 23ba1a3..2f052b9 100644
--- a/internal/tailwind/transformer.go
+++ b/internal/tailwind/transformer.go
@@ -1,9 +1,6 @@
package tailwind
import (
- "bytes"
- "fmt"
-
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/text"
@@ -20,48 +17,44 @@ 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-patua text-[2.5vmax] font-bold text-main-dark mb-[1.2vmin] tracking-[0.04vmin]",
+ 2: "font-spectral text-[2vmax] font-bold text-main-dark mb-[0.8vmin] tracking-[0.04vmin]",
+ 3: "font-spectral text-[1.5vmax] font-medium text-main-dark mb-[0.6vmin] tracking-[0.04vmin]",
+ 4: "font-spectral text-[1.2vmax] font-medium text-main-medium mb-[0.4vmin] tracking-[0.04vmin]",
+ 5: "font-spectral text-[1vmax] font-medium text-main-medium mb-[0.4vmin] italic tracking-[0.04vmin]",
+ 6: "font-spectral text-[1vmax] font-medium text-secondary mb-[0.4vmin]",
}
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-[1vmax]/[2] font-spectral tracking-[0.04vmin] -indent-[2vmax] ml-[2vmax] mb-[1.6vmin]"))
case *ast.List:
if node.IsOrdered() {
- node.SetAttribute([]byte("class"), []byte("list-decimal list-inside space-y-2 mb-4 pl-8"))
+ node.SetAttribute([]byte("class"), []byte("list-decimal list-inside space-y-[0.8vmin] mb-[1.6vmin] pl-[2vmax]"))
} else {
- node.SetAttribute([]byte("class"), []byte("list-disc list-inside space-y-2 mb-4 pl-8"))
+ node.SetAttribute([]byte("class"), []byte("list-disc list-inside space-y-[0.8vmin] mb-[1.6vmin] pl-[2vmax]"))
}
case *ast.ListItem:
- node.SetAttribute([]byte("class"), []byte("text-base/[2] font-spectral tracking-[.0125rem]"))
+ node.SetAttribute([]byte("class"), []byte("text-[1vmax]/[2] font-spectral tracking-[0.04vmin]"))
case *ast.Blockquote:
- node.SetAttribute([]byte("class"), []byte("border-l-2 border-main-medium bg-background-dark p-1 mb-2 italic"))
+ node.SetAttribute([]byte("class"), []byte("border-l-[0.4vmin] border-main-medium bg-background-dark p-[0.8vmin] mb-[0.8vmin] italic"))
case *ast.CodeSpan:
node.SetAttribute([]byte("class"), []byte("bg-background-dark"))
case *ast.CodeBlock, *ast.FencedCodeBlock:
- node.SetAttribute([]byte("class"), []byte("bg-background-dark p-1 rounded-lg overflow-x-auto mb-4"))
+ node.SetAttribute([]byte("class"), []byte("bg-background-dark p-[0.8vmin] rounded-lg overflow-x-auto mb-[1.6vmin]"))
case *ast.Link:
- 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-dark underline"))
case *ast.Image:
- node.SetAttribute([]byte("class"), []byte("max-w-full h-auto rounded-lg shadow-lg mb-4"))
+ node.SetAttribute([]byte("class"), []byte("max-w-full h-auto rounded-lg shadow-lg mb-[1.6vmin]"))
case *ast.Emphasis:
switch node.Level {
diff --git a/internal/templatemanager/templatemanager.go b/internal/templatemanager/templatemanager.go
index eebc297..f89d0c8 100644
--- a/internal/templatemanager/templatemanager.go
+++ b/internal/templatemanager/templatemanager.go
@@ -22,22 +22,13 @@ type TemplateManagerTemplates struct {
Files []string
}
-var templateFuncMap = template.FuncMap{
- "contains": strings.Contains,
- "iterate": func(count uint) []uint {
- items := make([]uint, count)
- for i := range count {
- items[i] = i
- }
- return items
- },
-}
-
func NewTemplateManager(templates ...TemplateManagerTemplates) (*TemplateManager, error) {
templateMap := make(map[string]templateManagerRender)
for _, tmplStruct := range templates {
- tmpl := template.New(tmplStruct.Name).Funcs(templateFuncMap)
+ tmpl := template.New("").Funcs(template.FuncMap{
+ "contains": strings.Contains,
+ })
tmpl, err := tmpl.ParseFiles(tmplStruct.Files...)
if err != nil {
return nil, err
@@ -53,29 +44,14 @@ func NewTemplateManager(templates ...TemplateManagerTemplates) (*TemplateManager
}, nil
}
-func (tm *TemplateManager) Render(name string, data any, files ...string) ([]byte, error) {
+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 err error
- var tempTmpl *template.Template
- if len(files) == 0 {
- tempTmpl = tmpl.Tmpl
- } else {
- tempTmpl, err = tmpl.Tmpl.Clone()
- if err != nil {
- return nil, fmt.Errorf("couldn't clone existing template for rendering: %w", err)
- }
- tempTmpl, err = tempTmpl.ParseFiles(files...)
- if err != nil {
- return nil, fmt.Errorf("couldn't include additional files in template rendering: %w", err)
- }
- }
-
var buf bytes.Buffer
- err = tempTmpl.ExecuteTemplate(&buf, tmpl.Main, data)
+ err := tmpl.Tmpl.ExecuteTemplate(&buf, tmpl.Main, data)
return buf.Bytes(), err
}
@@ -84,7 +60,9 @@ func (tm *TemplateManager) Add(name string, files ...string) error {
return fmt.Errorf("you can't add template without any files")
}
- tmpl := template.New(name).Funcs(templateFuncMap)
+ tmpl := template.New("").Funcs(template.FuncMap{
+ "contains": strings.Contains,
+ })
tmpl, err := tmpl.ParseFiles(files...)
if err != nil {