From 0342539aac8d2c733cdbd36cb3ef4f6d14e7fb69 Mon Sep 17 00:00:00 2001
From: Wayne Sutton
Date: Tue, 23 Dec 2025 00:57:21 -0800
Subject: [PATCH] feat: add sidebar layout support for blog posts
- Add layout field to posts schema and frontmatter
- Enable docs-style sidebar layout for posts (previously pages only)
- Update Post.tsx to handle sidebar for both posts and pages
- Add layout field to Write.tsx frontmatter reference
- Update documentation in docs.md, setup-guide.md, how-to-publish.md
- Add documentation links to README.md
---
README.md | 293 ++------------------------------
TASK.md | 2 +-
changelog.md | 28 +++
content/blog/how-to-publish.md | 1 +
content/blog/setup-guide.md | 19 ++-
content/pages/changelog-page.md | 32 ++++
content/pages/docs.md | 58 +++++--
convex/posts.ts | 6 +
convex/schema.ts | 1 +
files.md | 2 +-
public/raw/changelog.md | 32 ++++
public/raw/docs.md | 58 +++++--
public/raw/how-to-publish.md | 1 +
public/raw/setup-guide.md | 18 +-
scripts/sync-posts.ts | 3 +
src/pages/Home.tsx | 8 +-
src/pages/Post.tsx | 19 ++-
src/pages/Write.tsx | 1 +
18 files changed, 244 insertions(+), 338 deletions(-)
diff --git a/README.md b/README.md
index 8bf7e26..8a7a27c 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,12 @@ npm run sync # dev
npm run sync:prod # production
```
+## Documentation
+
+- **[Setup Guide](content/blog/setup-guide.md)** - Complete fork and deployment guide
+- **[Configuration Guide](content/blog/fork-configuration-guide.md)** - Automated or manual fork setup
+- **[Full Documentation](content/pages/docs.md)** - Reference for all features and configuration
+
## Fork Configuration
After forking this project, you have two options to configure your site:
@@ -41,24 +47,6 @@ This updates all 11 configuration files in one step.
Follow the step-by-step guide in `FORK_CONFIG.md` to update each file manually. This guide includes code snippets and an AI agent prompt for assistance.
-### Files Updated
-
-| File | What to update |
-| ----------------------------------- | --------------------------------------------------------------------------- |
-| `src/config/siteConfig.ts` | Site name, title, intro, bio, blog page, logo gallery, GitHub contributions |
-| `src/pages/Home.tsx` | Intro paragraph text, footer links |
-| `convex/http.ts` | `SITE_URL`, `SITE_NAME`, description strings |
-| `convex/rss.ts` | `SITE_URL`, `SITE_TITLE`, `SITE_DESCRIPTION` |
-| `src/pages/Post.tsx` | `SITE_URL`, `SITE_NAME`, `DEFAULT_OG_IMAGE` |
-| `index.html` | Title, meta description, OG tags, JSON-LD |
-| `public/llms.txt` | Site name, URL, description, topics |
-| `public/robots.txt` | Sitemap URL and header comment |
-| `public/openapi.yaml` | API title, server URL, site name |
-| `public/.well-known/ai-plugin.json` | Site name, descriptions |
-| `src/context/ThemeContext.tsx` | Default theme |
-
-See `FORK_CONFIG.md` for detailed configuration examples and the full JSON schema.
-
## Features
- Markdown-based blog posts with frontmatter
@@ -169,130 +157,6 @@ excerpt: "Short text for featured cards"
Your markdown content here...
```
-## Images
-
-### Open Graph Images
-
-Add an `image` field to frontmatter for social media previews:
-
-```yaml
-image: "/images/my-header.png"
-```
-
-Recommended dimensions: 1200x630 pixels. Images can be local (`/images/...`) or external URLs.
-
-### Inline Images
-
-Add images in markdown content:
-
-```markdown
-
-```
-
-Place image files in `public/images/`. The alt text displays as a caption.
-
-### Image deployment
-
-Images are served as static files from your git repository, not synced to Convex. After adding images:
-
-1. Add image files to `public/images/`
-2. Reference in frontmatter (`image: "/images/my-image.png"`) or markdown (``)
-3. Commit and push to git
-4. Netlify rebuilds and serves the images
-
-The `npm run sync` command only syncs markdown text content. Images require a full deploy via git push.
-
-### Site Logo
-
-Edit `src/pages/Home.tsx` to set your site logo:
-
-```typescript
-const siteConfig = {
- logo: "/images/logo.svg", // Set to null to hide
- // ...
-};
-```
-
-Replace `public/images/logo.svg` with your own logo file.
-
-## Featured Section
-
-Posts and pages with `featured: true` in frontmatter appear in the featured section.
-
-### Add to Featured
-
-Add these fields to any post or page frontmatter:
-
-```yaml
-featured: true
-featuredOrder: 1
-excerpt: "A short description for the card view."
-image: "/images/thumbnail.png"
-```
-
-Then run `npm run sync` (dev) or `npm run sync:prod` (production). No redeploy needed.
-
-| Field | Description |
-| --------------- | ----------------------------------------- |
-| `featured` | Set `true` to show in featured section |
-| `featuredOrder` | Order in featured section (lower = first) |
-| `excerpt` | Short description for card view |
-| `image` | Thumbnail for card view (displays square) |
-| `authorName` | Author display name shown next to date |
-| `authorImage` | Round author avatar image URL |
-
-### Display Modes
-
-The featured section supports two display modes:
-
-- **List view** (default): Bullet list of links
-- **Card view**: Grid of cards with thumbnail, title, and excerpt
-
-Users can toggle between views. To change the default:
-
-```typescript
-const siteConfig = {
- featuredViewMode: "cards", // 'list' or 'cards'
- showViewToggle: true, // Allow users to switch views
-};
-```
-
-### Thumbnail Images
-
-In card view, the `image` field displays as a square thumbnail above the title. Non-square images are automatically cropped to center. The list view shows links only (no images).
-
-Square thumbnails: 400x400px minimum (800x800px for retina). The same image can serve as both the OG image for social sharing and the featured card thumbnail.
-
-## Blog Page
-
-The site supports a dedicated blog page at `/blog`. Configure in `src/config/siteConfig.ts`:
-
-```typescript
-blogPage: {
- enabled: true, // Enable /blog route
- showInNav: true, // Show in navigation
- title: "Blog", // Nav link and page title
- order: 0, // Nav order (lower = first)
-},
-displayOnHomepage: true, // Show posts on homepage
-```
-
-| Option | Description |
-| ------------------- | -------------------------------------- |
-| `enabled` | Enable the `/blog` route |
-| `showInNav` | Show Blog link in navigation |
-| `title` | Text for nav link and page heading |
-| `order` | Position in navigation (lower = first) |
-| `displayOnHomepage` | Show post list on homepage |
-
-**Display options:**
-
-- Homepage only: `displayOnHomepage: true`, `blogPage.enabled: false`
-- Blog page only: `displayOnHomepage: false`, `blogPage.enabled: true`
-- Both: `displayOnHomepage: true`, `blogPage.enabled: true`
-
-**Navigation order:** The Blog link merges with page links and sorts by order. Pages use the `order` field in frontmatter. Set `blogPage.order: 5` to position Blog after pages with order 0-4.
-
## Logo Gallery
The homepage includes a scrolling logo gallery with sample logos. Configure in `siteConfig`:
@@ -306,35 +170,6 @@ logoGallery: {
},
```
-### Replace with your own logos
-
-1. Add logo images to `public/images/logos/` (SVG recommended)
-2. Update the images array with logos and links:
-
-```typescript
-logoGallery: {
- enabled: true,
- images: [
- { src: "/images/logos/your-logo-1.svg", href: "https://example.com" },
- { src: "/images/logos/your-logo-2.svg", href: "https://anothersite.com" },
- ],
- position: "above-footer", // or "below-featured"
- speed: 30, // Seconds for one scroll cycle
- title: "Trusted by", // Set to undefined to hide
-},
-```
-
-Each logo object supports:
-
-- `src`: Path to the logo image (required)
-- `href`: URL to link to when clicked (optional)
-
-### Remove sample logos
-
-Delete sample files from `public/images/logos/` and replace the images array with your own logos, or set `enabled: false` to hide the gallery entirely.
-
-The gallery uses CSS animations for smooth infinite scrolling. Logos appear grayscale and colorize on hover.
-
## GitHub Contributions Graph
Display your GitHub contribution activity on the homepage. Configure in `src/config/siteConfig.ts`:
@@ -349,23 +184,6 @@ gitHubContributions: {
},
```
-| Option | Description |
-| -------------------- | --------------------------------------------- |
-| `enabled` | `true` to show, `false` to hide |
-| `username` | Your GitHub username |
-| `showYearNavigation` | Show prev/next year navigation buttons |
-| `linkToProfile` | Click graph to visit GitHub profile |
-| `title` | Text above graph (set to `undefined` to hide) |
-
-The graph displays with theme-aware colors that match each site theme:
-
-- **Dark**: GitHub green on dark background
-- **Light**: Standard GitHub green
-- **Tan**: Warm brown tones
-- **Cloud**: Gray-blue tones
-
-Uses the public `github-contributions-api.jogruber.de` API (no GitHub token required).
-
## Visitor Map
Display real-time visitor locations on a world map on the stats page. Uses Netlify's built-in geo detection (no third-party API needed). Privacy friendly: only stores city, country, and coordinates. No IP addresses stored.
@@ -379,27 +197,6 @@ visitorMap: {
},
```
-| Option | Description |
-| --------- | ------------------------------------------- |
-| `enabled` | `true` to show, `false` to hide |
-| `title` | Text above map (set to `undefined` to hide) |
-
-The map displays with theme-aware colors. Visitor dots pulse to indicate live sessions. Location data comes from Netlify's automatic geo headers at the edge.
-
-### Favicon
-
-Replace `public/favicon.svg` with your own icon. The default is a rounded square with the letter "m". Edit the SVG to change the letter or style.
-
-### Default Open Graph Image
-
-The default OG image is used when posts do not have an `image` field. Replace `public/images/og-default.svg` with your own image (1200x630 recommended).
-
-Update the reference in `src/pages/Post.tsx`:
-
-```typescript
-const DEFAULT_OG_IMAGE = "/images/og-default.svg";
-```
-
## Syncing Posts
Posts are synced to Convex. The sync script reads markdown files from `content/blog/` and `content/pages/`, then uploads them to your Convex database.
@@ -602,25 +399,6 @@ FIRECRAWL_API_KEY=fc-your-api-key
Imported posts are created as drafts (`published: false`). Review, edit, set `published: true`, then sync.
-## How Blog Post Slugs Work
-
-Slugs are defined in the frontmatter of each markdown file:
-
-```markdown
----
-slug: "my-post-slug"
----
-```
-
-The slug becomes the URL path: `yourdomain.com/my-post-slug`
-
-Rules:
-
-- Slugs must be unique across all posts
-- Use lowercase letters, numbers, and hyphens
-- The sync script reads the `slug` field from frontmatter
-- Posts are queried by slug using a Convex index
-
## Theme Configuration
The default theme is Tan. Users can cycle through themes using the toggle:
@@ -640,67 +418,22 @@ const DEFAULT_THEME: Theme = "tan"; // Change to "dark", "light", or "cloud"
The blog uses a serif font (New York) by default. To switch fonts, edit `src/styles/global.css`:
-```css
-body {
- /* Sans-serif option */
- font-family:
- -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
- Cantarell, sans-serif;
-
- /* Serif option (default) */
- font-family:
- "New York",
- -apple-system-ui-serif,
- ui-serif,
- Georgia,
- Cambria,
- "Times New Roman",
- Times,
- serif;
-}
-```
-
-Replace the `font-family` property with your preferred font stack.
-
### Font Sizes
All font sizes use CSS variables defined in `:root`. Customize sizes by editing the variables:
-```css
-:root {
- /* Base size scale */
- --font-size-base: 16px;
- --font-size-sm: 13px;
- --font-size-lg: 17px;
- --font-size-xl: 18px;
- --font-size-2xl: 20px;
- --font-size-3xl: 24px;
-
- /* Component-specific (examples) */
- --font-size-blog-content: 17px;
- --font-size-post-title: 32px;
- --font-size-nav-link: 14px;
-}
-```
-
-Mobile responsive sizes are defined in a `@media (max-width: 768px)` block with smaller values.
-
## Write Page
-A public markdown writing page at `/write` (not linked in navigation). Features:
-
-- Three-column Cursor docs-style layout
-- Content type selector (Blog Post or Page) with dynamic frontmatter templates
-- Frontmatter reference panel with copy buttons for each field
-- Font switcher (Serif/Sans-serif) with localStorage persistence
-- Theme toggle matching the site themes (Moon, Sun, Half2Icon, Cloud)
-- Word, line, and character counts
-- localStorage persistence for content, content type, and font preference
-- Works with Grammarly and browser spellcheck
-- Warning message about refresh losing content
+A public markdown writing page at `/write` (not linked in navigation).
Access directly at `yourdomain.com/write`. Content is stored in localStorage only (not synced to database). Use it to draft posts, then copy the content to a markdown file in `content/blog/` or `content/pages/` and run `npm run sync`.
## Source
Fork this project: [github.com/waynesutton/markdown-site](https://github.com/waynesutton/markdown-site)
+
+## License
+
+This project is licensed under the [MIT License](https://github.com/waynesutton/markdown-site/blob/main/LICENSE).
+
+You are free to use, modify, and distribute this project in accordance with the [MIT license terms](https://opensource.org/licenses/MIT).
diff --git a/TASK.md b/TASK.md
index 5c753d3..01dfc9b 100644
--- a/TASK.md
+++ b/TASK.md
@@ -15,7 +15,7 @@
## Current Status
-v1.21.0 deployed. Blog page view mode toggle with list and card views.
+v1.24.0 deployed. Sidebar layout support for blog posts.
## Completed
diff --git a/changelog.md b/changelog.md
index 48b080b..0655660 100644
--- a/changelog.md
+++ b/changelog.md
@@ -4,6 +4,34 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+## [1.24.0] - 2025-12-23
+
+### Added
+
+- Sidebar layout support for blog posts
+ - Blog posts can now use `layout: "sidebar"` frontmatter field (previously only available for pages)
+ - Enables docs-style layout with table of contents sidebar for long-form posts
+ - Same features as page sidebar: automatic TOC extraction, active heading highlighting, smooth scroll navigation
+ - Mobile responsive: stacks to single column below 1024px
+
+### Changed
+
+- Updated `Post.tsx` to handle sidebar layout for both posts and pages
+- Updated `Write.tsx` to include `layout` field in blog post frontmatter reference
+
+### Technical
+
+- Updated `convex/schema.ts`: Added optional `layout` field to posts table
+- Updated `scripts/sync-posts.ts`: Parses `layout` field from post frontmatter
+- Updated `convex/posts.ts`: Includes `layout` field in queries, mutations, and sync operations
+- Reuses existing sidebar components and CSS (no new components needed)
+
+### Documentation
+
+- Updated `docs.md`: Added `layout` field to blog posts frontmatter table, updated sidebar layout section
+- Updated `setup-guide.md`: Clarified sidebar layout works for both posts and pages
+- Updated `how-to-publish.md`: Added `layout` field to frontmatter reference table
+
## [1.23.0] - 2025-12-23
### Added
diff --git a/content/blog/how-to-publish.md b/content/blog/how-to-publish.md
index 6fe18b0..bf09201 100644
--- a/content/blog/how-to-publish.md
+++ b/content/blog/how-to-publish.md
@@ -88,6 +88,7 @@ readTime: "5 min read"
| `featured` | No | Set `true` to show in featured section |
| `featuredOrder` | No | Order in featured section (lower first) |
| `excerpt` | No | Short description for card view |
+| `layout` | No | Set to `"sidebar"` for docs-style layout with TOC |
## Write Your Content
diff --git a/content/blog/setup-guide.md b/content/blog/setup-guide.md
index 70b317d..8444529 100644
--- a/content/blog/setup-guide.md
+++ b/content/blog/setup-guide.md
@@ -8,6 +8,7 @@ tags: ["convex", "netlify", "tutorial", "deployment"]
readTime: "8 min read"
featured: true
featuredOrder: 6
+layout: "sidebar"
image: "/images/setupguide.png"
authorName: "Markdown"
authorImage: "/images/authors/markdown.png"
@@ -915,21 +916,21 @@ order: 1
Your page content here...
```
-| Field | Required | Description |
-| ------------- | -------- | -------------------------------------- |
-| `title` | Yes | Page title (shown in nav) |
-| `slug` | Yes | URL path (e.g., `/about`) |
-| `published` | Yes | Set `true` to show |
-| `order` | No | Display order (lower = first) |
-| `authorName` | No | Author display name shown next to date |
-| `authorImage` | No | Round author avatar image URL |
+| Field | Required | Description |
+| ------------- | -------- | ------------------------------------------------- |
+| `title` | Yes | Page title (shown in nav) |
+| `slug` | Yes | URL path (e.g., `/about`) |
+| `published` | Yes | Set `true` to show |
+| `order` | No | Display order (lower = first) |
+| `authorName` | No | Author display name shown next to date |
+| `authorImage` | No | Round author avatar image URL |
| `layout` | No | Set to `"sidebar"` for docs-style layout with TOC |
3. Run `npm run sync` to sync pages
Pages appear automatically in the navigation when published.
-**Sidebar layout:** Add `layout: "sidebar"` to any page frontmatter to enable a docs-style layout with a table of contents sidebar. The sidebar extracts headings (H1, H2, H3) automatically and provides smooth scroll navigation. Only appears if headings exist in the page content.
+**Sidebar layout:** Add `layout: "sidebar"` to any post or page frontmatter to enable a docs-style layout with a table of contents sidebar. The sidebar extracts headings (H1, H2, H3) automatically and provides smooth scroll navigation. Only appears if headings exist in the content.
### Update SEO Meta Tags
diff --git a/content/pages/changelog-page.md b/content/pages/changelog-page.md
index ef260aa..9909736 100644
--- a/content/pages/changelog-page.md
+++ b/content/pages/changelog-page.md
@@ -8,6 +8,38 @@ layout: "sidebar"
All notable changes to this project.
+## v1.24.0
+
+Released December 23, 2025
+
+**Sidebar layout for blog posts**
+
+- Blog posts now support `layout: "sidebar"` frontmatter field
+- Previously only available for static pages, now works for posts too
+- Enables docs-style layout with table of contents sidebar for long-form content
+- Same features as page sidebar: automatic TOC extraction, active heading highlighting, smooth scroll navigation
+- Mobile responsive: stacks to single column below 1024px
+
+Add `layout: "sidebar"` to any blog post frontmatter to enable the sidebar layout. The sidebar extracts headings (H1, H2, H3) automatically and only appears if headings exist in the content.
+
+Example:
+
+```yaml
+---
+title: "My Tutorial"
+description: "A detailed guide"
+date: "2025-01-20"
+slug: "my-tutorial"
+published: true
+tags: ["tutorial"]
+layout: "sidebar"
+---
+```
+
+Updated files: `convex/schema.ts`, `scripts/sync-posts.ts`, `convex/posts.ts`, `src/pages/Post.tsx`, `src/pages/Write.tsx`
+
+Documentation updated: `docs.md`, `setup-guide.md`, `how-to-publish.md`
+
## v1.23.0
Released December 23, 2025
diff --git a/content/pages/docs.md b/content/pages/docs.md
index 1ca9c90..a9e73d1 100644
--- a/content/pages/docs.md
+++ b/content/pages/docs.md
@@ -83,21 +83,22 @@ image: "/images/og-image.png"
Content here...
```
-| Field | Required | Description |
-| --------------- | -------- | -------------------------------------- |
-| `title` | Yes | Post title |
-| `description` | Yes | SEO description |
-| `date` | Yes | YYYY-MM-DD format |
-| `slug` | Yes | URL path (unique) |
-| `published` | Yes | `true` to show |
-| `tags` | Yes | Array of strings |
-| `readTime` | No | Display time estimate |
-| `image` | No | OG image and featured card thumbnail |
-| `excerpt` | No | Short text for card view |
-| `featured` | No | `true` to show in featured section |
-| `featuredOrder` | No | Order in featured (lower = first) |
-| `authorName` | No | Author display name shown next to date |
-| `authorImage` | No | Round author avatar image URL |
+| Field | Required | Description |
+| --------------- | -------- | ------------------------------------------------- |
+| `title` | Yes | Post title |
+| `description` | Yes | SEO description |
+| `date` | Yes | YYYY-MM-DD format |
+| `slug` | Yes | URL path (unique) |
+| `published` | Yes | `true` to show |
+| `tags` | Yes | Array of strings |
+| `readTime` | No | Display time estimate |
+| `image` | No | OG image and featured card thumbnail |
+| `excerpt` | No | Short text for card view |
+| `featured` | No | `true` to show in featured section |
+| `featuredOrder` | No | Order in featured (lower = first) |
+| `authorName` | No | Author display name shown next to date |
+| `authorImage` | No | Round author avatar image URL |
+| `layout` | No | Set to `"sidebar"` for docs-style layout with TOC |
### Static pages
@@ -130,7 +131,7 @@ Content here...
### Sidebar layout
-Pages can use a docs-style layout with a table of contents sidebar. Add `layout: "sidebar"` to the page frontmatter:
+Posts and pages can use a docs-style layout with a table of contents sidebar. Add `layout: "sidebar"` to the frontmatter:
```markdown
---
@@ -153,13 +154,36 @@ layout: "sidebar"
- Left sidebar displays table of contents extracted from H1, H2, H3 headings
- Two-column layout: 220px sidebar + flexible content area
-- Sidebar only appears if headings exist in the page content
+- Sidebar only appears if headings exist in the content
- Active heading highlighting as you scroll
- Smooth scroll navigation when clicking TOC links
- Mobile responsive: stacks to single column below 1024px
+- Works for both blog posts and static pages
The sidebar extracts headings automatically from your markdown content. No manual TOC needed.
+**Example for blog post:**
+
+```markdown
+---
+title: "My Tutorial"
+description: "A detailed guide"
+date: "2025-01-20"
+slug: "my-tutorial"
+published: true
+tags: ["tutorial"]
+layout: "sidebar"
+---
+
+# Introduction
+
+## Getting Started
+
+### Prerequisites
+
+## Advanced Topics
+```
+
### How frontmatter works
Frontmatter is the YAML metadata at the top of each markdown file between `---` markers. Here is how it flows through the system:
diff --git a/convex/posts.ts b/convex/posts.ts
index e38bfc3..0e03073 100644
--- a/convex/posts.ts
+++ b/convex/posts.ts
@@ -119,6 +119,7 @@ export const getPostBySlug = query({
featuredOrder: v.optional(v.number()),
authorName: v.optional(v.string()),
authorImage: v.optional(v.string()),
+ layout: v.optional(v.string()),
}),
v.null(),
),
@@ -149,6 +150,7 @@ export const getPostBySlug = query({
featuredOrder: post.featuredOrder,
authorName: post.authorName,
authorImage: post.authorImage,
+ layout: post.layout,
};
},
});
@@ -172,6 +174,7 @@ export const syncPosts = internalMutation({
featuredOrder: v.optional(v.number()),
authorName: v.optional(v.string()),
authorImage: v.optional(v.string()),
+ layout: v.optional(v.string()),
}),
),
},
@@ -212,6 +215,7 @@ export const syncPosts = internalMutation({
featuredOrder: post.featuredOrder,
authorName: post.authorName,
authorImage: post.authorImage,
+ layout: post.layout,
lastSyncedAt: now,
});
updated++;
@@ -256,6 +260,7 @@ export const syncPostsPublic = mutation({
featuredOrder: v.optional(v.number()),
authorName: v.optional(v.string()),
authorImage: v.optional(v.string()),
+ layout: v.optional(v.string()),
}),
),
},
@@ -296,6 +301,7 @@ export const syncPostsPublic = mutation({
featuredOrder: post.featuredOrder,
authorName: post.authorName,
authorImage: post.authorImage,
+ layout: post.layout,
lastSyncedAt: now,
});
updated++;
diff --git a/convex/schema.ts b/convex/schema.ts
index 64eb50b..48abab6 100644
--- a/convex/schema.ts
+++ b/convex/schema.ts
@@ -18,6 +18,7 @@ export default defineSchema({
featuredOrder: v.optional(v.number()), // Order in featured section (lower = first)
authorName: v.optional(v.string()), // Author display name
authorImage: v.optional(v.string()), // Author avatar image URL (round)
+ layout: v.optional(v.string()), // Layout type: "sidebar" for docs-style layout
lastSyncedAt: v.number(),
})
.index("by_slug", ["slug"])
diff --git a/files.md b/files.md
index c057ed8..c6d35e6 100644
--- a/files.md
+++ b/files.md
@@ -41,7 +41,7 @@ A brief description of each file in the codebase.
| ----------- | ----------------------------------------------------------------- |
| `Home.tsx` | Landing page with featured content and optional post list |
| `Blog.tsx` | Dedicated blog page with post list or card grid view (configurable via siteConfig.blogPage, supports view toggle) |
-| `Post.tsx` | Individual blog post view (update SITE_URL/SITE_NAME when forking) |
+| `Post.tsx` | Individual blog post or page view with optional sidebar layout (update SITE_URL/SITE_NAME when forking) |
| `Stats.tsx` | Real-time analytics dashboard with visitor stats and GitHub stars |
| `Write.tsx` | Three-column markdown writing page with Cursor docs-style UI, frontmatter reference with copy buttons, theme toggle, font switcher (serif/sans-serif), and localStorage persistence (not linked in nav) |
diff --git a/public/raw/changelog.md b/public/raw/changelog.md
index d193875..0fa6084 100644
--- a/public/raw/changelog.md
+++ b/public/raw/changelog.md
@@ -7,6 +7,38 @@ Date: 2025-12-23
All notable changes to this project.
+## v1.24.0
+
+Released December 23, 2025
+
+**Sidebar layout for blog posts**
+
+- Blog posts now support `layout: "sidebar"` frontmatter field
+- Previously only available for static pages, now works for posts too
+- Enables docs-style layout with table of contents sidebar for long-form content
+- Same features as page sidebar: automatic TOC extraction, active heading highlighting, smooth scroll navigation
+- Mobile responsive: stacks to single column below 1024px
+
+Add `layout: "sidebar"` to any blog post frontmatter to enable the sidebar layout. The sidebar extracts headings (H1, H2, H3) automatically and only appears if headings exist in the content.
+
+Example:
+
+```yaml
+---
+title: "My Tutorial"
+description: "A detailed guide"
+date: "2025-01-20"
+slug: "my-tutorial"
+published: true
+tags: ["tutorial"]
+layout: "sidebar"
+---
+```
+
+Updated files: `convex/schema.ts`, `scripts/sync-posts.ts`, `convex/posts.ts`, `src/pages/Post.tsx`, `src/pages/Write.tsx`
+
+Documentation updated: `docs.md`, `setup-guide.md`, `how-to-publish.md`
+
## v1.23.0
Released December 23, 2025
diff --git a/public/raw/docs.md b/public/raw/docs.md
index 96d73ff..74dafaa 100644
--- a/public/raw/docs.md
+++ b/public/raw/docs.md
@@ -82,21 +82,22 @@ image: "/images/og-image.png"
Content here...
```
-| Field | Required | Description |
-| --------------- | -------- | -------------------------------------- |
-| `title` | Yes | Post title |
-| `description` | Yes | SEO description |
-| `date` | Yes | YYYY-MM-DD format |
-| `slug` | Yes | URL path (unique) |
-| `published` | Yes | `true` to show |
-| `tags` | Yes | Array of strings |
-| `readTime` | No | Display time estimate |
-| `image` | No | OG image and featured card thumbnail |
-| `excerpt` | No | Short text for card view |
-| `featured` | No | `true` to show in featured section |
-| `featuredOrder` | No | Order in featured (lower = first) |
-| `authorName` | No | Author display name shown next to date |
-| `authorImage` | No | Round author avatar image URL |
+| Field | Required | Description |
+| --------------- | -------- | ------------------------------------------------- |
+| `title` | Yes | Post title |
+| `description` | Yes | SEO description |
+| `date` | Yes | YYYY-MM-DD format |
+| `slug` | Yes | URL path (unique) |
+| `published` | Yes | `true` to show |
+| `tags` | Yes | Array of strings |
+| `readTime` | No | Display time estimate |
+| `image` | No | OG image and featured card thumbnail |
+| `excerpt` | No | Short text for card view |
+| `featured` | No | `true` to show in featured section |
+| `featuredOrder` | No | Order in featured (lower = first) |
+| `authorName` | No | Author display name shown next to date |
+| `authorImage` | No | Round author avatar image URL |
+| `layout` | No | Set to `"sidebar"` for docs-style layout with TOC |
### Static pages
@@ -129,7 +130,7 @@ Content here...
### Sidebar layout
-Pages can use a docs-style layout with a table of contents sidebar. Add `layout: "sidebar"` to the page frontmatter:
+Posts and pages can use a docs-style layout with a table of contents sidebar. Add `layout: "sidebar"` to the frontmatter:
```markdown
---
@@ -152,13 +153,36 @@ layout: "sidebar"
- Left sidebar displays table of contents extracted from H1, H2, H3 headings
- Two-column layout: 220px sidebar + flexible content area
-- Sidebar only appears if headings exist in the page content
+- Sidebar only appears if headings exist in the content
- Active heading highlighting as you scroll
- Smooth scroll navigation when clicking TOC links
- Mobile responsive: stacks to single column below 1024px
+- Works for both blog posts and static pages
The sidebar extracts headings automatically from your markdown content. No manual TOC needed.
+**Example for blog post:**
+
+```markdown
+---
+title: "My Tutorial"
+description: "A detailed guide"
+date: "2025-01-20"
+slug: "my-tutorial"
+published: true
+tags: ["tutorial"]
+layout: "sidebar"
+---
+
+# Introduction
+
+## Getting Started
+
+### Prerequisites
+
+## Advanced Topics
+```
+
### How frontmatter works
Frontmatter is the YAML metadata at the top of each markdown file between `---` markers. Here is how it flows through the system:
diff --git a/public/raw/how-to-publish.md b/public/raw/how-to-publish.md
index 9712f13..0de9b0e 100644
--- a/public/raw/how-to-publish.md
+++ b/public/raw/how-to-publish.md
@@ -83,6 +83,7 @@ readTime: "5 min read"
| `featured` | No | Set `true` to show in featured section |
| `featuredOrder` | No | Order in featured section (lower first) |
| `excerpt` | No | Short description for card view |
+| `layout` | No | Set to `"sidebar"` for docs-style layout with TOC |
## Write Your Content
diff --git a/public/raw/setup-guide.md b/public/raw/setup-guide.md
index 055ecc9..b0dbb67 100644
--- a/public/raw/setup-guide.md
+++ b/public/raw/setup-guide.md
@@ -910,21 +910,21 @@ order: 1
Your page content here...
```
-| Field | Required | Description |
-| ------------- | -------- | -------------------------------------- |
-| `title` | Yes | Page title (shown in nav) |
-| `slug` | Yes | URL path (e.g., `/about`) |
-| `published` | Yes | Set `true` to show |
-| `order` | No | Display order (lower = first) |
-| `authorName` | No | Author display name shown next to date |
-| `authorImage` | No | Round author avatar image URL |
+| Field | Required | Description |
+| ------------- | -------- | ------------------------------------------------- |
+| `title` | Yes | Page title (shown in nav) |
+| `slug` | Yes | URL path (e.g., `/about`) |
+| `published` | Yes | Set `true` to show |
+| `order` | No | Display order (lower = first) |
+| `authorName` | No | Author display name shown next to date |
+| `authorImage` | No | Round author avatar image URL |
| `layout` | No | Set to `"sidebar"` for docs-style layout with TOC |
3. Run `npm run sync` to sync pages
Pages appear automatically in the navigation when published.
-**Sidebar layout:** Add `layout: "sidebar"` to any page frontmatter to enable a docs-style layout with a table of contents sidebar. The sidebar extracts headings (H1, H2, H3) automatically and provides smooth scroll navigation. Only appears if headings exist in the page content.
+**Sidebar layout:** Add `layout: "sidebar"` to any post or page frontmatter to enable a docs-style layout with a table of contents sidebar. The sidebar extracts headings (H1, H2, H3) automatically and provides smooth scroll navigation. Only appears if headings exist in the content.
### Update SEO Meta Tags
diff --git a/scripts/sync-posts.ts b/scripts/sync-posts.ts
index c75f2ed..b1c4538 100644
--- a/scripts/sync-posts.ts
+++ b/scripts/sync-posts.ts
@@ -36,6 +36,7 @@ interface PostFrontmatter {
featuredOrder?: number; // Order in featured section (lower = first)
authorName?: string; // Author display name
authorImage?: string; // Author avatar image URL (round)
+ layout?: string; // Layout type: "sidebar" for docs-style layout
}
interface ParsedPost {
@@ -53,6 +54,7 @@ interface ParsedPost {
featuredOrder?: number; // Order in featured section (lower = first)
authorName?: string; // Author display name
authorImage?: string; // Author avatar image URL (round)
+ layout?: string; // Layout type: "sidebar" for docs-style layout
}
// Page frontmatter (for static pages like About, Projects, Contact)
@@ -122,6 +124,7 @@ function parseMarkdownFile(filePath: string): ParsedPost | null {
featuredOrder: frontmatter.featuredOrder, // Order in featured section
authorName: frontmatter.authorName, // Author display name
authorImage: frontmatter.authorImage, // Author avatar image URL
+ layout: frontmatter.layout, // Layout type: "sidebar" for docs-style layout
};
} catch (error) {
console.error(`Error parsing ${filePath}:`, error);
diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx
index 7a09db7..ba2a296 100644
--- a/src/pages/Home.tsx
+++ b/src/pages/Home.tsx
@@ -269,7 +269,13 @@ export default function Home() {
>
GitHub
- .
+ . This project is licensed under the MIT{" "}
+
+ License.
+ {" "}
diff --git a/src/pages/Post.tsx b/src/pages/Post.tsx
index 6abdddf..db83457 100644
--- a/src/pages/Post.tsx
+++ b/src/pages/Post.tsx
@@ -236,10 +236,14 @@ export default function Post() {
);
};
+ // Extract headings for sidebar TOC (only for posts with layout: "sidebar")
+ const headings = post?.layout === "sidebar" ? extractHeadings(post.content) : [];
+ const hasSidebar = headings.length > 0;
+
// Render blog post with full metadata
return (
-