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