Files
hmgrisen-static/AGENTS.md
T
2026-03-13 02:51:23 +01:00

239 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Implementation Overview
This document describes the architecture and implementation plan for converting the Squarespace/WordPress XML export into a Next.js static site.
See [EXPORT_XML_STRUCTURE.md](EXPORT_XML_STRUCTURE.md) for a detailed description of the XML format.
---
## Project layout
```
.
├── content/
│ ├── posts/
│ │ ├── nyheter/ # Main news posts (34 files)
│ │ ├── historierogmyter/ # Stories & myths ( 7 files)
│ │ └── den-hviide-knap/ # DHK-specific news (10 files)
│ ├── pages/
│ │ ├── den-gyldne-gris-orden/ # DGG section (7 files, index.md = anno 1859)
│ │ ├── den-hviide-knap/ # DHK section (5 files)
│ │ ├── bokuglen/ # Bokuglen section (5 files, index.md = anno 1924)
│ │ ├── grisefestene/ # Festival pages (4 files, index.md)
│ │ └── _squarespace/ # Squarespace template pages – not for rendering
│ └── image-manifest.json # CDN URL → local path mapping for all 394 images
├── public/
│ ├── images/ # Downloaded images (not committed)
│ └── files/ # Document files, e.g. PDFs (not committed)
├── scripts/
│ ├── convert-xml.mjs # Step 1: XML → Markdown
│ ├── reorganize-content.mjs # Step 2: sort into section subdirectories (run once)
│ └── download-images.mjs # Step 3: fetch images from CDN
├── src/ # Next.js app source
└── export-2026-03-09.xml # Original Squarespace XML export
```
## Site navigation (from hmgrisen.com)
The old site's top-level navigation, which determines the section structure above:
| Menu item | URL(s) | Content type |
| --------------------------- | ------------------------ | ------------------------------------------------ |
| Nyheter | `/` | Posts in `posts/nyheter/` |
| Historier og myter | `/historierogmyter` | Posts in `posts/historierogmyter/` |
| Om | `/about` | Static page |
| **Den Gyldne Gris' Orden** | `/den-gyldne-gris-orden` | Section index + sub-pages |
| ↳ Anno 1859 | `/den-gyldne-gris-orden` | `pages/den-gyldne-gris-orden/index.md` |
| ↳ Presensliste | `/presensliste` | `pages/den-gyldne-gris-orden/presensliste.md` |
| ↳ Statutter | `/statutter` | `pages/den-gyldne-gris-orden/statutter.md` |
| ↳ Embedsverk | `/hoff` | `pages/den-gyldne-gris-orden/hoff.md` |
| ↳ Matrikkel | `/matrikkel` | `pages/den-gyldne-gris-orden/matrikkel.md` |
| **Den Hviide Knap** | `/anno-1899` | Posts + static sub-pages |
| ↳ Knappenytt (news) | `/anno-1899` | Posts in `posts/den-hviide-knap/` |
| ↳ Historie | `/dhkhistorie` | `pages/den-hviide-knap/dhkhistorie.md` |
| ↳ Presensliste | `/dhkpresensliste` | `pages/den-hviide-knap/dhkpresensliste.md` |
| ↳ Matrikkel | `/dhkmatrikkel` | `pages/den-hviide-knap/dhkmatrikkel.md` |
| ↳ Statutter | `/dhkstatutter` | `pages/den-hviide-knap/dhkstatutter.md` |
| ↳ Studentteateret 175 år | `/studentteateret-175-r` | `pages/den-hviide-knap/studentteateret-175-r.md` |
| **Bokuglen** | `/anno-1924` | Section index + sub-pages |
| ↳ Anno 1924 | `/anno-1924` | `pages/bokuglen/index.md` |
| ↳ Bokuglens historie | `/bokuglens-historie` | `pages/bokuglen/bokuglens-historie.md` |
| ↳ Presensliste | `/bupresensliste` | `pages/bokuglen/bupresensliste.md` |
| ↳ Matrikkel | `/bumatrikkel` | `pages/bokuglen/bumatrikkel.md` |
| ↳ Statutter | `/bustatutter` | `pages/bokuglen/bustatutter.md` |
| **Grisefestene** | `/grisefestene` | Section index + per-festival pages |
| Chateau Neuf | `/chateau-neuf` | Static page |
| Det Norske Studentersamfund | `studentersamfundet.no` | External link |
---
## Step 1: XML → Markdown conversion (`scripts/convert-xml.mjs`)
> **Note:** Steps 1–3 are one-time setup scripts, not part of the build pipeline.
**Run:**
```bash
node scripts/convert-xml.mjs # convert only
node scripts/convert-xml.mjs --download-images # convert + fetch all images
```
### What it does
1. **Parses XML** using `fast-xml-parser` (handles CDATA, namespaces).
2. **Builds an author map** from `<wp:author>` entries: `email → display name`.
3. **Processes each `post` and `page` item:**
- Resolves the author display name via the author map.
- Pre-processes WordPress `[caption]` shortcodes → plain `<img>` with alt text.
- Rewrites Squarespace CDN image URLs to local `/images/FILENAME` paths.
- Converts HTML → Markdown using `turndown`.
- Writes a `.md` file with YAML frontmatter.
4. **Skips `attachment` items** (images are embedded in content and handled inline).
5. **Writes `content/image-manifest.json`** mapping every unique CDN URL to its local filename.
### Frontmatter fields
| Field | Source | Notes |
| -------------- | --------------------------- | --------------------------------------------------- |
| `title` | `<title>` | |
| `date` | `<wp:post_date>` | ISO date, truncated to `YYYY-MM-DD` |
| `author` | `<dc:creator>` → author map | Display name resolved from email |
| `status` | `<wp:status>` | `publish` or `draft` |
| `type` | `<wp:post_type>` | `post` or `page` |
| `oldPermalink` | `<link>` | **Original relative URL** — used for redirect rules |
| `postName` | `<wp:post_name>` | Original WP slug (may include path) |
| `excerpt` | `<excerpt:encoded>` | Only present when non-empty |
### Output filename conventions
- Posts: `content/posts/YYYY-MM-DD-<last-slug-segment>.md`
- Pages: `content/pages/<slug>.md`
The last path segment of `<wp:post_name>` is used as the slug.
### Image handling
- All `<img src="...">` URLs in content are collected and deduplicated (by URL without query string).
- Each gets a local filename derived from the last path segment of the CDN URL.
- The HTML is rewritten with local paths _before_ HTML→Markdown conversion, so the Markdown references `/images/FILENAME`.
- `content/image-manifest.json` maps CDN URL → `{ localPath, filename, downloadUrl }`.
- Actual file downloads happen only with `--download-images` (supports one redirect hop).
---
## Step 2: Content reorganization (`scripts/reorganize-content.mjs`)
Moves Markdown files into section subdirectories and adds a `section` frontmatter field.
Run once, after `convert-xml.mjs`. The section assignment uses the first path segment of
`oldPermalink` for posts, and exact `oldPermalink` matching for pages.
---
## Step 3: Old permalink redirects
The `oldPermalink` frontmatter field contains the original Squarespace URL path (e.g. `/nyheter/2013/10/6/nye-riddere-kommandrer-og-storkors`). This enables a redirect map to be generated if the new URL structure differs.
**Planned:** A script or Next.js `next.config.ts` redirect generator that reads all Markdown frontmatter and emits `redirects()` entries mapping `oldPermalink → newPath`.
---
## Step 3: Next.js rendering
### Tech stack
| Layer | Choice |
| ---------------- | ------------------------------------------------------ |
| Framework | Next.js 16 App Router, static export (SSG) |
| Markdown parsing | `gray-matter` (frontmatter) + `remark` + `remark-html` |
| Styling | SASS — `src/css/main.scss` (no Tailwind) |
| Fonts | `next/font/google` — Cormorant Garamond + Cormorant SC |
### Source layout
```
src/
app/ # Next.js App Router routes
layout.tsx # Root layout: header with logo + nav, imports main.scss
page.tsx # / → Nyheter post index (full content, paginated)
nyheter/[slug]/page.tsx # Single nyheter post
historierogmyter/
page.tsx # Post index (full content, paginated)
[slug]/page.tsx # Single post
den-gyldne-gris-orden/
page.tsx # index.md (Anno 1859)
[slug]/page.tsx # presensliste, statutter, hoff, matrikkel, …
den-hviide-knap/
page.tsx # Post index (Knappenytt, full content, paginated)
[slug]/page.tsx # Static sub-pages: historie, presensliste, matrikkel, …
nyheter/[slug]/page.tsx # Single DHK post
bokuglen/
page.tsx # index.md (Anno 1924)
[slug]/page.tsx # historie, presensliste, matrikkel, statutter, …
grisefestene/
page.tsx # index.md
[slug]/page.tsx # 2004, 2009, 2013
about/page.tsx
chateau-neuf/page.tsx
components/
Nav.tsx # Site-wide nav with CSS hover dropdowns
SectionNav.tsx # Sub-section tab nav (takes links prop)
PageLayout.tsx # Wraps page: optional SectionNav + title + article body
PostListExpanded.tsx # Client component: paginated full-content post list
SlideshowInit.tsx # Client component: initialises image slideshows
css/
main.scss # All styles; variables at top
lib/
content.ts # FS helpers: getPostsBySection, getFullPostsBySection,
# getPostBySlug, getSectionPage, getPostSlugs, getPageSlugs
nav.ts # Centralised nav config: NAV, DGG_LINKS, DHK_LINKS, …
```
### URL → content mapping
| URL | Source file |
| -------------------------------- | --------------------------------------------------------- |
| `/` | `posts/nyheter/` index (full content, paginated) |
| `/nyheter/:slug` | `posts/nyheter/YYYY-MM-DD-{slug}.md` |
| `/historierogmyter` | `posts/historierogmyter/` index (full content, paginated) |
| `/historierogmyter/:slug` | `posts/historierogmyter/YYYY-MM-DD-{slug}.md` |
| `/den-gyldne-gris-orden` | `pages/den-gyldne-gris-orden/index.md` |
| `/den-gyldne-gris-orden/:slug` | `pages/den-gyldne-gris-orden/{slug}.md` |
| `/den-hviide-knap` | `posts/den-hviide-knap/` index (full content, paginated) |
| `/den-hviide-knap/nyheter/:slug` | `posts/den-hviide-knap/YYYY-MM-DD-{slug}.md` |
| `/den-hviide-knap/:slug` | `pages/den-hviide-knap/{slug}.md` |
| `/bokuglen` | `pages/bokuglen/index.md` |
| `/bokuglen/:slug` | `pages/bokuglen/{slug}.md` |
| `/grisefestene` | `pages/grisefestene/index.md` |
| `/grisefestene/:slug` | `pages/grisefestene/{slug}.md` |
| `/about` | `pages/about.md` |
| `/chateau-neuf` | `pages/chateau-neuf.md` |
Old Squarespace flat URLs are redirected in `next.config.ts` (`redirects()`).
### Header
Logo image: `public/images/hm-grisen-sign.png` (downloaded from Squarespace CDN).
Tagline: the full royal proclamation from the live site.
Nav: CSS-only hover dropdowns matching live site structure.
---
## Dependencies added
| Package | Purpose |
| ------------------------ | -------------------------------------------- |
| `fast-xml-parser` | XML parsing with CDATA and namespace support |
| `turndown` | HTML → Markdown conversion |
| `gray-matter` | Frontmatter parsing at build time |
| `remark` + `remark-html` | Markdown → HTML |
| `sass` | SCSS compilation |
---
## Known content quirks to handle in rendering
1. **Gallery images** — multiple `<img>` tags with no captions appear inline in Markdown. A custom renderer could display them as a grid.
2. **`<br>` / `&nbsp;` artefacts** — Squarespace content sometimes contains empty paragraphs and non-breaking spaces; rendering should collapse these gracefully.
3. **Some posts have no content** — only a title and metadata (e.g. very old posts that were images-only on Squarespace).
4. **Draft posts** are included in the output — filter by `status: publish` if needed.