diff --git a/.gitignore b/.gitignore
index 86de21c..f889f1a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,5 +37,5 @@ build/
### Mac OS ###
.DS_Store
-/example_blog/
+/example-blog/
/m724/
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index a4c6019..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "example-blog"]
- path = example-blog
- url = git@git.m724.eu:Minecon724/example-blog.git
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 0d1ca8f..8ba20b6 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -2,7 +2,7 @@
-
+
\ No newline at end of file
diff --git a/src/main/java/eu/m724/blog/BlogBuilder.java b/src/main/java/eu/m724/blog/BlogBuilder.java
index ef1f98a..2c13ece 100644
--- a/src/main/java/eu/m724/blog/BlogBuilder.java
+++ b/src/main/java/eu/m724/blog/BlogBuilder.java
@@ -10,10 +10,7 @@ import eu.m724.blog.compress.CommonsCompressor;
import eu.m724.blog.compress.CompressException;
import eu.m724.blog.compress.FileCompressor;
import eu.m724.blog.compress.NoSuchAlgorithmException;
-import eu.m724.blog.object.Article;
-import eu.m724.blog.object.Feed;
-import eu.m724.blog.object.RenderOptions;
-import eu.m724.blog.object.Site;
+import eu.m724.blog.object.*;
import eu.m724.blog.server.Server;
import eu.m724.blog.template.TemplateRenderer;
import eu.m724.blog.vc.GitVersionControl;
@@ -153,7 +150,9 @@ public class BlogBuilder {
LOGGER.debug("Rendering meta...");
articles.sort(Comparator.comparing(Article::createdAt).reversed());
- Files.writeString(outputDirectory.resolve("index.html"), template.renderIndex(articles));
+
+ LOGGER.debug("Rendering pages...");
+ renderIndexPages(articles);
Files.writeString(outputDirectory.resolve("articles.rss"), Feed.generateRss(site, articles));
@@ -173,6 +172,25 @@ public class BlogBuilder {
}
}
+ private void renderIndexPages(List articles) throws IOException {
+ int lastPage = Math.max(Math.ceilDiv(articles.size(), site.articlesPerPage()), 1);
+ Files.writeString(outputDirectory.resolve("index.html"), template.renderIndexPage(PageNumbers.create(1, lastPage), articles.subList(0, site.articlesPerPage())));
+
+ var pageDirectory = outputDirectory.resolve("page");
+ Files.createDirectory(pageDirectory);
+
+ // yes we do render page 1 twice, because https://git.m724.eu/Minecon724/blog-software-java/issues/8
+ for (int page=1; page<=lastPage; page++) {
+ var startIndex = (page - 1) * site.articlesPerPage();
+ var endIndex = Math.min(startIndex + site.articlesPerPage(), articles.size());
+ var pageArticles = articles.subList(startIndex, endIndex);
+
+ var renderedPage = template.renderIndexPage(PageNumbers.create(page, lastPage), pageArticles);
+
+ Files.writeString(pageDirectory.resolve(page + ".html"), renderedPage);
+ }
+ }
+
private List renderArticles() throws IOException {
Files.createDirectory(outputDirectory.resolve("article"));
var articleDirectory = workingDirectory.resolve("articles");
diff --git a/src/main/java/eu/m724/blog/object/PageNumbers.java b/src/main/java/eu/m724/blog/object/PageNumbers.java
new file mode 100644
index 0000000..c6dbb0c
--- /dev/null
+++ b/src/main/java/eu/m724/blog/object/PageNumbers.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2025 blog-software-java developers
+ * blog-software-java is licensed under the GNU General Public License. See the LICENSE.md file
+ * in the project root for the full license text.
+ */
+
+package eu.m724.blog.object;
+
+/**
+ * PageNumbers simplifies tracking page numbers
+ * @param current current page number (1 is first)
+ * @param last last page number (equals number of pages)
+ * @param previous previous page number (>=1)
+ * @param next next page number (<=last)
+ * @param isFirstPage is current page the first page (=1)
+ * @param isLastPage is current page the last page (=last)
+ */
+public record PageNumbers(
+ int current,
+ int last,
+
+ int previous,
+ int next,
+
+ boolean isFirstPage,
+ boolean isLastPage
+) {
+ // after upgrade to java 22+ this could be a constructor
+ public static PageNumbers create(int current, int last) {
+ var previous = Math.max(current - 1, 1);
+ var next = Math.min(current + 1, last);
+
+ var isFirstPage = current == 1;
+ var isLastPage = current == last;
+
+ return new PageNumbers(current, last, previous, next, isFirstPage, isLastPage);
+ }
+}
diff --git a/src/main/java/eu/m724/blog/object/Site.java b/src/main/java/eu/m724/blog/object/Site.java
index 47b4e67..407af1e 100644
--- a/src/main/java/eu/m724/blog/object/Site.java
+++ b/src/main/java/eu/m724/blog/object/Site.java
@@ -27,6 +27,8 @@ public record Site(
String directory,
+ int articlesPerPage,
+
boolean templateArticles,
Map custom
@@ -35,6 +37,7 @@ public record Site(
"Misconfigured blog",
"/",
"/",
+ 10,
false,
Map.of()
);
@@ -52,6 +55,7 @@ public record Site(
String name = (String) yaml.getOrDefault("name", DEFAULT.name());
String baseUrl = (String) yaml.getOrDefault("baseUrl", DEFAULT.baseUrl());
+ var articlesPerPage = (int) yaml.getOrDefault("articlesPerPage", DEFAULT.articlesPerPage());
var templateArticles = (boolean) yaml.getOrDefault("templateArticles", DEFAULT.templateArticles());
String directory = DEFAULT.directory();
@@ -64,7 +68,7 @@ public record Site(
}
return new Site(
- name, baseUrl, directory, templateArticles, yaml
+ name, baseUrl, directory, articlesPerPage, templateArticles, yaml
);
}
}
diff --git a/src/main/java/eu/m724/blog/template/TemplateRenderer.java b/src/main/java/eu/m724/blog/template/TemplateRenderer.java
index a6e2fae..ee373ac 100644
--- a/src/main/java/eu/m724/blog/template/TemplateRenderer.java
+++ b/src/main/java/eu/m724/blog/template/TemplateRenderer.java
@@ -8,6 +8,7 @@ package eu.m724.blog.template;
import eu.m724.blog.Minifier;
import eu.m724.blog.object.Article;
+import eu.m724.blog.object.PageNumbers;
import eu.m724.blog.object.Site;
import io.pebbletemplates.pebble.PebbleEngine;
import io.pebbletemplates.pebble.loader.FileLoader;
@@ -58,12 +59,14 @@ public class TemplateRenderer {
/**
* Renders the index page using this template.
*
- * @param articles the {@link Article}s to be included in the index page
+ * @param articles the {@link Article}s to be included in the CURRENT page
+ * @param pageNumbers a {@link PageNumbers} instance for the current page
* @return the rendered index HTML page as a string
* @throws IOException if an error occurs during the template evaluation
*/
- public String renderIndex(List articles) throws IOException {
+ public String renderIndexPage(PageNumbers pageNumbers, List articles) throws IOException {
var context = Map.of(
+ "page", pageNumbers,
"articles", articles
);