- vault → notes (PKM-exported content) - posts → articles (short-form, no TOC) - papers → essays (long-form, with TOC) - type: post → type: article (posts are just short articles) - layouts/paper → layouts/essay - 08a-paper.css → 08a-essay.css - CSS: fix redundant li resets, remove role="main" from article, replace <small> prev/next labels, add console layout - Update hugo.toml menus, internal URLs, front matter throughout - Add docs/context.md, docs/css-refactor-plan.md
73 lines
2.5 KiB
Markdown
73 lines
2.5 KiB
Markdown
# Template Design: Handling the Markdown H1 / Front Matter Title Conflict
|
|
|
|
## The Problem
|
|
|
|
Standard markdown convention — Obsidian, agent-written files, generic `.md` — opens
|
|
with a level-1 heading as the document title:
|
|
|
|
```markdown
|
|
# My Note Title
|
|
|
|
Body text...
|
|
```
|
|
|
|
Hugo templates also render a title from front matter:
|
|
|
|
```html
|
|
<h1>{{ .Title }}</h1>
|
|
```
|
|
|
|
When both exist, the page gets two `<h1>` elements: one from the template,
|
|
one from the rendered markdown content. The content one also carries an
|
|
auto-generated `id` attribute from Hugo's heading anchor renderer.
|
|
|
|
## The Two Template Contracts
|
|
|
|
**Default (`_default/single.html`)** — bare markdown, minimal or no front matter.
|
|
The `# Title` in content IS the h1. The template header renders only metadata
|
|
(type, date, author, tags). No title rendered from front matter.
|
|
|
|
**Vault (`vault/single.html`)** — enriched front matter (`title`, `type`, `date`,
|
|
`author`, `tags`). Front matter `title` is authoritative. The `# Title` in content
|
|
is still present (markdown convention) but must be suppressed.
|
|
|
|
## The Fix: Engine-Agnostic Regex Strip
|
|
|
|
When a template owns the title (renders `<h1>{{ .Title }}</h1>` from front matter),
|
|
strip the first h1 from the rendered content before outputting it.
|
|
|
|
**Hugo:**
|
|
```
|
|
{{ replaceRE "<h1[^>]*>.*?</h1>" "" .Content 1 | safeHTML }}
|
|
```
|
|
|
|
**Jinja2 / Flask:**
|
|
```python
|
|
import re
|
|
content = re.sub(r'<h1[^>]*>.*?</h1>', '', content, count=1, flags=re.DOTALL)
|
|
```
|
|
|
|
**Nunjucks / Liquid / any engine:** equivalent string replace on the rendered HTML.
|
|
|
|
This is a string operation on already-rendered HTML, not a template-engine concept.
|
|
It ports to any engine without modification.
|
|
|
|
## Why Not Other Approaches
|
|
|
|
- **Author convention** (don't write `# Title` in vault files): breaks compatibility
|
|
with the entire markdown ecosystem.
|
|
- **Hugo render hooks** (`layouts/vault/_markup/render-heading.html`): Hugo-specific,
|
|
not portable.
|
|
- **CSS `display: none`**: h1 still exists in DOM — screen readers read it,
|
|
search engines index it. Semantically wrong.
|
|
|
|
## Engine-Agnostic Principle
|
|
|
|
ASW templates are prototyped in Hugo but must be portable to Flask/Jinja2 or
|
|
any other engine. Template logic should express *what*, not *how*:
|
|
|
|
- What: "strip h1 from content if front matter title is present"
|
|
- How: engine-specific implementation of the same string operation
|
|
|
|
Hugo-specific features (render hooks, shortcodes) are acceptable as prototyping
|
|
tools but should not become load-bearing parts of the template design.
|