Page templates
All checks were successful
/ build (push) Successful in 1m3s

Closes #8

Signed-off-by: Minecon724 <git@m724.eu>
This commit is contained in:
Minecon724 2025-03-12 19:26:18 +01:00
parent 25cdedfe2b
commit c57ae37666
Signed by untrusted user who does not match committer: Minecon724
GPG key ID: 3CCC4D267742C8E8
4 changed files with 78 additions and 11 deletions

View file

@ -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<Article> articles) throws IOException {
private void renderIndexAndPages(List<Article> 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);
}
}

View file

@ -30,6 +30,7 @@ public record Site(
int articlesPerPage,
boolean templateArticles,
boolean separateFirstPage,
Map<String, Object> 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
);
}
}

View file

@ -82,6 +82,30 @@ public class TemplateExtension extends AbstractExtension {
return site.directory() + path;
}
},
"url_to_page", new Function() {
@Override
public List<String> getArgumentNames() {
return List.of("page");
}
@Override
public Object execute(Map<String, Object> 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";
}
}
}
);
}

View file

@ -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.<br>
* This is different from index, as this uses page template.
*
* @param articles the {@link Article}s to be included in the <strong>CURRENT</strong> page
* @param pageArticles the {@link Article}s to be included in the <strong>CURRENT</strong> 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<Article> articles) throws IOException {
public String renderPage(PageNumbers pageNumbers, List<Article> pageArticles) throws IOException {
var context = Map.of(
"page", pageNumbers,
"articles", articles
"articles", pageArticles
);
return renderTemplate(pageTemplate, context);
}
/**
* Renders the index page using this template.<br>
* This is different from rendering page 1, as this uses index template.
*
* @param pageArticles the {@link Article}s to be included in the <strong>CURRENT</strong> 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<Article> pageArticles) throws IOException {
var context = Map.of(
"page", pageNumbers,
"articles", pageArticles
);
return renderTemplate(indexTemplate, context);