diff --git a/TASK.md b/TASK.md
index 182c798..1d97b95 100644
--- a/TASK.md
+++ b/TASK.md
@@ -7,10 +7,19 @@
## Current Status
-v1.39.0 ready. HTTP-based MCP (Model Context Protocol) server deployed on Netlify Edge Functions. Accessible 24/7 at /mcp endpoint with public access (50 req/min) and optional API key authentication (1000 req/min). Seven tools available: list_posts, get_post, list_pages, get_page, get_homepage, search_content, export_all. Blog post "How to Use the MCP Server" includes setup instructions for forks.
+v1.41.0 ready. Blog heading styles added to home intro content. Headings in `content/pages/home.md` now use same styling as blog posts (blog-h1 through blog-h6) with clickable anchor links. Home intro content matches blog post typography and spacing.
## Completed
+- [x] Blog heading styles for home intro content
+ - [x] Added generateSlug, getTextContent, HeadingAnchor helper functions to Home.tsx
+ - [x] Updated ReactMarkdown components to include h1-h6 with blog-h* classes
+ - [x] Added clickable anchor links (#) that appear on hover for each heading
+ - [x] Automatic ID generation from heading text for anchor navigation
+ - [x] Added blog styling for lists (blog-ul, blog-ol, blog-li), blockquotes (blog-blockquote), horizontal rules (blog-hr), and links (blog-link)
+ - [x] Updated files.md, changelog.md, changelog-page.md, TASK.md with feature documentation
+ - [x] Home intro headings now match blog post typography and spacing
+
- [x] HTTP-based MCP Server on Netlify
- [x] Created netlify/edge-functions/mcp.ts with JSON-RPC 2.0 implementation
- [x] Added @modelcontextprotocol/sdk dependency to package.json
diff --git a/changelog.md b/changelog.md
index acf75fb..6455a2e 100644
--- a/changelog.md
+++ b/changelog.md
@@ -4,6 +4,66 @@ 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.41.0] - 2025-12-28
+
+### Added
+
+- Blog heading styles for home intro content
+ - Headings (h1-h6) in `content/pages/home.md` now use same styling as blog posts
+ - Classes: `blog-h1`, `blog-h2`, `blog-h3`, `blog-h4`, `blog-h5`, `blog-h6`
+ - Clickable anchor links (#) appear on hover for each heading
+ - Automatic ID generation from heading text for anchor navigation
+- Additional blog styling for home intro
+ - Lists (`ul`, `ol`, `li`) use `blog-ul`, `blog-ol`, `blog-li` classes
+ - Blockquotes use `blog-blockquote` class
+ - Horizontal rules use `blog-hr` class
+ - Links use `blog-link` class
+
+### Changed
+
+- `src/pages/Home.tsx`: Added custom ReactMarkdown components for headings and other elements
+- Home intro headings now match blog post typography and spacing
+
+### Technical
+
+- Updated: `src/pages/Home.tsx` (added generateSlug, getTextContent, HeadingAnchor helper functions, custom ReactMarkdown components for h1-h6, ul, ol, li, blockquote, hr, and updated a component to use blog-link class)
+- Headings in home intro content now have IDs and anchor links matching blog post behavior
+
+## [1.40.0] - 2025-12-28
+
+### Added
+
+- Synced home intro content via markdown file
+ - New `content/pages/home.md` file for homepage intro/bio text
+ - Home intro content now syncs with `npm run sync` like other pages
+ - No redeploy needed for homepage text changes
+ - Full markdown support with links: `[text](url)`
+ - External links automatically open in new tab
+ - Fallback to `siteConfig.bio` if page not found
+- New `textAlign` frontmatter field for pages
+ - Control text alignment: "left", "center", "right"
+ - Default: "left" (previously was always centered)
+ - Used by `home.md` to control home intro alignment
+- New `featuredTitle` config option in siteConfig.ts
+ - Customize the featured section title (e.g., "Get started:", "Featured", "Popular")
+ - Previously hardcoded as "Get started:" in Home.tsx
+
+### Changed
+
+- `src/pages/Home.tsx`: Now fetches home intro from Convex instead of hardcoded JSX
+- Combined `home-intro` and `home-bio` into single markdown-powered section
+- Home intro content defaults to left alignment (can be set to center/right via frontmatter)
+
+### Technical
+
+- New file: `content/pages/home.md` (slug: `home-intro`, `showInNav: false`, `textAlign: left`)
+- Updated: `src/pages/Home.tsx` (added ReactMarkdown, useQuery for home-intro, textAlign support, featuredTitle from siteConfig)
+- Updated: `src/styles/global.css` (added `.home-intro-content` styles)
+- Updated: `convex/schema.ts` (added `textAlign` field to pages table)
+- Updated: `convex/pages.ts` (added `textAlign` to getPageBySlug and syncPagesPublic)
+- Updated: `scripts/sync-posts.ts` (added `textAlign` to PageFrontmatter and ParsedPage)
+- Updated: `src/config/siteConfig.ts` (added `featuredTitle` to SiteConfig interface and config object)
+
## [1.39.0] - 2025-12-28
### Added
diff --git a/content/blog/markdown-with-code-examples.md b/content/blog/markdown-with-code-examples.md
index 722a033..3f66f77 100644
--- a/content/blog/markdown-with-code-examples.md
+++ b/content/blog/markdown-with-code-examples.md
@@ -9,6 +9,7 @@ readTime: "5 min read"
authorName: "Markdown"
authorImage: "/images/authors/markdown.png"
featured: false
+layout: "sidebar"
featuredOrder: 5
image: "/images/markdown.png"
---
@@ -122,6 +123,22 @@ Reference files with inline code: `convex/schema.ts`, `src/pages/Home.tsx`.
## Tables
+### Basic table
+
+Copy this markdown to create a table:
+
+```markdown
+| Command | Description |
+| ------------------- | ------------------------------ |
+| `npm run dev` | Start development server |
+| `npm run build` | Build for production |
+| `npm run sync` | Sync markdown to Convex (dev) |
+| `npm run sync:prod` | Sync markdown to Convex (prod) |
+| `npx convex dev` | Start Convex dev server |
+```
+
+Result:
+
| Command | Description |
| ------------------- | ------------------------------ |
| `npm run dev` | Start development server |
@@ -132,20 +149,75 @@ Reference files with inline code: `convex/schema.ts`, `src/pages/Home.tsx`.
## Lists
-### Unordered
+### Unordered list
+
+Copy this markdown to create an unordered list:
+
+```markdown
+- Write posts in markdown
+- Store in Convex database
+- Deploy to Netlify
+- Updates sync in real-time
+```
+
+Result:
- Write posts in markdown
- Store in Convex database
- Deploy to Netlify
- Updates sync in real-time
-### Ordered
+### Ordered list
+
+Copy this markdown to create an ordered list:
+
+```markdown
+1. Fork the repository
+2. Set up Convex backend
+3. Configure Netlify
+4. Start writing
+```
+
+Result:
1. Fork the repository
2. Set up Convex backend
3. Configure Netlify
4. Start writing
+### List without bullets or numbers
+
+Use HTML with inline styles to create a list without visible bullets or numbers:
+
+```html
+
+
Write posts in markdown
+
Store in Convex database
+
Deploy to Netlify
+
Updates sync in real-time
+
+```
+
+Result:
+
+
+
Write posts in markdown
+
Store in Convex database
+
Deploy to Netlify
+
Updates sync in real-time
+
+
+**Alternative:** If you just need simple text items without list semantics, use plain paragraphs with line breaks:
+
+```markdown
+Write posts in markdown
+Store in Convex database
+Deploy to Netlify
+Updates sync in real-time
+```
+
+(Each line ends with two spaces for a line break)
+
## Blockquotes
> Markdown files in your repo are simpler than a CMS. Commit changes, review diffs, roll back anytime. AI agents can create posts programmatically. No admin panel needed.
@@ -298,6 +370,19 @@ Result:
2. Second step
- Another sub-point
+## HTML comments
+
+Use HTML comments to add notes that won't appear in the rendered output:
+
+```html
+
+Your visible content here.
+```
+
+Result: Only "Your visible content here." is displayed.
+
+**Note:** HTML comments are automatically stripped from the rendered output. Special comments like `` and `` are preserved for embedding components.
+
## Escaping characters
Use backslash to display literal markdown characters:
@@ -390,15 +475,10 @@ Use HTML `` and `` tags to create expandable/collapsible conte
```html
-Click to expand
-
-Hidden content goes here. You can include:
-
-- Lists
-- **Bold** and _italic_ text
-- Code blocks
-- Any markdown content
+ Click to expand
+ Hidden content goes here. You can include: - Lists - **Bold** and _italic_
+ text - Code blocks - Any markdown content
```
@@ -420,10 +500,9 @@ Add the `open` attribute to start expanded:
```html
-Already expanded
-
-This section starts open. Users can click to collapse it.
+ Already expanded
+ This section starts open. Users can click to collapse it.
```
@@ -436,18 +515,14 @@ This section starts open. Users can click to collapse it.
### Toggle with code
-```html
+````html
-View the code example
+ View the code example
-```typescript
-export const getPosts = query({
- args: {},
- handler: async (ctx) => {
- return await ctx.db.query("posts").collect();
- },
-});
-```
+ ```typescript export const getPosts = query({ args: {}, handler: async (ctx)
+ => { return await ctx.db.query("posts").collect(); }, });
+
+````
```
@@ -472,17 +547,15 @@ You can nest collapsible sections:
```html
-Outer section
+ Outer section
-Some content here.
+ Some content here.
-
-Inner section
-
-Nested content inside.
-
-
+
+ Inner section
+ Nested content inside.
+
```
diff --git a/content/pages/changelog-page.md b/content/pages/changelog-page.md
index 467605f..bcb4261 100644
--- a/content/pages/changelog-page.md
+++ b/content/pages/changelog-page.md
@@ -9,6 +9,21 @@ layout: "sidebar"
All notable changes to this project.

+## v1.41.0
+
+Released December 28, 2025
+
+**Blog heading styles for home intro content**
+
+- Headings (h1-h6) in `content/pages/home.md` now use same styling as blog posts
+- Classes: `blog-h1`, `blog-h2`, `blog-h3`, `blog-h4`, `blog-h5`, `blog-h6`
+- Clickable anchor links (#) appear on hover for each heading
+- Automatic ID generation from heading text for anchor navigation
+- Additional blog styling for lists, blockquotes, horizontal rules, and links
+- Home intro headings now match blog post typography and spacing
+
+Updated files: `src/pages/Home.tsx`
+
## v1.39.0
Released December 28, 2025
diff --git a/content/pages/docs.md b/content/pages/docs.md
index 2e30f4c..4c3f08f 100644
--- a/content/pages/docs.md
+++ b/content/pages/docs.md
@@ -998,15 +998,15 @@ The site includes an HTTP-based Model Context Protocol (MCP) server for AI tool
**Available tools:**
-| Tool | Description |
-|------|-------------|
-| `list_posts` | Get all published blog posts with metadata |
-| `get_post` | Get a single post by slug with full content |
-| `list_pages` | Get all published pages |
-| `get_page` | Get a single page by slug with full content |
-| `get_homepage` | Get homepage data with featured and recent posts |
-| `search_content` | Full text search across posts and pages |
-| `export_all` | Batch export all content |
+| Tool | Description |
+| ---------------- | ------------------------------------------------ |
+| `list_posts` | Get all published blog posts with metadata |
+| `get_post` | Get a single post by slug with full content |
+| `list_pages` | Get all published pages |
+| `get_page` | Get a single page by slug with full content |
+| `get_homepage` | Get homepage data with featured and recent posts |
+| `search_content` | Full text search across posts and pages |
+| `export_all` | Batch export all content |
**Cursor configuration:**
diff --git a/content/pages/home.md b/content/pages/home.md
new file mode 100644
index 0000000..c176233
--- /dev/null
+++ b/content/pages/home.md
@@ -0,0 +1,30 @@
+---
+title: "Home Intro"
+slug: "home-intro"
+published: true
+showInNav: false
+order: -1
+textAlign: "left"
+---
+
+An open-source publishing framework built for AI agents and developers to ship websites, docs, or blogs.
+
+Write markdown, sync from the terminal. [Fork it](https://github.com/waynesutton/markdown-site), customize it, ship it.
+
+
+
+## Features
+
+**AI agent integration** — API endpoints, raw markdown files, and MCP server included.
+
+**File-based publishing** — Write markdown locally, run `npm run sync`, content syncs everywhere.
+
+**URL content import** — Import urls to scrape any webpage into markdown with Firecrawl.
+
+**Newsletter automation** — Built-in subscription forms and admin dashboard powered by AgentMail.
+
+**Multiple output formats** — JSON via API endpoints, raw .md files, and RSS feeds.
+
+**Real-time team sync** — Multiple developers run npm run sync from different machines.
diff --git a/convex/pages.ts b/convex/pages.ts
index d7d7faa..ceb1f38 100644
--- a/convex/pages.ts
+++ b/convex/pages.ts
@@ -132,6 +132,7 @@ export const getPageBySlug = query({
aiChat: v.optional(v.boolean()),
contactForm: v.optional(v.boolean()),
newsletter: v.optional(v.boolean()),
+ textAlign: v.optional(v.string()),
}),
v.null(),
),
@@ -168,6 +169,7 @@ export const getPageBySlug = query({
aiChat: page.aiChat,
contactForm: page.contactForm,
newsletter: page.newsletter,
+ textAlign: page.textAlign,
};
},
});
@@ -198,6 +200,7 @@ export const syncPagesPublic = mutation({
aiChat: v.optional(v.boolean()),
contactForm: v.optional(v.boolean()),
newsletter: v.optional(v.boolean()),
+ textAlign: v.optional(v.string()),
}),
),
},
@@ -245,6 +248,7 @@ export const syncPagesPublic = mutation({
aiChat: page.aiChat,
contactForm: page.contactForm,
newsletter: page.newsletter,
+ textAlign: page.textAlign,
lastSyncedAt: now,
});
updated++;
diff --git a/convex/schema.ts b/convex/schema.ts
index ae41880..57fc0e8 100644
--- a/convex/schema.ts
+++ b/convex/schema.ts
@@ -67,6 +67,7 @@ export default defineSchema({
aiChat: v.optional(v.boolean()), // Enable AI chat in right sidebar
contactForm: v.optional(v.boolean()), // Enable contact form on this page
newsletter: v.optional(v.boolean()), // Override newsletter signup display (true/false)
+ textAlign: v.optional(v.string()), // Text alignment: "left", "center", "right" (default: "left")
lastSyncedAt: v.number(),
})
.index("by_slug", ["slug"])
diff --git a/files.md b/files.md
index e0d99a0..866a1c0 100644
--- a/files.md
+++ b/files.md
@@ -39,7 +39,7 @@ A brief description of each file in the codebase.
| File | Description |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `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 |
+| `Home.tsx` | Landing page with featured content and optional post list. Fetches home intro content from `content/pages/home.md` (slug: `home-intro`) for synced markdown intro text. Supports configurable post limit (homePostsLimit) and optional "read more" link (homePostsReadMore) via siteConfig.postsDisplay. Falls back to siteConfig.bio if home-intro page not found. Home intro content uses blog heading styles (blog-h1 through blog-h6) with clickable anchor links, matching blog post typography. Includes helper functions (generateSlug, getTextContent, HeadingAnchor) for heading ID generation and anchor links. |
| `Blog.tsx` | Dedicated blog page with featured layout: hero post (first blogFeatured), featured row (remaining blogFeatured in 2 columns with excerpts), and regular posts (3 columns without excerpts). Supports list/card view toggle. Includes back button in navigation |
| `Post.tsx` | Individual blog post or page view with optional left sidebar (TOC) and right sidebar (CopyPageDropdown). Includes back button (hidden when used as homepage), tag links, related posts section in footer for blog posts, footer component with markdown support, and social footer. Supports 3-column layout at 1135px+. Can display image at top when showImageAtTop: true. Can be used as custom homepage via siteConfig.homepage (update SITE_URL/SITE_NAME when forking) |
| `Stats.tsx` | Real-time analytics dashboard with visitor stats and GitHub stars |
@@ -168,6 +168,9 @@ Markdown files with frontmatter for blog posts. Each file becomes a blog post.
Markdown files for static pages like About, Projects, Contact, Changelog.
+**Special pages:**
+- `home.md` (slug: `home-intro`): Homepage intro/bio content. Set `showInNav: false` to hide from navigation. Content syncs with `npm run sync` and displays on the homepage without redeploy.
+
| Field | Description |
| --------------- | ----------------------------------------------------------------------- |
| `title` | Page title |
@@ -189,6 +192,7 @@ Markdown files for static pages like About, Projects, Contact, Changelog.
| `aiChat` | Enable AI Agent chat in right sidebar (optional). Set `true` to enable (requires `rightSidebar: true` and `siteConfig.aiChat.enabledOnContent: true`). Set `false` to explicitly hide even if global config is enabled. |
| `newsletter` | Override newsletter signup display (optional, true/false) |
| `contactForm` | Enable contact form on this page (optional). Requires siteConfig.contactForm.enabled: true and AGENTMAIL_API_KEY/AGENTMAIL_INBOX environment variables. |
+| `textAlign` | Text alignment: "left", "center", "right" (optional, default: "left"). Used by home.md for home intro content alignment |
## Scripts (`scripts/`)
diff --git a/public/images/122.png b/public/images/122.png
index 74b8e31..dba156e 100644
Binary files a/public/images/122.png and b/public/images/122.png differ
diff --git a/public/images/markdown.png b/public/images/markdown.png
index c4a63ff..99a9eb4 100644
Binary files a/public/images/markdown.png and b/public/images/markdown.png differ
diff --git a/public/images/v16.png b/public/images/v16.png
index a8483ea..463b493 100644
Binary files a/public/images/v16.png and b/public/images/v16.png differ
diff --git a/public/images/v17.png b/public/images/v17.png
index 00217d4..996f523 100644
Binary files a/public/images/v17.png and b/public/images/v17.png differ
diff --git a/public/raw/about.md b/public/raw/about.md
index adb78de..b79beec 100644
--- a/public/raw/about.md
+++ b/public/raw/about.md
@@ -2,7 +2,7 @@
---
Type: page
-Date: 2025-12-28
+Date: 2025-12-29
---
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.
diff --git a/public/raw/changelog.md b/public/raw/changelog.md
index 38f7464..9800676 100644
--- a/public/raw/changelog.md
+++ b/public/raw/changelog.md
@@ -2,7 +2,7 @@
---
Type: page
-Date: 2025-12-28
+Date: 2025-12-29
---
All notable changes to this project.
diff --git a/public/raw/contact.md b/public/raw/contact.md
index b170d18..e408b0c 100644
--- a/public/raw/contact.md
+++ b/public/raw/contact.md
@@ -2,7 +2,7 @@
---
Type: page
-Date: 2025-12-28
+Date: 2025-12-29
---
You found the contact page. Nice
diff --git a/public/raw/docs.md b/public/raw/docs.md
index baab48d..ef8e96f 100644
--- a/public/raw/docs.md
+++ b/public/raw/docs.md
@@ -2,7 +2,7 @@
---
Type: page
-Date: 2025-12-28
+Date: 2025-12-29
---
## Getting Started
@@ -994,15 +994,15 @@ The site includes an HTTP-based Model Context Protocol (MCP) server for AI tool
**Available tools:**
-| Tool | Description |
-|------|-------------|
-| `list_posts` | Get all published blog posts with metadata |
-| `get_post` | Get a single post by slug with full content |
-| `list_pages` | Get all published pages |
-| `get_page` | Get a single page by slug with full content |
-| `get_homepage` | Get homepage data with featured and recent posts |
-| `search_content` | Full text search across posts and pages |
-| `export_all` | Batch export all content |
+| Tool | Description |
+| ---------------- | ------------------------------------------------ |
+| `list_posts` | Get all published blog posts with metadata |
+| `get_post` | Get a single post by slug with full content |
+| `list_pages` | Get all published pages |
+| `get_page` | Get a single page by slug with full content |
+| `get_homepage` | Get homepage data with featured and recent posts |
+| `search_content` | Full text search across posts and pages |
+| `export_all` | Batch export all content |
**Cursor configuration:**
diff --git a/public/raw/home-intro.md b/public/raw/home-intro.md
new file mode 100644
index 0000000..348f1c3
--- /dev/null
+++ b/public/raw/home-intro.md
@@ -0,0 +1,28 @@
+# Home Intro
+
+---
+Type: page
+Date: 2025-12-29
+---
+
+An open-source publishing framework built for AI agents and developers to ship websites, docs, or blogs.
+
+Write markdown, sync from the terminal. [Fork it](https://github.com/waynesutton/markdown-site), customize it, ship it.
+
+
+
+## Features
+
+**AI agent integration** — API endpoints, raw markdown files, and MCP server included.
+
+**File-based publishing** — Write markdown locally, run `npm run sync`, content syncs everywhere.
+
+**URL content import** — Import urls to scrape any webpage into markdown with Firecrawl.
+
+**Newsletter automation** — Built-in subscription forms and admin dashboard powered by AgentMail.
+
+**Multiple output formats** — JSON via API endpoints, raw .md files, and RSS feeds.
+
+**Real-time team sync** — Multiple developers run npm run sync from different machines.
\ No newline at end of file
diff --git a/public/raw/index.md b/public/raw/index.md
index 4852ae5..3121390 100644
--- a/public/raw/index.md
+++ b/public/raw/index.md
@@ -33,8 +33,9 @@ This is the homepage index of all published content.
- **[Using Images in Blog Posts](/raw/using-images-in-posts.md)** - Learn how to add header images, inline images, and Open Graph images to your markdown posts.
- Date: 2025-12-14 | Reading time: 4 min read | Tags: images, tutorial, markdown, open-graph
-## Pages (6)
+## Pages (7)
+- **[Home Intro](/raw/home-intro.md)**
- **[Docs](/raw/docs.md)**
- **[About](/raw/about.md)** - An open-source publishing framework built for AI agents and developers to ship websites, docs, or blogs..
- **[Projects](/raw/projects.md)**
@@ -44,6 +45,6 @@ This is the homepage index of all published content.
---
-**Total Content:** 14 posts, 6 pages
+**Total Content:** 14 posts, 7 pages
All content is available as raw markdown files at `/raw/{slug}.md`
diff --git a/public/raw/markdown-with-code-examples.md b/public/raw/markdown-with-code-examples.md
index 8f1eae7..74fff85 100644
--- a/public/raw/markdown-with-code-examples.md
+++ b/public/raw/markdown-with-code-examples.md
@@ -118,6 +118,22 @@ Reference files with inline code: `convex/schema.ts`, `src/pages/Home.tsx`.
## Tables
+### Basic table
+
+Copy this markdown to create a table:
+
+```markdown
+| Command | Description |
+| ------------------- | ------------------------------ |
+| `npm run dev` | Start development server |
+| `npm run build` | Build for production |
+| `npm run sync` | Sync markdown to Convex (dev) |
+| `npm run sync:prod` | Sync markdown to Convex (prod) |
+| `npx convex dev` | Start Convex dev server |
+```
+
+Result:
+
| Command | Description |
| ------------------- | ------------------------------ |
| `npm run dev` | Start development server |
@@ -128,20 +144,75 @@ Reference files with inline code: `convex/schema.ts`, `src/pages/Home.tsx`.
## Lists
-### Unordered
+### Unordered list
+
+Copy this markdown to create an unordered list:
+
+```markdown
+- Write posts in markdown
+- Store in Convex database
+- Deploy to Netlify
+- Updates sync in real-time
+```
+
+Result:
- Write posts in markdown
- Store in Convex database
- Deploy to Netlify
- Updates sync in real-time
-### Ordered
+### Ordered list
+
+Copy this markdown to create an ordered list:
+
+```markdown
+1. Fork the repository
+2. Set up Convex backend
+3. Configure Netlify
+4. Start writing
+```
+
+Result:
1. Fork the repository
2. Set up Convex backend
3. Configure Netlify
4. Start writing
+### List without bullets or numbers
+
+Use HTML with inline styles to create a list without visible bullets or numbers:
+
+```html
+
+
Write posts in markdown
+
Store in Convex database
+
Deploy to Netlify
+
Updates sync in real-time
+
+```
+
+Result:
+
+
+
Write posts in markdown
+
Store in Convex database
+
Deploy to Netlify
+
Updates sync in real-time
+
+
+**Alternative:** If you just need simple text items without list semantics, use plain paragraphs with line breaks:
+
+```markdown
+Write posts in markdown
+Store in Convex database
+Deploy to Netlify
+Updates sync in real-time
+```
+
+(Each line ends with two spaces for a line break)
+
## Blockquotes
> Markdown files in your repo are simpler than a CMS. Commit changes, review diffs, roll back anytime. AI agents can create posts programmatically. No admin panel needed.
@@ -294,6 +365,19 @@ Result:
2. Second step
- Another sub-point
+## HTML comments
+
+Use HTML comments to add notes that won't appear in the rendered output:
+
+```html
+
+Your visible content here.
+```
+
+Result: Only "Your visible content here." is displayed.
+
+**Note:** HTML comments are automatically stripped from the rendered output. Special comments like `` and `` are preserved for embedding components.
+
## Escaping characters
Use backslash to display literal markdown characters:
@@ -386,15 +470,10 @@ Use HTML `` and `` tags to create expandable/collapsible conte
```html
-Click to expand
-
-Hidden content goes here. You can include:
-
-- Lists
-- **Bold** and _italic_ text
-- Code blocks
-- Any markdown content
+ Click to expand
+ Hidden content goes here. You can include: - Lists - **Bold** and _italic_
+ text - Code blocks - Any markdown content
```
@@ -416,10 +495,9 @@ Add the `open` attribute to start expanded:
```html
-Already expanded
-
-This section starts open. Users can click to collapse it.
+ Already expanded
+ This section starts open. Users can click to collapse it.
```
@@ -432,18 +510,14 @@ This section starts open. Users can click to collapse it.
### Toggle with code
-```html
+````html
-View the code example
+ View the code example
-```typescript
-export const getPosts = query({
- args: {},
- handler: async (ctx) => {
- return await ctx.db.query("posts").collect();
- },
-});
-```
+ ```typescript export const getPosts = query({ args: {}, handler: async (ctx)
+ => { return await ctx.db.query("posts").collect(); }, });
+
+````
```
@@ -468,17 +542,15 @@ You can nest collapsible sections:
```html
-Outer section
+ Outer section
-Some content here.
+ Some content here.
-
-Inner section
-
-Nested content inside.
-
-
+
+ Inner section
+ Nested content inside.
+
```
diff --git a/public/raw/newsletter.md b/public/raw/newsletter.md
index 9031f13..9668cf2 100644
--- a/public/raw/newsletter.md
+++ b/public/raw/newsletter.md
@@ -2,7 +2,7 @@
---
Type: page
-Date: 2025-12-28
+Date: 2025-12-29
---
# Newsletter Demo Page
diff --git a/public/raw/projects.md b/public/raw/projects.md
index af87e96..06dad0f 100644
--- a/public/raw/projects.md
+++ b/public/raw/projects.md
@@ -2,7 +2,7 @@
---
Type: page
-Date: 2025-12-28
+Date: 2025-12-29
---
This markdown framework is open source and built to be extended. Here is what ships out of the box.
diff --git a/scripts/sync-posts.ts b/scripts/sync-posts.ts
index 3971036..b51757f 100644
--- a/scripts/sync-posts.ts
+++ b/scripts/sync-posts.ts
@@ -97,6 +97,7 @@ interface PageFrontmatter {
aiChat?: boolean; // Enable AI chat in right sidebar (requires rightSidebar: true)
contactForm?: boolean; // Enable contact form on this page
newsletter?: boolean; // Override newsletter signup display (true/false)
+ textAlign?: string; // Text alignment: "left", "center", "right" (default: "left")
}
interface ParsedPage {
@@ -121,6 +122,7 @@ interface ParsedPage {
aiChat?: boolean; // Enable AI chat in right sidebar (requires rightSidebar: true)
contactForm?: boolean; // Enable contact form on this page
newsletter?: boolean; // Override newsletter signup display (true/false)
+ textAlign?: string; // Text alignment: "left", "center", "right" (default: "left")
}
// Calculate reading time based on word count
@@ -229,6 +231,7 @@ function parsePageFile(filePath: string): ParsedPage | null {
aiChat: frontmatter.aiChat, // Enable AI chat in right sidebar
contactForm: frontmatter.contactForm, // Enable contact form on this page
newsletter: frontmatter.newsletter, // Override newsletter signup display
+ textAlign: frontmatter.textAlign, // Text alignment: "left", "center", "right"
};
} catch (error) {
console.error(`Error parsing page ${filePath}:`, error);
diff --git a/src/components/BlogPost.tsx b/src/components/BlogPost.tsx
index 70f0526..e0a2b39 100644
--- a/src/components/BlogPost.tsx
+++ b/src/components/BlogPost.tsx
@@ -11,13 +11,16 @@ import NewsletterSignup from "./NewsletterSignup";
import ContactForm from "./ContactForm";
import siteConfig from "../config/siteConfig";
-// Sanitize schema that allows collapsible sections (details/summary)
+// Sanitize schema that allows collapsible sections (details/summary) and inline styles for lists
const sanitizeSchema = {
...defaultSchema,
tagNames: [...(defaultSchema.tagNames || []), "details", "summary"],
attributes: {
...defaultSchema.attributes,
details: ["open"], // Allow the 'open' attribute for expanded by default
+ ul: ["style"], // Allow inline styles on ul for list-style control
+ ol: ["style"], // Allow inline styles on ol for list-style control
+ li: ["style"], // Allow inline styles on li elements
},
};
@@ -274,6 +277,31 @@ type ContentSegment =
| { type: "newsletter" }
| { type: "contactform" };
+// Strip HTML comments from content, preserving special placeholders
+// Removes but keeps and
+function stripHtmlComments(content: string): string {
+ // First, temporarily replace special placeholders with markers
+ const markers = {
+ newsletter: "___NEWSLETTER_PLACEHOLDER___",
+ contactform: "___CONTACTFORM_PLACEHOLDER___",
+ };
+
+ let processed = content;
+
+ // Replace special placeholders with markers
+ processed = processed.replace(//gi, markers.newsletter);
+ processed = processed.replace(//gi, markers.contactform);
+
+ // Remove all remaining HTML comments (including multi-line)
+ processed = processed.replace(//g, "");
+
+ // Restore special placeholders
+ processed = processed.replace(markers.newsletter, "");
+ processed = processed.replace(markers.contactform, "");
+
+ return processed;
+}
+
// Parse content for inline embed placeholders
// Supports: and
function parseContentForEmbeds(content: string): ContentSegment[] {
@@ -382,8 +410,11 @@ export default function BlogPost({ content, slug, pageType = "post" }: BlogPostP
}
};
+ // Strip HTML comments (except special placeholders) before processing
+ const cleanedContent = stripHtmlComments(content);
+
// Parse content for inline embeds
- const segments = parseContentForEmbeds(content);
+ const segments = parseContentForEmbeds(cleanedContent);
const hasInlineEmbeds = segments.some((s) => s.type !== "content");
// Helper to render a single markdown segment
@@ -792,7 +823,7 @@ export default function BlogPost({ content, slug, pageType = "post" }: BlogPostP
},
}}
>
- {content}
+ {cleanedContent}
);
diff --git a/src/config/siteConfig.ts b/src/config/siteConfig.ts
index ca6d962..469cdd3 100644
--- a/src/config/siteConfig.ts
+++ b/src/config/siteConfig.ts
@@ -226,6 +226,7 @@ export interface SiteConfig {
// Featured section configuration
featuredViewMode: "cards" | "list";
+ featuredTitle: string; // Featured section title (e.g., "Get started:", "Featured", "Popular")
showViewToggle: boolean;
// Logo gallery configuration
@@ -311,6 +312,8 @@ export const siteConfig: SiteConfig = {
// Featured section configuration
// viewMode: 'list' shows bullet list, 'cards' shows card grid with excerpts
featuredViewMode: "cards",
+ // Featured section title (e.g., "Get started:", "Featured", "Popular")
+ featuredTitle: "Get started:",
// Allow users to toggle between list and card views
showViewToggle: true,
diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx
index 96535e9..12cb094 100644
--- a/src/pages/Home.tsx
+++ b/src/pages/Home.tsx
@@ -1,7 +1,9 @@
-import { useState, useEffect } from "react";
+import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { useQuery } from "convex/react";
import { api } from "../../convex/_generated/api";
+import ReactMarkdown from "react-markdown";
+import remarkGfm from "remark-gfm";
import PostList from "../components/PostList";
import FeaturedCards from "../components/FeaturedCards";
import LogoMarquee from "../components/LogoMarquee";
@@ -14,6 +16,76 @@ import siteConfig from "../config/siteConfig";
// Local storage key for view mode preference
const VIEW_MODE_KEY = "featured-view-mode";
+// Strip HTML comments from content, preserving special placeholders
+// Removes but keeps and
+function stripHtmlComments(content: string): string {
+ // First, temporarily replace special placeholders with markers
+ const markers = {
+ newsletter: "___NEWSLETTER_PLACEHOLDER___",
+ contactform: "___CONTACTFORM_PLACEHOLDER___",
+ };
+
+ let processed = content;
+
+ // Replace special placeholders with markers
+ processed = processed.replace(//gi, markers.newsletter);
+ processed = processed.replace(//gi, markers.contactform);
+
+ // Remove all remaining HTML comments (including multi-line)
+ processed = processed.replace(//g, "");
+
+ // Restore special placeholders
+ processed = processed.replace(markers.newsletter, "");
+ processed = processed.replace(markers.contactform, "");
+
+ return processed;
+}
+
+// Generate slug from text for heading IDs
+function generateSlug(text: string): string {
+ return text
+ .toLowerCase()
+ .replace(/[^a-z0-9\s-]/g, "")
+ .replace(/\s+/g, "-")
+ .replace(/-+/g, "-")
+ .trim();
+}
+
+// Extract text content from React children
+function getTextContent(children: React.ReactNode): string {
+ if (typeof children === "string") return children;
+ if (Array.isArray(children)) {
+ return children.map(getTextContent).join("");
+ }
+ if (children && typeof children === "object" && "props" in children) {
+ return getTextContent((children as React.ReactElement).props.children);
+ }
+ return "";
+}
+
+// Anchor link component for headings
+function HeadingAnchor({ id }: { id: string }) {
+ const handleClick = (e: React.MouseEvent) => {
+ // Copy URL to clipboard, but allow default scroll behavior
+ const url = `${window.location.origin}${window.location.pathname}#${id}`;
+ navigator.clipboard.writeText(url).catch(() => {
+ // Silently fail if clipboard API is not available
+ });
+ };
+
+ return (
+
+ #
+
+ );
+}
+
export default function Home() {
// Fetch published posts from Convex (only if showing on home)
const posts = useQuery(
@@ -25,6 +97,9 @@ export default function Home() {
const featuredPosts = useQuery(api.posts.getFeaturedPosts);
const featuredPages = useQuery(api.pages.getFeaturedPages);
+ // Fetch home intro content from Convex (synced via markdown)
+ const homeIntro = useQuery(api.pages.getPageBySlug, { slug: "home-intro" });
+
// State for view mode toggle (list or cards)
const [viewMode, setViewMode] = useState<"list" | "cards">(
siteConfig.featuredViewMode,
@@ -96,22 +171,115 @@ export default function Home() {
)}
{siteConfig.name}
- {/* Intro with JSX support for links */}
-
- An open-source publishing framework built for AI agents and developers
- to ship websites, docs, or blogs.
- Write markdown, sync from the terminal.{" "}
-
- Fork it
-
- , customize it, ship it.
-
-
-
{siteConfig.bio}
+ (
+
+ {children}
+
+ ),
+ // Headings with blog styling
+ h1({ children }) {
+ const id = generateSlug(getTextContent(children));
+ return (
+
+
+ {children}
+
+ );
+ },
+ h2({ children }) {
+ const id = generateSlug(getTextContent(children));
+ return (
+
+
+ {children}
+
+ );
+ },
+ h3({ children }) {
+ const id = generateSlug(getTextContent(children));
+ return (
+
+
+ {children}
+
+ );
+ },
+ h4({ children }) {
+ const id = generateSlug(getTextContent(children));
+ return (
+
+
+ {children}
+
+ );
+ },
+ h5({ children }) {
+ const id = generateSlug(getTextContent(children));
+ return (
+
+
+ {children}
+
+ );
+ },
+ h6({ children }) {
+ const id = generateSlug(getTextContent(children));
+ return (
+
+
+ {children}
+
+ );
+ },
+ // Lists with blog styling
+ ul({ children }) {
+ return
{children}
;
+ },
+ ol({ children }) {
+ return {children};
+ },
+ li({ children }) {
+ return
{children}
;
+ },
+ // Blockquote with blog styling
+ blockquote({ children }) {
+ return
{children}
;
+ },
+ // Horizontal rule with blog styling
+ hr() {
+ return ;
+ },
+ }}
+ >
+ {stripHtmlComments(homeIntro.content)}
+
+
+ ) : (
+ // Fallback to siteConfig.bio while loading or if page doesn't exist
+