From c56e6c8aa03710f3a79115d9a2f60f83799b7531 Mon Sep 17 00:00:00 2001 From: "manik.magar" Date: Fri, 24 Jun 2016 00:20:06 -0400 Subject: [PATCH 1/6] Implemented Categories feature --- src/main/java/org/jbake/app/ConfigUtil.java | 25 ++++ src/main/java/org/jbake/app/ContentStore.java | 18 +++ src/main/java/org/jbake/app/Crawler.java | 2 + src/main/java/org/jbake/app/Renderer.java | 52 ++++++++ .../java/org/jbake/parser/MarkupEngine.java | 34 +++++- .../org/jbake/render/CategoriesRenderer.java | 33 ++++++ .../model/AllCategoriesExtractor.java | 35 ++++++ .../model/CategoryPostsExtractor.java | 23 ++++ ....jbake.template.ModelExtractors.properties | 2 + .../services/org.jbake.render.RenderingTool | 3 +- src/main/resources/default.properties | 17 ++- src/test/java/org/jbake/app/ParserTest.java | 111 ++++++++++++++++++ .../AbstractTemplateEngineRenderingTest.java | 5 + ...FreemarkerTemplateEngineRenderingTest.java | 33 ++++++ .../GroovyTemplateEngineRenderingTest.java | 2 - .../content/blog/2012/first-post.html | 16 +++ .../freemarkerTemplates/categories.ftl | 15 +++ .../freemarkerTemplates/category.ftl | 27 +++++ .../resources/groovyTemplates/categories.gsp | 14 +++ .../resources/groovyTemplates/category.gsp | 27 +++++ 20 files changed, 489 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/jbake/render/CategoriesRenderer.java create mode 100644 src/main/java/org/jbake/template/model/AllCategoriesExtractor.java create mode 100644 src/main/java/org/jbake/template/model/CategoryPostsExtractor.java create mode 100644 src/test/resources/content/blog/2012/first-post.html create mode 100644 src/test/resources/freemarkerTemplates/categories.ftl create mode 100644 src/test/resources/freemarkerTemplates/category.ftl create mode 100644 src/test/resources/groovyTemplates/categories.gsp create mode 100644 src/test/resources/groovyTemplates/category.gsp diff --git a/src/main/java/org/jbake/app/ConfigUtil.java b/src/main/java/org/jbake/app/ConfigUtil.java index 63b9f986e..7884977cd 100644 --- a/src/main/java/org/jbake/app/ConfigUtil.java +++ b/src/main/java/org/jbake/app/ConfigUtil.java @@ -157,6 +157,11 @@ public interface Keys { */ String RENDER_TAGS = "render.tags"; + /** + * Flag indicating if category files should be generated + */ + String RENDER_CATEGORIES = "render.categories"; + /** * Port used when running Jetty server */ @@ -211,6 +216,26 @@ public interface Keys { * The configured base URI for the hosted content */ String SITE_HOST = "site.host"; + + /** + * Should Category generation and usage be enabled? Default is true. Posts without categories will be marked as 'Uncategorized'. Default category can be changed with {#link #CATEGORY_DEFAULT}. + */ + String CATEGORIES_ENABLE = "categories.enable"; + + /** + * Default Category for posts when {@link CATEGORIES_ENABLE} is {@code true} and no category is specified for post. + */ + String CATEGORY_DEFAULT = "category.default"; + + /** + * Tags output path, used only when {@link #RENDER_CATEGORIES} is true + */ + String CATEGORY_PATH = "categories.path"; + + /** + * Should Categories be sanitized for space? + */ + String CATEGORY_SANITIZE = "categories.sanitize"; } private final static Logger LOGGER = LoggerFactory.getLogger(ConfigUtil.class); diff --git a/src/main/java/org/jbake/app/ContentStore.java b/src/main/java/org/jbake/app/ContentStore.java index 9ccd9fec5..49dbd4781 100644 --- a/src/main/java/org/jbake/app/ContentStore.java +++ b/src/main/java/org/jbake/app/ContentStore.java @@ -145,6 +145,10 @@ public DocumentList getPublishedPostsByTag(String tag) { return query("select * from post where status='published' and ? in tags order by date desc", tag); } + public DocumentList getPublishedPostsByCategories(String category) { + return query("select * from post where status='published' and ? in categories order by date desc", category); + } + public DocumentList getPublishedDocumentsByTag(String tag) { final DocumentList documents = new DocumentList(); for (final String docType : DocumentTypes.getDocumentTypes()) { @@ -174,6 +178,10 @@ public DocumentList getAllContent(String docType) { return query(query); } + public DocumentList getAllCategoriesFromPublishedPosts() { + return query("select categories from post where status='published'"); + } + public DocumentList getAllTagsFromPublishedPosts() { return query("select tags from post where status='published'"); } @@ -242,6 +250,16 @@ public Set getAllTags() { return result; } + public Set getCategories() { + DocumentList docs = this.getAllCategoriesFromPublishedPosts(); + Set result = new HashSet(); + for (Map document : docs) { + String[] categories = DBUtil.toStringArray(document.get("categories")); + Collections.addAll(result, categories); + } + return result; + } + private void createDocType(final OSchema schema, final String doctype) { logger.debug("Create document class '{}'", doctype ); diff --git a/src/main/java/org/jbake/app/Crawler.java b/src/main/java/org/jbake/app/Crawler.java index bdf68cb91..4077ba02e 100644 --- a/src/main/java/org/jbake/app/Crawler.java +++ b/src/main/java/org/jbake/app/Crawler.java @@ -50,6 +50,8 @@ interface Status { String ALLTAGS = "alltags"; String PUBLISHED_DATE = "published_date"; String BODY = "body"; + String CATEGORIES = "categories"; + String CATEGORY = "category"; } private static final Logger LOGGER = LoggerFactory.getLogger(Crawler.class); diff --git a/src/main/java/org/jbake/app/Renderer.java b/src/main/java/org/jbake/app/Renderer.java index 633d2c05e..31e91e628 100644 --- a/src/main/java/org/jbake/app/Renderer.java +++ b/src/main/java/org/jbake/app/Renderer.java @@ -364,6 +364,58 @@ public int renderTags(String tagPath) throws Exception { return renderedCount; } } + + /** + * Render tag files using the supplied content. + * + * @param categories The content to renderDocument + * @param categoriesPath The output path + * @throws Exception + */ + public int renderCategories(String categoriesPath) throws Exception { + int renderedCount = 0; + final List errors = new LinkedList(); + for (String category : db.getCategories()) { + try { + Map model = new HashMap(); + model.put("renderer", renderingEngine); + model.put(Attributes.CATEGORY, category); + Map map = buildSimpleModel(Attributes.CATEGORY); + map.put(Attributes.ROOTPATH, "../"); + model.put("content", map); + + String pathCategory = category.trim().replace(" ", "-") + config.getString(Keys.OUTPUT_EXTENSION); + File path = new File(destination.getPath() + File.separator + categoriesPath + File.separator + pathCategory); + render(new ModelRenderingConfig(path, Attributes.CATEGORY, model, findTemplateName(Attributes.CATEGORY))); + renderedCount++; + } catch (Exception e) { + errors.add(e.getCause().getMessage()); + } + } + + // Add an index file at root folder of categories. + // This will prevent directory listing and also provide an option to display all categories page. + Map model = new HashMap(); + model.put("renderer", renderingEngine); + Map map = buildSimpleModel(Attributes.CATEGORIES); + map.put(Attributes.ROOTPATH, "../"); + model.put("content", map); + + File path = new File(destination.getPath() + File.separator + categoriesPath + File.separator + "index" + config.getString(Keys.OUTPUT_EXTENSION)); + render(new ModelRenderingConfig(path, Attributes.CATEGORIES, model, findTemplateName(Attributes.CATEGORIES))); + renderedCount++; + + if (!errors.isEmpty()) { + StringBuilder sb = new StringBuilder(); + sb.append("Failed to render Categories. Cause(s):"); + for(String error: errors) { + sb.append("\n" + error); + } + throw new Exception(sb.toString()); + } else { + return renderedCount; + } + } /** * Builds simple map of values, which are exposed when rendering index/archive/sitemap/feed/tags. diff --git a/src/main/java/org/jbake/parser/MarkupEngine.java b/src/main/java/org/jbake/parser/MarkupEngine.java index 803ca4023..e6a479286 100644 --- a/src/main/java/org/jbake/parser/MarkupEngine.java +++ b/src/main/java/org/jbake/parser/MarkupEngine.java @@ -15,6 +15,7 @@ import org.apache.commons.configuration.Configuration; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; import org.jbake.app.ConfigUtil.Keys; import org.jbake.app.Crawler; import org.json.simple.JSONValue; @@ -140,6 +141,31 @@ public Map parse(Configuration config, File file, String content content.put(Crawler.Attributes.TAGS, tags); } + // If categories are not disabled then add a default category + if(config.getBoolean(Keys.CATEGORIES_ENABLE)){ + if (content.get("categories") != null) { + String[] categories = (String[]) content.get("categories"); + for( int i=0; i contents, final Ma } } else if (key.equalsIgnoreCase(Crawler.Attributes.TAGS)) { content.put(key, getTags(value)); - } else if (isJson(value)) { + } else if (key.equalsIgnoreCase("categories")){ + List categories = new ArrayList(); + for (String category : parts[1].split(",")){ + categories.add(category.trim()); + } + content.put(parts[0], categories.toArray(new String[0])); + } else if (isJson(value)) { content.put(key, JSONValue.parse(value)); } else { content.put(key, value); diff --git a/src/main/java/org/jbake/render/CategoriesRenderer.java b/src/main/java/org/jbake/render/CategoriesRenderer.java new file mode 100644 index 000000000..cd6a34f12 --- /dev/null +++ b/src/main/java/org/jbake/render/CategoriesRenderer.java @@ -0,0 +1,33 @@ +package org.jbake.render; + +import java.io.File; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.jbake.app.ConfigUtil.Keys; +import org.jbake.app.ContentStore; +import org.jbake.app.Renderer; +import org.jbake.template.RenderingException; + +/** + * + * This class renders Post Categories. + * + * @author Manik Magar + * + */ +public class CategoriesRenderer implements RenderingTool { + + @Override + public int render(Renderer renderer, ContentStore db, File destination, File templatesPath, CompositeConfiguration config) throws RenderingException { + if (config.getBoolean(Keys.RENDER_CATEGORIES)) { + try { + return renderer.renderCategories(config.getString(Keys.CATEGORY_PATH)); + } catch (Exception e) { + throw new RenderingException(e); + } + } else { + return 0; + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/jbake/template/model/AllCategoriesExtractor.java b/src/main/java/org/jbake/template/model/AllCategoriesExtractor.java new file mode 100644 index 000000000..db565797f --- /dev/null +++ b/src/main/java/org/jbake/template/model/AllCategoriesExtractor.java @@ -0,0 +1,35 @@ +package org.jbake.template.model; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.jbake.app.ContentStore; +import org.jbake.app.Crawler; +import org.jbake.app.DBUtil; +import org.jbake.app.DocumentList; +import org.jbake.template.ModelExtractor; + + +/** + * + * This extractor model will list of categories from all published content. + * + * @author Manik MAgar + * + */ +public class AllCategoriesExtractor implements ModelExtractor> { + + @Override + public Set get(ContentStore db, Map model, String key) { + DocumentList query = db.getAllCategoriesFromPublishedPosts(); + Set result = new HashSet(); + for (Map document : query) { + String[] categories = DBUtil.toStringArray(document.get(Crawler.Attributes.CATEGORIES)); + Collections.addAll(result, categories); + } + return result; + } + +} \ No newline at end of file diff --git a/src/main/java/org/jbake/template/model/CategoryPostsExtractor.java b/src/main/java/org/jbake/template/model/CategoryPostsExtractor.java new file mode 100644 index 000000000..ca2fa6a0a --- /dev/null +++ b/src/main/java/org/jbake/template/model/CategoryPostsExtractor.java @@ -0,0 +1,23 @@ +package org.jbake.template.model; + +import java.util.Map; + +import org.jbake.app.ContentStore; +import org.jbake.app.Crawler; +import org.jbake.app.DocumentList; +import org.jbake.template.ModelExtractor; + + +public class CategoryPostsExtractor implements ModelExtractor { + + @Override + public DocumentList get(ContentStore db, Map model, String key) { + String category = null; + if (model.get(Crawler.Attributes.CATEGORY) != null) { + category = model.get(Crawler.Attributes.CATEGORY).toString(); + } + DocumentList query = db.getPublishedPostsByCategories(category); + return query; + } + +} \ No newline at end of file diff --git a/src/main/resources/META-INF/org.jbake.template.ModelExtractors.properties b/src/main/resources/META-INF/org.jbake.template.ModelExtractors.properties index 5a2f1dfde..c6878f54b 100644 --- a/src/main/resources/META-INF/org.jbake.template.ModelExtractors.properties +++ b/src/main/resources/META-INF/org.jbake.template.ModelExtractors.properties @@ -8,3 +8,5 @@ org.jbake.template.model.PublishedDateExtractor=published_date org.jbake.template.model.DBExtractor=db org.jbake.template.model.TagPostsExtractor=tag_posts org.jbake.template.model.TaggedDocumentsExtractor=tagged_documents +org.jbake.template.model.CategoryPostsExtractor=category_posts +org.jbake.template.model.AllCategoriesExtractor=all_categories diff --git a/src/main/resources/META-INF/services/org.jbake.render.RenderingTool b/src/main/resources/META-INF/services/org.jbake.render.RenderingTool index ff03c55c7..adf4c2064 100644 --- a/src/main/resources/META-INF/services/org.jbake.render.RenderingTool +++ b/src/main/resources/META-INF/services/org.jbake.render.RenderingTool @@ -3,4 +3,5 @@ org.jbake.render.DocumentsRenderer org.jbake.render.FeedRenderer org.jbake.render.IndexRenderer org.jbake.render.SitemapRenderer -org.jbake.render.TagsRenderer \ No newline at end of file +org.jbake.render.TagsRenderer +org.jbake.render.CategoriesRenderer \ No newline at end of file diff --git a/src/main/resources/default.properties b/src/main/resources/default.properties index 97d90b3f2..b62ab0c64 100644 --- a/src/main/resources/default.properties +++ b/src/main/resources/default.properties @@ -20,6 +20,10 @@ template.sitemap.file=sitemap.ftl template.post.file=post.ftl # filename of page template file template.page.file=page.ftl +# filename of page template file +template.category.file=category.ftl +# file name of page template for {site.url}/categories +template.categories.file=categories.ftl # folder that contains all content files content.folder=content # folder that contains all asset files @@ -90,4 +94,15 @@ db.path=cache # enable extension-less URI option? uri.noExtension=false # Set to a prefix path (starting with a slash) for which to generate extension-less URI's (i.e. a folder with index.html in) -uri.noExtension.prefix= \ No newline at end of file +uri.noExtension.prefix= +#Enable usage of categories. If disabled then nothing related to categories is processed or generated. +categories.enable=true +# Should categories pages be genetated +render.categories=true +#What should be the default category when no categories are specified for post and categories are enabled. +category.default=Uncategorized +# where to generate category pages +categories.path=categories +# Should spaces be replaced with hyphens +categories.sanitize=false + diff --git a/src/test/java/org/jbake/app/ParserTest.java b/src/test/java/org/jbake/app/ParserTest.java index dd38333c9..784402b27 100644 --- a/src/test/java/org/jbake/app/ParserTest.java +++ b/src/test/java/org/jbake/app/ParserTest.java @@ -2,14 +2,21 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.io.BufferedWriter; import java.io.File; +import java.io.FileWriter; +import java.io.IOException; import java.io.PrintWriter; +import java.text.SimpleDateFormat; import java.util.Calendar; +import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.Map; import org.apache.commons.configuration.CompositeConfiguration; import org.apache.commons.configuration.ConfigurationException; +import org.assertj.core.util.Arrays; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; @@ -125,6 +132,33 @@ public void createSampleFile() throws Exception { out.close(); } + public Map getCommonTestPostData(){ + Map data = new HashMap(); + data.put("title", "This is a test post"); + data.put("type", "post"); + data.put("date", new SimpleDateFormat("yyyy-MM-dd").format(new Date())); + data.put("tags", "tagA,tagB"); + data.put("status", "published"); + data.put("body", "This content is for body of the post."); + return data; + } + public String convertMapToStringHeader(Map data){ + if (data == null) return null; + StringBuffer header = new StringBuffer(); + Map lockedMap = Collections.unmodifiableMap(data); + for (String key : lockedMap.keySet()) { + if(key != "body") + header.append(key).append("=").append(lockedMap.get(key)).append("\n"); + } + if(!lockedMap.isEmpty()){ + header.append("~~~~~~\n"); + } + if(lockedMap.containsKey("body")){ + header.append(lockedMap.get("body")); + } + return header.toString(); + } + @Test public void parseValidHTMLFile() { Map map = parser.processFile(validHTMLFile); @@ -217,4 +251,81 @@ public void parseValidAsciiDocFileWithoutJBakeMetaDataUsingDefaultTypeAndStatus( assertThat(map.get("body").toString()) .contains("

JBake now supports AsciiDoc documents without JBake meta data.

"); } + + @Test + public void parseCategoriesCheckDefaultCategory() throws IOException{ + File tempHtml = folder.newFile("test.html"); + PrintWriter out = new PrintWriter(tempHtml); + out.print(convertMapToStringHeader(getCommonTestPostData())); + out.close(); + Map map = parser.processFile(tempHtml); + assertThat(map.get("categories")) + .isNotNull() + .isInstanceOf(String[].class) + .isEqualTo(Arrays.array("Uncategorized")); + + } + + @Test + public void parseCategoriesCheckNoDefaultCategory() throws IOException{ + File tempHtml = folder.newFile("test.html"); + PrintWriter out = new PrintWriter(tempHtml); + out.print(convertMapToStringHeader(getCommonTestPostData())); + out.close(); + config.setProperty("categories.enable", false); + parser = new Parser(config,rootPath.getPath()); + Map map = parser.processFile(tempHtml); + assertThat(map.get("categories")) + .isNull(); + } + + @Test + public void parseCategoriesCheckSpecificDefaultCategoryWithSanitize() throws IOException{ + File tempHtml = folder.newFile("test.html"); + BufferedWriter out = new BufferedWriter(new FileWriter(tempHtml)); + out.write(convertMapToStringHeader(getCommonTestPostData())); + out.flush(); + out.close(); + config.setProperty("category.default", "Java Collections"); + config.setProperty("categories.sanitize", true); + parser = new Parser(config,rootPath.getPath()); + Map map = parser.processFile(tempHtml); + assertThat(map.get("categories")) + .isNotNull() + .isInstanceOf(String[].class) + .isEqualTo(Arrays.array("Java-Collections")); + + } + + @Test + public void parseCategoriesCheckMultipleCategories() throws IOException{ + File tempHtml = folder.newFile("test.html"); + PrintWriter out = new PrintWriter(tempHtml); + Map data = getCommonTestPostData(); + data.put("categories", "Java, J2EE, Spring Framework"); + out.print(convertMapToStringHeader(data)); + out.close(); + parser = new Parser(config,rootPath.getPath()); + Map map = parser.processFile(tempHtml); + assertThat(map.get("categories")) + .isNotNull() + .isInstanceOf(String[].class) + .isEqualTo(Arrays.array("Java","J2EE","Spring Framework")); + } + @Test + public void parseCategoriesCheckMultipleCategoriesWithSanitize() throws IOException{ + File tempHtml = folder.newFile("test.html"); + PrintWriter out = new PrintWriter(tempHtml); + Map data = getCommonTestPostData(); + data.put("categories", "Java, J2EE, Spring Framework"); + out.print(convertMapToStringHeader(data)); + out.close(); + config.setProperty("categories.sanitize", true); + parser = new Parser(config,rootPath.getPath()); + Map map = parser.processFile(tempHtml); + assertThat(map.get("categories")) + .isNotNull() + .isInstanceOf(String[].class) + .isEqualTo(Arrays.array("Java","J2EE","Spring-Framework")); + } } diff --git a/src/test/java/org/jbake/app/template/AbstractTemplateEngineRenderingTest.java b/src/test/java/org/jbake/app/template/AbstractTemplateEngineRenderingTest.java index 732d3476a..919f65445 100644 --- a/src/test/java/org/jbake/app/template/AbstractTemplateEngineRenderingTest.java +++ b/src/test/java/org/jbake/app/template/AbstractTemplateEngineRenderingTest.java @@ -269,4 +269,9 @@ protected List getOutputStrings(String type) { return outputStrings.get(type); } + + @Test + public void renderCategories() throws Exception { + + } } diff --git a/src/test/java/org/jbake/app/template/FreemarkerTemplateEngineRenderingTest.java b/src/test/java/org/jbake/app/template/FreemarkerTemplateEngineRenderingTest.java index fa4d33b00..9534ff05b 100644 --- a/src/test/java/org/jbake/app/template/FreemarkerTemplateEngineRenderingTest.java +++ b/src/test/java/org/jbake/app/template/FreemarkerTemplateEngineRenderingTest.java @@ -25,8 +25,12 @@ import org.apache.commons.io.FileUtils; import org.jbake.app.ConfigUtil.Keys; +import org.jbake.app.Crawler; +import org.jbake.app.Renderer; import org.junit.Test; +import junit.framework.Assert; + import java.io.File; import java.nio.charset.Charset; import java.util.Arrays; @@ -82,4 +86,33 @@ public void shouldFallbackToRenderSingleIndexIfNoPostArePresent() throws Excepti } + @Test + @Override + public void renderCategories() throws Exception { + Crawler crawler = new Crawler(db, sourceFolder, config); + crawler.crawl(new File(sourceFolder.getPath() + File.separator + "content")); + Renderer renderer = new Renderer(db, destinationFolder, templateFolder, config); + renderer.renderCategories("categories"); + + // verify + File outputFile = new File(destinationFolder + File.separator + "categories" + File.separator + "Technology.html"); + Assert.assertTrue(outputFile.exists()); + String output = FileUtils.readFileToString(outputFile, "UTF8"); + for (String string : outputStrings.get("categories")) { + assertThat(output).contains(string); + } + + // verify index.html file + File indexFile = new File(destinationFolder + File.separator + "categories" + File.separator + "index.html"); + Assert.assertTrue(indexFile.exists()); + String indexData = FileUtils.readFileToString(indexFile); + + // outputStrings.put("categories", Arrays.asList("blog/2012/first-post.html")); + //outputStrings.put("categories_index", Arrays.asList("")); + + + for (String string : outputStrings.get("categories_index")) { + assertThat(indexData).contains(string); + } + } } diff --git a/src/test/java/org/jbake/app/template/GroovyTemplateEngineRenderingTest.java b/src/test/java/org/jbake/app/template/GroovyTemplateEngineRenderingTest.java index 37d4958a0..769415bfc 100644 --- a/src/test/java/org/jbake/app/template/GroovyTemplateEngineRenderingTest.java +++ b/src/test/java/org/jbake/app/template/GroovyTemplateEngineRenderingTest.java @@ -23,8 +23,6 @@ */ package org.jbake.app.template; -import java.util.Arrays; - /** * * @author jdlee diff --git a/src/test/resources/content/blog/2012/first-post.html b/src/test/resources/content/blog/2012/first-post.html new file mode 100644 index 000000000..ef14766cd --- /dev/null +++ b/src/test/resources/content/blog/2012/first-post.html @@ -0,0 +1,16 @@ +title=First Post +date=2012-02-27 +type=post +tags=blog +status=published +categories=Technology +~~~~~~ + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque vel diam purus. Curabitur ut nisi lacus. +Nam id nisl quam. Donec a lorem sit amet libero pretium vulputate vel ut purus. Suspendisse leo arcu, +mattis et imperdiet luctus, pulvinar vitae mi. Quisque fermentum sollicitudin feugiat. Mauris nec leo +ligula. Vestibulum tristique odio ut risus ultricies a hendrerit quam iaculis. Duis tempor elit sit amet +ligula vehicula et iaculis sem placerat. Fusce dictum, metus at volutpat lacinia, elit massa auctor risus, +id auctor arcu enim eu augue. Donec ultrices turpis in mi imperdiet ac venenatis sapien sodales. In +consequat imperdiet nunc quis bibendum. Nulla semper, erat quis ornare tristique, lectus massa posuere +libero, ut vehicula lectus nunc ut lorem. Aliquam erat volutpat. \ No newline at end of file diff --git a/src/test/resources/freemarkerTemplates/categories.ftl b/src/test/resources/freemarkerTemplates/categories.ftl new file mode 100644 index 000000000..ff5b957e4 --- /dev/null +++ b/src/test/resources/freemarkerTemplates/categories.ftl @@ -0,0 +1,15 @@ +<#include "header.ftl"> + + <#include "menu.ftl"> + + + + + +<#include "footer.ftl"> \ No newline at end of file diff --git a/src/test/resources/freemarkerTemplates/category.ftl b/src/test/resources/freemarkerTemplates/category.ftl new file mode 100644 index 000000000..e70a059df --- /dev/null +++ b/src/test/resources/freemarkerTemplates/category.ftl @@ -0,0 +1,27 @@ +<#include "header.ftl"> + + <#include "menu.ftl"> + + + + + <#list category_posts as post> + <#if (last_month)??> + <#if post.date?string("MMMM yyyy") != last_month> + +

${post.date?string("MMMM yyyy")}

+
    + + <#else> +

    ${post.date?string("MMMM yyyy")}

    +
      + + +
    • ${post.date?string("dd")} - ${post.title}
    • + <#assign last_month = post.date?string("MMMM yyyy")> + +
    + +<#include "footer.ftl"> \ No newline at end of file diff --git a/src/test/resources/groovyTemplates/categories.gsp b/src/test/resources/groovyTemplates/categories.gsp new file mode 100644 index 000000000..ff4af9191 --- /dev/null +++ b/src/test/resources/groovyTemplates/categories.gsp @@ -0,0 +1,14 @@ +<%include 'header.gsp'%> + + +
    +
      + <%content.categories.each {category,path ->%> +
    • ${category}
    • + <%}%> +
    +
    + +<%include "footer.gsp"%> diff --git a/src/test/resources/groovyTemplates/category.gsp b/src/test/resources/groovyTemplates/category.gsp new file mode 100644 index 000000000..6d56ead50 --- /dev/null +++ b/src/test/resources/groovyTemplates/category.gsp @@ -0,0 +1,27 @@ +<%include 'header.gsp'%> + + +
    + + <%def last_month=null;%> + <%category_posts.each {post ->%> + <%if (last_month) {%> + <%if (post.date.format("MMMM yyyy") != last_month) {%> +
+

${post.date.format("MMMM yyyy")}

+