18

I'm using Jekyll on GitHub pages, and I want to have hierarchical categories like this:

  • animals -> mammals -> cats -> _posts -> housecat.md, tiger.md
  • animals -> mammals -> dogs -> _posts -> poodle.md, doberman.md
  • animals -> reptiles -> lizards -> _posts -> iguana.md, chameleon.md

I'd like users to be able to visit /animals and see a listing of every post from every category. But if they go to /animals/mammals, they'd only see mammals. If they go to /animals/mammals/cats, then they only see cats.

I know I can do this manually by putting an index.html file in every single directory and then looping through site.categories.mammals or site.categories.cats, for example.

But that seems a little bit too brute force, and I'm hoping there's a better way. If I want to change how I'm showing the listings, I'll have to change that in every single subcategory. I'll also have problems when subcategories share a name, like /ABC/XYZ/_posts/one.md and /DEF/XYZ/_posts/two.md.

I've tried to follow this article, which uses one main category.html page that loops through page.category:

{% for post in site.categories.[page.category] %}
  <h2><a href=""></a></h2>
  <p></p>
{% endfor %}

Then every index.html file uses this as its layout. That almost works, but it seems limited to one category, not multiple hierarchical categories.

Is there a less brute-force approach to creating listings for hierarchical categories?

Kevin Workman
  • 41,537
  • 9
  • 68
  • 107
  • 2
    Did you consider to write a generator plugin? – rocambille Aug 05 '16 at 12:33
  • 1
    @wasthishelpful I'm honestly not sure what that means. I'm also using GitHub pages, so I'm limited by what they allow. – Kevin Workman Aug 05 '16 at 12:33
  • 1
    You could write a plugin to your jekyll installation to generate the category pages (see the second example [here](https://jekyllrb.com/docs/plugins/#generators)). But indeed if you're using GitHub pages, the plugin won't be accepted once pushed on your repository. An alternative could be to generate your site locally and then push your site's static files instead of the jekyll sources (dixit the [doc](https://help.github.com/articles/adding-jekyll-plugins-to-a-github-pages-site/)) – rocambille Aug 05 '16 at 12:42
  • 1
    @wasthishelpful Thanks, but I'd really like to keep this completely compatible with GitHub Pages. If my choices are just doing it manually or being incompatible, I'll just do it manually. – Kevin Workman Aug 05 '16 at 12:43
  • 1
    After a quick search on google, it seems to be a "usual" way to have 2 repositories on github in this case: 1 repo eventually private for the sources with plugins and the one with the generated pages. But if you prefer not hacking github limitations, I understand your position – rocambille Aug 05 '16 at 12:55
  • 1
    @wasthishelpful Yeah exactly, I'm trying to keep it as simple as possible. Thanks for the thoughts though. – Kevin Workman Aug 05 '16 at 12:58

2 Answers2

5

page.categories is a list

https://stackoverflow.com/a/23927986

{% for cat in page.categories %}
  <h1>{{ cat }}</h1>
  <ul>
    {% for post in site.categories[cat] %}
      <li><a href="{{ post.url }}">{{ post.title }}</a></li>
    {% endfor %}
  </ul>
{% endfor %}

From jekyll's documentation about page.category http://jekyllrb.com/docs/variables/#page-variables

The list of categories to which this post belongs. Categories are derived from the directory structure above the _posts directory. For example, a post at /work/code/_posts/2008-12-24-closures.md would have this field set to ['work', 'code']. These can also be specified in the YAML Front Matter.

You should be easily able to add a simple dynamic index.html to every folder and the categories should be hierarchical automatically.

Update

The above does NOT work. You need to treat the combination of categories of each hierarchy as a unique item. Concat the index page's categories, and compare that against all the posts in the site.

/foo/bar/_posts/2016-08-01-foo-bar-test.html

---
categories:
  - foo
  - bar
title: test foo bar
---

<h2>Foo Bar</h2>

/var/bar/_posts/2016-08-01-test-var-bar.html

---
categories:
  - var
  - bar
title: test var bar
---

<h2>Var Bar</h2>

/foo/bar/index.html

---
categories:
 - foo
 - bar
---

{% assign pagecat = page.categories | join ' ' | append: ' '%}
{% assign pagecatlen = page.categories.size %}
  <h1>{{ cat }}</h1>
  <ul>
    {% for post in site.posts %}
    {% assign postcat = '' %}
    {% for thispostcat in post.categories limit: pagecatlen %}
      {% assign postcat = postcat | append: thispostcat %}
      {% assign postcat = postcat | append: ' ' %}
    {% endfor %}

    {% if (postcat == pagecat) %}
      <li><a href="{{ post.url }}">{{ post.title }}</a></li>
    {% endif %}
    {% endfor %}
  </ul>

The categories are optional for the files in _posts, but they are required for the front matter of each index file.

Community
  • 1
  • 1
chugadie
  • 2,786
  • 1
  • 24
  • 33
  • This doesn't respect the hierarchy. If I'm at `/abc/xyz/` it will also show me `xyz` stuff from `/def/xyz`. That's what I'm trying to get around. – Kevin Workman Aug 09 '16 at 22:04
  • You are correct, I updated with a working example of concatenating all categories into strings and comparing that way. The order of the categories will matter. I'm not sure if jekyll will ensure the order of categories values in front matter parsing. – chugadie Aug 09 '16 at 22:41
  • you can use a common _layout for all your index pages and just put the front matter. Pages with just foo will show all posts that start with foo, pages with 'foo' and 'bar' will only show posts with both foo and bar categories, but not pages with 'var' and 'bar' categories. – chugadie Aug 09 '16 at 22:55
  • Thanks, this seems to do the trick. The only problem is that this doesn't work with automatic categories based on the directory structure; you have to specify them manually in the front matter. Not the end of the world though. Could you sort the categories to get around the potential issue with order? This seems like a lot of hoops to jump through, but it does do exactly what I asked for. Thanks! – Kevin Workman Aug 09 '16 at 23:40
  • you could sort, but then you're kind of destroying your hierarchy in favor of alphabetical. items under /b/a/c would appear to be under /a/b/c (if you had both paths to 'c' and wanted them to be different) – chugadie Aug 10 '16 at 12:21
  • Also, it WFM with auto categories based on dir structure. I have to specify categories of the index files, but not the /foo/bar/_posts/* files. (because the index files aren't in a special _posts folder) – chugadie Aug 10 '16 at 12:26
  • I've asked a follow-up question [here](http://stackoverflow.com/questions/39237526/list-subcategories-in-githubpages), if you feel like taking a look. – Kevin Workman Aug 30 '16 at 21:41
  • With the latest version of Jekyll, I get this error: `Liquid Warning: Liquid syntax error: Expected dotdot but found comparison` on the `{% if (postcat == pagecat) %}` line. Any idea how to fix this? – Kevin Workman Apr 08 '17 at 01:23
  • @KevinWorkman The same error is still happening in 2023. – Mew Aug 27 '23 at 16:49
  • In fact, two warnings are given, with the other being `Expected end_of_string but found string in "{{page.categories | join ' ' | append: ' '}}"`. Probably caused by the lack of `:` after `join`. – Mew Aug 27 '23 at 16:58
  • Aha, the fix is that Liquid doesn't understand parentheses `( )`, as described [here](https://stackoverflow.com/a/76185063/9352077). Just delete them and the warnings go away. – Mew Aug 27 '23 at 17:22
4

Modify your _config.yml accordingly

collections:
    animals:
        output: true
        mammals:
            output: true
            cats:
                output: true
            dogs:
                output: true
        reptiles:
            output: true
            lizards:
                output: true

then created the structure:

mkdir -p _animals/reptiles/lizards
mkdir -p _animals/mammals/cats
mkdir _animals/mammals/dogs

add your md files and all index.html which will index items with filter. It should look like this (maybe with more indexes) :

_animals/
├── index.html
├── mammals
│   ├── cats
│   │   ├── housecat.md
│   │   └── tiger.md
│   ├── dogs
│   │   ├── doberman.md
│   │   └── poodle.md
│   └── index.html
└── reptiles
    └── lizards
        ├── chameleon.md
        └── iguana.md

then you create _includes/list_animals.html

{% assign animals = site.animals| sort:'title' %}
{% for animal in animals %}
{% if page.url != animal.url  and include.taxonomy == nil or animal.url contains include.taxonomy %}
<a  href={{ animal.url | prepend: site.baseurl }}>{{animal.title}}</a>
{% endif %}
{% endfor %} 

to list all animals in animals/index.html :

---
title: animals
---
{% include list_animals.html %}

For example, to list all mammals in animals/mammals/index.html :

---
title: animals
---
{% include list_animals.html taxonomy="mammals" %}

Finally the generated structure should look like this (with some more index.html):

_site
└── animals
    ├── index.html
    ├── mammals
    │   ├── cats
    │   │   ├── housecat.html
    │   │   └── tiger.html
    │   ├── dogs
    │   │   ├── doberman.html
    │   │   └── poodle.html
    │   └── index.html
    └── reptiles
        └── lizards
            ├── chameleon.html
            └── iguana.html
jibe
  • 578
  • 1
  • 4
  • 12
  • So, for this I'd have to have both an `_animals` directory and an `animals` directory? – Kevin Workman Aug 10 '16 at 21:25
  • no. The `_animals` is your working directory and `animals` is generated (as you can see it is under `_site` folder) – jibe Aug 11 '16 at 08:59
  • This is for collections, not for categories, right? – Wickramaranga Sep 04 '16 at 07:51
  • That is true, it is collections not categories. But a part from the word "categories", it answers the OP question. – jibe Sep 05 '16 at 08:33
  • 1
    Hey @jibe. Not sure if you say my reply on [my other post](http://stackoverflow.com/questions/39237526/list-subcategories-in-github-pages), but this almost works, but it's got a few problems. Most importantly, it doesn't support subcategories with the same name (like `animals->bats` and `baseball->bats`). It also lists every subcategory and every post under a particular category. I only want to list the subcategories, not the posts. Is there a way to modify your approach to meet those requirements? – Kevin Workman Sep 08 '16 at 13:08