feat: add visibilityifnocontent per-category config key

When set to visible, the category always appears in the selector
even when no variant exists for the current page. Navigating to
such a page shows pagenotfoundmessage with no fallback to
default-category content. Default behaviour (hidden) is unchanged.

Updates pageShouldDisplay and visibleCategoryCodesForCurrentPage
to honour the new key alongside the existing notfoundmessage logic.
Docs updated with key description, summary table, and full example.
This commit is contained in:
Claude 2026-05-21 15:02:31 +00:00
parent cc4ed7b881
commit ef4197fa83
No known key found for this signature in database
2 changed files with 16 additions and 6 deletions

View file

@ -952,7 +952,7 @@ body {
// Category state (phase 3) // Category state (phase 3)
let categoriesUse = false; let categoriesUse = false;
let categoriesList = []; // [{code, name, direction, message, notfoundmessage, pagenotfoundmessage, font, ...}] let categoriesList = []; // [{code, name, direction, message, notfoundmessage, pagenotfoundmessage, visibilityifnocontent, font, ...}]
let categoriesByCode = {}; // code → category object let categoriesByCode = {}; // code → category object
let defaultCategoryCode = null; let defaultCategoryCode = null;
let activeCategory = null; // current code let activeCategory = null; // current code
@ -1202,13 +1202,14 @@ body {
// - Home page: always show (per config.homepage or default 'pages/home.md') // - Home page: always show (per config.homepage or default 'pages/home.md')
// - Variant exists for active category: show // - Variant exists for active category: show
// - Active category has notfoundmessage: show (renderer falls back to default language) // - Active category has notfoundmessage: show (renderer falls back to default language)
// - Active category has visibilityifnocontent: visible: show (renderer shows pagenotfoundmessage)
// - Otherwise: hide // - Otherwise: hide
if (!categoriesUse) return true; if (!categoriesUse) return true;
if (page.file === defaultPage()) return true; if (page.file === defaultPage()) return true;
const variants = page.variants || []; const variants = page.variants || [];
if (variants.includes(activeCategory)) return true; if (variants.includes(activeCategory)) return true;
const cat = categoriesByCode[activeCategory]; const cat = categoriesByCode[activeCategory];
return !!(cat && cat.notfoundmessage); return !!(cat && (cat.notfoundmessage || cat.visibilityifnocontent === 'visible'));
} }
// ─── Theme ──────────────────────────────────────────────── // ─── Theme ────────────────────────────────────────────────
@ -2243,7 +2244,8 @@ function fmtDatetime(dtStr) {
function visibleCategoryCodesForCurrentPage() { function visibleCategoryCodesForCurrentPage() {
// Which categories should appear in the dropdown: // Which categories should appear in the dropdown:
// - the variant exists for this page, OR // - the variant exists for this page, OR
// - the category has a notfoundmessage // - the category has a notfoundmessage (fallback to default content), OR
// - the category has visibilityifnocontent: visible (shows pagenotfoundmessage instead)
// - always include the active category so user can see what they're on // - always include the active category so user can see what they're on
const out = new Set(); const out = new Set();
const page = currentPage const page = currentPage
@ -2251,8 +2253,8 @@ function fmtDatetime(dtStr) {
: null; : null;
categoriesList.forEach(cat => { categoriesList.forEach(cat => {
const hasVariant = !page || !page.variants || page.variants.includes(cat.code); const hasVariant = !page || !page.variants || page.variants.includes(cat.code);
const hasMsg = !!cat.notfoundmessage; const alwaysVisible = !!cat.notfoundmessage || cat.visibilityifnocontent === 'visible';
if (hasVariant || hasMsg || cat.code === activeCategory) out.add(cat.code); if (hasVariant || alwaysVisible || cat.code === activeCategory) out.add(cat.code);
}); });
return out; return out;
} }

View file

@ -163,6 +163,12 @@ default-category: # The category used when no ?cat= parameter is in
# current page. Also enables fallback: the renderer will fall back to # current page. Also enables fallback: the renderer will fall back to
# the default-category content instead of hiding the page. # the default-category content instead of hiding the page.
# Omit to hide the category from the dropdown when no variant exists. # Omit to hide the category from the dropdown when no variant exists.
visibilityifnocontent: hidden # hidden (default) or visible.
# hidden: category disappears from the selector when no variant exists
# for the current page (unless notfoundmessage is also set).
# visible: category stays in the selector regardless. When the user
# navigates to a page with no variant, pagenotfoundmessage is shown
# in the content area. No fallback to default-category content.
pagenotfoundmessage: "This page is not yet available in English." pagenotfoundmessage: "This page is not yet available in English."
# Message shown in the content area when a page cannot be fetched for # Message shown in the content area when a page cannot be fetched for
# this category. Overrides the top-level pagenotfoundmessage. # this category. Overrides the top-level pagenotfoundmessage.
@ -204,6 +210,7 @@ categories-selecttext: "Language" # Label shown next to the icon in the categor
| `name-latin` | No | Secondary label in the dropdown, shown alongside `name` when `name` uses a non-Latin script. | | `name-latin` | No | Secondary label in the dropdown, shown alongside `name` when `name` uses a non-Latin script. |
| `direction` | No | `ltr` or `rtl`. Default: `ltr`. RTL flips nav and content direction. | | `direction` | No | `ltr` or `rtl`. Default: `ltr`. RTL flips nav and content direction. |
| `notfoundmessage` | No | Short note shown in the dropdown when no variant exists for the current page. Also enables fallback to default-category content. | | `notfoundmessage` | No | Short note shown in the dropdown when no variant exists for the current page. Also enables fallback to default-category content. |
| `visibilityifnocontent` | No | `hidden` (default) or `visible`. `visible` keeps the category in the selector when no variant exists; navigating to it shows `pagenotfoundmessage` with no fallback to default content. |
| `pagenotfoundmessage` | No | Message shown in the content area when a page cannot be fetched for this category. Overrides the top-level `pagenotfoundmessage`. | | `pagenotfoundmessage` | No | Message shown in the content area when a page cannot be fetched for this category. Overrides the top-level `pagenotfoundmessage`. |
| `font` | No | Font filename from `assets/fonts/`. Loaded on demand when this category is activated. | | `font` | No | Font filename from `assets/fonts/`. Loaded on demand when this category is activated. |
| `line-height` | No | Body line height override for this category. Restores to theme default when switching away. | | `line-height` | No | Body line height override for this category. Restores to theme default when switching away. |
@ -277,7 +284,8 @@ categories:
- code: nb - code: nb
name: Norsk name: Norsk
direction: ltr direction: ltr
notfoundmessage: "Ikke tilgjengelig på norsk" visibilityifnocontent: visible
pagenotfoundmessage: "Denne siden er ikke tilgjengelig på norsk ennå."
- code: ar - code: ar
name: عربي name: عربي
name-latin: Arabic name-latin: Arabic