Monorepo for Tangled
tangled.org
1package timeline
2
3import (
4 "bytes"
5 "io/fs"
6 "log/slog"
7 "net/http"
8 "os"
9 "strings"
10 "time"
11
12 "github.com/adrg/frontmatter"
13 "github.com/go-chi/chi/v5"
14 "tangled.org/core/appview/config"
15 "tangled.org/core/appview/db"
16 "tangled.org/core/appview/oauth"
17 "tangled.org/core/appview/pages"
18)
19
20type postMeta struct {
21 Slug string `yaml:"slug"`
22 Title string `yaml:"title"`
23 Subtitle string `yaml:"subtitle"`
24 Date string `yaml:"date"`
25 Draft bool `yaml:"draft"`
26}
27
28type Timeline struct {
29 oauth *oauth.OAuth
30 db *db.DB
31 config *config.Config
32 pages *pages.Pages
33 logger *slog.Logger
34 recentPosts []pages.BlogPost
35}
36
37func New(
38 oauth *oauth.OAuth,
39 db *db.DB,
40 config *config.Config,
41 pages *pages.Pages,
42 logger *slog.Logger,
43 postsDir string,
44) *Timeline {
45 t := &Timeline{
46 oauth: oauth,
47 db: db,
48 config: config,
49 pages: pages,
50 logger: logger,
51 }
52 t.recentPosts = loadRecentPosts(postsDir, logger)
53 return t
54}
55
56func (t *Timeline) Router() http.Handler {
57 r := chi.NewRouter()
58 r.Get("/", t.HomeOrTimeline)
59 r.Get("/home", t.Home)
60 r.Get("/timeline", t.Timeline)
61 return r
62}
63
64func loadRecentPosts(postsDir string, logger *slog.Logger) []pages.BlogPost {
65 fsys := os.DirFS(postsDir)
66 entries, err := fs.ReadDir(fsys, ".")
67 if err != nil {
68 logger.Warn("failed to read blog posts dir", "dir", postsDir, "err", err)
69 return nil
70 }
71
72 var posts []postMeta
73 for _, entry := range entries {
74 if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".md") {
75 continue
76 }
77 data, err := fs.ReadFile(fsys, entry.Name())
78 if err != nil {
79 continue
80 }
81 var meta postMeta
82 if _, err := frontmatter.Parse(bytes.NewReader(data), &meta); err != nil {
83 continue
84 }
85 if meta.Draft {
86 continue
87 }
88 posts = append(posts, meta)
89 }
90
91 // sort newest-first by date string (format "2006-01-02" sorts lexicographically)
92 for i := 1; i < len(posts); i++ {
93 for j := i; j > 0 && posts[j].Date > posts[j-1].Date; j-- {
94 posts[j], posts[j-1] = posts[j-1], posts[j]
95 }
96 }
97
98 if len(posts) > 3 {
99 posts = posts[:3]
100 }
101
102 result := make([]pages.BlogPost, len(posts))
103 for i, p := range posts {
104 t, _ := time.Parse("2006-01-02", p.Date)
105 result[i] = pages.BlogPost{Slug: p.Slug, Title: p.Title, Subtitle: p.Subtitle, Date: t}
106 }
107 return result
108}