package timeline import ( "bytes" "io/fs" "log/slog" "net/http" "strings" "time" "github.com/adrg/frontmatter" "github.com/go-chi/chi/v5" "tangled.org/core/appview/config" "tangled.org/core/appview/db" "tangled.org/core/appview/oauth" "tangled.org/core/appview/pages" ) type postMeta struct { Slug string `yaml:"slug"` Title string `yaml:"title"` Subtitle string `yaml:"subtitle"` Date string `yaml:"date"` Draft bool `yaml:"draft"` } type Timeline struct { oauth *oauth.OAuth db *db.DB config *config.Config pages *pages.Pages logger *slog.Logger recentPosts []pages.BlogPost } func New( oauth *oauth.OAuth, db *db.DB, config *config.Config, pages *pages.Pages, logger *slog.Logger, postsFS fs.FS, ) *Timeline { t := &Timeline{ oauth: oauth, db: db, config: config, pages: pages, logger: logger, } t.recentPosts = loadRecentPosts(postsFS, logger) return t } func (t *Timeline) Router() http.Handler { r := chi.NewRouter() r.Get("/", t.HomeOrTimeline) r.Get("/home", t.Home) r.Get("/timeline", t.Timeline) return r } func loadRecentPosts(postsFS fs.FS, logger *slog.Logger) []pages.BlogPost { entries, err := fs.ReadDir(postsFS, "posts") if err != nil { logger.Warn("failed to read blog posts", "err", err) return nil } var posts []postMeta for _, entry := range entries { if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".md") { continue } data, err := fs.ReadFile(postsFS, "posts/"+entry.Name()) if err != nil { continue } var meta postMeta if _, err := frontmatter.Parse(bytes.NewReader(data), &meta); err != nil { continue } if meta.Draft { continue } posts = append(posts, meta) } // sort newest-first by date string (format "2006-01-02" sorts lexicographically) for i := 1; i < len(posts); i++ { for j := i; j > 0 && posts[j].Date > posts[j-1].Date; j-- { posts[j], posts[j-1] = posts[j-1], posts[j] } } if len(posts) > 3 { posts = posts[:3] } result := make([]pages.BlogPost, len(posts)) for i, p := range posts { t, _ := time.Parse("2006-01-02", p.Date) result[i] = pages.BlogPost{Slug: p.Slug, Title: p.Title, Subtitle: p.Subtitle, Date: t} } return result }