diff --git a/docs/modules/ROOT/assets/images/roq-how-it-works.png b/docs/modules/ROOT/assets/images/roq-how-it-works.png index 3a87cb63..89b15108 100644 Binary files a/docs/modules/ROOT/assets/images/roq-how-it-works.png and b/docs/modules/ROOT/assets/images/roq-how-it-works.png differ diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index f584150f..449dd027 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -6,7 +6,7 @@ Roq allows to easily create a static website or blog using Quarkus super-powers. == How it works -// https://excalidraw.com/#json=FbBiH5H7j5qFS-XZ96TDI,GIDI6Ss_p_VFKo62ip9t4A +// https://excalidraw.com/#json=pZssfxY47ooeLKkHeH0cM,7jxUkcUdHu3WcR1ktCRFow image::roq-how-it-works.png[Roq - How it works] [TIP] diff --git a/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/scan/RoqFrontMatterScanProcessor.java b/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/scan/RoqFrontMatterScanProcessor.java index 47d09dd9..be7b3cec 100644 --- a/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/scan/RoqFrontMatterScanProcessor.java +++ b/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/scan/RoqFrontMatterScanProcessor.java @@ -42,6 +42,7 @@ import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem; +import io.quarkus.paths.PathVisit; import io.quarkus.qute.deployment.TemplatePathBuildItem; import io.quarkus.qute.deployment.TemplateRootBuildItem; import io.quarkus.runtime.configuration.ConfigurationException; @@ -150,8 +151,14 @@ public List resolveItems(RoqProjectBuildItem } roqProject.consumePathFromRoqResourceDir(config.contentDir(), - l -> scanContent(mapper, config, markups, watch, dataModifications, items, l.getPath())); - roqProject.consumePathFromRoqResourceDir(config.staticDir(), l -> scanStatic(config, staticFilesProducer, l.getPath())); + l -> { + watchResourceDir(watch, l); + scanContent(mapper, config, markups, watch, dataModifications, items, l.getPath()); + }); + roqProject.consumePathFromRoqResourceDir(config.staticDir(), l -> { + watchResourceDir(watch, l); + scanStatic(config, staticFilesProducer, l.getPath()); + }); return items; } @@ -165,12 +172,19 @@ private static Consumer createRoqDirConsumer(YAMLMapper mapper, RoqSiteCon if (!Files.isDirectory(root)) { return; } + // We scan Qute templates manually outside of resources for now - scanTemplates(config, watch, templatePathProducer, root.resolve(TEMPLATES_DIR)); - scanLayouts(mapper, config, markups, watch, dataModifications, items, root.resolve(TEMPLATES_DIR), + final Path templatesDir = root.resolve(TEMPLATES_DIR); + watchDirectory(templatesDir, watch); + scanTemplates(config, watch, templatePathProducer, templatesDir); + scanLayouts(mapper, config, markups, watch, dataModifications, items, templatesDir, TemplateType.LAYOUT); - scanContent(mapper, config, markups, watch, dataModifications, items, root.resolve(config.contentDir())); - scanStatic(config, staticFilesProducer, root.resolve(config.staticDir())); + final Path contentDir = root.resolve(config.contentDir()); + watchDirectory(contentDir, watch); + scanContent(mapper, config, markups, watch, dataModifications, items, contentDir); + final Path staticDir = root.resolve(config.staticDir()); + watchDirectory(staticDir, watch); + scanStatic(config, staticFilesProducer, staticDir); }; } @@ -202,6 +216,7 @@ private static void scanContent(YAMLMapper mapper, RoqSiteConfig config, if (!Files.isDirectory(contentDir)) { return; } + // scan content final Map collections = config.collections().stream() .collect(Collectors.toMap(ConfiguredCollection::id, Function.identity())); @@ -212,10 +227,10 @@ private static void scanContent(YAMLMapper mapper, RoqSiteConfig config, .forEach(p -> { final String dirName = contentDir.relativize(p).getName(0).toString(); if (collections.containsKey(dirName)) { - addBuildItem(contentDir, items, mapper, config, markups, dataModifications, watch, + addBuildItem(contentDir, items, mapper, config, markups, dataModifications, collections.get(dirName), TemplateType.DOCUMENT_PAGE).accept(p); } else { - addBuildItem(contentDir, items, mapper, config, markups, dataModifications, watch, null, + addBuildItem(contentDir, items, mapper, config, markups, dataModifications, null, TemplateType.NORMAL_PAGE).accept(p); } }); @@ -226,6 +241,30 @@ private static void scanContent(YAMLMapper mapper, RoqSiteConfig config, } + private static void watchDirectory(Path dir, BuildProducer watch) { + if (!Files.isDirectory(dir)) { + return; + } + try (var stream = Files.walk(dir)) { + stream.forEach(f -> { + watch.produce(HotDeploymentWatchedFileBuildItem.builder().setLocation(f.toAbsolutePath().toString()).build()); + if (LOGGER.isDebugEnabled()) { + LOGGER.debugf("Watching %s for changes", f); + } + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static void watchResourceDir(BuildProducer watch, PathVisit l) { + final String dir = l.getRelativePath(); + watch.produce(HotDeploymentWatchedFileBuildItem.builder().setLocationPredicate(p -> p.startsWith(dir)).build()); + if (LOGGER.isDebugEnabled()) { + LOGGER.debugf("Watching resources %s for changes", dir); + } + } + private static void scanLayouts(YAMLMapper mapper, RoqSiteConfig config, Map markups, @@ -245,7 +284,7 @@ private static void scanLayouts(YAMLMapper mapper, try (Stream stream = Files.walk(layoutsDir)) { final Consumer layoutsConsumer = addBuildItem(templatesRoot, items, mapper, config, markups, dataModifications, - watch, null, + null, type); stream .filter(Files::isRegularFile) @@ -271,7 +310,6 @@ private static void scanTemplates(RoqSiteConfig config, if (!Files.isDirectory(templatesRoot)) { return; } - // scan templates try (Stream stream = Files.walk(templatesRoot)) { stream @@ -284,8 +322,6 @@ private static void scanTemplates(RoqSiteConfig config, } // add Qute templates try { - watch.produce(HotDeploymentWatchedFileBuildItem.builder().setLocation(p.toAbsolutePath().toString()) - .build()); final String link = toUnixPath(templatesRoot.relativize(p).toString()); templatePathProducer.produce(TemplatePathBuildItem.builder() .path(link) @@ -309,11 +345,9 @@ private static Consumer addBuildItem(Path root, RoqSiteConfig config, Map markups, List dataModifications, - BuildProducer watch, ConfiguredCollection collection, TemplateType type) { return file -> { - watch.produce(HotDeploymentWatchedFileBuildItem.builder().setLocation(file.toAbsolutePath().toString()).build()); String sourcePath = toUnixPath(root.relativize(file).toString()); String quteTemplatePath = ROQ_GENERATED_QUTE_PREFIX + removeExtension(sourcePath) + resolveOutputExtension(markups, sourcePath); @@ -338,7 +372,8 @@ private static Consumer addBuildItem(Path root, fm.getString(LAYOUT_KEY)); final String content = stripFrontMatter(fullContent); ZonedDateTime date = parsePublishDate(file, fm, config.dateFormat(), config.timeZone()); - if (date != null && !config.future() && date.isAfter(ZonedDateTime.now())) { + final boolean noFuture = !config.future() && (collection == null || !collection.future()); + if (date != null && noFuture && date.isAfter(ZonedDateTime.now())) { return; } String dateString = date.format(DateTimeFormatter.ISO_ZONED_DATE_TIME); diff --git a/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/config/ConfiguredCollection.java b/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/config/ConfiguredCollection.java index 8e1662cc..b0dc1296 100644 --- a/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/config/ConfiguredCollection.java +++ b/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/config/ConfiguredCollection.java @@ -1,4 +1,4 @@ package io.quarkiverse.roq.frontmatter.runtime.config; -public record ConfiguredCollection(String id, boolean hidden) { +public record ConfiguredCollection(String id, boolean hidden, boolean future) { } diff --git a/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/config/RoqSiteConfig.java b/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/config/RoqSiteConfig.java index ba5eb73c..1520970a 100644 --- a/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/config/RoqSiteConfig.java +++ b/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/config/RoqSiteConfig.java @@ -21,7 +21,7 @@ public interface RoqSiteConfig { String CONTENT_DIR = "content"; String STATIC_DIR = "static"; String IGNORED_FILES = "**/_**,_**,.**"; - List DEFAULT_COLLECTIONS = List.of(new ConfiguredCollection("posts", false)); + List DEFAULT_COLLECTIONS = List.of(new ConfiguredCollection("posts", false, false)); /** * The root path of your site (e.g. /blog) relative the quarkus http root path. @@ -75,7 +75,7 @@ public interface RoqSiteConfig { boolean generator(); /** - * Show future pages + * Show future documents */ @WithDefault("false") boolean future(); @@ -122,7 +122,7 @@ default List collections() { return DEFAULT_COLLECTIONS; } return collectionsMap().entrySet().stream().filter(e -> e.getValue().enabled()) - .map(e -> new ConfiguredCollection(e.getKey(), e.getValue().hidden())).toList(); + .map(e -> new ConfiguredCollection(e.getKey(), e.getValue().hidden(), e.getValue().future())).toList(); } interface CollectionConfig { @@ -133,6 +133,12 @@ interface CollectionConfig { @WithDefault("true") boolean enabled(); + /** + * Show future documents (overrides global future for this collection) + */ + @WithDefault("false") + boolean future(); + /** * If true, the collection won't be available on path but consumable as data. */ diff --git a/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/model/RoqCollection.java b/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/model/RoqCollection.java index 4183e8b9..36bc1a27 100644 --- a/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/model/RoqCollection.java +++ b/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/model/RoqCollection.java @@ -1,5 +1,6 @@ package io.quarkiverse.roq.frontmatter.runtime.model; +import java.time.ZonedDateTime; import java.util.*; import java.util.stream.Collectors; @@ -49,7 +50,7 @@ public DocumentPage prevPage(DocumentPage page) { /** * Get the sub-list of documents depending on the given paginator */ - public List paginated(Paginator paginator) { + public Collection paginated(Paginator paginator) { if (paginator == null) { return this; } @@ -58,6 +59,20 @@ public List paginated(Paginator paginator) { Math.min(this.size(), (zeroBasedCurrent * paginator.limit()) + paginator.limit())); } + /** + * @return future documents + */ + public Collection future() { + return stream().filter(d -> d.date().isAfter(ZonedDateTime.now())).toList(); + } + + /** + * @return past documents + */ + public Collection past() { + return stream().filter(d -> d.date().isBefore(ZonedDateTime.now())).toList(); + } + /** * Retrieves a list of non-null values from the pages for the specified keys. * This method searches through all the pages for each of the provided keys and @@ -66,7 +81,7 @@ public List paginated(Paginator paginator) { * @param keys the keys to search for in the pages' data. Multiple keys can be passed. * @return a {@code List} containing all non-null values found in the pages for the specified keys. */ - public List by(String... keys) { + public Collection by(String... keys) { return this.stream() .flatMap(page -> Arrays.stream(keys) .map(page::data) // Get the data for each key diff --git a/theme/default/src/main/resources/templates/partials/roq-default/head.html b/theme/default/src/main/resources/templates/partials/roq-default/head.html index 1821c910..b6839b29 100644 --- a/theme/default/src/main/resources/templates/partials/roq-default/head.html +++ b/theme/default/src/main/resources/templates/partials/roq-default/head.html @@ -6,16 +6,12 @@ {#seo page site /} {#rss site /} - - - -