mirror of
https://github.com/kbenestad/mdcms.git
synced 2026-06-18 15:24:32 +00:00
v0.3.7 — Phase 5: Table of contents tag
v0.3.7 — Phase 5: Table of contents tag
This commit is contained in:
commit
321202e5e0
5 changed files with 110 additions and 119 deletions
117
app/index.html
117
app/index.html
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
|
||||||
|
|
|
||||||
6
mdcms.py
6
mdcms.py
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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" }
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue