diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index e023c9c..7eeeb4c 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -4,6 +4,21 @@ When contributing to this repository, please first discuss the change you wish t [GitHub issues](https://github.com/refinedmods/refinedarchitect-template/issues), [Discord](https://discordapp.com/invite/VYzsydb), or any other method with the owners of this repository before making a change. +## Quickstart + +These are the most important things to know before contributing (also explained in more detail later in this document): + +- Commit messages must adhere to [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). +- Branch names must be formatted correctly. The format is `{category}/GH-{issue number}/{lowercase-description}`. + Category must match a + category [used in our Commitlint config](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional#type-enum). You can also use `NO-ISSUE` instead of a GitHub issue number. +- We use [Checkstyle](https://checkstyle.sourceforge.io/) in our build workflow to validate coding style. It is + recommended to import the [config/checkstyle/checkstyle.xml](../config/checkstyle/checkstyle.xml) + or [config/intellij-code-style.xml](../config/intellij-code-style.xml) file into your + IDE, so that formatting rules are respected. +- Branches are kept up to date by rebasing, not by merging. +- For non-technical changes, adding a changelog entry is required. + ## Pull requests - Keep your pull request (PR) as small as possible, this makes reviewing easier. @@ -35,7 +50,7 @@ The format is `{category}/GH-{issue number}/{lowercase-description}` and a branc length. Category must match a -category [used in our Commitlint config](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional#type-enum). +category [used in our Commitlint config](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional#type-enum). You can also use `NO-ISSUE` instead of a GitHub issue number. Valid examples are: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dfd82ed..d7f3f83 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ on: types: [ opened, synchronize, reopened ] jobs: build: - uses: refinedmods/refinedarchitect/.github/workflows/build.yml@v0.7.1 + uses: refinedmods/refinedarchitect/.github/workflows/build.yml@v0.17.1 with: mutation-testing: false sonarqube: false diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index 9ad68df..e21ed03 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -17,7 +17,7 @@ on: type: string jobs: draft: - uses: refinedmods/refinedarchitect/.github/workflows/draft-release.yml@v0.7.1 + uses: refinedmods/refinedarchitect/.github/workflows/draft-release.yml@v0.17.1 with: release-type: ${{ inputs.release-type }} version-number-override: ${{ inputs.version-number-override }} diff --git a/.github/workflows/issue-for-unsupported-version.yml b/.github/workflows/issue-for-unsupported-version.yml index 682945e..f8ce865 100644 --- a/.github/workflows/issue-for-unsupported-version.yml +++ b/.github/workflows/issue-for-unsupported-version.yml @@ -4,4 +4,4 @@ on: types: [ labeled, unlabeled, reopened ] jobs: unsupported-labeler: - uses: refinedmods/refinedarchitect/.github/workflows/issue-for-unsupported-version.yml@v0.7.1 \ No newline at end of file + uses: refinedmods/refinedarchitect/.github/workflows/issue-for-unsupported-version.yml@v0.17.1 \ No newline at end of file diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 5204e83..b915c22 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -7,7 +7,7 @@ on: - closed jobs: publish-release: - uses: refinedmods/refinedarchitect/.github/workflows/publish-release.yml@v0.7.1 + uses: refinedmods/refinedarchitect/.github/workflows/publish-release.yml@v0.17.1 secrets: inherit with: project-name: 'Refined Sites' diff --git a/.github/workflows/resolved-issue-locking.yml b/.github/workflows/resolved-issue-locking.yml index a3c881c..67112b4 100644 --- a/.github/workflows/resolved-issue-locking.yml +++ b/.github/workflows/resolved-issue-locking.yml @@ -4,4 +4,4 @@ on: - cron: '0 0 * * *' jobs: lock: - uses: refinedmods/refinedarchitect/.github/workflows/resolved-issue-locking.yml@v0.7.1 \ No newline at end of file + uses: refinedmods/refinedarchitect/.github/workflows/resolved-issue-locking.yml@v0.17.1 \ No newline at end of file diff --git a/.github/workflows/validate-branch-name.yml b/.github/workflows/validate-branch-name.yml index 02b7d4c..ce5e06e 100644 --- a/.github/workflows/validate-branch-name.yml +++ b/.github/workflows/validate-branch-name.yml @@ -2,4 +2,4 @@ name: Validate branch name on: [ pull_request ] jobs: validate-branch-name: - uses: refinedmods/refinedarchitect/.github/workflows/validate-branch-name.yml@v0.7.1 \ No newline at end of file + uses: refinedmods/refinedarchitect/.github/workflows/validate-branch-name.yml@v0.17.1 \ No newline at end of file diff --git a/.github/workflows/validate-changelog.yml b/.github/workflows/validate-changelog.yml index 9fcfcab..db04af1 100644 --- a/.github/workflows/validate-changelog.yml +++ b/.github/workflows/validate-changelog.yml @@ -4,4 +4,4 @@ on: types: [ opened, synchronize, reopened, ready_for_review, labeled, unlabeled ] jobs: validate-changelog: - uses: refinedmods/refinedarchitect/.github/workflows/validate-changelog.yml@v0.7.1 \ No newline at end of file + uses: refinedmods/refinedarchitect/.github/workflows/validate-changelog.yml@v0.17.1 \ No newline at end of file diff --git a/.github/workflows/validate-commit-messages.yml b/.github/workflows/validate-commit-messages.yml index 2eb1843..043dba2 100644 --- a/.github/workflows/validate-commit-messages.yml +++ b/.github/workflows/validate-commit-messages.yml @@ -2,4 +2,4 @@ name: Validate commit messages on: [ pull_request ] jobs: validate-commit-messages: - uses: refinedmods/refinedarchitect/.github/workflows/validate-commit-messages.yml@v0.7.1 \ No newline at end of file + uses: refinedmods/refinedarchitect/.github/workflows/validate-commit-messages.yml@v0.17.1 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e8253c..4a6668d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,21 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [0.4.0] - 2024-08-15 + +### Added + +- Support for article pages. +- Support for RSS feed generation for articles. + +### Changed + +- Snapshot versions are no longer considered the latest version. + +### Fixed + +- Asciidoc Xref bug on Windows. + ## [0.3.0] - 2023-12-25 ### Added @@ -55,7 +70,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Initial release. -[Unreleased]: https://github.com/refinedmods/refinedsites/compare/v0.3.0...HEAD +[Unreleased]: https://github.com/refinedmods/refinedsites/compare/v0.4.0...HEAD + +[0.4.0]: https://github.com/refinedmods/refinedsites/compare/v0.3.0...v0.4.0 [0.3.0]: https://github.com/refinedmods/refinedsites/compare/v0.2.3...v0.3.0 diff --git a/README.md b/README.md index 239c013..9448193 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,7 @@ Refined Sites is a static site generator used by Refined Mods. - [Issues](https://github.com/refinedmods/refinedsites/issues) - [Refined Mods on GitHub](https://github.com/refinedmods) - [Discord](https://discordapp.com/invite/VYzsydb) -- [Twitter](https://twitter.com/refinedmods) -- [Mastodon](https://anvil.social/@refinedmods) +- [X (Twitter)](https://x.com/refinedmods) ## Building diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 2f0b759..0000000 --- a/build.gradle +++ /dev/null @@ -1,35 +0,0 @@ -plugins { - id "application" - id 'com.github.johnrengelman.shadow' version '8.1.1' -} - -apply from: "https://raw.githubusercontent.com/refinedmods/refinedarchitect/v0.7.1/helper.gradle" - -application { - mainClassName = "com.refinedmods.refinedsites.RefinedSites" -} - -group = 'com.refinedmods' -archivesBaseName = 'refinedsites' - -dependencies { - compileOnly 'org.projectlombok:lombok:1.18.30' - implementation 'com.google.code.gson:gson:2.10.1' - annotationProcessor 'org.projectlombok:lombok:1.18.30' - implementation 'org.thymeleaf:thymeleaf:3.1.2.RELEASE' - implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.3.0' - implementation 'org.asciidoctor:asciidoctorj:2.5.10' - implementation 'com.github.slugify:slugify:3.0.6' - implementation 'com.vdurmont:semver4j:3.1.0' - implementation 'org.slf4j:slf4j-api:2.0.9' - implementation 'org.slf4j:slf4j-simple:2.0.9' - implementation 'org.kohsuke:github-api:1.318' - implementation 'org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r' - implementation 'org.commonmark:commonmark:0.21.0' - implementation 'com.github.dfabulich:sitemapgen4j:1.1.2' -} - -// https://github.com/ultraq/thymeleaf-layout-dialect/issues/220#issuecomment-944874151 -shadowJar { - mergeGroovyExtensionModules() -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..8d3159c --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,41 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + +plugins { + id("application") + id("com.github.johnrengelman.shadow").version("8.1.1") + id("refinedarchitect.root") +} + +application { + mainClass.set("com.refinedmods.refinedsites.RefinedSites") +} + +group = "com.refinedmods" +base { + archivesName.set("refinedsites") +} + +dependencies { + compileOnly("org.projectlombok:lombok:1.18.30") + implementation("com.google.code.gson:gson:2.10.1") + annotationProcessor("org.projectlombok:lombok:1.18.30") + implementation("org.thymeleaf:thymeleaf:3.1.2.RELEASE") + implementation("nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.3.0") + implementation("org.asciidoctor:asciidoctorj:2.5.10") + implementation("com.github.slugify:slugify:3.0.6") + implementation("com.vdurmont:semver4j:3.1.0") + implementation("org.slf4j:slf4j-api:2.0.9") + implementation("org.slf4j:slf4j-simple:2.0.9") + implementation("org.kohsuke:github-api:1.318") + implementation("org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r") + implementation("org.commonmark:commonmark:0.21.0") + implementation("com.github.dfabulich:sitemapgen4j:1.1.2") + implementation("com.rometools:rome:2.1.0") +} + +// https://github.com/ultraq/thymeleaf-layout-dialect/issues/220#issuecomment-944874151 +tasks { + named("shadowJar") { + mergeGroovyExtensionModules() + } +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 5db25a3..7436fde 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -99,7 +99,9 @@ - + + + diff --git a/config/intellij-code-style.xml b/config/intellij-code-style.xml new file mode 100644 index 0000000..49b79c2 --- /dev/null +++ b/config/intellij-code-style.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index a8d260d..4da18d7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,3 @@ +refinedarchitectVersion=0.17.1 # Gradle org.gradle.jvmargs=-Xmx1G diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index fae0804..a441313 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..71ef372 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + maven { + url = uri("https://maven.pkg.github.com/refinedmods/refinedarchitect") + credentials { + username = "anything" + password = "\u0067hp_oGjcDFCn8jeTzIj4Ke9pLoEVtpnZMP4VQgaX" + } + } + maven { + name = "Fabric" + url = uri("https://maven.fabricmc.net/") + } + } + val refinedarchitectVersion: String by settings + plugins { + id("refinedarchitect.root").version(refinedarchitectVersion) + } +} + +rootProject.name = "refinedsites" diff --git a/src/main/java/com/refinedmods/refinedsites/model/Site.java b/src/main/java/com/refinedmods/refinedsites/model/Site.java index 5760cb0..c8eb69e 100644 --- a/src/main/java/com/refinedmods/refinedsites/model/Site.java +++ b/src/main/java/com/refinedmods/refinedsites/model/Site.java @@ -48,7 +48,7 @@ private void sortComponents(final List componentsWithSameName) { // sort componentsWithSameName based on NEWEST-first semver version componentsWithSameName.sort((component1, component2) -> { if (component1.getVersion().snapshot()) { - return -1; + return 1; } final Semver semver1 = new Semver(component1.getVersion().name()); final Semver semver2 = new Semver(component2.getVersion().name()); diff --git a/src/main/java/com/refinedmods/refinedsites/render/ArticleRender.java b/src/main/java/com/refinedmods/refinedsites/render/ArticleRender.java new file mode 100644 index 0000000..f40c724 --- /dev/null +++ b/src/main/java/com/refinedmods/refinedsites/render/ArticleRender.java @@ -0,0 +1,15 @@ +package com.refinedmods.refinedsites.render; + +import java.time.LocalDate; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class ArticleRender { + private final String title; + private final String description; + private final String url; + private final LocalDate date; +} diff --git a/src/main/java/com/refinedmods/refinedsites/render/IncludeProcessorImpl.java b/src/main/java/com/refinedmods/refinedsites/render/IncludeProcessorImpl.java index 9ae3a1a..2ce94c4 100644 --- a/src/main/java/com/refinedmods/refinedsites/render/IncludeProcessorImpl.java +++ b/src/main/java/com/refinedmods/refinedsites/render/IncludeProcessorImpl.java @@ -33,7 +33,7 @@ public void process(final Document document, final String xrefPath = matchResult.group(1); final Path xrefPathFull = path.getParent().resolve(xrefPath); final Path relativeToSource = Paths.get(reader.getDir()).relativize(xrefPathFull); - return "xref:" + relativeToSource; + return "xref:" + relativeToSource.toString().replace("\\", "/"); }); reader.pushInclude(fixedContent, path.toString(), path.toString(), 1, attributes); } catch (final IOException e) { diff --git a/src/main/java/com/refinedmods/refinedsites/render/PageAttributeCache.java b/src/main/java/com/refinedmods/refinedsites/render/PageAttributeCache.java index 1b08100..ee40955 100644 --- a/src/main/java/com/refinedmods/refinedsites/render/PageAttributeCache.java +++ b/src/main/java/com/refinedmods/refinedsites/render/PageAttributeCache.java @@ -1,6 +1,7 @@ package com.refinedmods.refinedsites.render; import java.nio.file.Path; +import java.time.LocalDate; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -29,11 +30,20 @@ public PageAttributes getAttributes(final Asciidoctor asciidoctor, final Path pa private PageAttributes doLoadAttributes(final Asciidoctor asciidoctor, final Path path) { final Document document = asciidoctor.loadFile(path.toFile(), Options.builder().build()); return new PageAttributes( + Optional.ofNullable(document.getAttribute("type")).map(Object::toString).orElse("page"), document.getDoctitle(), + Optional.ofNullable(document.getAttribute("description")).map(Object::toString).orElse(""), + Optional.ofNullable(document.getAttribute("date")) + .map(Object::toString) + .map(LocalDate::parse), Optional.ofNullable(document.getAttribute("icon")).map(Object::toString) ); } - public record PageAttributes(String name, Optional icon) { + public record PageAttributes(String type, + String name, + String description, + Optional date, + Optional icon) { } } diff --git a/src/main/java/com/refinedmods/refinedsites/render/PageInfo.java b/src/main/java/com/refinedmods/refinedsites/render/PageInfo.java index ab353e6..9238b12 100644 --- a/src/main/java/com/refinedmods/refinedsites/render/PageInfo.java +++ b/src/main/java/com/refinedmods/refinedsites/render/PageInfo.java @@ -1,15 +1,20 @@ package com.refinedmods.refinedsites.render; import java.nio.file.Path; +import java.time.LocalDate; import java.util.List; +import java.util.Optional; import lombok.Builder; @Builder -record PageInfo(String title, +record PageInfo(String type, + String title, + String description, String parsedContent, String relativePath, String icon, + Optional date, IconReferences iconReferences, List tableOfContents, Path pageOutputPath) { diff --git a/src/main/java/com/refinedmods/refinedsites/render/Renderer.java b/src/main/java/com/refinedmods/refinedsites/render/Renderer.java index 2873929..2cd1917 100644 --- a/src/main/java/com/refinedmods/refinedsites/render/Renderer.java +++ b/src/main/java/com/refinedmods/refinedsites/render/Renderer.java @@ -15,11 +15,15 @@ import java.io.FileWriter; import java.io.IOException; +import java.io.Writer; import java.net.MalformedURLException; import java.nio.file.Files; import java.nio.file.Path; +import java.time.LocalDate; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -35,6 +39,14 @@ import com.redfin.sitemapgenerator.SitemapIndexGenerator; import com.redfin.sitemapgenerator.WebSitemapGenerator; import com.redfin.sitemapgenerator.WebSitemapUrl; +import com.rometools.rome.feed.synd.SyndContent; +import com.rometools.rome.feed.synd.SyndContentImpl; +import com.rometools.rome.feed.synd.SyndEntry; +import com.rometools.rome.feed.synd.SyndEntryImpl; +import com.rometools.rome.feed.synd.SyndFeed; +import com.rometools.rome.feed.synd.SyndFeedImpl; +import com.rometools.rome.io.FeedException; +import com.rometools.rome.io.SyndFeedOutput; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import nz.net.ultraq.thymeleaf.layoutdialect.LayoutDialect; @@ -167,16 +179,32 @@ private void renderComponent(final Component component, ); final PageAttributeCache pageAttributeCache = new PageAttributeCache(); final Map pageInfo = new HashMap<>(); + final Map> infosByPageType = new HashMap<>(); for (final Path pagePath : component.getPages()) { - pageInfo.put(pagePath, renderPagePre( + final PageInfo singleInfo = renderPagePre( pagePath, component, pageAttributeCache, sourceToDestinationAssets, componentOutputPath - )); + ); + pageInfo.put(pagePath, singleInfo); + infosByPageType.computeIfAbsent(singleInfo.type(), k -> new ArrayList<>()).add(singleInfo); } prepareNavigationItems(component.getNavigationItems(), pageInfo); + final List articles = infosByPageType.getOrDefault("article", Collections.emptyList()) + .stream() + .map(info -> new ArticleRender( + info.title(), + info.description(), + info.relativePath(), + info.date().orElse(LocalDate.EPOCH) + )) + .sorted(Comparator.comparing(ArticleRender::getDate).reversed()) + .toList(); + + writeRssFeed(component, sitemapBaseUrl, articles, componentOutputPath); + for (final Path pagePath : component.getPages()) { renderPage( pagePath, @@ -188,7 +216,8 @@ private void renderComponent(final Component component, parsedReleases, releaseMatchingComponentVersion, sitemapBaseUrl, - componentSitemap + componentSitemap, + articles ); } if (componentSitemap != null) { @@ -197,6 +226,37 @@ private void renderComponent(final Component component, } } + private static void writeRssFeed(final Component component, + final String sitemapBaseUrl, + final List articles, + final Path componentOutputPath) { + final SyndFeed feed = new SyndFeedImpl(); + feed.setFeedType("rss_1.0"); + feed.setTitle(component.getName() + " news"); + feed.setLink(sitemapBaseUrl + "/rss.xml"); + feed.setDescription("The latest news about " + component.getName()); + for (final ArticleRender article : articles) { + final SyndEntry entry = new SyndEntryImpl(); + entry.setTitle(article.getTitle()); + final SyndContent description = new SyndContentImpl(); + description.setType("text/html"); + description.setValue(article.getDescription()); + entry.setDescription(description); + entry.setPublishedDate(asDate(article.getDate())); + entry.setUpdatedDate(asDate(article.getDate())); + entry.setLink(sitemapBaseUrl + "/" + article.getUrl().replace("\\", "/")); + feed.getEntries().add(entry); + } + try { + final Writer writer = new FileWriter(componentOutputPath.resolve("rss.xml").toFile()); + final SyndFeedOutput syndFeedOutput = new SyndFeedOutput(); + syndFeedOutput.output(feed, writer); + writer.close(); + } catch (final IOException | FeedException e) { + throw new RuntimeException(e); + } + } + private Path getComponentOutputPath(final Component component) throws IOException { final Path componentOutputPath; if (component.isRoot()) { @@ -366,7 +426,10 @@ private PageInfo renderPagePre(final Path pagePath, .toList(); final String parsedContent = (String) document.getContent(); return PageInfo.builder() + .type(pageAttributes.type()) .title(pageAttributes.name()) + .description(pageAttributes.description()) + .date(pageAttributes.date()) .tableOfContents(toc) .iconReferences(icons) .parsedContent(parsedContent @@ -397,7 +460,8 @@ private void renderPage(final Path pagePath, final List releases, final ParsedRelease releaseMatchingComponentVersion, final String baseUrl, - @Nullable final WebSitemapGenerator sitemapGenerator) throws IOException { + @Nullable final WebSitemapGenerator sitemapGenerator, + final List articles) throws IOException { log.info("Rendering page {}", pagePath); final Context context = new Context(); final PageInfo info = pageInfo.get(pagePath); @@ -422,22 +486,29 @@ private void renderPage(final Path pagePath, .collect(Collectors.toList()); Collections.reverse(otherReleases); context.setVariable("otherReleases", otherReleases); + context.setVariable("articles", articles); + context.setVariable("date", info.date().orElse(LocalDate.EPOCH)); + context.setVariable("description", info.description()); linkBuilder.setCurrentPageOutputPath(info.pageOutputPath()); - final String template = getTemplate(pagePath); + final String template = getTemplate(pagePath, info.type()); templateEngine.process(template, context, fileWriter); if (sitemapGenerator != null && !info.relativePath().contains("404")) { sitemapGenerator.addUrl(new WebSitemapUrl.Options( baseUrl + "/" + componentOutputPath.relativize(info.pageOutputPath()) - ).lastMod(renderDate).changeFreq(ChangeFreq.DAILY).build()); + ).lastMod(info.date().map(Renderer::asDate).orElse(renderDate)).changeFreq(ChangeFreq.DAILY).build()); } } - private String getTemplate(final Path pagePath) { + private static Date asDate(final LocalDate localDate) { + return Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()); + } + + private String getTemplate(final Path pagePath, final String pageType) { final Path potentialTemplateOverridePath = Path.of( pagePath.toString().replace(".adoc", ".html") ); if (!Files.exists(potentialTemplateOverridePath)) { - return "page.html"; + return "article".equals(pageType) ? "article.html" : "page.html"; } return sourcePath.relativize(potentialTemplateOverridePath).toString(); }