{% extends "base.html" %} {# ================================================================================ Category Browser Template (Kida-Native v2) ================================================================================ Displays all categories with post counts and recent posts. Perfect for documentation sites to browse by category/type. Uses query indexes for O(1) category lookups. URL Pattern: /categories/ KIDA FEATURES SHOWCASED: - {% let %} for template-scoped variables (multi-assignment) - {% match %} for layout dispatch and pluralization - {% cache %} for expensive category computations - {% unless %} for negative conditionals - Pipeline operators (|>) for filter chains - Optional chaining (?.) and null coalescing (??) - {% spaceless %} for clean HTML output Usage: Create content/categories/_index.md: --- title: "Browse by Category" template: category-browser.html description: "Explore documentation organized by category" --- Optional metadata: - sections: ["docs", "blog"] (only show categories from these sections) - show_recent: 3 (number of recent posts to show per category) - layout: "grid" or "list" (default: "grid") Context variables: - page: Current page - site.indexes.category: Category index ================================================================================ #} {% from 'partials/navigation-components.html' import breadcrumbs %} {% from 'partials/components/tags.html' import tag_list %} {# ============================================================================= CONFIGURATION (Kida multi-let) ============================================================================= #} {% let section_filters = params?.sections ?? [], show_recent = params?.show_recent ?? 3, layout = params?.layout ?? 'grid', all_categories = (site?.indexes?.category?.keys() ?? []) |> sort |> list %} {# ============================================================================= HELPER MACROS ============================================================================= #} {# Filter posts by section - returns filtered list #} {% def filter_by_section(posts, filters) %} {% if filters | length > 0 %} {{ posts |> selectattr('_section.name', 'in', filters) |> list }} {% else %} {{ posts }} {% end %} {% end %} {# Category card component with caching #} {% def category_card(category, section_filters, show_recent) %} {% let raw_posts = site.indexes.category.get(category) |> resolve_pages %} {% let category_posts = raw_posts |> selectattr('_section.name', 'in', section_filters) |> list if section_filters | length > 0 else raw_posts %} {% if category_posts | length > 0 %} {% let post_count = category_posts | length, total_words = category_posts |> map(attribute='content') |> join('') |> wordcount, authors = category_posts |> map(attribute='params.author') |> unique |> list, recent_posts = category_posts |> sort(attribute='date', reverse=true) |> take(show_recent) %}

{{ category | title }}

{% spaceless %} {% match post_count %} {% case 1 %}1 page {% case n %}{{ n }} pages {% end %} {% end %}
{# Category stats #}
{{ total_words |> format_number }} words {% if authors | length > 0 %} {% spaceless %} {% match authors | length %} {% case 1 %}1 author {% case n %}{{ n }} authors {% end %} {% end %} {% end %}
{# Recent posts in this category #} {% if show_recent > 0 and recent_posts | length > 0 %}

Recent:

{% if post_count > show_recent %} View all {{ post_count }} → {% end %}
{% end %}
{% end %} {% end %} {# Letter group for A-Z index - uses caching for expensive lookups #} {% def letter_group(letter, categories, section_filters) %}
{{ letter }}
{% end %} {# ============================================================================= MAIN TEMPLATE ============================================================================= #} {% block content %} {{ breadcrumbs(page) }}

{{ page?.title ?? 'Browse by Category' }}

{% with params?.description as desc %}

{{ desc }}

{% end %}
{# Main content from markdown #} {% with content as page_content %}
{{ page_content | safe }}
{% end %} {% if all_categories | length > 0 %} {# ========================================================================= SUMMARY STATS - Cached for expensive computation ========================================================================= #} {% cache 'category-browser-stats-' ~ (section_filters |> join('-')) %} {% let total_pages = all_categories |> map('site.indexes.category.get') |> map('resolve_pages') |> map('length') |> sum ?? 0 %}
{% spaceless %} {{ all_categories | length }} {% match all_categories | length %} {% case 1 %}category {% case _ %}categories {% end %} {% end %} {{ total_pages }} total pages
{% end %} {# ========================================================================= LAYOUT TOGGLE - Pattern matched for active state ========================================================================= #}
{% for layout_option in ['grid', 'list'] %} {% let is_active = layout == layout_option %} {% let label = 'Grid view' if layout_option == 'grid' else 'List view' %} {% let emoji = '' %} {% end %}
{# ========================================================================= CATEGORY GRID/LIST - Main content area ========================================================================= #}
{% for category in all_categories %} {{ category_card(category, section_filters, show_recent) }} {% end %}
{# ========================================================================= ALPHABETICAL INDEX - Grouped by first letter ========================================================================= #} {% cache 'category-browser-index-' ~ (section_filters |> join('-')) %} {% end %} {% else %} {# ========================================================================= EMPTY STATE ========================================================================= #}

No categories found.

Add categories: ["tutorial", "guide"] to page frontmatter to create categories.

{% end %}
{% end %}