Categories/sections with hierarchy

I would create a categories tree in order to organize posts. For example I would have following architecture:

dev/
├── go/
│   ├── revel/
├── java/
├── js/
...

Firstly I was thinking about using frontmatter categories but I didn’t find anyway to represent tree, at the end categories is flat array.

So I was thinking about section by applying such organization inside content

content/
└── post/
    └── dev/
        ├── go/
        │   ├── a-go-post.md
        │   ├── revel/
        │   │   └── a-revel-post.md
        ├── java/
        │   └── a-java-post.md
        └── js/
            └── a-js-post.md

But now I would create a page all categories (sections?) that display this architecture (see possible final result) and related posts for each categories (to be honest I stole this idea from following theme based on Hexo platform http://louisbarranqueiro.github.io/hexo-theme-tranquilpeak/all-categories/)

I tried to iterate over .Site.Sections but I didn’t succeed to something good? Any idea? Is it possible?

PS: I read https://discuss.gohugo.io/t/how-to-list-sections/1606/5 but I didn’t find help or I didn’t understand well (sorry)

It’s not clear to me what you question is. Can you show us what you’re doing in your template? What are you expecting it to output?

I wanted to create linked categories (parent and child relation) and be able to list them after

# Using taxonomy (categories)
/categories/dev (OK)
/categories/dev/go (NOK)
/categories/go (OK but not really fit my needs)
# Using section
/post/dev (OK)
/post/dev/go (NOK)

There are issues for sections https://github.com/spf13/hugo/issues/465 or https://github.com/spf13/hugo/issues/455 but not for taxonomy (but I think taxonomy is not designed as tree but more as flat array that why)

Exactly. I really liked your solution. I ended up with similar - without using Scratch, it has pros (far simpler) and cons (the count can’t be displayed, or just the count for the given taxonomy can be added which is different as the count of the articles in the given tree-node).

As said: taxonomies are not hierarchical, the only way to go is to display the underlying articles within the taxonomy.term.html - but the solution above works well for e.g. listing and finding series of articles for various content types, assuming that the final series category name is unique, e.g.:

  • blog / technical / series / domain-drive-design-quick-start-series
  • blog / management / series / estimation-101

Maybe it’s worth to consider also this: pages do work well in hierarchies (directory structure) and thus we’d probably use the path nodes to generate a similar tree; by adding _index.md to each directory inside the content, we’d end up also with a _default/list.html being generated on each node, which could then list that node’s (directory’s) content (pages). In this way we could probably achieve the desired hierarchy stuff!

My solution:

Main:

<!-- partial "taxonomy/category-node" (dict "ctx" . "parentCategories" nil "level" 0 "maximumLevel" 4 ) -->

<!-- init -->
{{ $ctx := .ctx }}
{{ $parentCategories := .parentCategories | default slice }}
{{ $level := .level }}
{{ $maximumLevel := .maximumLevel }}

{{ $allCategoryNames := $ctx.Data.Terms.Alphabetical }}
{{ $subCategories := slice }}

<!-- get pages for which we will search for next category nodes -->
{{ $allPages := $ctx.Site.RegularPages }}
{{ $nodePages := slice }}

<!-- fetching next category node names -->
{{ range $allPages }}   
    {{ $hasNextLevel := gt (len (.Params.categories | default slice)) (len $parentCategories) }}
    {{ $isMatch := partial "taxonomy/category-match-fnc" (dict "pageCategories" .Params.categories "parentCategories" $parentCategories) }}
    {{ if $isMatch }}
        {{ if $hasNextLevel }}
            {{ $subCategories = union $subCategories (slice (index .Params.categories $level)) }}
        {{ else }}
            {{ $nodePages = union $nodePages (slice .) }}
        {{ end }}
    {{ end }}
{{ end }}

<!-- some debugging -->
{{ if eq $level 0 }}
    <!-- <textarea>{{ $ctx.Data.Terms | jsonify }}</textarea> -->
{{ end }}

<!-- rendering & nesting -->
{{ if or (gt (len $subCategories) 0) (gt (len $nodePages)) }}

    <ul>

        <div class="node-pages">
            {{ range $nodePages }}
                <div class="node-page-item">
                    <a href="{{ .Permalink }}">{{ .Title }}</a>                
                </div>    
            {{ end }}
        </div>

        {{ range $subCategories }}
        <li>
            <!-- find category in taxonomy terms to display count (count of the given category, not the category hierarchy node) -->
            {{ $category := index $ctx.Data.Terms (. | lower) }}

            <div class="node-sub-category">
                <a href="{{ $category.Page.Permalink }}">{{ . }}</a> {{ $category.Count }}
            </div>

            {{ if lt (add $level 1) $maximumLevel }}
                {{ partial "taxonomy/category-node" (dict "ctx" $ctx "parentCategories" (union $parentCategories (slice .)) "level" (add $level 1) "maximumLevel" $maximumLevel ) }}
            {{ end }}            

        </li>
        {{ end }}
        
    </ul>

{{ end }}

Support partial fnc:

<!-- $isMatch := partial "taxonomy/category-match-fnc" (dict "pageCategories" .Params.categories "parentCategories" $parentCategories) -->
<!-- NOTE: partials with return values won't render anything (except output value)! -->

{{ $result := true }}

{{ $pageCategories := .pageCategories | default slice }}
{{ $parentCategories := .parentCategories | default slice }}

<!-- if parent is more specific than page, then we don't have a match -->
{{ if and $result (gt (len $parentCategories) (len $pageCategories)) }}
    {{ $result = false }}
{{ end}}

<!-- match all parentCategory nodes agains pageCategory nodes and if one fails, we don't have a match, hence FALSE result is set -->
{{ if and $result (gt $parentCategories 0) }}
    
    {{range $index, $element := $parentCategories }}
        {{ if not (eq (index $parentCategories $index) (index $pageCategories $index)) }}
            {{ $result = false }}
        {{ end}}        
    {{ end }}    

{{ end }}

<!-- can't have multiple returns -->
{{ return $result }}