v0.4 Phase 5: TOC tag + bump to 0.3.7

- Add renderTocTag(): groups visible pages by section in section sort
  order; sorts pages within each section by sort then filename; excludes
  current page, draft sections, and category-invisible pages; renders
  section headings and linked page lists; replaces the tag placeholder
- Add toc dispatch in hydrateMdcmsTags()
- Add .mdcms-toc, .mdcms-toc-section, .mdcms-toc-list CSS

https://claude.ai/code/session_015XtsgTMi8UtmgxEgb5Qt2c
This commit is contained in:
Claude 2026-05-16 16:55:04 +00:00
parent ca8deba23f
commit 2412608c7c
No known key found for this signature in database
5 changed files with 110 additions and 119 deletions

View file

@ -785,6 +785,39 @@ body {
.main-content { padding: 1rem 1rem 3rem; } .main-content { padding: 1rem 1rem 3rem; }
} }
/* ═══════════════════════════════════════════
TAG SYSTEM: CALLOUTS
═══════════════════════════════════════════ */
.mdcms-callout {
border-left: 4px solid;
border-radius: 0 6px 6px 0;
padding: 0.85rem 1rem 0.85rem 1rem;
margin: 1.25rem 0;
}
.mdcms-callout-title {
display: flex;
align-items: center;
gap: 0.4rem;
font-weight: 700;
font-size: 0.95rem;
margin-bottom: 0.45rem;
}
.mdcms-callout-title .mdcms-icon { font-size: 1.1em; }
.mdcms-callout-body { font-size: 0.95rem; }
.mdcms-callout-body > *:first-child { margin-top: 0; }
.mdcms-callout-body > *:last-child { margin-bottom: 0; }
/* ═══════════════════════════════════════════
TAG SYSTEM: TABLE OF CONTENTS
═══════════════════════════════════════════ */
.mdcms-toc { margin: 1rem 0; }
.mdcms-toc-section { font-size: 1rem; font-weight: 600; margin: 1.5rem 0 0.4rem; color: var(--font-colour-muted); border-bottom: 1px solid var(--divider); padding-bottom: 0.25rem; }
.mdcms-toc-list { list-style: none; padding: 0; margin: 0 0 0.5rem; }
.mdcms-toc-list li { padding: 0.2rem 0; border-bottom: 1px solid var(--divider); }
.mdcms-toc-list li:last-child { border-bottom: none; }
.mdcms-toc-list a { color: var(--accent); text-decoration: none; font-size: 0.95rem; }
.mdcms-toc-list a:hover { text-decoration: underline; }
/* ═══════════════════════════════════════════ /* ═══════════════════════════════════════════
TAG SYSTEM: POST LISTINGS TAG SYSTEM: POST LISTINGS
═══════════════════════════════════════════ */ ═══════════════════════════════════════════ */
@ -862,32 +895,6 @@ body {
} }
.post-load-more:hover { background: var(--nav-hover-bg); } .post-load-more:hover { background: var(--nav-hover-bg); }
/* ── Callout tags ──────────────────────────────────────── */
.mdcms-callout {
border-left: 4px solid var(--callout-primary, var(--accent));
background: var(--callout-bg, transparent);
border-radius: 0 0.4rem 0.4rem 0;
padding: 0.75rem 1rem;
margin: 1rem 0;
}
.mdcms-callout-title {
display: flex;
align-items: center;
gap: 0.45rem;
font-weight: 700;
color: var(--callout-primary, var(--accent));
margin-bottom: 0.4rem;
}
.mdcms-callout-title .mdcms-icon svg {
fill: var(--callout-primary, var(--accent));
width: 1.2em;
height: 1.2em;
display: block;
}
.mdcms-callout-body { margin: 0; }
.mdcms-callout-body > :first-child { margin-top: 0; }
.mdcms-callout-body > :last-child { margin-bottom: 0; }
@media print { @media print {
.sidebar, .topbar, .scroll-top, .hamburger, .sidebar, .topbar, .scroll-top, .hamburger,
.mobile-header, .theme-toggle, .search-container { display: none !important; } .mobile-header, .theme-toggle, .search-container { display: none !important; }
@ -1889,12 +1896,70 @@ function fmtDatetime(dtStr) {
return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')'; return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')';
} }
function renderTocTag(container) {
const byCode = {};
navSections.forEach(s => { byCode[s.code] = s; });
const sortedSections = navSections
.filter(s => !isDraftSection(s.code, byCode))
.sort((a, b) => ((a.sort ?? 999) - (b.sort ?? 999)) || (a.code || '').localeCompare(b.code || ''));
const visiblePages = navData.filter(p => {
if (p.file === currentPage) return false;
if (!pageShouldDisplay(p)) return false;
const sid = p['section-id'];
if (sid && isDraftSection(sid, byCode)) return false;
return true;
});
const bySection = {};
const unsectioned = [];
visiblePages.forEach(p => {
const sid = p['section-id'] || null;
if (sid) { (bySection[sid] = bySection[sid] || []).push(p); }
else unsectioned.push(p);
});
function sortPages(pages) {
return [...pages].sort((a, b) =>
((a.sort ?? 999) - (b.sort ?? 999)) || a.file.localeCompare(b.file));
}
function makeList(pages) {
const ul = document.createElement('ul');
ul.className = 'mdcms-toc-list';
pages.forEach(p => {
const a = el('a', { href: '#' + p.file, textContent: pageDisplayTitle(p) });
a.addEventListener('click', e => { e.preventDefault(); navigateTo(p.file); });
ul.appendChild(el('li', {}, a));
});
return ul;
}
const div = el('div', { className: 'mdcms-toc' });
if (unsectioned.length) div.appendChild(makeList(sortPages(unsectioned)));
sortedSections.forEach(section => {
const pages = bySection[section.code];
if (!pages || !pages.length) return;
div.appendChild(el('h3', { className: 'mdcms-toc-section', textContent: sectionDisplayName(section) }));
div.appendChild(makeList(sortPages(pages)));
});
if (!div.children.length) div.textContent = 'No pages found.';
container.replaceWith(div);
}
function hydrateMdcmsTags() { function hydrateMdcmsTags() {
document.querySelectorAll('.mdcms-tag').forEach(function(tagEl) { document.querySelectorAll('.mdcms-tag').forEach(function(tagEl) {
try { try {
var cfg = JSON.parse(tagEl.getAttribute('data-config')); var cfg = JSON.parse(tagEl.getAttribute('data-config'));
if (/^callout-(info|warning|success|error)$/.test(cfg.tagName)) { if (/^callout-(info|warning|success|error)$/.test(cfg.tagName)) {
renderCalloutTag(tagEl, cfg); renderCalloutTag(tagEl, cfg);
} else if (cfg.tagName === 'toc') {
renderTocTag(tagEl);
} else { } else {
renderPostTag(tagEl, cfg); renderPostTag(tagEl, cfg);
} }

View file

@ -3,101 +3,24 @@ title: Home
sort: 100 sort: 100
--- ---
# Phase 4 — Callout Tags # Phase 5 — Table of Contents Tag
Check each callout below. Each should show a coloured left border, an icon, a bold title in the accent colour, and a rendered body. The `toc` tag renders a section-grouped list of all pages visible for the active category. The TOC page itself is excluded.
--- ---
## Basic types ## Basic TOC
```mdcms callout-info ```mdcms
title: Information toc
This is an **info** callout. Supports *italic*, `code`, and lists:
- Item one
- Item two
```
```mdcms callout-warning
title: Warning
Something needs your attention. This is a **warning** callout.
```
```mdcms callout-success
title: Success
The operation completed successfully. This is a **success** callout.
```
```mdcms callout-error
title: Error
Something went wrong. This is an **error** callout.
``` ```
--- ---
## No title ## What to verify
```mdcms callout-info - All sections appear as headings in sort order
No title key here. The title row should not appear at all — just the body. - Pages within each section appear in sort order
``` - This page (Home) does **not** appear in the list
- Draft pages are excluded
--- - Switching category (if enabled) updates the page list
## Markdown body
```mdcms callout-warning
title: Rich body
- List item one
- List item two
A paragraph with `inline code` and a [link](https://example.com).
```
---
## Custom icon override
```mdcms callout-info
title: Info with warning icon
icon: warning
This info callout uses the warning icon instead of the default info icon.
```
---
## Config-defined message (message: key)
The callout below uses `message: aitranslation` to pull its title and body from the `callouts:` block in `config.yml`. The type (`warning`) also comes from the config entry, not the tag name.
```mdcms callout-info
message: aitranslation
```
---
## message: overrides inline content
When `message:` is present, any inline `title:` or body text is ignored. A warning should appear in the browser console.
```mdcms callout-info
message: aitranslation
title: This title should be ignored
This body text should also be ignored. Check the console for a warning.
```
---
## Missing icon
This callout uses a non-existent icon name. A broken image should appear where the icon would be.
```mdcms callout-info
title: Custom icon that does not exist
icon: nonexistent_icon
The icon to the left of this title should show as a broken image.
```
---
Toggle dark mode and check all four callout types still look correct.

View file

@ -1,11 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# #
# mdcms v0.3.6 — CLI companion # mdcms v0.3.7 — CLI companion
# #
# Copyright 2026 Kristian Benestad # Copyright 2026 Kristian Benestad
# Apache License, Version 2.0 — https://www.apache.org/licenses/LICENSE-2.0 # Apache License, Version 2.0 — https://www.apache.org/licenses/LICENSE-2.0
"""MD-CMS v0.3.5 — CLI tool for managing and building MD-CMS sites.""" """MD-CMS v0.3.7 — CLI tool for managing and building MD-CMS sites."""
import json import json
import os import os
@ -21,7 +21,7 @@ import certifi
import click import click
import yaml import yaml
CLI_VERSION = "0.3.6" CLI_VERSION = "0.3.7"
CLI_RELEASE_DATE = "17 May 2026" CLI_RELEASE_DATE = "17 May 2026"
MIN_SUPPORTED_VERSION = "0.3" MIN_SUPPORTED_VERSION = "0.3"

View file

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "mdcms" name = "mdcms"
version = "0.3.6" version = "0.3.7"
description = "MD-CMS — Markdown-based CMS companion CLI" description = "MD-CMS — Markdown-based CMS companion CLI"
readme = "README.md" readme = "README.md"
license = { text = "Apache-2.0" } license = { text = "Apache-2.0" }

View file

@ -28,7 +28,7 @@ PHASES = {
2: ("v0.4_phase2", "Icon system — local SVGs, no Google Fonts"), 2: ("v0.4_phase2", "Icon system — local SVGs, no Google Fonts"),
3: ("v0.4_phase3", "Asset validation in mdcms build"), 3: ("v0.4_phase3", "Asset validation in mdcms build"),
4: ("claude/debug-api-errors-gd730", "Callout tags"), 4: ("claude/debug-api-errors-gd730", "Callout tags"),
5: ("v0.4_phase5", "Table of contents tag"), 5: ("claude/toc-tag-phase5", "Table of contents tag"),
6: ("v0.4_phase6", "Offline / fetch-deps"), 6: ("v0.4_phase6", "Offline / fetch-deps"),
7: ("v0.4_phase7", "PWA — service worker and manifest"), 7: ("v0.4_phase7", "PWA — service worker and manifest"),
} }
@ -108,6 +108,9 @@ EXTRA_FILES = {
"app/config.yml", # has callouts: block for message: key test "app/config.yml", # has callouts: block for message: key test
"app/pages/home.md", # has Phase 4 callout test cases "app/pages/home.md", # has Phase 4 callout test cases
], ],
5: [
"app/pages/home.md", # has Phase 5 TOC test case
],
} }