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></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?
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>{{ post.title }}</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>{{ post.title }}</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.
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
Related
I encountered a weird issue when trying to display subsection titles on a list page. Compared to single pages in this theme (which is hugo-coder), the text on the list page and its subpages (as shown in the picture below) is too small and too much to the left.
Another issue is that content in /content/projects/_index.md didn't render at all.
The only thing I did was to add a list.html file in /layouts/projects. Why did this happen and how can I render the list page more properly? Thanks!! (GitHub repo and website.)
{{ define "title" }}
{{ .Title }} · {{ .Site.Title }}
{{ end }}
{{ define "content" }}
{{ if (eq $.Parent.Title "Projects") }}
<ul class="no-bullet">
{{ range .Paginator.Pages }}
{{ .Render "li" }}
{{ end }}
</ul>
{{ partial "pagination.html" . }}
{{ else }}
{{ range (where .Site.Pages "Section" "projects") }}
<ul class="no-bullet">
{{ range .Sections }}
<li>
<span class="date">{{ .Date.Format (.Site.Params.dateFormat | default "January 2, 2006" ) }}</span>
<a class="title" href="{{ .Params.ExternalLink | default .RelPermalink }}">{{ .Title }}</a>
</li>
{{ end }}
</ul>
{{ end }}
{{ end }}
{{ end }}
Original post:
Under /content/projects, I have two separate categories, cs and ds. I wish to create a list page that displays the names of these two categories along with the URL to both. I know people asked similar questions before. However, none of the solutions I tried worked for me.
Below are my folder structure (relevant folders only) and the original code in /layouts/_default/list.html and /layouts/partial/list.html. Which files should I modify and how in order to display subsections correctly? Thank you so much!!
In /layouts/_default:
{{ define "title" }} {{- if eq .Kind "taxonomy" -}}
{{- i18n .Data.Singular | title -}}
{{- print ": " -}}
{{- end -}}
{{- .Title }} · {{ .Site.Title -}}
{{ end }}
{{ define "content" }}
{{ partial "list.html" . }}
{{ end }}
In /layouts/partial:
<section class="container list">
<h1 class="title">
{{- if eq .Kind "taxonomy" -}}
{{- i18n .Data.Singular | title -}}
{{- print ": " -}}
{{- end -}}
{{- .Title -}}
</h1>
{{ .Content }}
<ul>
{{ range .Paginator.Pages }}
<li>
<span class="date">{{ .Date.Format (.Site.Params.dateFormat | default "January 2, 2006" ) }}</span>
<a class="title" href="{{ .Params.ExternalLink | default .RelPermalink }}">{{ .Title }}</a>
</li>
{{ end }}
</ul>
{{ partial "pagination.html" . }}
</section>
Folder structure of content and layout:
├── content
│ ├── about.md
│ ├── posts
│ │ └── amazon.md
│ └── projects
│ ├── _index.md
│ ├── cs
│ │ ├── _index.md
│ │ └── covid19.md
│ └── ds
│ ├── _index.md
│ └── covid19.md
├── layouts
│ ├── 404.html
│ ├── _default
│ │ ├── baseof.html
│ │ ├── list.html
│ │ └── single.html
│ ├── index.html
│ ├── partials
│ │ ├── 404.html
│ │ ├── analytics
│ │ │ └── fathom.html
│ │ ├── footer.html
│ │ ├── header.html
│ │ ├── home.html
│ │ ├── list.html
│ │ ├── page.html
│ │ ├── pagination.html
│ │ ├── posts
│ │ │ ├── commento.html
│ │ │ ├── disqus.html
│ │ │ ├── math.html
│ │ │ ├── series.html
│ │ │ └── utteranc.html
│ │ └── taxonomy
│ │ ├── categories.html
│ │ └── tags.html
│ ├── posts
│ │ ├── li.html
│ │ ├── list.html
│ │ └── single.html
│ └── projects
│ └── list.html
...
I'm not entirely sure how to phrase my request, but, basically, I have a single collection with multiple categories, all the articles of this collection are shown on a single page. It's already sorted by category, however, I'm trying to find a way to sort these categories in a specific fashion. I've tried assigning the categories weight and using that to sort, but it did not work. I'm not sure what else to try?
You can find the code for all that I've done on GitHub...https://github.com/yndrelbosch/yndrelbosch.github.io
I'm trying to make this page: https://yndrelbosch.github.io/tutorials/ use a custom sorting for the categories on the page...
The code for that page is here: /pages/tutorials.html
the articles in the collection are here: /_tutorials/
Can anyone help me?
In pages/tutorials.html, your {% assign categories = site.tutorials | group_by:"category" | sort: "category-weight" %} will definitely not work.
As category-weight is not a key in site.tutorials documents, the sort filter will fail silently.
Saving some typing
We can avoid setting category in tutorials front matter by defining defaults variable depending on document path.
Let's change _tutorials organization :
_tutorials
├── advanced
│ ├── adding-next-previous-to-blog.md
│ └── ...
├── getting-started
│ ├── getting-started-with-jekyll.md
│ └── ...
└── setup
├── jekyll-on-windows-2.md
└── ...
Add some defaults in _config.yml :
defaults:
- ...
- { scope: { path: "_tutorials/advanced" }, values: { category: "Advanced" } }
- { scope: { path: "_tutorials/getting-started" }, values: { category: "Getting started" } }
- { scope: { path: "_tutorials/setup" }, values: { category: "Setup" } }
Create a reference for tutorials categories order
In _config.yml, add
# this is used to display tutorials in the right categories order
# be sure to match default category names defined in site.defaults
tutorials-categories:
- "Setup"
- "Getting started"
- "Advanced"
Note that moving categories order from pages/tutorials.html to _config.yml is made to centralize configuration. It helps to stay in synch between defaults settings and categories order.
Let's display our tutos in the right categories order
In pages/tutorials, we can do something like :
{% assign categories = site.tutorials | group_by:"category" %}
{% comment %} ++++++++++++++++++++++++++
We loop site.tutorials-categories to sort categories in a defined order
++++++++++++++++++++++++++ {% endcomment %}
{% for category in site.tutorials-categories %}
{% assign current-category = categories | where:"name", category | first %}
{% assign category-tutorials = current-category.items %}
<h2>{{ category }}</h2>
<ul>
{% for item in category-tutorials %}
<li>{{ item.title }}</li>
{% endfor %}
</ul>
{% endfor %}
I'm using mmistakes/minimal-mistakes which is build with jekyll to build my github pages.
I want to list all of my posts under _posts/android in _pages/android.html. Meaning, I want to put 2016-09-01-aaa.md,2016-09-02-bbb.md and 2016-08-26-ccc.md but not 2016-09-03-ddd.md in _pages/android.html.
Here is my project's directory structure:
.
├── _config.yml
├── _pages
│ └── android.html
├── _posts
│ ├── android
│ │ ├── third-party
│ │ │ ├── 2016-09-01-aaa.md
│ │ │ └── 2016-09-02-bbb.md
│ │ └── handler
│ │ └── 2016-08-26-ccc.md
│ └── java
│ └── effective-java
│ └── 2016-09-03-ddd.md
└── _site
├── index.html (my github home page)
├── android
│ ├── index.html (generated from "_pages/android.html")
│ ├── third-party
│ │ ├── aaa
│ │ │ └── index.html
│ │ └── bbb
│ │ └── index.html
│ └── handler
│ └── ccc
│ └── index.html
└── java
└── effective-java
└── ddd
└── index.html
Here is _pages/android.html:
(But it will list all the posts under _posts ,
How can I make it list posts under _posts/android only ?)
---
layout: archive
permalink: /android/
excerpt: "android"
author_profile: false
sidebar:
nav: "android"
---
{% include base_path %}
<h3 class="archive__subtitle">{{ site.data.ui-text[site.locale].recent_posts | default: "Recent Posts" }}</h3>
{% for post in site.posts %}
{% include archive-single.html %}
{% endfor %}
Here is _include/base_path:
{% if site.url %}
{% assign base_path = site.url | append: site.baseurl %}
{% else %}
{% assign base_path = site.github.url %}
{% endif %}
Here is 2016-09-01-aaa.md:
(Other posts is similar to this.)
---
title: aaa
categories: /android/third-party/
---
write some contents.
Here is the snippet of _config.yml:
# Site Settings
locale : "zh-CN"
title : "IT Tech"
title_separator : "-"
name : "TesTName"
description : "Android Java HTML CSS JavaScript Ubuntu"
url : "http://localhost:4000" # the base hostname & protocol for your site e.g. "https://mmistakes.github.io"
baseurl : # the subpath of your site, e.g. "/blog"
# Defaults
defaults:
# _posts
- scope:
path: ""
type: posts
values:
layout: single
author_profile: true
read_time: false
comments: true
share: true
related: false
# _pages
- scope:
path: ""
type: pages
values:
layout: single
author_profile: true
related: true
So, how can I make it ? I'm really not good at the html and jekyll things.
As stated here, the following should work:
{% for post in site.categories.android %}
{% include archive-single.html %}
{% endfor %}
With categories declared like this:
categories: android third-party
The post.path will list all your posts in subfolder _posts/android
{% for post in site.posts %}
{% if post.path contains 'android' %}
{% include archive-single.html %}
{% endif %}
{% endfor %}
Edit: I've created a repository here that tests jibe's answer below. I just end up getting a blank page when I visit /animals, so any help is appreciated!
This is a follow-up to this question: Hierarchical Categories in GitHub Pages
In that question, I found out how to list the posts of a particular hierarchical category. Now I'm trying to figure out how to list the subcategories of a particular hierarchical category.
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 the subcategories (mammals and reptiles). Then if they go to /animals/mammals, they'd see cats and dogs listed as subcategories.
I'm currently doing this manually by putting an index.html file inside each subcategory. But that makes updating things much more complicated than it probably should be.
I've tried following this answer, but that's meant for single tags, not multiple categories.
The catch is that any particular category might not be unique, so I can have stuff like this:
animals -> mammals -> bats -> _posts -> vampire.md, fruit.md
sports -> baseball -> bats -> _posts -> wiffle.md, teeball.md
I'd also like to be able to define frontmatter attributes in the subcategories, maybe in the index.html file of each? For example the animals->mammals->bats->index.html file would contain a frontmatter variable thumbnail with a value of "VampireBat.jpg", and sports->baseball->bats->index.html would have a thumbnail of "YellowWiffleBat.png". I'd like to be able to access these variables from the parent level (to show a thumbnail and a link to the subcategory).
My first thought was to access the subcategories directly, like this:
{% for mammalType in site.categories.animals.mammals %}
<p>{{ mammalType.title }}</p>
<img src="(( mammalType.thumbnail }}" />
{% endfor %}
Which I'd generalize using the categories from the page itself:
{% for subcategory in page.categories %}
<p>{{ subcategory.title }}</p>
<img src="(( subcategory.thumbnail }}" />
{% endfor %}
But that doesn't work at all, since site.categories.whatever is a list of all of the posts in that category, ignoring any hierarchical information.
Is there a better way to approach this other than doing it manually?
See simpyll.com for demo
See github for website code
assign var page_depth as the current pages depth using the path '/' as a count variable
{% assign page_depth = page.url | split: '/' | size %}
assign var page_parent as the slug of the last directory housing 'index.md'
{% assign page_parent = page.url | split: '/' | last %}
loop through every page in the website
{% for node in site.pages offset:1 %}
skip website root
{% if node.url == '/' %}
{{ continue }}
{% else %}
remove backslashed from each page in website
{% assign split_path = node.url | split: "/" %}
assign var node_last for each page in website
{% assign node_last = split_path | last %}
assign var node_parent as the slug of the last directory housing 'index.md' for each page in website
{% assign node_parent = node.url | remove: node_last | split: '/' | last %}
assign node_url for each page in website
{% assign node_url = node.url %}
loop through each slug in each page path in website
{% for slug in split_path offset:1 %}
assign var slug as the name of each slug therefore giving it a name
{% assign slug = slug %}
assign slug_depth with a forloop.index
{% assign slug_depth = forloop.index %}
close for
{% endfor %}
obtain sub-directories for every page in website comparing depth and parent of current page to that of every other page in website
{% if slug_depth == page_depth and page_parent == node_parent %}<li>{{ slug }}</li>{% endif %}
obtain sub-directories for root (which we skipped early in this script). we can use depth alone to define this.
{% if slug_depth == 1 and page.url == '/' and slug != 'search.json' and slug != 'sitemap.xml' %}<li>{{{slug}}</li>{% endif %}
close if and for
{% endif %}
{% endfor %}
altogether:
{% assign page_depth = page.url | split: '/' | size %}
{% assign page_parent = page.url | split: '/' | last %}
{% for node in site.pages offset:1 %}
{% if node.url == '/' %}
{{ continue }}
{% else %}
{% assign split_path = node.url | split: "/" %}
{% assign node_last = split_path | last %}
{% assign node_parent = node.url | remove: node_last | split: '/' | last %}
{% assign node_url = node.url %}
{% for slug in split_path offset:1 %}
{% assign slug = slug %}
{% assign slug_depth = forloop.index %}
{% endfor %}
{% if slug_depth == page_depth and page_parent == node_parent %}
<li>{{ slug }}</li>
{% endif %}
{% if slug_depth == 1 and page.url == '/' and slug != 'search.json' and slug != 'sitemap.xml' %}
<li>{{{slug}}</li>
{% endif %}
{% endif %}
{% endfor %}
As it was suggested in my deleted answer, I post an improved version of my answer of your previous question. I also add information to answer your new questions (also deleted) :
Thanks for the reply. 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 yesterday
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 you need to make the list you want.
which will index items with filter. From the top directory, your animals collection should look like this (with index.html in each folder) :
cleaner
root/
└── _animals/
├── index.html
├── mammals
│ ├── cats
│ │ ├── housecat.md
│ │ └── tiger.md
│ ├── dogs
│ │ ├── doberman.md
│ │ └── poodle.md
│ └── index.html
└── reptiles
└── lizards
├── chameleon.md
└── iguana.md
new you can list only subcategories with or without going deeply (with an optional parameters) _includes/list_subcategories.html
{% assign animals = site.animals| sort:'title' %}
{% assign from = page.url | remove: '/index.html' %}
{% assign deep = (page.url | split: '/' | size) + 1 %}
{% for animal in animals %}
{% assign d = animal.url | remove: '/index.html' | split: '/' | size %}
{% if animal.url != page.url and animal.url contains from and animal.url contains "index" and (include.dig or deep == d) %}
<a href={{ animal.url | prepend: site.baseurl }}>{{animal.title}}</a>
{% endif %}
{% endfor %}
improved similarly to list animals _includes/list_animals.html
{% assign animals = site.animals| sort:'title' %}
{% assign from = page.url | remove: '/index.html' %}
{% assign deep = (page.url | split: '/' | size) + 1 %}
{% for animal in animals %}
{% assign d = animal.url | remove: '/index.html' | split: '/' | size %}
{% if animal.url contains "index" or animal.url == page.url %}
{% else %}
{% if animal.url contains from and (include.dig or deep == d) %}
<a href={{ animal.url | prepend: site.baseurl }}>{{animal.title}}</a>
{% endif %}
{% endif %}
{% endfor %}
list all subcategories and all animals in animals/index.html :
---
title: animals
---
{% include list_subcategories.html dig=true %}
<hr>
{% include list_animals.html dig=true %}
For example, to list all mammals and subcategoeries in animals/mammals/index.html :
---
title: animals
---
{% include list_subcategories.html %}
<hr>
{% include list_animals.html %}
Finally the generated structure should look like this (with some more index.html):
cleaner
root/
├─ _animals/
│ └─── ...
└── _site
└── animals
├── index.html
├── mammals
│ ├── cats
│ │ ├── housecat.html
│ │ └── tiger.html
│ ├── dogs
│ │ ├── doberman.html
│ │ └── poodle.html
│ └── index.html
└── reptiles
└── lizards
├── chameleon.html
└── iguana.html
it solves your question. I changed from taxonomy to dig, but you could also have distinguished between animals->bats and baseball->bats by putting taxonomy="baseball/bats" or taxonomy="animals/bats".
I have a collection called opcodes and created a loop to generate a listing of all the members. In abbreviated form:
{% assign ops = site.opcodes | where: 'pst', 1 %}
{% for opcode in ops %}
<div id="op{{ opcode.n }}" class="opcode">
<span class="op-data">#{{ opcode.n }} ({{ opcode.n | dec_to_hex }})</span>
{{ opcode.content }}
{% endfor %}
The individual members are html files with some front matter. I'll be generating several listings and the members sometimes need to change slightly. It looks like by the time the loop runs, their content is already generated though. I tried affecting their front matter or site variables, but this is all too late, none of the liquid if statements work. The variables are either empty or at their original values.
Is there a way to make this work, somehow pass parameters to the members before their content gets rendered?
The obvious fix is to just copy the problematic files and edit them, since I'm already using a "where" clause to select them. The question is about any better ways.
EDIT: this is how the individual files look like:
---
title: bla
pst = 0
---
bla bla
{% if x == "big" %}
blu blu
{% else %}
ble ble
{% endif %}
x is what I'd like to set externally, from the first loop.
You can use front matter default configuration to set variable depending on containing folder.
─ _myCol
├── cat1
│ ├── item0.md
│ └── item1.md
└── cat2
├── item2.md
└── item3.md
If you want to set a variable myVar for all you items in myCol collection, you do :
# _config.yml
defaults:
- scope:
type: myCol
values:
myVar: "YOLO"
Now if you want to set a different value for myVar depending on containing folder, you add :
- scope:
path: _myCol/cat1
values:
myVar: "TOTO"
You can then use this variable in your collection's item layout, like this :
{% if page.myVar == "YOLO" %}
blu blu
{% else %}
ble ble
{% endif %}
I've looked at it again, but found no solution, so the answer appears to be: no, it is not possible to pass parameters to collection members before they get rendered.