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()); articles.sort(Comparator.comparing(Article::createdAt).reversed());
LOGGER.debug("Rendering pages..."); LOGGER.debug("Rendering pages...");
renderIndexPages(articles); renderIndexAndPages(articles);
Files.writeString(outputDirectory.resolve("articles.rss"), Feed.generateRss(site, 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); 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"); var pageDirectory = outputDirectory.resolve("page");
Files.createDirectory(pageDirectory); 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++) { for (int page=1; page<=lastPage; page++) {
var startIndex = (page - 1) * site.articlesPerPage(); var startIndex = (page - 1) * site.articlesPerPage();
var endIndex = Math.min(startIndex + site.articlesPerPage(), articles.size()); var endIndex = Math.min(startIndex + site.articlesPerPage(), articles.size());
var pageArticles = articles.subList(startIndex, endIndex); 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); Files.writeString(pageDirectory.resolve(page + ".html"), renderedPage);
} }
} }

View file

@ -30,6 +30,7 @@ public record Site(
int articlesPerPage, int articlesPerPage,
boolean templateArticles, boolean templateArticles,
boolean separateFirstPage,
Map<String, Object> custom Map<String, Object> custom
) { ) {
@ -39,6 +40,7 @@ public record Site(
"/", "/",
10, 10,
false, false,
false,
Map.of() Map.of()
); );
@ -57,6 +59,7 @@ public record Site(
String baseUrl = (String) yaml.getOrDefault("baseUrl", DEFAULT.baseUrl()); String baseUrl = (String) yaml.getOrDefault("baseUrl", DEFAULT.baseUrl());
var articlesPerPage = (int) yaml.getOrDefault("articlesPerPage", DEFAULT.articlesPerPage()); var articlesPerPage = (int) yaml.getOrDefault("articlesPerPage", DEFAULT.articlesPerPage());
var templateArticles = (boolean) yaml.getOrDefault("templateArticles", DEFAULT.templateArticles()); var templateArticles = (boolean) yaml.getOrDefault("templateArticles", DEFAULT.templateArticles());
var separateFirstPage = (boolean) yaml.getOrDefault("separateFirstPage", DEFAULT.separateFirstPage());
String directory = DEFAULT.directory(); String directory = DEFAULT.directory();
if (baseUrl != null) { if (baseUrl != null) {
@ -68,7 +71,7 @@ public record Site(
} }
return new 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; 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.PageNumbers;
import eu.m724.blog.object.Site; import eu.m724.blog.object.Site;
import io.pebbletemplates.pebble.PebbleEngine; import io.pebbletemplates.pebble.PebbleEngine;
import io.pebbletemplates.pebble.error.LoaderException;
import io.pebbletemplates.pebble.loader.FileLoader; import io.pebbletemplates.pebble.loader.FileLoader;
import io.pebbletemplates.pebble.template.PebbleTemplate; import io.pebbletemplates.pebble.template.PebbleTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
@ -25,11 +28,13 @@ import java.util.Map;
* using the Pebble templating engine. * using the Pebble templating engine.
*/ */
public class TemplateRenderer { public class TemplateRenderer {
private static final Logger LOGGER = LoggerFactory.getLogger(TemplateRenderer.class);
private final Site site; private final Site site;
private final Minifier minifier; private final Minifier minifier;
private final PebbleEngine pebbleEngine; 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. * 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.indexTemplate = pebbleEngine.getTemplate("index_template");
this.articleTemplate = pebbleEngine.getTemplate("article_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 * @param pageNumbers a {@link PageNumbers} instance for the current page
* @return the rendered index HTML page as a string * @return the rendered index HTML page as a string
* @throws IOException if an error occurs during the template evaluation * @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( var context = Map.of(
"page", pageNumbers, "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); return renderTemplate(indexTemplate, context);