From c57ae37666d2f86bf86b37e284fa70f339d0b806 Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Wed, 12 Mar 2025 19:26:18 +0100 Subject: [PATCH] Page templates Closes #8 Signed-off-by: Minecon724 --- src/main/java/eu/m724/blog/BlogBuilder.java | 16 ++++--- src/main/java/eu/m724/blog/object/Site.java | 5 ++- .../m724/blog/template/TemplateExtension.java | 24 ++++++++++ .../m724/blog/template/TemplateRenderer.java | 44 ++++++++++++++++--- 4 files changed, 78 insertions(+), 11 deletions(-) diff --git a/src/main/java/eu/m724/blog/BlogBuilder.java b/src/main/java/eu/m724/blog/BlogBuilder.java index 2c13ece..344cf87 100644 --- a/src/main/java/eu/m724/blog/BlogBuilder.java +++ b/src/main/java/eu/m724/blog/BlogBuilder.java @@ -152,7 +152,7 @@ public class BlogBuilder { articles.sort(Comparator.comparing(Article::createdAt).reversed()); LOGGER.debug("Rendering pages..."); - renderIndexPages(articles); + renderIndexAndPages(articles); Files.writeString(outputDirectory.resolve("articles.rss"), Feed.generateRss(site, articles)); @@ -172,21 +172,27 @@ public class BlogBuilder { } } - private void renderIndexPages(List
articles) throws IOException { + private void renderIndexAndPages(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); + var pageNumbers = PageNumbers.create(page, lastPage); + if (page == 1) { + var renderedIndex = template.renderIndex(pageNumbers, pageArticles); + Files.writeString(outputDirectory.resolve("index.html"), renderedIndex); + + if (!site.separateFirstPage()) continue; + } + + var renderedPage = template.renderPage(pageNumbers, pageArticles); Files.writeString(pageDirectory.resolve(page + ".html"), renderedPage); } } diff --git a/src/main/java/eu/m724/blog/object/Site.java b/src/main/java/eu/m724/blog/object/Site.java index 407af1e..5f46272 100644 --- a/src/main/java/eu/m724/blog/object/Site.java +++ b/src/main/java/eu/m724/blog/object/Site.java @@ -30,6 +30,7 @@ public record Site( int articlesPerPage, boolean templateArticles, + boolean separateFirstPage, Map custom ) { @@ -39,6 +40,7 @@ public record Site( "/", 10, false, + false, Map.of() ); @@ -57,6 +59,7 @@ public record Site( String baseUrl = (String) yaml.getOrDefault("baseUrl", DEFAULT.baseUrl()); var articlesPerPage = (int) yaml.getOrDefault("articlesPerPage", DEFAULT.articlesPerPage()); var templateArticles = (boolean) yaml.getOrDefault("templateArticles", DEFAULT.templateArticles()); + var separateFirstPage = (boolean) yaml.getOrDefault("separateFirstPage", DEFAULT.separateFirstPage()); String directory = DEFAULT.directory(); if (baseUrl != null) { @@ -68,7 +71,7 @@ public record Site( } return new Site( - name, baseUrl, directory, articlesPerPage, templateArticles, yaml + name, baseUrl, directory, articlesPerPage, templateArticles, separateFirstPage, yaml ); } } diff --git a/src/main/java/eu/m724/blog/template/TemplateExtension.java b/src/main/java/eu/m724/blog/template/TemplateExtension.java index 20ed933..777e59e 100644 --- a/src/main/java/eu/m724/blog/template/TemplateExtension.java +++ b/src/main/java/eu/m724/blog/template/TemplateExtension.java @@ -82,6 +82,30 @@ public class TemplateExtension extends AbstractExtension { return site.directory() + path; } + }, + "url_to_page", new Function() { + @Override + public List getArgumentNames() { + return List.of("page"); + } + + @Override + public Object execute(Map args, PebbleTemplate self, EvaluationContext context, int lineNumber) { + var pageObj = args.get("page"); + int page; + + if (pageObj instanceof Number pageNumber) { + page = pageNumber.intValue(); + } else { + throw new IllegalArgumentException("\"page\" must be Number"); + } + + if (page == 1 && !site.separateFirstPage()) { + return site.directory(); + } else { + return site.directory() + "page/" + page + ".html"; + } + } } ); } diff --git a/src/main/java/eu/m724/blog/template/TemplateRenderer.java b/src/main/java/eu/m724/blog/template/TemplateRenderer.java index ee373ac..e2c7a73 100644 --- a/src/main/java/eu/m724/blog/template/TemplateRenderer.java +++ b/src/main/java/eu/m724/blog/template/TemplateRenderer.java @@ -11,8 +11,11 @@ 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.error.LoaderException; import io.pebbletemplates.pebble.loader.FileLoader; import io.pebbletemplates.pebble.template.PebbleTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.StringWriter; @@ -25,11 +28,13 @@ import java.util.Map; * using the Pebble templating engine. */ public class TemplateRenderer { + private static final Logger LOGGER = LoggerFactory.getLogger(TemplateRenderer.class); + private final Site site; private final Minifier minifier; private final PebbleEngine pebbleEngine; - private final PebbleTemplate indexTemplate, articleTemplate; + private final PebbleTemplate indexTemplate, articleTemplate, pageTemplate; /** * Constructs a TemplateRenderer instance for rendering templates from the specified directory. @@ -54,20 +59,49 @@ public class TemplateRenderer { this.indexTemplate = pebbleEngine.getTemplate("index_template"); this.articleTemplate = pebbleEngine.getTemplate("article_template"); + + var pageTemplate = indexTemplate; + + try { + pageTemplate = pebbleEngine.getTemplate("page_template"); + } catch (LoaderException e) { + LOGGER.debug("Template has no page template, using index_template instead"); + } + + this.pageTemplate = pageTemplate; } /** - * Renders the index page using this template. + * Renders a page using this template.
+ * This is different from index, as this uses page template. * - * @param articles the {@link Article}s to be included in the CURRENT page + * @param pageArticles 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 renderIndexPage(PageNumbers pageNumbers, List
articles) throws IOException { + public String renderPage(PageNumbers pageNumbers, List
pageArticles) throws IOException { var context = Map.of( "page", pageNumbers, - "articles", articles + "articles", pageArticles + ); + + return renderTemplate(pageTemplate, context); + } + + /** + * Renders the index page using this template.
+ * This is different from rendering page 1, as this uses index template. + * + * @param pageArticles 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(PageNumbers pageNumbers, List
pageArticles) throws IOException { + var context = Map.of( + "page", pageNumbers, + "articles", pageArticles ); return renderTemplate(indexTemplate, context);