calevans / staticforge-chapternav
StaticForge Feature: ChapterNav
Package info
github.com/calevans/staticforge-chapternav
pkg:composer/calevans/staticforge-chapternav
Requires
- php: ^8.5
- eicc/staticforge: ^3.0
Requires (Dev)
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^10.5
- squizlabs/php_codesniffer: ^4.0
README
What it does: Automatically generates sequential prev/next navigation links for documentation pages based on menu ordering.
Requires PHP 8.5+ and StaticForge 3.x.
Installation
composer require calevans/staticforge-chapternav php vendor/bin/staticforge feature:setup calevans/staticforge-chapternav
Configuration
Set via your siteconfig.yaml file.
# Chapter Navigation Configuration chapter_nav: menus: "2" prev_symbol: "←" next_symbol: "→"
| Key | Default | Meaning |
|---|---|---|
menus |
(none) | Which MenuBuilder menu numbers get sequential navigation. A single number or a comma-separated list. |
prev_symbol |
← |
Prefix for the "previous chapter" link. |
next_symbol |
→ |
Suffix for the "next chapter" link. |
menus accepts a bare number (menus: 2), a quoted one (menus: "2"), a
comma-separated list (menus: "2, 3"), or a YAML list (menus: [2, 3]).
Entries that are not numeric are ignored with a warning in the build log —
menu numbers are always integers, because that is what MenuBuilder produces.
Environment variable fallback
If siteconfig.yaml has no chapter_nav: block at all, these are read
instead. Prefer siteconfig.yaml; these exist for parity with older installs.
CHAPTER_NAV_MENUS="2" CHAPTER_NAV_PREV_SYMBOL="←" CHAPTER_NAV_NEXT_SYMBOL="→"
The two sources are not merged. A present chapter_nav: block wins outright.
Disabling Chapter Navigation
To completely disable chapter navigation processing, either:
- Set
menus: ""(empty string) in yoursiteconfig.yaml - Remove the
chapter_navsection fromsiteconfig.yamland ensure theCHAPTER_NAV_MENUSenvironment variable is not set.
How It Works
Chapter Navigation uses the menu ordering from MenuBuilder to create sequential navigation between pages. Pages that appear in the configured menus automatically get prev/next links based on their menu position.
It runs on POST_GLOB at priority 150, after MenuBuilder (priority 100) has
published its menu structure. MenuBuilder must be enabled; if it is disabled,
ChapterNav logs a warning and does nothing.
Example Setup
Frontmatter is YAML.
--- title: "Quick Start Guide" menu: 2.1 template: docs ---
--- title: "Configuration Guide" menu: 2.2 template: docs ---
Results:
- Quick Start Guide (2.1): Shows only "Next →" link to Configuration Guide
- Configuration Guide (2.2): Shows "← Prev" to Quick Start
Third-level menu entries (menu = 2.2.1) are dropdown children. They still get
sequential navigation, but a dropdown's own label — the X.Y.0 entry under a
parent that is not itself a page — is skipped, since it links nowhere.
Using in Templates
The chapter navigation HTML is automatically generated. To display it in your template:
{% if features.ChapterNav.pages[source_file] is defined %}
{% for menu_num, nav_data in features.ChapterNav.pages[source_file] %}
{{ nav_data.html|raw }}
{% endfor %}
{% endif %}
Navigation Data Structure
Each page gets:
prev- Previous page data (title, url, file) or nullcurrent- Current page datanext- Next page data or nullhtml- Pre-generated HTML for the navigation
features.ChapterNav is written only when there is something to write. If no
menus are configured, or MenuBuilder published no menu files, the key is absent
entirely — hence the is defined guard above. A configured menu that matches no
pages yields pages: [].
URL handling
Every URL passes through a scheme allowlist: relative, fragment, and
protocol-relative URLs are allowed, as are http, https, and mailto.
Anything else — javascript:, data:, and obfuscated variants — is replaced
with # and logged as a warning.
This applies to prev.url, current.url, and next.url as well as to the
pre-generated html, so building your own <a href="{{ nav.next.url }}"> is
as safe as using nav_data.html. The values in the data are not
HTML-escaped — Twig autoescapes them for you, and escaping twice would turn
every & in a query string into &amp;. Inside html, where Twig cannot
help because it is emitted with |raw, titles, symbols, and hrefs are escaped.
Development
composer install composer test # PHPUnit composer phpcs # PSR-12 composer phpstan # static analysis, level 8 composer check # all three