Diffstat (limited to 'internal')
| -rw-r--r-- | internal/b2/client.go | 12 | ||||
| -rw-r--r-- | internal/frontmatter/parser.go | 1 | ||||
| -rw-r--r-- | internal/lightgallery/block.go | 11 | ||||
| -rw-r--r-- | internal/lightgallery/html_renderer.go | 30 | ||||
| -rw-r--r-- | internal/lightgallery/parser.go | 35 | ||||
| -rw-r--r-- | internal/templatemanager/templatemanager.go | 57 |
6 files changed, 117 insertions, 29 deletions
diff --git a/internal/b2/client.go b/internal/b2/client.go index f2b6cad..5322b84 100644 --- a/internal/b2/client.go +++ b/internal/b2/client.go @@ -33,13 +33,14 @@ func NewB2Client(cfg *config.B2Config) (*B2Client, error) { type BlogPage struct { Link string + FileName string Metadata *frontmatter.Metadata } -func (c *B2Client) Scan() ([]*BlogPage, error) { +func (c *B2Client) Scan(prefix string) ([]*BlogPage, error) { filePaths := []*BlogPage{} - iter := c.bucket.List(context.Background(), b2.ListPrefix(c.prefix)) + iter := c.bucket.List(context.Background(), b2.ListPrefix(c.prefix+prefix)) for iter.Next() { obj := iter.Object() @@ -69,10 +70,13 @@ func (c *B2Client) Scan() ([]*BlogPage, error) { return nil, fmt.Errorf("failed to parse published time metadata field: %w", err) } - linkParts := strings.Split(obj.Name(), ".") + linkParts := strings.Split(obj.Name(), "/") + nameParts := strings.Split(linkParts[len(linkParts)-1], ".") + fileName := strings.Join(nameParts[:len(linkParts)-1], ".") filePaths = append(filePaths, &BlogPage{ - Link: strings.Join(linkParts[:len(linkParts)-1], "."), + Link: obj.Name(), + FileName: fileName, Metadata: &frontmatter.Metadata{ Title: attrs.Info["title"], ShortDescription: attrs.Info["short-description"], diff --git a/internal/frontmatter/parser.go b/internal/frontmatter/parser.go index 1a521c0..3c00a7f 100644 --- a/internal/frontmatter/parser.go +++ b/internal/frontmatter/parser.go @@ -16,6 +16,7 @@ type Metadata struct { Thumbnail string `yaml:"thumbnail"` Tags []string `yaml:"tags"` Geolocation string `yaml:"geolocation"` + Timezone string `yaml:"timezone"` } func ParseFrontmatter(content []byte) (metadata *Metadata, markdown []byte, err error) { diff --git a/internal/lightgallery/block.go b/internal/lightgallery/block.go index dade2ea..8eb7c9e 100644 --- a/internal/lightgallery/block.go +++ b/internal/lightgallery/block.go @@ -1,16 +1,21 @@ package lightgallery -import "github.com/yuin/goldmark/ast" +import ( + "time" + + "github.com/yuin/goldmark/ast" +) // LightGalleryBlock represents a light gallery block in the AST type LightGalleryBlock struct { ast.BaseBlock - Images []LightGalleryImage + Images []LightGalleryImage + Location *time.Location } type LightGalleryImage struct { URL string - Caption string + Caption []byte } var KindLightGalleryBlock = ast.NewNodeKind("LightGalleryBlock") diff --git a/internal/lightgallery/html_renderer.go b/internal/lightgallery/html_renderer.go index e611f0b..850cd95 100644 --- a/internal/lightgallery/html_renderer.go +++ b/internal/lightgallery/html_renderer.go @@ -1,11 +1,13 @@ 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" @@ -14,11 +16,18 @@ import ( 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) @@ -53,7 +62,17 @@ func (r *LightGalleryHTMLRenderer) renderLightGallery(w util.BufWriter, source [ 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", @@ -70,7 +89,7 @@ func (r *LightGalleryHTMLRenderer) renderLightGallery(w util.BufWriter, source [ <p class="grow !text-[1vmax]/[0.9] text-left font-spectral text-main-dark">%s</p> <p class="!text-[1vmax]/[0.9] text-right font-spectral text-secondary">%s</p> </div>`+"`"+` - }`, img.URL, img.URL, img.Caption, imageUrlWithoutExt, imageUrlWithoutExt, img.Caption, dayDate.Format("2006-01-02 15:04:05 -07:00"))) + }`, img.URL, img.URL, util.EscapeHTML(captionHTML), imageUrlWithoutExt, imageUrlWithoutExt, captionHTML, dayDate.Format("2006-01-02 15:04:05 -07:00"))) } w.WriteString(fmt.Sprintf(` @@ -117,15 +136,6 @@ window.addEventListener("resize", () => { return ast.WalkContinue, nil } -func escapeHTML(s string) string { - s = strings.ReplaceAll(s, "&", "&") - s = strings.ReplaceAll(s, "<", "<") - s = strings.ReplaceAll(s, ">", ">") - s = strings.ReplaceAll(s, "\"", """) - s = strings.ReplaceAll(s, "'", "'") - return s -} - func generateDivId(length int) string { const charset = "abcdefghijklmnopqrstuvwxyz" seededRand := rand.New(rand.NewSource(time.Now().UnixNano())) // Seed with current time diff --git a/internal/lightgallery/parser.go b/internal/lightgallery/parser.go index 0ce209c..daec797 100644 --- a/internal/lightgallery/parser.go +++ b/internal/lightgallery/parser.go @@ -2,7 +2,9 @@ package lightgallery import ( "bytes" - "strings" + "log/slog" + "regexp" + "time" "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/parser" @@ -22,43 +24,52 @@ func (p *LightGalleryParser) Trigger() []byte { 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}")) { + if !bytes.HasPrefix(line, []byte("{Gallery:")) { return nil, parser.NoChildren } + r := regexp.MustCompile(`^\{Gallery:([A-Za-z0-9\+\-/]+)\}$`) + trimmed := bytes.TrimSpace(line) - if !bytes.Equal(trimmed, []byte("{Gallery}")) { + parts := r.FindSubmatch(trimmed) + if len(parts) < 2 { + slog.Warn("invalid gallery header format", slog.String("line", string(trimmed)), slog.Int("submatch_count", len(parts))) return nil, parser.NoChildren } - return &LightGalleryBlock{}, parser.NoChildren + loc, err := time.LoadLocation(string(parts[1])) + if err != nil { + slog.Warn("invalid location specified for a gallery", slog.String("error", err.Error()), slog.String("line", string(trimmed)), slog.String("location", string(parts[1]))) + return nil, parser.NoChildren + } + + return &LightGalleryBlock{Location: loc}, parser.NoChildren } 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.Close + return parser.Continue | parser.NoChildren } trimmed := bytes.TrimSpace(line) - if bytes.Equal(trimmed, []byte("{Gallery}")) { + if bytes.Equal(trimmed, []byte("{Gallery}")) || bytes.Equal(trimmed, []byte("{/Gallery}")) { reader.AdvanceLine() return parser.Close } gallery := node.(*LightGalleryBlock) - lineStr := string(trimmed) - parts := strings.SplitN(lineStr, "|", 2) - url := strings.TrimSpace(parts[0]) - caption := "" + parts := bytes.SplitN(trimmed, []byte{'|'}, 2) + url := bytes.TrimSpace(parts[0]) + caption := make([]byte, 0) if len(parts) > 1 { - caption = strings.TrimSpace(parts[1]) + caption = bytes.TrimSpace(parts[1]) } gallery.Images = append(gallery.Images, LightGalleryImage{ - URL: url, + URL: string(url), Caption: caption, }) diff --git a/internal/templatemanager/templatemanager.go b/internal/templatemanager/templatemanager.go new file mode 100644 index 0000000..5effcb3 --- /dev/null +++ b/internal/templatemanager/templatemanager.go @@ -0,0 +1,57 @@ +package templatemanager + +import ( + "bytes" + "fmt" + "html/template" + "path/filepath" + "strings" +) + +type TemplateManager struct { + templates map[string]templateManagerRender +} + +type templateManagerRender struct { + Main string + Tmpl *template.Template +} + +type TemplateManagerTemplates struct { + Name string + Files []string +} + +func NewTemplateManager(templates []TemplateManagerTemplates) (*TemplateManager, error) { + templateMap := make(map[string]templateManagerRender) + + for _, tmplStruct := range templates { + tmpl := template.New("").Funcs(template.FuncMap{ + "contains": strings.Contains, + }) + tmpl, err := tmpl.ParseFiles(tmplStruct.Files...) + if err != nil { + return nil, err + } + templateMap[tmplStruct.Name] = templateManagerRender{ + Main: filepath.Base(tmplStruct.Files[0]), + Tmpl: tmpl, + } + + } + + return &TemplateManager{ + templates: templateMap, + }, nil +} + +func (tm *TemplateManager) Render(name string, data interface{}) ([]byte, error) { + tmpl, exists := tm.templates[name] + if !exists { + return nil, fmt.Errorf("template %s is not found", name) + } + + var buf bytes.Buffer + err := tmpl.Tmpl.ExecuteTemplate(&buf, tmpl.Main, data) + return buf.Bytes(), err +} |