docs: update changelog page with right sidebar feature and fixes

Add v1.30.0, v1.30.1, and v1.30.2 entries to changelog-page.md covering:
- Right sidebar feature implementation
- TypeScript error fixes
- Right sidebar default behavior fix
This commit is contained in:
Wayne Sutton
2025-12-25 21:06:06 -08:00
parent 6b776733d5
commit d00f204fa7
30 changed files with 698 additions and 104 deletions

View File

@@ -0,0 +1,177 @@
---
name: Mobile PWA Optimization
overview: Add full mobile iOS/Android optimization and PWA support with automatic manifest.json generation from fork-config.json, iOS/Android meta tags, safe area CSS, and touch optimizations.
todos:
- id: update-fork-config-interface
content: Add optional pwa field to ForkConfig interface in scripts/configure-fork.ts
status: pending
- id: add-manifest-generation
content: Create updateManifestJson() function to generate public/manifest.json from fork-config.json
status: pending
dependencies:
- update-fork-config-interface
- id: update-index-html-generation
content: Enhance updateIndexHtml() to add iOS/Android meta tags and manifest link
status: pending
dependencies:
- update-fork-config-interface
- id: add-mobile-css
content: Add safe area insets, touch optimizations, and input zoom prevention to src/styles/global.css
status: pending
- id: update-fork-config-example
content: Add optional pwa section to fork-config.json.example
status: pending
- id: update-netlify-headers
content: Add manifest.json headers to netlify.toml
status: pending
- id: wire-up-manifest-generation
content: Call updateManifestJson() in main() function of configure-fork.ts
status: pending
dependencies:
- add-manifest-generation
- id: update-documentation
content: Update fork-configuration-guide.md with PWA configuration section
status: pending
---
# Mobile and PWA Optimization Plan
## Overview
Add full mobile iOS/Android optimization and Progressive Web App (PWA) support. The manifest.json will be automatically generated from `fork-config.json` during the configure step, so users don't need to manually set it up.
## Implementation Steps
### 1. Update ForkConfig Interface
Add optional PWA configuration to `scripts/configure-fork.ts`:
```typescript
interface ForkConfig {
// ... existing fields ...
pwa?: {
shortName?: string; // Short name for home screen (max 12 chars)
themeColor?: string; // Theme color (default: "#faf8f5")
backgroundColor?: string; // Background color (default: "#faf8f5")
};
}
```
### 2. Add manifest.json Generation Function
Create `updateManifestJson()` function in `scripts/configure-fork.ts`:
- Generate `public/manifest.json` with values from `fork-config.json`
- Use `siteName` for full name, `pwa.shortName` or truncated `siteName` for short name
- Use `siteDescription` for description
- Use `pwa.themeColor` and `pwa.backgroundColor` with defaults
- Include icon references (favicon.svg, icon-192.png, icon-512.png)
- Set `display: "standalone"` for home screen app behavior
- Set `orientation: "portrait-primary"`
### 3. Update index.html Generation
Enhance `updateIndexHtml()` function in `scripts/configure-fork.ts`:
- Update viewport meta tag to include `viewport-fit=cover` for iOS notches
- Add manifest link: `<link rel="manifest" href="/manifest.json" />`
- Add iOS meta tags:
- `apple-mobile-web-app-capable`
- `apple-mobile-web-app-status-bar-style`
- `apple-mobile-web-app-title`
- `apple-touch-icon` link
- Add Android meta tags:
- `mobile-web-app-capable`
- Enhanced `theme-color` with media queries for light/dark
- Update existing `theme-color` meta tag to support both light and dark modes
### 4. Add Mobile CSS Optimizations
Update `src/styles/global.css`:**Safe Area Insets (iOS notches):**
- Add `@supports (padding: max(0px))` blocks for safe area insets
- Apply to `.main-content`, `.top-nav`, `.mobile-menu-drawer`
- Use `env(safe-area-inset-*)` CSS variables
**Touch Optimizations:**
- Add `-webkit-tap-highlight-color: transparent` globally
- Add `touch-action: manipulation` to interactive elements
- Add `user-select: none` to buttons
- Add `-webkit-touch-callout: none` to buttons
**Prevent Input Zoom (iOS):**
- Ensure all `input`, `textarea`, `select` have `font-size: 16px` minimum
- Add mobile-specific rule to prevent zoom on focus
### 5. Update fork-config.json.example
Add optional PWA section:
```json
{
// ... existing fields ...
"pwa": {
"shortName": "Your Site",
"themeColor": "#faf8f5",
"backgroundColor": "#faf8f5"
}
}
```
### 6. Update netlify.toml
Add headers for manifest.json:
```toml
[[headers]]
for = "/manifest.json"
[headers.values]
Content-Type = "application/manifest+json"
Cache-Control = "public, max-age=3600"
```
### 7. Update configure-fork.ts Main Function
Add `updateManifestJson(config)` call in `main()` function after `updateAiPluginJson(config)`.
### 8. Update Documentation
Update `content/blog/fork-configuration-guide.md`:
- Add PWA section explaining optional `pwa` fields
- Note that manifest.json is auto-generated
- Mention icon file requirements (icon-180.png, icon-192.png, icon-512.png)
## Files to Modify
1. `scripts/configure-fork.ts` - Add manifest generation, update index.html generation, add PWA interface
2. `fork-config.json.example` - Add optional PWA fields
3. `src/styles/global.css` - Add safe area insets and touch optimizations
4. `netlify.toml` - Add manifest.json headers
5. `content/blog/fork-configuration-guide.md` - Document PWA configuration
## User Requirements
After running `npm run configure`, users need to:
1. Add icon files to `public/images/`:
- `icon-180.png` (180x180) - iOS home screen
- `icon-192.png` (192x192) - Android
- `icon-512.png` (512x512) - Android splash
The manifest.json will be automatically generated with correct values from their fork-config.json.
## Benefits
- Zero manual setup for manifest.json (auto-generated)
- Full iOS/Android home screen support
- Safe area support for notched devices

View File

@@ -22,7 +22,7 @@ Your content is instantly available to browsers, LLMs, and AI agents.. Write mar
- **Total Posts**: 12
- **Total Pages**: 4
- **Latest Post**: 2025-12-25
- **Last Updated**: 2025-12-25T08:46:57.371Z
- **Last Updated**: 2025-12-25T20:18:52.316Z
## Tech stack

17
TASK.md
View File

@@ -8,10 +8,25 @@
## Current Status
v1.29.0 ready. Added font family configuration system with monospace option.
v1.30.2 ready. Right sidebar now opt-in only via frontmatter.
## Completed
- [x] Fixed right sidebar default behavior: now requires explicit `rightSidebar: true` in frontmatter
- [x] Pages/posts without rightSidebar frontmatter render normally with CopyPageDropdown in nav
- [x] Fixed TypeScript errors: Added rightSidebar to syncPosts and syncPostsPublic args validators
- [x] Right sidebar feature with CopyPageDropdown support
- [x] RightSidebar component created
- [x] Three-column layout CSS (left sidebar, main content, right sidebar)
- [x] Right sidebar configuration in siteConfig.ts
- [x] rightSidebar frontmatter field for posts and pages
- [x] Updated Post.tsx to conditionally render right sidebar
- [x] Updated schema.ts, posts.ts, pages.ts to handle rightSidebar field
- [x] Updated sync-posts.ts to parse rightSidebar frontmatter
- [x] Updated Write.tsx to include rightSidebar option
- [x] Responsive behavior: right sidebar hidden below 1135px
- [x] CopyPageDropdown automatically moves from nav to right sidebar when enabled
- [x] Font family configuration system with siteConfig integration
- [x] Added FontContext.tsx for global font state management
- [x] Monospace font option added to FONT SWITCHER (IBM Plex Mono)

View File

@@ -4,6 +4,65 @@ 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.30.2] - 2025-12-25
### Fixed
- Right sidebar no longer appears on pages/posts without explicit `rightSidebar: true` in frontmatter
- Changed default behavior: right sidebar is now opt-in only
- Pages like About and Contact now render without the right sidebar as expected
- `CopyPageDropdown` correctly appears in nav bar when right sidebar is disabled
- Logic in `Post.tsx` changed from `(page.rightSidebar ?? true)` to `page.rightSidebar === true`
## [1.30.1] - 2025-12-25
### Fixed
- TypeScript error in `convex/posts.ts` where `rightSidebar` was used in mutation handlers but missing from args validators
- Added `rightSidebar: v.optional(v.boolean())` to `syncPosts` args validator
- Added `rightSidebar: v.optional(v.boolean())` to `syncPostsPublic` args validator
## [1.30.0] - 2025-12-25
### Added
- Right sidebar feature for posts and pages
- New `RightSidebar` component that displays `CopyPageDropdown` in a right sidebar
- Appears at 1135px+ viewport width when enabled
- Controlled by `siteConfig.rightSidebar.enabled` (global toggle)
- Per-post/page control via `rightSidebar: true` frontmatter field (opt-in only)
- Three-column layout support: left sidebar (TOC), main content, right sidebar (CopyPageDropdown)
- CopyPageDropdown automatically moves from nav to right sidebar when enabled
- Responsive: right sidebar hidden below 1135px, CopyPageDropdown returns to nav
- Right sidebar configuration in siteConfig
- `rightSidebar.enabled`: Global toggle for right sidebar feature
- `rightSidebar.minWidth`: Minimum viewport width to show sidebar (default: 1135px)
- `rightSidebar` frontmatter field
- Available for both blog posts and pages
- Optional boolean field to enable/disable right sidebar per post/page
- Defaults to true when `siteConfig.rightSidebar.enabled` is true
- Added to Write page frontmatter reference with copy button
### Changed
- `Post.tsx`: Updated to support three-column layout with conditional right sidebar rendering
- CSS refactoring: Separated left and right sidebar styles
- `.post-sidebar-wrapper` is now left-specific with `margin-left` and right border
- `.post-sidebar-right` has complete independent styles with `margin-right` and left border
- Both sidebars maintain consistent styling (sticky positioning, background, borders, scrollbar hiding)
- `src/styles/global.css`: Added CSS for right sidebar positioning and 3-column grid layout
- `convex/schema.ts`: Added `rightSidebar` field to posts and pages tables
- `convex/posts.ts` and `convex/pages.ts`: Updated queries and mutations to handle `rightSidebar` field
- `scripts/sync-posts.ts`: Updated parsing logic to include `rightSidebar` frontmatter field
- `src/pages/Write.tsx`: Added `rightSidebar` field to POST_FIELDS and PAGE_FIELDS arrays
### Technical
- Right sidebar uses sticky positioning with top offset matching left sidebar
- CSS grid automatically adjusts from 2-column to 3-column layout when right sidebar is present
- Main content padding adjusts when right sidebar is enabled
- Mobile responsive: right sidebar hidden below 1135px breakpoint
## [1.29.0] - 2025-12-25
### Added

View File

@@ -9,6 +9,8 @@ readTime: "3 min read"
featured: false
featuredOrder: 3
authorName: "Markdown"
layout: "sidebar"
rightSidebar: true
authorImage: "/images/authors/markdown.png"
image: "/images/matthew-smith-Rfflri94rs8-unsplash.jpg"
excerpt: "Quick guide to writing and publishing markdown posts with npm run sync."

View File

@@ -345,6 +345,7 @@ Your markdown content here...
| `featuredOrder` | No | Order in featured section (lower = first) |
| `authorName` | No | Author display name shown next to date |
| `authorImage` | No | Round author avatar image URL |
| `rightSidebar` | No | Enable right sidebar with CopyPageDropdown (opt-in, requires explicit `true`) |
### How Frontmatter Works
@@ -1043,6 +1044,8 @@ Pages appear automatically in the navigation when published.
**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.
**Right sidebar:** When enabled in `siteConfig.rightSidebar.enabled`, posts and pages can display a right sidebar containing the CopyPageDropdown at 1135px+ viewport width. Add `rightSidebar: true` to frontmatter to enable. Without this field, pages render normally with CopyPageDropdown in the nav bar. When enabled, CopyPageDropdown moves from the navigation bar to the right sidebar on wide screens. The right sidebar is hidden below 1135px, and CopyPageDropdown returns to the nav bar automatically.
### Update SEO Meta Tags
Edit `index.html` to update:

View File

@@ -9,6 +9,57 @@ layout: "sidebar"
All notable changes to this project.
![](https://img.shields.io/badge/License-MIT-yellow.svg)
## v1.30.2
Released December 25, 2025
**Right sidebar default behavior fix**
- Right sidebar no longer appears on pages/posts without explicit `rightSidebar: true` in frontmatter
- Changed default behavior: right sidebar is now opt-in only
- Pages like About and Contact now render without the right sidebar as expected
- `CopyPageDropdown` correctly appears in nav bar when right sidebar is disabled
- Logic in `Post.tsx` changed from `(page.rightSidebar ?? true)` to `page.rightSidebar === true`
Updated files: `src/pages/Post.tsx`
## v1.30.1
Released December 25, 2025
**TypeScript error fix**
- TypeScript error in `convex/posts.ts` where `rightSidebar` was used in mutation handlers but missing from args validators
- Added `rightSidebar: v.optional(v.boolean())` to `syncPosts` args validator
- Added `rightSidebar: v.optional(v.boolean())` to `syncPostsPublic` args validator
Updated files: `convex/posts.ts`
## v1.30.0
Released December 25, 2025
**Right sidebar feature for posts and pages**
- New `RightSidebar` component that displays `CopyPageDropdown` in a right sidebar
- Appears at 1135px+ viewport width when enabled
- Controlled by `siteConfig.rightSidebar.enabled` (global toggle)
- Per-post/page control via `rightSidebar: true` frontmatter field (opt-in only)
- Three-column layout support: left sidebar (TOC), main content, right sidebar (CopyPageDropdown)
- CopyPageDropdown automatically moves from nav to right sidebar when enabled
- Responsive: right sidebar hidden below 1135px, CopyPageDropdown returns to nav
- Right sidebar configuration in siteConfig
- `rightSidebar.enabled`: Global toggle for right sidebar feature
- `rightSidebar.minWidth`: Minimum viewport width to show sidebar (default: 1135px)
- `rightSidebar` frontmatter field
- Available for both blog posts and pages
- Optional boolean field to enable/disable right sidebar per post/page
- Added to Write page frontmatter reference with copy button
Updated files: `src/components/RightSidebar.tsx`, `src/pages/Post.tsx`, `src/config/siteConfig.ts`, `src/styles/global.css`, `convex/schema.ts`, `convex/posts.ts`, `convex/pages.ts`, `scripts/sync-posts.ts`, `src/pages/Write.tsx`
Documentation updated: `content/blog/setup-guide.md`, `content/pages/docs.md`, `files.md`, `changelog.md`
## v1.29.0
Released December 25, 2025

View File

@@ -2,6 +2,7 @@
title: "Contact"
slug: "contact"
published: true
layout: "sidebar"
order: 4
---

View File

@@ -4,6 +4,7 @@ slug: "docs"
published: true
order: 0
layout: "sidebar"
rightSidebar: true
---
Reference documentation for setting up, customizing, and deploying this markdown framework.
@@ -129,20 +130,21 @@ order: 1
Content here...
```
| Field | Required | Description |
| --------------- | -------- | ------------------------------------------------- |
| `title` | Yes | Nav link text |
| `slug` | Yes | URL path |
| `published` | Yes | `true` to show |
| `order` | No | Nav order (lower = first) |
| `showInNav` | No | Show in navigation menu (default: `true`) |
| `excerpt` | No | Short text for card view |
| `image` | No | Thumbnail for featured 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 |
| Field | Required | Description |
| --------------- | -------- | ----------------------------------------------------------------------------- |
| `title` | Yes | Nav link text |
| `slug` | Yes | URL path |
| `published` | Yes | `true` to show |
| `order` | No | Nav order (lower = first) |
| `showInNav` | No | Show in navigation menu (default: `true`) |
| `excerpt` | No | Short text for card view |
| `image` | No | Thumbnail for featured 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 |
| `rightSidebar` | No | Enable right sidebar with CopyPageDropdown (opt-in, requires explicit `true`) |
**Hide pages from navigation:** Set `showInNav: false` to keep a page published and accessible via direct URL, but hidden from the navigation menu. Pages with `showInNav: false` remain searchable and available via API endpoints. Useful for pages you want to link directly but not show in the main nav.
@@ -179,6 +181,46 @@ layout: "sidebar"
The sidebar extracts headings automatically from your markdown content. No manual TOC needed.
### Right sidebar
When enabled in `siteConfig.rightSidebar.enabled`, posts and pages can display a right sidebar containing the CopyPageDropdown at 1135px+ viewport width.
**Configuration:**
Enable globally in `src/config/siteConfig.ts`:
```typescript
rightSidebar: {
enabled: true, // Set to false to disable right sidebar globally
minWidth: 1135, // Minimum viewport width to show sidebar
},
```
Control per post/page with frontmatter:
```markdown
---
title: "My Post"
rightSidebar: true # Enable right sidebar for this post
---
```
**Features:**
- Right sidebar appears at 1135px+ viewport width
- Contains CopyPageDropdown with all sharing options
- Three-column layout: left sidebar (TOC), main content, right sidebar
- CopyPageDropdown automatically moves from nav to right sidebar when enabled
- Hidden below 1135px breakpoint, CopyPageDropdown returns to nav
- Per-post/page control via `rightSidebar: true` frontmatter field
- Opt-in only: right sidebar only appears when explicitly enabled in frontmatter
**Use cases:**
- Keep CopyPageDropdown accessible on wide screens without cluttering the nav
- Provide quick access to sharing options while reading long content
- Works alongside left sidebar TOC for comprehensive navigation
**Example for blog post:**
```markdown
@@ -289,19 +331,19 @@ Follow the step-by-step guide in `FORK_CONFIG.md` to update each file manually.
### Files updated by configuration
| 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 (3 locations) |
| `convex/rss.ts` | `SITE_URL`, `SITE_TITLE`, `SITE_DESCRIPTION` (RSS feeds) |
| `src/pages/Post.tsx` | `SITE_URL`, `SITE_NAME`, `DEFAULT_OG_IMAGE` (OG tags) |
| `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 in examples |
| `public/.well-known/ai-plugin.json` | Site name, descriptions |
| `src/context/ThemeContext.tsx` | Default theme |
| File | What to update |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `src/config/siteConfig.ts` | Site name, title, intro, bio, blog page, logo gallery, GitHub contributions, right sidebar configuration |
| `src/pages/Home.tsx` | Intro paragraph text, footer links |
| `convex/http.ts` | `SITE_URL`, `SITE_NAME`, description strings (3 locations) |
| `convex/rss.ts` | `SITE_URL`, `SITE_TITLE`, `SITE_DESCRIPTION` (RSS feeds) |
| `src/pages/Post.tsx` | `SITE_URL`, `SITE_NAME`, `DEFAULT_OG_IMAGE` (OG tags) |
| `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 in examples |
| `public/.well-known/ai-plugin.json` | Site name, descriptions |
| `src/context/ThemeContext.tsx` | Default theme |
### Site title and description metadata

View File

@@ -19,6 +19,7 @@ export const getAllPages = query({
authorName: v.optional(v.string()),
authorImage: v.optional(v.string()),
layout: v.optional(v.string()),
rightSidebar: v.optional(v.boolean()),
}),
),
handler: async (ctx) => {
@@ -55,6 +56,7 @@ export const getAllPages = query({
authorName: page.authorName,
authorImage: page.authorImage,
layout: page.layout,
rightSidebar: page.rightSidebar,
}));
},
});
@@ -119,6 +121,7 @@ export const getPageBySlug = query({
authorName: v.optional(v.string()),
authorImage: v.optional(v.string()),
layout: v.optional(v.string()),
rightSidebar: v.optional(v.boolean()),
}),
v.null(),
),
@@ -147,6 +150,7 @@ export const getPageBySlug = query({
authorName: page.authorName,
authorImage: page.authorImage,
layout: page.layout,
rightSidebar: page.rightSidebar,
};
},
});
@@ -169,6 +173,7 @@ export const syncPagesPublic = mutation({
authorName: v.optional(v.string()),
authorImage: v.optional(v.string()),
layout: v.optional(v.string()),
rightSidebar: v.optional(v.boolean()),
}),
),
},
@@ -208,6 +213,7 @@ export const syncPagesPublic = mutation({
authorName: page.authorName,
authorImage: page.authorImage,
layout: page.layout,
rightSidebar: page.rightSidebar,
lastSyncedAt: now,
});
updated++;

View File

@@ -21,6 +21,8 @@ export const getAllPosts = query({
featuredOrder: v.optional(v.number()),
authorName: v.optional(v.string()),
authorImage: v.optional(v.string()),
layout: v.optional(v.string()),
rightSidebar: v.optional(v.boolean()),
}),
),
handler: async (ctx) => {
@@ -51,6 +53,8 @@ export const getAllPosts = query({
featuredOrder: post.featuredOrder,
authorName: post.authorName,
authorImage: post.authorImage,
layout: post.layout,
rightSidebar: post.rightSidebar,
}));
},
});
@@ -120,6 +124,7 @@ export const getPostBySlug = query({
authorName: v.optional(v.string()),
authorImage: v.optional(v.string()),
layout: v.optional(v.string()),
rightSidebar: v.optional(v.boolean()),
}),
v.null(),
),
@@ -151,6 +156,7 @@ export const getPostBySlug = query({
authorName: post.authorName,
authorImage: post.authorImage,
layout: post.layout,
rightSidebar: post.rightSidebar,
};
},
});
@@ -175,6 +181,7 @@ export const syncPosts = internalMutation({
authorName: v.optional(v.string()),
authorImage: v.optional(v.string()),
layout: v.optional(v.string()),
rightSidebar: v.optional(v.boolean()),
}),
),
},
@@ -216,6 +223,7 @@ export const syncPosts = internalMutation({
authorName: post.authorName,
authorImage: post.authorImage,
layout: post.layout,
rightSidebar: post.rightSidebar,
lastSyncedAt: now,
});
updated++;
@@ -261,6 +269,7 @@ export const syncPostsPublic = mutation({
authorName: v.optional(v.string()),
authorImage: v.optional(v.string()),
layout: v.optional(v.string()),
rightSidebar: v.optional(v.boolean()),
}),
),
},
@@ -302,6 +311,7 @@ export const syncPostsPublic = mutation({
authorName: post.authorName,
authorImage: post.authorImage,
layout: post.layout,
rightSidebar: post.rightSidebar,
lastSyncedAt: now,
});
updated++;

View File

@@ -19,6 +19,7 @@ export default defineSchema({
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
rightSidebar: v.optional(v.boolean()), // Enable right sidebar with CopyPageDropdown
lastSyncedAt: v.number(),
})
.index("by_slug", ["slug"])
@@ -49,11 +50,12 @@ export default defineSchema({
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
rightSidebar: v.optional(v.boolean()), // Enable right sidebar with CopyPageDropdown
lastSyncedAt: v.number(),
})
.index("by_slug", ["slug"])
.index("by_published", ["published"])
.index("by_featured", ["featured"])
.index("by_slug", ["slug"])
.index("by_published", ["published"])
.index("by_featured", ["featured"])
.searchIndex("search_content", {
searchField: "content",
filterFields: ["published"],

View File

@@ -33,7 +33,7 @@ A brief description of each file in the codebase.
| File | Description |
| --------------- | --------------------------------------------------------------------------------------------------------- |
| `siteConfig.ts` | Centralized site configuration (name, logo, blog page, posts display with homepage post limit and read more link, GitHub contributions, nav order, inner page logo settings, hardcoded navigation items for React routes, GitHub repository config for AI service raw URLs, font family configuration) |
| `siteConfig.ts` | Centralized site configuration (name, logo, blog page, posts display with homepage post limit and read more link, GitHub contributions, nav order, inner page logo settings, hardcoded navigation items for React routes, GitHub repository config for AI service raw URLs, font family configuration, right sidebar configuration) |
### Pages (`src/pages/`)
@@ -41,7 +41,7 @@ A brief description of each file in the codebase.
| ----------- | ----------------------------------------------------------------- |
| `Home.tsx` | Landing page with featured content and optional post list. Supports configurable post limit (homePostsLimit) and optional "read more" link (homePostsReadMore) via siteConfig.postsDisplay |
| `Blog.tsx` | Dedicated blog page with post list or card grid view (configurable via siteConfig.blogPage, supports view toggle). Includes back button in navigation |
| `Post.tsx` | Individual blog post or page view with optional sidebar layout. Includes back button, CopyPageDropdown, tag links, and related posts section in footer for blog posts (update SITE_URL/SITE_NAME when forking) |
| `Post.tsx` | Individual blog post or page view with optional left sidebar (TOC) and right sidebar (CopyPageDropdown). Includes back button, tag links, and related posts section in footer for blog posts. Supports 3-column layout at 1135px+ (update SITE_URL/SITE_NAME when forking) |
| `Stats.tsx` | Real-time analytics dashboard with visitor stats and GitHub stars |
| `TagPage.tsx` | Tag archive page displaying posts filtered by a specific tag. Includes view mode toggle (list/cards) with localStorage persistence |
| `Write.tsx` | Three-column markdown writing page with Cursor docs-style UI, frontmatter reference with copy buttons, theme toggle, font switcher (serif/sans/monospace), and localStorage persistence (not linked in nav) |
@@ -63,6 +63,7 @@ A brief description of each file in the codebase.
| `GitHubContributions.tsx` | GitHub activity graph with theme-aware colors and year navigation |
| `VisitorMap.tsx` | Real-time visitor location map with dotted world display and theme-aware colors |
| `PageSidebar.tsx` | Collapsible table of contents sidebar for pages/posts with sidebar layout, extracts headings (H1-H6), active heading highlighting, smooth scroll navigation, localStorage persistence for expanded/collapsed state |
| `RightSidebar.tsx` | Right sidebar component that displays CopyPageDropdown on posts/pages at 1135px+ viewport width, controlled by siteConfig.rightSidebar.enabled and frontmatter rightSidebar field |
### Context (`src/context/`)
@@ -88,7 +89,7 @@ A brief description of each file in the codebase.
| File | Description |
| ------------ | ------------------------------------------------------------------------------------ |
| `global.css` | Global CSS with theme variables, centralized font-size CSS variables for all themes, sidebar styling with alternate background colors, hidden scrollbar, and consistent borders using box-shadow for docs-style layout |
| `global.css` | Global CSS with theme variables, centralized font-size CSS variables for all themes, sidebar styling with alternate background colors, hidden scrollbar, and consistent borders using box-shadow for docs-style layout. Left sidebar (`.post-sidebar-wrapper`) and right sidebar (`.post-sidebar-right`) have separate, independent styles |
## Convex Backend (`convex/`)
@@ -157,6 +158,7 @@ Markdown files for static pages like About, Projects, Contact, Changelog.
| `featuredOrder` | Order in featured section (optional) |
| `authorName` | Author display name (optional) |
| `authorImage` | Round author avatar image URL (optional) |
| `rightSidebar` | Enable right sidebar with CopyPageDropdown (optional) |
## Scripts (`scripts/`)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 367 KiB

After

Width:  |  Height:  |  Size: 491 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 348 KiB

After

Width:  |  Height:  |  Size: 423 KiB

View File

@@ -0,0 +1,12 @@
<svg width="172" height="40" viewBox="0 0 172 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M23.3606 12.8281C21.8137 13.2873 20.6476 14.3261 19.7936 15.4544C19.6102 15.6966 19.228 15.5146 19.3008 15.2178C20.936 8.49401 18.7759 2.90556 12.0422 0.154735C11.7006 0.0147435 11.345 0.321324 11.4346 0.679702C14.4977 12.9779 1.61412 11.9406 3.24224 25.8823C3.27024 26.1217 3.00145 26.2855 2.80546 26.1455C2.19509 25.7073 1.51332 24.7932 1.04575 24.1506C0.908555 23.9616 0.611769 24.0148 0.548773 24.2402C0.176391 25.5869 0 26.8553 0 28.1152C0 33.0149 2.51847 37.328 6.33048 39.8283C6.54887 39.9711 6.82886 39.7667 6.75466 39.5161C6.55867 38.8581 6.44808 38.1638 6.43968 37.4456C6.43968 37.0046 6.46768 36.5539 6.53627 36.1339C6.69587 35.0784 7.06265 34.0732 7.67862 33.1577C9.79111 29.9869 14.0259 26.9239 13.3497 22.7647C13.3063 22.5015 13.6171 22.328 13.8131 22.5085C16.7964 25.2342 17.3871 28.9005 16.8972 32.1889C16.8552 32.4745 17.2135 32.6271 17.3941 32.4031C17.8505 31.832 18.4077 31.3308 19.0138 30.9542C19.165 30.8604 19.3666 30.9318 19.424 31.0998C19.7614 32.0811 20.2626 33.0023 20.7358 33.9234C21.3013 35.0308 21.6023 36.2949 21.5547 37.6332C21.5309 38.2842 21.4231 38.9141 21.2425 39.5133C21.1655 39.7667 21.4427 39.9781 21.6653 39.8325C25.4801 37.3322 28 33.0191 28 28.1166C28 26.4129 27.7018 24.7428 27.1376 23.1777C25.9547 19.8949 22.9533 17.4297 23.712 13.1515C23.7484 12.9471 23.5594 12.7693 23.3606 12.8281Z" fill="#666666"/>
<path d="M41 34.0522V10.9619H55.7586V14.3265H44.7969V21.0227H53.8436V24.2883H44.7969V34.0522H41Z" fill="#666666"/>
<path d="M59.9565 14.7882C58.7348 14.7882 57.7773 13.8976 57.7773 12.6441C57.7773 11.3906 58.7348 10.5 59.9565 10.5C61.1781 10.5 62.1356 11.3906 62.1356 12.6441C62.1356 13.8976 61.1781 14.7882 59.9565 14.7882ZM58.1405 34.0521V17.1632H61.7064V34.0521H58.1405Z" fill="#666666"/>
<path d="M73.5866 17.1631H74.379V20.4947H72.7941C69.6245 20.4947 68.601 22.9686 68.601 25.5746V34.052H65.0352V17.1631H68.2048L68.601 19.703C69.4594 18.2846 70.8131 17.1631 73.5866 17.1631Z" fill="#666666"/>
<path d="M83.6309 34.25C78.3152 34.25 74.9805 30.8194 74.9805 25.6406C74.9805 20.4288 78.3152 16.9653 83.3008 16.9653C88.1873 16.9653 91.4559 20.066 91.555 25.0139C91.555 25.4427 91.522 25.9045 91.4559 26.3663H78.7114V26.5972C78.8105 29.467 80.6264 31.3472 83.4328 31.3472C85.6119 31.3472 87.1968 30.2587 87.692 28.3785H91.2578C90.6635 31.7101 87.8241 34.25 83.6309 34.25ZM78.8435 23.7604H87.8571C87.5599 21.2535 85.8101 19.8351 83.3338 19.8351C81.0556 19.8351 79.1076 21.3524 78.8435 23.7604Z" fill="#666666"/>
<path d="M102.035 34.25C96.917 34.25 93.6484 30.9184 93.6484 25.6406C93.6484 20.4288 97.0161 16.9653 102.134 16.9653C106.492 16.9653 109.199 19.3733 109.893 23.1997H106.162C105.7 21.2205 104.28 20 102.068 20C99.1952 20 97.3132 22.309 97.3132 25.6406C97.3132 28.9392 99.1952 31.2153 102.068 31.2153C104.247 31.2153 105.7 29.9618 106.129 28.0156H109.893C109.232 31.842 106.36 34.25 102.035 34.25Z" fill="#666666"/>
<path d="M121.004 17.1631H121.797V20.4947H120.212C117.042 20.4947 116.019 22.9686 116.019 25.5746V34.052H112.453V17.1631H115.623L116.019 19.703C116.877 18.2846 118.231 17.1631 121.004 17.1631Z" fill="#666666"/>
<path d="M130.613 16.9653C135.103 16.9653 137.678 19.1094 137.678 23.1007V34.0521H134.575L134.278 31.6441C133.122 33.1615 131.504 34.25 128.83 34.25C125.132 34.25 122.656 32.4358 122.656 29.3021C122.656 25.8385 125.165 23.8924 129.919 23.8924H134.146V22.8698C134.146 20.9896 132.792 19.8351 130.448 19.8351C128.335 19.8351 126.915 20.8247 126.651 22.309H123.151C123.514 19.0104 126.354 16.9653 130.613 16.9653ZM129.424 31.4792C132.396 31.4792 134.113 29.7309 134.146 27.125V26.5312H129.721C127.509 26.5312 126.288 27.3559 126.288 29.0712C126.288 30.4896 127.476 31.4792 129.424 31.4792Z" fill="#666666"/>
<path d="M144.655 34.052L139.141 17.1631H142.905L146.768 30.0936L150.631 17.1631H153.899L157.597 30.0936L161.592 17.1631H165.224L159.611 34.052H155.781L152.216 22.5728L148.518 34.052H144.655Z" fill="#666666"/>
<path d="M166.934 34.0522V10.9619H170.5V34.0522H166.934Z" fill="#666666"/>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 331 KiB

After

Width:  |  Height:  |  Size: 423 KiB

View File

@@ -1,6 +1,6 @@
# llms.txt - Information for AI assistants and LLMs
# Learn more: https://llmstxt.org/
# Last updated: 2025-12-25T08:46:57.373Z
# Last updated: 2025-12-25T20:18:52.317Z
> Your content is instantly available to browsers, LLMs, and AI agents.

View File

@@ -2,7 +2,7 @@
---
Type: page
Date: 2025-12-25
Date: 2025-12-26
---
An open-source publishing framework built for AI agents and developers to ship websites, docs, or blogs.. Write markdown, sync from the terminal. Your content is instantly available to browsers, LLMs, and AI agents. Built on Convex and Netlify.

View File

@@ -2,12 +2,35 @@
---
Type: page
Date: 2025-12-25
Date: 2025-12-26
---
All notable changes to this project.
![](https://img.shields.io/badge/License-MIT-yellow.svg)
## v1.29.0
Released December 25, 2025
**Font family configuration system**
- Font family configuration via siteConfig.ts
- Three font options: "serif" (New York), "sans" (system fonts), "monospace" (IBM Plex Mono)
- Configure default font in `src/config/siteConfig.ts` with `fontFamily` option
- Font preference persists in localStorage across page reloads
- SiteConfig default font overrides localStorage when siteConfig changes
- Monospace font option added
- Added monospace to FONT SWITCHER options in global.css
- Uses "IBM Plex Mono", "Liberation Mono", ui-monospace, monospace
- Write page font switcher now cycles through all three options
- Fork configuration support
- Added fontFamily field to fork-config.json.example
- Automated fork configuration script supports fontFamily option
Updated files: `src/config/siteConfig.ts`, `src/context/FontContext.tsx`, `src/main.tsx`, `src/pages/Write.tsx`, `src/styles/global.css`, `scripts/configure-fork.ts`, `fork-config.json.example`
Documentation updated: `content/blog/setup-guide.md`, `content/pages/docs.md`, `files.md`
## v1.28.2
Released December 25, 2025

View File

@@ -2,7 +2,7 @@
---
Type: page
Date: 2025-12-25
Date: 2025-12-26
---
You found the contact page. Nice

View File

@@ -2,7 +2,7 @@
---
Type: page
Date: 2025-12-25
Date: 2025-12-26
---
Reference documentation for setting up, customizing, and deploying this markdown framework.
@@ -128,20 +128,21 @@ order: 1
Content here...
```
| Field | Required | Description |
| --------------- | -------- | ------------------------------------------------- |
| `title` | Yes | Nav link text |
| `slug` | Yes | URL path |
| `published` | Yes | `true` to show |
| `order` | No | Nav order (lower = first) |
| `showInNav` | No | Show in navigation menu (default: `true`) |
| `excerpt` | No | Short text for card view |
| `image` | No | Thumbnail for featured 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 |
| Field | Required | Description |
| --------------- | -------- | ----------------------------------------------------------------------------- |
| `title` | Yes | Nav link text |
| `slug` | Yes | URL path |
| `published` | Yes | `true` to show |
| `order` | No | Nav order (lower = first) |
| `showInNav` | No | Show in navigation menu (default: `true`) |
| `excerpt` | No | Short text for card view |
| `image` | No | Thumbnail for featured 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 |
| `rightSidebar` | No | Enable right sidebar with CopyPageDropdown (opt-in, requires explicit `true`) |
**Hide pages from navigation:** Set `showInNav: false` to keep a page published and accessible via direct URL, but hidden from the navigation menu. Pages with `showInNav: false` remain searchable and available via API endpoints. Useful for pages you want to link directly but not show in the main nav.
@@ -178,6 +179,46 @@ layout: "sidebar"
The sidebar extracts headings automatically from your markdown content. No manual TOC needed.
### Right sidebar
When enabled in `siteConfig.rightSidebar.enabled`, posts and pages can display a right sidebar containing the CopyPageDropdown at 1135px+ viewport width.
**Configuration:**
Enable globally in `src/config/siteConfig.ts`:
```typescript
rightSidebar: {
enabled: true, // Set to false to disable right sidebar globally
minWidth: 1135, // Minimum viewport width to show sidebar
},
```
Control per post/page with frontmatter:
```markdown
---
title: "My Post"
rightSidebar: true # Enable right sidebar for this post
---
```
**Features:**
- Right sidebar appears at 1135px+ viewport width
- Contains CopyPageDropdown with all sharing options
- Three-column layout: left sidebar (TOC), main content, right sidebar
- CopyPageDropdown automatically moves from nav to right sidebar when enabled
- Hidden below 1135px breakpoint, CopyPageDropdown returns to nav
- Per-post/page control via `rightSidebar: true` frontmatter field
- Opt-in only: right sidebar only appears when explicitly enabled in frontmatter
**Use cases:**
- Keep CopyPageDropdown accessible on wide screens without cluttering the nav
- Provide quick access to sharing options while reading long content
- Works alongside left sidebar TOC for comprehensive navigation
**Example for blog post:**
```markdown
@@ -288,19 +329,19 @@ Follow the step-by-step guide in `FORK_CONFIG.md` to update each file manually.
### Files updated by configuration
| 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 (3 locations) |
| `convex/rss.ts` | `SITE_URL`, `SITE_TITLE`, `SITE_DESCRIPTION` (RSS feeds) |
| `src/pages/Post.tsx` | `SITE_URL`, `SITE_NAME`, `DEFAULT_OG_IMAGE` (OG tags) |
| `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 in examples |
| `public/.well-known/ai-plugin.json` | Site name, descriptions |
| `src/context/ThemeContext.tsx` | Default theme |
| File | What to update |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `src/config/siteConfig.ts` | Site name, title, intro, bio, blog page, logo gallery, GitHub contributions, right sidebar configuration |
| `src/pages/Home.tsx` | Intro paragraph text, footer links |
| `convex/http.ts` | `SITE_URL`, `SITE_NAME`, description strings (3 locations) |
| `convex/rss.ts` | `SITE_URL`, `SITE_TITLE`, `SITE_DESCRIPTION` (RSS feeds) |
| `src/pages/Post.tsx` | `SITE_URL`, `SITE_NAME`, `DEFAULT_OG_IMAGE` (OG tags) |
| `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 in examples |
| `public/.well-known/ai-plugin.json` | Site name, descriptions |
| `src/context/ThemeContext.tsx` | Default theme |
### Site title and description metadata

View File

@@ -2,7 +2,7 @@
---
Type: page
Date: 2025-12-25
Date: 2025-12-26
---
This markdown framework is open source and built to be extended. Here is what ships out of the box.

View File

@@ -339,6 +339,7 @@ Your markdown content here...
| `featuredOrder` | No | Order in featured section (lower = first) |
| `authorName` | No | Author display name shown next to date |
| `authorImage` | No | Round author avatar image URL |
| `rightSidebar` | No | Enable right sidebar with CopyPageDropdown (opt-in, requires explicit `true`) |
### How Frontmatter Works
@@ -1037,6 +1038,8 @@ Pages appear automatically in the navigation when published.
**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.
**Right sidebar:** When enabled in `siteConfig.rightSidebar.enabled`, posts and pages can display a right sidebar containing the CopyPageDropdown at 1135px+ viewport width. Add `rightSidebar: true` to frontmatter to enable. Without this field, pages render normally with CopyPageDropdown in the nav bar. When enabled, CopyPageDropdown moves from the navigation bar to the right sidebar on wide screens. The right sidebar is hidden below 1135px, and CopyPageDropdown returns to the nav bar automatically.
### Update SEO Meta Tags
Edit `index.html` to update:

View File

@@ -37,6 +37,7 @@ interface PostFrontmatter {
authorName?: string; // Author display name
authorImage?: string; // Author avatar image URL (round)
layout?: string; // Layout type: "sidebar" for docs-style layout
rightSidebar?: boolean; // Enable right sidebar with CopyPageDropdown (default: true when siteConfig.rightSidebar.enabled)
}
interface ParsedPost {
@@ -55,6 +56,7 @@ interface ParsedPost {
authorName?: string; // Author display name
authorImage?: string; // Author avatar image URL (round)
layout?: string; // Layout type: "sidebar" for docs-style layout
rightSidebar?: boolean; // Enable right sidebar with CopyPageDropdown (default: true when siteConfig.rightSidebar.enabled)
}
// Page frontmatter (for static pages like About, Projects, Contact)
@@ -71,6 +73,7 @@ interface PageFrontmatter {
authorName?: string; // Author display name
authorImage?: string; // Author avatar image URL (round)
layout?: string; // Layout type: "sidebar" for docs-style layout
rightSidebar?: boolean; // Enable right sidebar with CopyPageDropdown (default: true when siteConfig.rightSidebar.enabled)
}
interface ParsedPage {
@@ -87,6 +90,7 @@ interface ParsedPage {
authorName?: string; // Author display name
authorImage?: string; // Author avatar image URL (round)
layout?: string; // Layout type: "sidebar" for docs-style layout
rightSidebar?: boolean; // Enable right sidebar with CopyPageDropdown (default: true when siteConfig.rightSidebar.enabled)
}
// Calculate reading time based on word count
@@ -127,6 +131,7 @@ function parseMarkdownFile(filePath: string): ParsedPost | null {
authorName: frontmatter.authorName, // Author display name
authorImage: frontmatter.authorImage, // Author avatar image URL
layout: frontmatter.layout, // Layout type: "sidebar" for docs-style layout
rightSidebar: frontmatter.rightSidebar, // Enable right sidebar with CopyPageDropdown
};
} catch (error) {
console.error(`Error parsing ${filePath}:`, error);
@@ -178,6 +183,7 @@ function parsePageFile(filePath: string): ParsedPage | null {
authorName: frontmatter.authorName, // Author display name
authorImage: frontmatter.authorImage, // Author avatar image URL
layout: frontmatter.layout, // Layout type: "sidebar" for docs-style layout
rightSidebar: frontmatter.rightSidebar, // Enable right sidebar with CopyPageDropdown
};
} catch (error) {
console.error(`Error parsing page ${filePath}:`, error);

View File

@@ -0,0 +1,23 @@
import CopyPageDropdown from "./CopyPageDropdown";
interface RightSidebarProps {
title: string;
content: string;
url: string;
slug: string;
description?: string;
date?: string;
tags?: string[];
readTime?: string;
}
export default function RightSidebar(props: RightSidebarProps) {
return (
<aside className="post-sidebar-right">
<div className="right-sidebar-content">
<CopyPageDropdown {...props} />
</div>
</aside>
);
}

View File

@@ -80,6 +80,13 @@ export interface GitHubRepoConfig {
// default font family options: "serif" (New York), "sans" (system fonts), "monospace" (IBM Plex Mono)
export type FontFamily = "serif" | "sans" | "monospace";
// Right sidebar configuration
// Shows CopyPageDropdown in a right sidebar on posts/pages at 1135px+ viewport width
export interface RightSidebarConfig {
enabled: boolean; // Enable/disable the right sidebar globally
minWidth?: number; // Minimum viewport width to show sidebar (default: 1135)
}
// Site configuration interface
export interface SiteConfig {
// Basic site info
@@ -126,6 +133,9 @@ export interface SiteConfig {
// GitHub repository configuration for AI service links
gitHubRepo: GitHubRepoConfig;
// Right sidebar configuration
rightSidebar: RightSidebarConfig;
}
// Default site configuration
@@ -271,6 +281,14 @@ export const siteConfig: SiteConfig = {
branch: "main", // Default branch
contentPath: "public/raw", // Path to raw markdown files
},
// Right sidebar configuration
// Shows CopyPageDropdown in a right sidebar on posts/pages at 1135px+ viewport width
// When enabled, CopyPageDropdown moves from nav to right sidebar on wide screens
rightSidebar: {
enabled: true, // Set to false to disable right sidebar globally
minWidth: 1135, // Minimum viewport width in pixels to show sidebar
},
};
// Export the config as default for easy importing

View File

@@ -4,11 +4,13 @@ import { api } from "../../convex/_generated/api";
import BlogPost from "../components/BlogPost";
import CopyPageDropdown from "../components/CopyPageDropdown";
import PageSidebar from "../components/PageSidebar";
import RightSidebar from "../components/RightSidebar";
import { extractHeadings } from "../utils/extractHeadings";
import { useSidebar } from "../context/SidebarContext";
import { format, parseISO } from "date-fns";
import { ArrowLeft, Link as LinkIcon, Twitter, Rss, Tag } from "lucide-react";
import { useState, useEffect } from "react";
import siteConfig from "../config/siteConfig";
// Site configuration
const SITE_URL = "https://markdown.fast";
@@ -185,35 +187,40 @@ export default function Post() {
if (page) {
// Extract headings for sidebar TOC (only for pages with layout: "sidebar")
const headings = page.layout === "sidebar" ? extractHeadings(page.content) : [];
const hasSidebar = headings.length > 0;
const hasLeftSidebar = headings.length > 0;
// Check if right sidebar is enabled (only when explicitly set in frontmatter)
const hasRightSidebar = siteConfig.rightSidebar.enabled && page.rightSidebar === true;
const hasAnySidebar = hasLeftSidebar || hasRightSidebar;
return (
<div className={`post-page ${hasSidebar ? "post-page-with-sidebar" : ""}`}>
<nav className={`post-nav ${hasSidebar ? "post-nav-with-sidebar" : ""}`}>
<div className={`post-page ${hasAnySidebar ? "post-page-with-sidebar" : ""}`}>
<nav className={`post-nav ${hasAnySidebar ? "post-nav-with-sidebar" : ""}`}>
<button onClick={() => navigate("/")} className="back-button">
<ArrowLeft size={16} />
<span>Back</span>
</button>
{/* CopyPageDropdown in nav */}
<CopyPageDropdown
title={page.title}
content={page.content}
url={window.location.href}
slug={page.slug}
description={page.excerpt}
/>
{/* Only show CopyPageDropdown in nav if right sidebar is disabled */}
{!hasRightSidebar && (
<CopyPageDropdown
title={page.title}
content={page.content}
url={window.location.href}
slug={page.slug}
description={page.excerpt}
/>
)}
</nav>
<div className={hasSidebar ? "post-content-with-sidebar" : ""}>
<div className={hasAnySidebar ? "post-content-with-sidebar" : ""}>
{/* Left sidebar - TOC */}
{hasSidebar && (
{hasLeftSidebar && (
<aside className="post-sidebar-wrapper post-sidebar-left">
<PageSidebar headings={headings} activeId={location.hash.slice(1)} />
</aside>
)}
{/* Main content */}
<article className={`post-article ${hasSidebar ? "post-article-with-sidebar" : ""}`}>
<article className={`post-article ${hasAnySidebar ? "post-article-with-sidebar" : ""}`}>
<header className="post-header">
<h1 className="post-title">{page.title}</h1>
{/* Author avatar and name for pages (optional) */}
@@ -237,6 +244,17 @@ export default function Post() {
<BlogPost content={page.content} />
</article>
{/* Right sidebar - CopyPageDropdown */}
{hasRightSidebar && (
<RightSidebar
title={page.title}
content={page.content}
url={window.location.href}
slug={page.slug}
description={page.excerpt}
/>
)}
</div>
</div>
);
@@ -276,38 +294,43 @@ 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;
const hasLeftSidebar = headings.length > 0;
// Check if right sidebar is enabled (only when explicitly set in frontmatter)
const hasRightSidebar = siteConfig.rightSidebar.enabled && post.rightSidebar === true;
const hasAnySidebar = hasLeftSidebar || hasRightSidebar;
// Render blog post with full metadata
return (
<div className={`post-page ${hasSidebar ? "post-page-with-sidebar" : ""}`}>
<nav className={`post-nav ${hasSidebar ? "post-nav-with-sidebar" : ""}`}>
<div className={`post-page ${hasAnySidebar ? "post-page-with-sidebar" : ""}`}>
<nav className={`post-nav ${hasAnySidebar ? "post-nav-with-sidebar" : ""}`}>
<button onClick={() => navigate("/")} className="back-button">
<ArrowLeft size={16} />
<span>Back</span>
</button>
{/* Copy page dropdown for sharing with full metadata */}
<CopyPageDropdown
title={post.title}
content={post.content}
url={window.location.href}
slug={post.slug}
description={post.description}
date={post.date}
tags={post.tags}
readTime={post.readTime}
/>
{/* Only show CopyPageDropdown in nav if right sidebar is disabled */}
{!hasRightSidebar && (
<CopyPageDropdown
title={post.title}
content={post.content}
url={window.location.href}
slug={post.slug}
description={post.description}
date={post.date}
tags={post.tags}
readTime={post.readTime}
/>
)}
</nav>
<div className={hasSidebar ? "post-content-with-sidebar" : ""}>
<div className={hasAnySidebar ? "post-content-with-sidebar" : ""}>
{/* Left sidebar - TOC */}
{hasSidebar && (
{hasLeftSidebar && (
<aside className="post-sidebar-wrapper post-sidebar-left">
<PageSidebar headings={headings} activeId={location.hash.slice(1)} />
</aside>
)}
<article className={`post-article ${hasSidebar ? "post-article-with-sidebar" : ""}`}>
<article className={`post-article ${hasAnySidebar ? "post-article-with-sidebar" : ""}`}>
<header className="post-header">
<h1 className="post-title">{post.title}</h1>
<div className="post-meta-header">
@@ -409,6 +432,20 @@ export default function Post() {
)}
</footer>
</article>
{/* Right sidebar - CopyPageDropdown */}
{hasRightSidebar && (
<RightSidebar
title={post.title}
content={post.content}
url={window.location.href}
slug={post.slug}
description={post.description}
date={post.date}
tags={post.tags}
readTime={post.readTime}
/>
)}
</div>
</div>
);

View File

@@ -39,6 +39,7 @@ const POST_FIELDS = [
{ name: "authorName", required: false, example: '"Jane Doe"' },
{ name: "authorImage", required: false, example: '"/images/authors/jane.png"' },
{ name: "layout", required: false, example: '"sidebar"' },
{ name: "rightSidebar", required: false, example: "true" },
];
// Frontmatter field definitions for pages
@@ -55,6 +56,7 @@ const PAGE_FIELDS = [
{ name: "authorName", required: false, example: '"Jane Doe"' },
{ name: "authorImage", required: false, example: '"/images/authors/jane.png"' },
{ name: "layout", required: false, example: '"sidebar"' },
{ name: "rightSidebar", required: false, example: "true" },
];
// Generate frontmatter template based on content type

View File

@@ -871,7 +871,20 @@ body {
width: 100%;
}
/* Sidebar wrapper - docs-style with alt background and borders */
/* Three-column layout when right sidebar is enabled */
@media (min-width: 1135px) {
.post-content-with-sidebar:has(.post-sidebar-right) {
grid-template-columns: 240px 1fr 280px;
}
/* Adjust main content padding when right sidebar exists */
.post-content-with-sidebar:has(.post-sidebar-right)
.post-article-with-sidebar {
padding-right: 48px;
}
}
/* Left sidebar wrapper - docs-style with alt background and borders */
.post-sidebar-wrapper {
position: sticky;
top: 80px;
@@ -884,10 +897,11 @@ body {
background-color: var(--bg-sidebar);
margin-left: -24px;
padding-left: 24px;
padding-right: 24px;
padding-top: 24px;
padding-bottom: 24px;
margin-top: -24px;
border-radius: 8px;
border-radius: 6px;
/* Extend background to fill height */
min-height: calc(100vh - 80px);
/* Top border using CSS variable for theme consistency */
@@ -903,10 +917,50 @@ body {
}
/* Left sidebar - flush left with internal padding */
.post-sidebar-left {
/* All styles inherited from .post-sidebar-wrapper */
/* Right sidebar - complete styles based on left sidebar wrapper */
.post-sidebar-right {
position: sticky;
top: 80px;
align-self: flex-start;
max-height: calc(100vh - 100px);
overflow-y: auto;
/* Hide scrollbar while keeping scroll functionality */
-ms-overflow-style: none; /* IE */
scrollbar-width: none; /* Firefox */
background-color: var(--bg-sidebar);
margin-right: -24px;
padding-left: 24px;
padding-right: 24px;
padding-top: 24px;
padding-bottom: 24px;
margin-top: -24px;
border-radius: 6px;
/* Extend background to fill height */
min-height: calc(100vh - 80px);
/* Top border using CSS variable for theme consistency */
border-top: 1px solid var(--border-sidebar);
padding-right: 24px;
/* Left border using box-shadow for consistent 1px width regardless of scrollbar */
box-shadow: inset 1px 0 0 var(--border-sidebar);
}
/* Hide scrollbar for Chrome/Safari/Edge */
.post-sidebar-right::-webkit-scrollbar {
width: 0;
height: 0;
}
.right-sidebar-content {
position: sticky;
top: 0px;
}
/* Hide right sidebar below 1135px */
@media (max-width: 1134px) {
.post-sidebar-right {
display: none;
}
}
/* Content area - flexible width with left padding for gap from sidebar */
@@ -1040,11 +1094,16 @@ body {
gap: 24px;
}
/* Hide sidebar on mobile - it's now in the hamburger menu */
/* Hide sidebars on mobile - left sidebar is now in the hamburger menu */
.post-sidebar-wrapper {
display: none;
}
/* Ensure right sidebar is hidden on mobile */
.post-sidebar-right {
display: none;
}
/* Reset sidebar styles for mobile (when shown in hamburger) */
.post-sidebar-left {
position: static;