Files
indiekit-blog/_includes/layouts/page.njk
svemagie e8ba3b9ae6 feat: nested tags (Obsidian-style) for categories system
Adds hierarchical tag support using "/" separator (e.g. "tech/programming/js").
- New filters: nestedSlugify, categoryMatches, categoryBreadcrumb,
  categoryGroupByRoot, categoryDirectChildren
- categories collection auto-generates ancestor pages for nested tags
- categories.njk: breadcrumb nav, sub-tags section, ancestor-aware post matching
- categories-index.njk: grouped tree view (root + indented children)
- categories widget: shows root tags only with child count badge
- All category links updated from slugify → nestedSlugify (backward-compatible)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 10:56:34 +01:00

146 lines
8.0 KiB
Plaintext

---
layout: layouts/base.njk
withSidebar: true
---
{# Layout for slash pages (/about, /now, /uses, etc.) #}
{# These are root-level pages created via Indiekit's page post type #}
{# AI metadata compatibility: support nested ai object plus legacy top-level keys #}
{% set aiMeta = ai or {} %}
{% set aiTextLevel = aiTextLevel or ai_text_level or aiMeta.textLevel or aiMeta.aiTextLevel or "0" %}
{% set aiCodeLevel = aiCodeLevel or ai_code_level or aiMeta.codeLevel or aiMeta.aiCodeLevel or "0" %}
{% set aiTools = aiTools or ai_tools or aiMeta.aiTools or aiMeta.tools %}
{% set aiDescription = aiDescription or ai_description or aiMeta.aiDescription or aiMeta.description %}
{% set aiUsed = (aiTextLevel and aiTextLevel !== "0") or (aiCodeLevel and aiCodeLevel !== "0") %}
<article class="h-entry{% if aiUsed %} h-ai-usage{% endif %}" data-ai-text-level="{{ aiTextLevel }}" data-ai-code-level="{{ aiCodeLevel or '0' }}" data-ai-used="{% if aiUsed %}true{% else %}false{% endif %}"{% if aiTools %} data-ai-tools="{{ aiTools }}"{% endif %}>
<header class="mb-6 sm:mb-8">
<h1 class="p-name text-2xl sm:text-3xl font-bold text-surface-900 dark:text-surface-100 mb-2">
{{ title }}
</h1>
{% if summary %}
<p class="p-summary text-lg text-surface-600 dark:text-surface-400">
{{ summary }}
</p>
{% endif %}
{% set lastUpdated = updated or page.date %}
{% if lastUpdated %}
<p class="text-sm text-surface-600 dark:text-surface-400 mt-2">
Last updated: <time class="dt-updated font-mono text-sm" datetime="{{ lastUpdated | isoDate }}">{{ lastUpdated | dateDisplay }}</time>
</p>
{% endif %}
</header>
<div class="e-content prose dark:prose-invert prose-lg max-w-none">
{{ content | safe }}
</div>
{# AI post-graph — shown only on the /ai/ page #}
{% if page.url == "/ai/" and collections.posts %}
{% set stats = collections.posts | aiStats %}
{% set aiPostsList = collections.posts | aiPosts %}
<section class="mt-8 sm:mt-12 p-6 rounded-lg bg-surface-50 dark:bg-surface-800/50 border border-surface-200 dark:border-surface-700 shadow-sm">
<h2 class="text-xl font-bold text-surface-900 dark:text-surface-100 mb-4">AI Usage Across Posts</h2>
<div class="grid gap-4 sm:grid-cols-4 mb-6">
<div class="text-center p-3 rounded-lg bg-surface-50 dark:bg-surface-800 border border-surface-200 dark:border-surface-700 shadow-sm">
<div class="text-2xl font-bold text-surface-900 dark:text-surface-100">{{ stats.total }}</div>
<div class="text-xs text-surface-600 dark:text-surface-400">Total posts</div>
</div>
<div class="text-center p-3 rounded-lg bg-surface-50 dark:bg-surface-800 border border-surface-200 dark:border-surface-700 shadow-sm">
<div class="text-2xl font-bold text-amber-600 dark:text-amber-400">{{ stats.aiCount }}</div>
<div class="text-xs text-surface-600 dark:text-surface-400">AI-involved</div>
</div>
<div class="text-center p-3 rounded-lg bg-surface-50 dark:bg-surface-800 border border-surface-200 dark:border-surface-700 shadow-sm">
<div class="text-2xl font-bold text-emerald-600 dark:text-emerald-400">{{ stats.total - stats.aiCount }}</div>
<div class="text-xs text-surface-600 dark:text-surface-400">Human-only</div>
</div>
<div class="text-center p-3 rounded-lg bg-surface-50 dark:bg-surface-800 border border-surface-200 dark:border-surface-700 shadow-sm">
<div class="text-2xl font-bold text-surface-900 dark:text-surface-100">{{ stats.percentage }}%</div>
<div class="text-xs text-surface-600 dark:text-surface-400">AI ratio</div>
</div>
</div>
{# Breakdown by level #}
<div class="flex flex-wrap gap-3 text-sm mb-6">
<span class="px-3 py-1 rounded-full bg-surface-100 dark:bg-surface-700 text-surface-600 dark:text-surface-300">
Level 0 (None): {{ stats.byLevel[0] }}
</span>
<span class="px-3 py-1 rounded-full bg-amber-50 dark:bg-amber-900/20 text-amber-700 dark:text-amber-300">
Level 1 (Editorial): {{ stats.byLevel[1] }}
</span>
<span class="px-3 py-1 rounded-full bg-amber-100 dark:bg-amber-900/40 text-amber-800 dark:text-amber-200">
Level 2 (Co-drafted): {{ stats.byLevel[2] }}
</span>
<span class="px-3 py-1 rounded-full bg-amber-200 dark:bg-amber-900/60 text-amber-900 dark:text-amber-100">
Level 3 (AI-generated): {{ stats.byLevel[3] }}
</span>
</div>
{# Post graph showing AI posts (highlighted) on the full year grid #}
<h3 class="text-lg font-semibold text-surface-900 dark:text-surface-100 mb-3">AI-Involved Posts Over Time</h3>
<p class="text-sm text-surface-600 dark:text-surface-400 mb-4">Highlighted days had posts with AI involvement (level 1+). Empty boxes represent days with no AI-involved posts.</p>
{% postGraph aiPostsList, { prefix: "ai", highlightColorLight: "#d97706", highlightColorDark: "#fbbf24" } %}
</section>
{% endif %}
{# Categories/tags if present #}
{% if category %}
<footer class="mt-8 pt-6 border-t border-surface-200 dark:border-surface-700">
<div class="flex flex-wrap gap-2">
{% if category is string %}
<a href="/categories/{{ category | nestedSlugify }}/" class="p-category text-sm px-3 py-1 bg-surface-100 dark:bg-surface-800 rounded-full hover:bg-surface-200 dark:hover:bg-surface-700 transition-colors">
{{ category }}
</a>
{% else %}
{% for cat in category %}
<a href="/categories/{{ cat | nestedSlugify }}/" class="p-category text-sm px-3 py-1 bg-surface-100 dark:bg-surface-800 rounded-full hover:bg-surface-200 dark:hover:bg-surface-700 transition-colors">
{{ cat }}
</a>
{% endfor %}
{% endif %}
</div>
</footer>
{% endif %}
{# Hidden metadata for microformats #}
<a class="u-url hidden" href="{{ page.url }}"></a>
<data class="p-author h-card hidden" value="{{ site.author.name }}"></data>
{# Pagefind filter metadata #}
<div hidden>
<span data-pagefind-filter="type">Page</span>
<span data-pagefind-filter="ai-text-level">{{ aiTextLevel }}</span>
<span data-pagefind-filter="ai-code-level">{{ aiCodeLevel or "0" }}</span>
<span data-pagefind-filter="ai-used">{% if aiUsed %}yes{% else %}no{% endif %}</span>
{% if category %}
{% if category is string %}
<span data-pagefind-filter="category">{{ category }}</span>
{% else %}
{% for cat in category %}
<span data-pagefind-filter="category">{{ cat }}</span>
{% endfor %}
{% endif %}
{% endif %}
</div>
{# JSON-LD Structured Data for pages with AI transparency metadata #}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebPage",
"name": {{ title | dump | safe }},
"url": "{{ site.url }}{{ page.url }}",
"datePublished": "{{ page.date | isoDate }}",
"dateModified": "{{ (updated or page.date) | isoDate }}",
"usageInfo": "{{ site.url }}/ai"{% set _aiParts = [] %}{% if aiTextLevel %}{% set _textLabel %}{% if aiTextLevel === "0" %}None{% elif aiTextLevel === "1" %}Editorial assistance{% elif aiTextLevel === "2" %}Co-drafting{% elif aiTextLevel === "3" %}AI-generated{% endif %}{% endset %}{% set _aiParts = (_aiParts.push("Text: " + _textLabel), _aiParts) %}{% endif %}{% if aiCodeLevel %}{% set _codeLabel %}{% if aiCodeLevel === "0" %}Human-written{% elif aiCodeLevel === "1" %}AI-assisted{% elif aiCodeLevel === "2" %}AI-generated{% endif %}{% endset %}{% set _aiParts = (_aiParts.push("Code: " + _codeLabel), _aiParts) %}{% endif %}{% if aiTools %}{% set _aiParts = (_aiParts.push("Tools: " + aiTools), _aiParts) %}{% endif %},
"creativeWorkStatus": "{{ _aiParts | join(', ') }}",
"additionalProperty": [
{ "@type": "PropertyValue", "name": "aiTextLevel", "value": "{{ aiTextLevel }}" },
{ "@type": "PropertyValue", "name": "aiCodeLevel", "value": "{{ aiCodeLevel or '0' }}" }{% if aiTools %},
{ "@type": "PropertyValue", "name": "aiTools", "value": {{ aiTools | dump | safe }} }{% endif %}
]{% if aiDescription %},
"abstract": {{ aiDescription | dump | safe }}{% endif %}
}
</script>
</article>