LabCodeHub öffentliche Ansicht
Anmelden
Küpper / Quildrop öffentlich

feat: add navigation for previous and next posts

R Rüdiger Küpper <rpr@9it.de> committete am 19.02.2026 02:21
d1bf374fa130243a8a3a18ff4545bbdd270e9448
5 geänderte Datei(en) +110 −6
Kontextzeilen: 0 1 2 3 10
geändert internal/generator/generator.go
+10 −2 Datei ansehen
@@ -68,9 +68,17 @@ func Generate(cfg *config.Config, posts []*content.Post, pages []*content.Page)
68 68
69 69
	// 2. Individual posts
70 -
	for _, post := range posts {
70 +
	for i, post := range posts {
71 71
		dir := filepath.Join(out, "posts", post.Slug)
72 72
		os.MkdirAll(dir, 0755)
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 +
		}
73 81
		writeTemplate(filepath.Join(dir, "index.html"), func(f *os.File) error {
74 -
			return templates.RenderPost(f, templates.PostData{Site: site, Post: post})
82 +
			return templates.RenderPost(f, data)
75 83
		})
76 84
	}
geändert internal/server/server.go
+11 −1 Datei ansehen
@@ -22,6 +22,8 @@ func Start(cfg *config.Config, posts []*content.Post, pages []*content.Page) {
22 22
	catMap := content.CollectCategories(posts)
23 23
	postMap := make(map[string]*content.Post)
24 -
	for _, p := range posts {
24 +
	postIndex := make(map[string]int)
25 +
	for i, p := range posts {
25 26
		postMap[p.Slug] = p
27 +
		postIndex[p.Slug] = i
26 28
	}
27 29
	pageMap := make(map[string]*content.Page)
@@ -175,4 +177,12 @@ func Start(cfg *config.Config, posts []*content.Post, pages []*content.Page) {
175 177
		}
176 178
		data := templates.PostData{Site: site, Post: post}
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 +
		}
177 187
		if err := templates.RenderPost(w, data); err != nil {
178 188
			log.Printf("Error rendering post: %v", err)
geändert internal/templates/post.html
+18 −0 Datei ansehen
@@ -25,4 +25,22 @@
25 25
        {{safeHTML .Post.HTMLContent}}
26 26
    </div>
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">&larr; 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 &rarr;</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>
27 45
    <div class="post-footer">
28 46
        <a href="/" class="back-link">&larr; Zurück zur Startseite</a>
geändert internal/templates/render.go
+4 −2 Datei ansehen
@@ -32,6 +32,8 @@ type HomeData struct {
32 32
33 33
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)
36 38
}
37 39
geändert static/css/style.css
+67 −1 Datei ansehen
@@ -902,5 +902,8 @@ body {
902 902
903 903
/* === Post Footer === */
904 -
.post-footer {
904 +
/* === Post Navigation (Prev/Next) === */
905 +
.post-nav {
906 +
    display: flex;
907 +
    gap: 1.5rem;
905 908
    margin-top: 3rem;
906 909
    padding-top: 1.5rem;
@@ -908,4 +911,67 @@ body {
908 911
}
909 912
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 +
910 976
.back-link {
911 977
    font-family: 'JetBrains Mono', monospace;