Skip to content

Commit

Permalink
Merge pull request #156 from ia3andy/pagination-and-modification-api
Browse files Browse the repository at this point in the history
Better pagination and introduce data modif api
  • Loading branch information
ia3andy authored Oct 9, 2024
2 parents c28bfd6 + c0bcf88 commit 66a4a7a
Show file tree
Hide file tree
Showing 22 changed files with 273 additions and 92 deletions.
25 changes: 4 additions & 21 deletions blog/site/_includes/pagination.html
Original file line number Diff line number Diff line change
@@ -1,23 +1,6 @@
{@io.quarkiverse.roq.frontmatter.runtime.model.Site site}

<div class="container">
<nav class="pagination" role="pagination">
<ul>
{#if page.paginator.previous}
{#if page.paginator.isSecond}
<p><a class="newer-posts" href="{site.url}"><i class="fa fa-long-arrow-left" aria-hidden="true"></i></a></p>
{#else}
<p><a class="newer-posts" href="{page.paginator.previous}/"><i class="fa fa-long-arrow-left" aria-hidden="true"></i></a></p>
{/if}
{/if}

{#if page.paginator.total > 1}
<p><span class="page-number">Page {page.paginator.currentIndex} of {page.paginator.total}</span></p>
{/if}

{#if page.paginator.next}
<p><a class="older-posts" href="{page.paginator.next}"><i class="fa fa-long-arrow-right" aria-hidden="true"></i></a></p>
{/if}
</ul>
</nav>
{#include fm/pagination.html}
{#newer}<i class="fa fa-long-arrow-left" aria-hidden="true"></i>{/newer}
{#older}<i class="fa fa-long-arrow-right" aria-hidden="true"></i>{/older}
{/include}
</div>
25 changes: 4 additions & 21 deletions blog/site/_posts/2024-09-20-pagination.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,10 @@ Next, in your template, loop through the paginated posts using:
To add pagination controls, add something like this to `_includes/pagination.html` and include it in your page `\{#include pagination.html/}`:

```html
<div class="container">
<nav class="pagination" role="pagination">
<ul>
\{#if page.paginator.previous}
\{#if page.paginator.isSecond}
<p><a class="newer-posts" href="\{site.url}"><i class="fa fa-long-arrow-left" aria-hidden="true"></i></a></p>
\{#else}
<p><a class="newer-posts" href="\{page.paginator.previous)}/"><i class="fa fa-long-arrow-left" aria-hidden="true"></i></a></p>
\{/if}
\{/if}

\{#if page.paginator.total > 1}
<p><span class="page-number">Page \{page.paginator.currentIndex} of \{page.paginator.total}</span></p>
\{/if}

\{#if page.paginator.next}
<p><a class="older-posts" href="\{page.paginator.next)}"><i class="fa fa-long-arrow-right" aria-hidden="true"></i></a></p>
\{/if}
</ul>
</nav>
</div>
\{#include fm/pagination.html}
\{#newer}<i class="fa fa-long-arrow-left" aria-hidden="true"></i>\{/newer}
\{#older}<i class="fa fa-long-arrow-right" aria-hidden="true"></i>\{/older}
\{/include}
```

You can further customize your pagination by setting the page size and link format:
Expand Down
29 changes: 7 additions & 22 deletions docs/modules/ROOT/pages/quarkus-roq-frontmatter.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -39,33 +39,18 @@ Next, in your template, loop through the paginated posts using:

=== Step 2: Including Pagination Controls

To add pagination controls, create something like this in `_includes/pagination.html`:
To add pagination controls, use the provided `fm/pagination.html` in your own `_includes/pagination.html`:

[source,html]
----
<div class="container">
<nav class="pagination" role="pagination">
<ul>
{#if page.paginator.previous}
{#if page.paginator.isSecond}
<p><a class="newer-posts" href="{site.url}"><i class="fa fa-long-arrow-left" aria-hidden="true"></i></a></p>
{#else}
<p><a class="newer-posts" href="{page.paginator.previous)}/"><i class="fa fa-long-arrow-left" aria-hidden="true"></i></a></p>
{/if}
{/if}
{#if page.paginator.total > 1}
<p><span class="page-number">Page {page.paginator.currentIndex} of {page.paginator.total}</span></p>
{/if}
{#if page.paginator.next}
<p><a class="older-posts" href="{page.paginator.next)}"><i class="fa fa-long-arrow-right" aria-hidden="true"></i></a></p>
{/if}
</ul>
</nav>
</div>
{#include fm/pagination.html}
{#newer}<i class="fa fa-long-arrow-left" aria-hidden="true"></i>{/newer}
{#older}<i class="fa fa-long-arrow-right" aria-hidden="true"></i>{/older}
{/include}
----

NOTE: If you want to write your own controls, find inspiration in the FM sources https://github.com/quarkiverse/quarkus-roq/tree/main/roq-frontmatter/runtime/src/main/resources/templates/fm/pagination.html[fm/pagination.html].

Just by doing so, Roq will generate a bunch of pages based on the pagination setting. For example with a pagination size of 4 and with 9 posts, you would get:

* `index.html` (posts 1 to 4)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.Set;

import io.quarkiverse.roq.frontmatter.deployment.scan.RoqFrontMatterRawTemplateBuildItem;
import io.quarkiverse.roq.frontmatter.runtime.RoqFrontMatterMessages;
import io.quarkiverse.roq.frontmatter.runtime.RoqTemplateExtension;
import io.quarkiverse.roq.frontmatter.runtime.RoqTemplateGlobal;
import io.quarkiverse.roq.frontmatter.runtime.model.*;
Expand Down Expand Up @@ -75,6 +76,7 @@ void registerAdditionalBeans(RoqFrontMatterOutputBuildItem roqOutput,
}
additionalBeans.produce(AdditionalBeanBuildItem.builder()
.addBeanClasses(
RoqFrontMatterMessages.class,
RoqTemplateExtension.class,
RoqTemplateGlobal.class,
Page.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.quarkiverse.roq.frontmatter.deployment.data;

import io.quarkus.builder.item.MultiBuildItem;
import io.vertx.core.json.JsonObject;

/**
* Allow to modify the FrontMatter data just before it is produced (and before it is merged with parents).
*/
public final class RoqFrontMatterDataModificationBuildItem extends MultiBuildItem {
private final DataModifier modifier;

/**
* Modifiers with the highest priority will run last.
*/
private final int order;

public RoqFrontMatterDataModificationBuildItem(DataModifier modifier, int order) {
this.modifier = modifier;
this.order = order;
}

public RoqFrontMatterDataModificationBuildItem(DataModifier modifier) {
this.modifier = modifier;
this.order = 0;
}

public DataModifier modifier() {
return modifier;
}

public int order() {
return order;
}

public interface DataModifier {

JsonObject modify(String id, String templatePath, JsonObject fm);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,19 @@ void prepareData(HttpBuildTimeConfig httpConfig,
if (roqFrontMatterTemplates.isEmpty()) {
return;
}

final var byKey = roqFrontMatterTemplates.stream()
.collect(Collectors.toMap(RoqFrontMatterRawTemplateBuildItem::id, Function.identity()));
final RootUrl rootUrl = new RootUrl(config.urlOptional().orElse(""), httpConfig.rootPath);
rootUrlProducer.produce(new RoqFrontMatterRootUrlBuildItem(rootUrl));

for (RoqFrontMatterRawTemplateBuildItem item : roqFrontMatterTemplates) {
final JsonObject data = mergeParents(item, byKey);
JsonObject data = mergeParents(item, byKey);
final String link = Link.pageLink(config.rootPath(), data.getString(LINK_KEY, DEFAULT_PAGE_LINK_TEMPLATE),
new Link.PageLinkData(item.info().baseFileName(), item.info().date(), item.collection(), data));
templatesProducer.produce(new RoqFrontMatterTemplateBuildItem(item, rootUrl.resolve(link), data));
RoqFrontMatterTemplateBuildItem templateItem = new RoqFrontMatterTemplateBuildItem(item, rootUrl.resolve(link),
data);
templatesProducer.produce(templateItem);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Matcher;
Expand All @@ -30,6 +28,7 @@
import io.quarkiverse.roq.deployment.items.RoqJacksonBuildItem;
import io.quarkiverse.roq.deployment.items.RoqProjectBuildItem;
import io.quarkiverse.roq.frontmatter.deployment.RoqFrontMatterConfig;
import io.quarkiverse.roq.frontmatter.deployment.data.RoqFrontMatterDataModificationBuildItem;
import io.quarkiverse.roq.frontmatter.runtime.model.PageInfo;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
Expand Down Expand Up @@ -75,11 +74,13 @@ public static Function<String, String> find(String fileName) {
void scan(RoqProjectBuildItem roqProject,
RoqJacksonBuildItem jackson,
BuildProducer<RoqFrontMatterRawTemplateBuildItem> dataProducer,
List<RoqFrontMatterDataModificationBuildItem> dataModifications,
RoqFrontMatterConfig roqDataConfig,
BuildProducer<HotDeploymentWatchedFileBuildItem> watch) {
try {
dataModifications.sort(Comparator.comparing(RoqFrontMatterDataModificationBuildItem::order));
Set<RoqFrontMatterRawTemplateBuildItem> items = resolveItems(roqProject, jackson.getYamlMapper(), roqDataConfig,
watch);
watch, dataModifications);

for (RoqFrontMatterRawTemplateBuildItem item : items) {
dataProducer.produce(item);
Expand All @@ -91,7 +92,8 @@ void scan(RoqProjectBuildItem roqProject,
}

public Set<RoqFrontMatterRawTemplateBuildItem> resolveItems(RoqProjectBuildItem roqProject, YAMLMapper mapper,
RoqFrontMatterConfig roqDataConfig, BuildProducer<HotDeploymentWatchedFileBuildItem> watch) throws IOException {
RoqFrontMatterConfig roqDataConfig, BuildProducer<HotDeploymentWatchedFileBuildItem> watch,
List<RoqFrontMatterDataModificationBuildItem> dataModifications) throws IOException {

HashSet<RoqFrontMatterRawTemplateBuildItem> items = new HashSet<>();
roqProject.consumeRoqDir(site -> {
Expand All @@ -104,7 +106,8 @@ public Set<RoqFrontMatterRawTemplateBuildItem> resolveItems(RoqProjectBuildItem
stream
.filter(Files::isRegularFile)
.filter(RoqFrontMatterScanProcessor::isExtensionSupported)
.forEach(addBuildItem(dir, items, mapper, roqDataConfig, watch, null, false));
.forEach(addBuildItem(dir, items, mapper, roqDataConfig, dataModifications, watch, null,
false));
} catch (IOException e) {
throw new RuntimeException("Was not possible to scan includes dir %s".formatted(dir), e);
}
Expand All @@ -120,7 +123,8 @@ public Set<RoqFrontMatterRawTemplateBuildItem> resolveItems(RoqProjectBuildItem
stream
.filter(Files::isRegularFile)
.filter(RoqFrontMatterScanProcessor::isExtensionSupported)
.forEach(addBuildItem(dir, items, mapper, roqDataConfig, watch, collectionsDir.getValue(),
.forEach(addBuildItem(dir, items, mapper, roqDataConfig, dataModifications, watch,
collectionsDir.getValue(),
true));
} catch (IOException e) {
throw new RuntimeException("Was not possible to scan includes dir %s".formatted(dir), e);
Expand All @@ -134,7 +138,7 @@ public Set<RoqFrontMatterRawTemplateBuildItem> resolveItems(RoqProjectBuildItem
.filter(Files::isRegularFile)
.filter(RoqFrontMatterScanProcessor::isExtensionSupported)
.filter(not(RoqFrontMatterScanProcessor::isFileExcluded))
.forEach(addBuildItem(site, items, mapper, roqDataConfig, watch, null, true));
.forEach(addBuildItem(site, items, mapper, roqDataConfig, dataModifications, watch, null, true));
} catch (IOException e) {
throw new RuntimeException("Was not possible to scan data files on location %s".formatted(site), e);
}
Expand All @@ -145,8 +149,13 @@ public Set<RoqFrontMatterRawTemplateBuildItem> resolveItems(RoqProjectBuildItem
}

@SuppressWarnings("unchecked")
private static Consumer<Path> addBuildItem(Path root, HashSet<RoqFrontMatterRawTemplateBuildItem> items, YAMLMapper mapper,
RoqFrontMatterConfig config, BuildProducer<HotDeploymentWatchedFileBuildItem> watch, String collection,
private static Consumer<Path> addBuildItem(Path root,
HashSet<RoqFrontMatterRawTemplateBuildItem> items,
YAMLMapper mapper,
RoqFrontMatterConfig config,
List<RoqFrontMatterDataModificationBuildItem> dataModifications,
BuildProducer<HotDeploymentWatchedFileBuildItem> watch,
String collection,
boolean isPage) {
return file -> {
watch.produce(HotDeploymentWatchedFileBuildItem.builder().setLocation(file.toAbsolutePath().toString()).build());
Expand All @@ -159,7 +168,10 @@ private static Consumer<Path> addBuildItem(Path root, HashSet<RoqFrontMatterRawT
if (hasFrontMatter(fullContent)) {
JsonNode rootNode = mapper.readTree(getFrontMatter(fullContent));
final Map<String, Object> map = mapper.convertValue(rootNode, Map.class);
final JsonObject fm = new JsonObject(map);
JsonObject fm = new JsonObject(map);
for (RoqFrontMatterDataModificationBuildItem modification : dataModifications) {
fm = modification.modifier().modify(id, templatePath, fm);
}
final boolean draft = fm.getBoolean(DRAFT_KEY, false);
if (!config.draft() && draft) {
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package io.quarkiverse.roq.frontmatter.deployment;

import static org.hamcrest.Matchers.equalTo;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkiverse.roq.frontmatter.deployment.data.RoqFrontMatterDataModificationBuildItem;
import io.quarkus.builder.BuildContext;
import io.quarkus.builder.BuildStep;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
import io.vertx.core.json.JsonObject;

public class RoqFrontMatterApiModificationTest {

// Start unit test with your extension loaded
@RegisterExtension
static final QuarkusUnitTest unitTest = new QuarkusUnitTest()
.overrideConfigKey("quarkus.roq.resource-dir", "simple-site")
.addBuildChainCustomizer(buildChainBuilder -> {
buildChainBuilder.addBuildStep(new BuildStep() {

@Override
public void execute(BuildContext context) {
context.produce(new RoqFrontMatterDataModificationBuildItem((id, path, data) -> {
if (id.equals("pages/some-page")) {
final JsonObject newData = data.copy();
newData.put("some-text", "modified text");
newData.put("link", "/somewhere-else");
return newData;
}
return data;
}));
}
}).produces(RoqFrontMatterDataModificationBuildItem.class).build();

})
.withApplicationRoot((jar) -> jar
.addAsResource("simple-site"));

@Test
public void testPage() {
RestAssured.when().get("/somewhere-else").then().statusCode(200).log().ifValidationFails()
.body("html.body.article.p", equalTo("modified text"));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.quarkiverse.roq.frontmatter.deployment;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;

public class RoqFrontMatterSimpleTest {

// Start unit test with your extension loaded
@RegisterExtension
static final QuarkusUnitTest unitTest = new QuarkusUnitTest()
.overrideConfigKey("quarkus.roq.resource-dir", "simple-site")
.withApplicationRoot((jar) -> jar
.addAsResource("simple-site"));

@Test
public void testPage() {
RestAssured.when().get("/page/some-page").then().statusCode(200).log().ifValidationFails()
.body("html.head.title", equalTo("Some page - Simple Site"))
.body("html.body.article.h1", equalTo("Some page"))
.body("html.body.article.p", equalTo("We can also use data"));
}

@Test
public void testIndex() {
RestAssured.when().get("/").then().statusCode(200).log().ifValidationFails()
.body("html.head.title", equalTo("Simple Site"))
.body("html.body.div.h1[0]", containsString("New Post"))
.body("html.body.div.h1[1]", containsString("Some Post"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,7 @@ public class RoqFrontMatterTest {
static final QuarkusUnitTest unitTest = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addAsResource("application.properties")
.addAsResource("site/_data/foo.yml")
.addAsResource("site/_includes/foo/bar.html")
.addAsResource("site/_includes/view.html")
.addAsResource("site/_includes/header.html")
.addAsResource("site/_layouts/default.html")
.addAsResource("site/_layouts/page.html")
.addAsResource("site/_layouts/post.html")
.addAsResource("site/index.html")
.addAsResource("site/_posts/awesome-post.html")
.addAsResource("site/_posts/2020-10-24-old-post.md")
.addAsResource("site/_posts/markdown-post.md")
.addAsResource("site/pages/cool-page.html"));
.addAsResource("site"));

@Test
public void testHtmlPost() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{#seo page site/}
Loading

0 comments on commit 66a4a7a

Please sign in to comment.