feat: add navigation for previous and next posts
R
Rüdiger Küpper
<rpr@9it.de>
committete am 19.02.2026 02:21
d1bf374fa130243a8a3a18ff4545bbdd270e9448
| |
@@ -70 +70 @@ func Generate(cfg *config.Config, posts []*content.Post, pages []*content.Page) |
| 70 |
|
- for _, post := range posts { |
|
70 |
+ for i, post := range posts { |
| |
@@ -72,0 +73,8 @@ func Generate(cfg *config.Config, posts []*content.Post, pages []*content.Page) |
|
73 |
+ data := templates.PostData{Site: site, Post: post} |
|
74 |
+ // Posts are sorted newest first: index 0 = newest |
|
75 |
+ if i > 0 { |
|
76 |
+ data.PrevPost = posts[i-1] // newer |
|
77 |
+ } |
|
78 |
+ if i < len(posts)-1 { |
|
79 |
+ data.NextPost = posts[i+1] // older |
|
80 |
+ } |
| |
@@ -74 +82 @@ func Generate(cfg *config.Config, posts []*content.Post, pages []*content.Page) |
| 74 |
|
- return templates.RenderPost(f, templates.PostData{Site: site, Post: post}) |
|
82 |
+ return templates.RenderPost(f, data) |
| |
@@ -24 +24,2 @@ func Start(cfg *config.Config, posts []*content.Post, pages []*content.Page) { |
| 24 |
|
- for _, p := range posts { |
|
24 |
+ postIndex := make(map[string]int) |
|
25 |
+ for i, p := range posts { |
| |
@@ -25,0 +27 @@ func Start(cfg *config.Config, posts []*content.Post, pages []*content.Page) { |
|
27 |
+ postIndex[p.Slug] = i |
| |
@@ -176,0 +179,8 @@ func Start(cfg *config.Config, posts []*content.Post, pages []*content.Page) { |
|
179 |
+ // Posts are sorted newest first: index 0 = newest |
|
180 |
+ idx := postIndex[slug] |
|
181 |
+ if idx > 0 { |
|
182 |
+ data.PrevPost = posts[idx-1] // newer |
|
183 |
+ } |
|
184 |
+ if idx < len(posts)-1 { |
|
185 |
+ data.NextPost = posts[idx+1] // older |
|
186 |
+ } |
| |
@@ -26,0 +27,18 @@ |
|
27 |
+ <nav class="post-nav"> |
|
28 |
+ {{if .PrevPost}} |
|
29 |
+ <a href="/posts/{{.PrevPost.Slug}}/" class="post-nav-link post-nav-prev"> |
|
30 |
+ <span class="post-nav-label">← Neuerer Artikel</span> |
|
31 |
+ <span class="post-nav-title">{{.PrevPost.Title}}</span> |
|
32 |
+ </a> |
|
33 |
+ {{else}} |
|
34 |
+ <span class="post-nav-link post-nav-prev disabled"></span> |
|
35 |
+ {{end}} |
|
36 |
+ {{if .NextPost}} |
|
37 |
+ <a href="/posts/{{.NextPost.Slug}}/" class="post-nav-link post-nav-next"> |
|
38 |
+ <span class="post-nav-label">Älterer Artikel →</span> |
|
39 |
+ <span class="post-nav-title">{{.NextPost.Title}}</span> |
|
40 |
+ </a> |
|
41 |
+ {{else}} |
|
42 |
+ <span class="post-nav-link post-nav-next disabled"></span> |
|
43 |
+ {{end}} |
|
44 |
+ </nav> |
| |
@@ -34,2 +34,4 @@ type PostData struct { |
| 34 |
|
- Site SiteData |
| 35 |
|
- Post *content.Post |
|
34 |
+ Site SiteData |
|
35 |
+ Post *content.Post |
|
36 |
+ PrevPost *content.Post // newer post (nil if this is the newest) |
|
37 |
+ NextPost *content.Post // older post (nil if this is the oldest) |
| |
@@ -904 +904,4 @@ body { |
| 904 |
|
-.post-footer { |
|
904 |
+/* === Post Navigation (Prev/Next) === */ |
|
905 |
+.post-nav { |
|
906 |
+ display: flex; |
|
907 |
+ gap: 1.5rem; |
| |
@@ -909,0 +913,63 @@ body { |
|
913 |
+.post-nav-link { |
|
914 |
+ flex: 1; |
|
915 |
+ display: flex; |
|
916 |
+ flex-direction: column; |
|
917 |
+ gap: 0.3rem; |
|
918 |
+ padding: 1rem; |
|
919 |
+ border-radius: 10px; |
|
920 |
+ background: var(--bg-card); |
|
921 |
+ border: 1px solid var(--border); |
|
922 |
+ text-decoration: none; |
|
923 |
+ transition: all 0.2s ease; |
|
924 |
+} |
|
925 |
+ |
|
926 |
+.post-nav-link:hover { |
|
927 |
+ border-color: var(--accent); |
|
928 |
+ background: var(--bg-card-hover); |
|
929 |
+ box-shadow: 0 0 16px var(--accent-glow); |
|
930 |
+} |
|
931 |
+ |
|
932 |
+.post-nav-link.disabled { |
|
933 |
+ visibility: hidden; |
|
934 |
+} |
|
935 |
+ |
|
936 |
+.post-nav-prev { |
|
937 |
+ align-items: flex-start; |
|
938 |
+} |
|
939 |
+ |
|
940 |
+.post-nav-next { |
|
941 |
+ align-items: flex-end; |
|
942 |
+ text-align: right; |
|
943 |
+} |
|
944 |
+ |
|
945 |
+.post-nav-label { |
|
946 |
+ font-family: 'JetBrains Mono', monospace; |
|
947 |
+ font-size: 0.75rem; |
|
948 |
+ color: var(--text-muted); |
|
949 |
+ text-transform: uppercase; |
|
950 |
+ letter-spacing: 0.05em; |
|
951 |
+} |
|
952 |
+ |
|
953 |
+.post-nav-title { |
|
954 |
+ font-size: 0.9rem; |
|
955 |
+ font-weight: 600; |
|
956 |
+ color: var(--accent); |
|
957 |
+ line-height: 1.3; |
|
958 |
+} |
|
959 |
+ |
|
960 |
+@media (max-width: 640px) { |
|
961 |
+ .post-nav { |
|
962 |
+ flex-direction: column; |
|
963 |
+ gap: 0.75rem; |
|
964 |
+ } |
|
965 |
+ .post-nav-next { |
|
966 |
+ align-items: flex-start; |
|
967 |
+ text-align: left; |
|
968 |
+ } |
|
969 |
+} |
|
970 |
+ |
|
971 |
+.post-footer { |
|
972 |
+ margin-top: 1.5rem; |
|
973 |
+ padding-top: 1rem; |
|
974 |
+} |
|
975 |
+ |