mirror of
https://github.com/waynesutton/markdown-site.git
synced 2026-01-12 04:09:14 +00:00
feat: Add semantic search with vector embeddings
Add vector-based semantic search to complement keyword search. Users can toggle between "Keyword" and "Semantic" modes in the search modal (Cmd+K, then Tab to switch). Semantic search: - Uses OpenAI text-embedding-ada-002 (1536 dimensions) - Finds content by meaning, not exact words - Shows similarity scores as percentages - ~300ms latency, ~$0.0001/query - Graceful fallback if OPENAI_API_KEY not set New files: - convex/embeddings.ts - Embedding generation actions - convex/embeddingsQueries.ts - Queries/mutations for embeddings - convex/semanticSearch.ts - Vector search action - convex/semanticSearchQueries.ts - Result hydration queries - content/pages/docs-search.md - Keyword search docs - content/pages/docs-semantic-search.md - Semantic search docs Changes: - convex/schema.ts: Add embedding field and by_embedding vectorIndex - SearchModal.tsx: Add mode toggle (TextAa/Brain icons) - sync-posts.ts: Generate embeddings after content sync - global.css: Search mode toggle styles Documentation updated: - changelog.md, TASK.md, files.md, about.md, home.md Configuration: npx convex env set OPENAI_API_KEY sk-your-key Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> Status: Ready to commit. All semantic search files are staged. The TypeScript warnings are pre-existing (unused variables) and don't affect the build.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# llms.txt - Information for AI assistants and LLMs
|
||||
# Learn more: https://llmstxt.org/
|
||||
# Last updated: 2026-01-04T17:25:36.682Z
|
||||
# Last updated: 2026-01-05T18:54:36.241Z
|
||||
|
||||
> Your content is instantly available to browsers, LLMs, and AI agents.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
---
|
||||
Type: page
|
||||
Date: 2026-01-04
|
||||
Date: 2026-01-06
|
||||
---
|
||||
|
||||
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.
|
||||
@@ -82,7 +82,9 @@ It's a hybrid: developer workflow for publishing + real-time delivery like a dyn
|
||||
|
||||
**Search and discovery:**
|
||||
|
||||
- Full text search with Command+K shortcut
|
||||
- Dual search modes: Keyword (exact match) and Semantic (meaning-based) with Cmd+K toggle
|
||||
- Semantic search uses OpenAI embeddings for finding conceptually similar content
|
||||
- Full text search with Command+K shortcut and result highlighting
|
||||
- Static raw markdown files at `/raw/{slug}.md`
|
||||
- RSS feeds (`/rss.xml` and `/rss-full.xml`) and sitemap for SEO
|
||||
- API endpoints for AI/LLM access (`/api/posts`, `/api/export`)
|
||||
|
||||
@@ -2,11 +2,141 @@
|
||||
|
||||
---
|
||||
Type: page
|
||||
Date: 2026-01-04
|
||||
Date: 2026-01-06
|
||||
---
|
||||
|
||||
All notable changes to this project.
|
||||
|
||||
## v2.10.0
|
||||
|
||||
Released January 5, 2026
|
||||
|
||||
**Semantic search with vector embeddings**
|
||||
|
||||
Search now supports two modes accessible via Cmd+K:
|
||||
|
||||
- **Keyword search** (existing) - Matches exact words using Convex full-text search. Instant, free, supports highlighting.
|
||||
- **Semantic search** (new) - Finds content by meaning using OpenAI embeddings. Toggle to "Semantic" mode in search modal.
|
||||
|
||||
**How semantic search works:**
|
||||
|
||||
1. Your query is converted to a 1536-dimension vector using OpenAI text-embedding-ada-002
|
||||
2. Convex compares this vector to stored embeddings for all posts and pages
|
||||
3. Results ranked by similarity score (displayed as percentage)
|
||||
4. Top 15 results returned
|
||||
|
||||
**When to use each mode:**
|
||||
|
||||
| Use Case | Mode |
|
||||
|----------|------|
|
||||
| Specific code, commands, exact phrases | Keyword |
|
||||
| Conceptual questions ("how do I deploy?") | Semantic |
|
||||
| Need to highlight matches on page | Keyword |
|
||||
| Not sure of exact terminology | Semantic |
|
||||
|
||||
**Configuration:**
|
||||
|
||||
Semantic search requires an OpenAI API key:
|
||||
|
||||
```bash
|
||||
npx convex env set OPENAI_API_KEY sk-your-key-here
|
||||
npm run sync # Generates embeddings for all content
|
||||
```
|
||||
|
||||
If OPENAI_API_KEY is not configured, semantic search returns empty results and keyword search continues to work normally.
|
||||
|
||||
**Technical details:**
|
||||
|
||||
- New files: `convex/embeddings.ts`, `convex/embeddingsQueries.ts`, `convex/semanticSearch.ts`, `convex/semanticSearchQueries.ts`
|
||||
- Added `embedding` field and `by_embedding` vector index to posts and pages tables
|
||||
- SearchModal.tsx updated with Keyword/Semantic toggle (TextAa and Brain icons)
|
||||
- Embeddings generated automatically during `npm run sync`
|
||||
- Cost: ~$0.0001 per search query (embedding generation)
|
||||
|
||||
Updated files: `convex/schema.ts`, `convex/embeddings.ts`, `convex/embeddingsQueries.ts`, `convex/semanticSearch.ts`, `convex/semanticSearchQueries.ts`, `src/components/SearchModal.tsx`, `scripts/sync-posts.ts`, `src/styles/global.css`, `content/pages/docs-search.md`, `content/pages/docs-semantic-search.md`
|
||||
|
||||
## v2.9.0
|
||||
|
||||
Released January 4, 2026
|
||||
|
||||
**Dashboard Cloud CMS Features**
|
||||
|
||||
The Dashboard now functions as a WordPress-style cloud CMS, allowing content creation and editing directly in the database without requiring the markdown file sync workflow.
|
||||
|
||||
**Dual Source Architecture:**
|
||||
|
||||
- Dashboard-created content marked with `source: "dashboard"`
|
||||
- Markdown-synced content marked with `source: "sync"`
|
||||
- Both coexist independently in the database
|
||||
- Sync operations only affect synced content (dashboard content protected)
|
||||
- Source badges in Posts and Pages list views (blue "Dashboard", gray "Synced")
|
||||
|
||||
**Direct Database Operations:**
|
||||
|
||||
- "Save to DB" button in Write Post/Page sections saves directly to database
|
||||
- "Save Changes" button in Post/Page editor updates content immediately
|
||||
- Delete button for dashboard-created content (synced content protected)
|
||||
- Changes appear instantly without requiring sync
|
||||
|
||||
**Delete Confirmation Modal:**
|
||||
|
||||
- Warning modal displayed before deleting posts or pages
|
||||
- Shows item name and type being deleted
|
||||
- Themed to match dashboard UI with danger button styling
|
||||
- Backdrop click and Escape key to cancel
|
||||
|
||||
**Rich Text Editor:**
|
||||
|
||||
- Three editing modes: Markdown (default), Rich Text (Quill WYSIWYG), Preview
|
||||
- Quill-based editor with formatting toolbar
|
||||
- Toolbar: headers (H1-H3), bold, italic, strikethrough, blockquote, code, lists, links
|
||||
- Automatic HTML-to-Markdown conversion when switching modes
|
||||
- Theme-aware styling using CSS variables
|
||||
|
||||
**Server-Side URL Import:**
|
||||
|
||||
- Direct database import via Firecrawl (no file sync needed)
|
||||
- Enter URL in Import section, content is scraped and saved to database
|
||||
- Optional "Publish immediately" checkbox
|
||||
- Imported posts tagged with `imported` by default
|
||||
- Requires `FIRECRAWL_API_KEY` in Convex environment variables
|
||||
|
||||
**Export to Markdown:**
|
||||
|
||||
- Export any post/page to `.md` file with complete frontmatter
|
||||
- Bulk export script: `npm run export:db` (dev) or `npm run export:db:prod` (prod)
|
||||
- Use for backup or converting dashboard content to file-based workflow
|
||||
|
||||
**Technical details:**
|
||||
|
||||
- New file: `convex/cms.ts` with CRUD mutations
|
||||
- New file: `convex/importAction.ts` with Firecrawl server-side action
|
||||
- New file: `scripts/export-db-posts.ts` for bulk markdown export
|
||||
- Added `source` field and `by_source` index to posts and pages tables
|
||||
- Added ConfirmDeleteModal component to Dashboard.tsx
|
||||
- Fixed list row grid layout for proper source badge display
|
||||
|
||||
Updated files: `convex/schema.ts`, `convex/posts.ts`, `convex/pages.ts`, `convex/cms.ts`, `convex/importAction.ts`, `scripts/export-db-posts.ts`, `src/pages/Dashboard.tsx`, `src/styles/global.css`, `package.json`, `content/pages/docs-dashboard.md`
|
||||
|
||||
## v2.8.7
|
||||
|
||||
Released January 4, 2026
|
||||
|
||||
**Write page frontmatter sidebar toggle fix**
|
||||
|
||||
- Frontmatter sidebar toggle now works outside focus mode
|
||||
- Grid layout adjusts properly when frontmatter sidebar is collapsed
|
||||
- Previously only worked in focus mode due to missing CSS rules
|
||||
- Both sidebars can now be collapsed independently or together
|
||||
|
||||
**Technical details:**
|
||||
|
||||
- Added `.write-layout.frontmatter-collapsed` CSS rule (grid-template-columns: 220px 1fr 56px)
|
||||
- Added `.write-layout.sidebar-collapsed.frontmatter-collapsed` CSS rule for both sidebars collapsed
|
||||
- Added responsive tablet styles for frontmatter collapsed state
|
||||
|
||||
Updated files: `src/styles/global.css`
|
||||
|
||||
## v2.8.6
|
||||
|
||||
Released January 4, 2026
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
---
|
||||
Type: page
|
||||
Date: 2026-01-04
|
||||
Date: 2026-01-06
|
||||
---
|
||||
|
||||
You found the contact page. Nice
|
||||
|
||||
436
public/raw/docs-configuration.md
Normal file
436
public/raw/docs-configuration.md
Normal file
@@ -0,0 +1,436 @@
|
||||
# Configuration
|
||||
|
||||
---
|
||||
Type: page
|
||||
Date: 2026-01-06
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Fork configuration
|
||||
|
||||
After forking, you have two options to configure your site:
|
||||
|
||||
**Option 1: Automated (Recommended)**
|
||||
|
||||
```bash
|
||||
cp fork-config.json.example fork-config.json
|
||||
# Edit fork-config.json with your site information
|
||||
npm run configure
|
||||
```
|
||||
|
||||
This updates all 11 configuration files in one command. See `FORK_CONFIG.md` for the full JSON schema and options.
|
||||
|
||||
**Option 2: Manual**
|
||||
|
||||
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, 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/config/siteConfig.ts` | Default theme (`defaultTheme` field) |
|
||||
|
||||
### Site title and description metadata
|
||||
|
||||
These files contain the main site description text. Update them with your own tagline:
|
||||
|
||||
| File | What to change |
|
||||
| --------------------------------- | -------------------------------------------------------------- |
|
||||
| `index.html` | meta description, og:description, twitter:description, JSON-LD |
|
||||
| `README.md` | Main description at top of file |
|
||||
| `src/config/siteConfig.ts` | name, title, and bio fields |
|
||||
| `src/pages/Home.tsx` | Intro paragraph (hardcoded JSX with links) |
|
||||
| `convex/http.ts` | SITE_NAME constant and description strings (3 locations) |
|
||||
| `convex/rss.ts` | SITE_TITLE and SITE_DESCRIPTION constants |
|
||||
| `public/llms.txt` | Header quote, Name, and Description fields |
|
||||
| `public/openapi.yaml` | API title and example site name |
|
||||
| `AGENTS.md` | Project overview section |
|
||||
| `content/blog/about-this-blog.md` | Title, description, excerpt, and opening paragraph |
|
||||
| `content/pages/about.md` | excerpt field and opening paragraph |
|
||||
| `content/pages/docs.md` | Opening description paragraph |
|
||||
|
||||
**Backend constants** (`convex/http.ts` and `convex/rss.ts`):
|
||||
|
||||
```typescript
|
||||
// convex/http.ts
|
||||
const SITE_URL = "https://your-site.netlify.app";
|
||||
const SITE_NAME = "Your Site Name";
|
||||
|
||||
// convex/rss.ts
|
||||
const SITE_URL = "https://your-site.netlify.app";
|
||||
const SITE_TITLE = "Your Site Name";
|
||||
const SITE_DESCRIPTION = "Your site description for RSS feeds.";
|
||||
```
|
||||
|
||||
**Post page constants** (`src/pages/Post.tsx`):
|
||||
|
||||
```typescript
|
||||
const SITE_URL = "https://your-site.netlify.app";
|
||||
const SITE_NAME = "Your Site Name";
|
||||
const DEFAULT_OG_IMAGE = "/images/og-default.svg";
|
||||
```
|
||||
|
||||
These constants affect RSS feeds, API responses, sitemaps, and social sharing metadata.
|
||||
|
||||
### Site settings
|
||||
|
||||
Edit `src/config/siteConfig.ts`:
|
||||
|
||||
```typescript
|
||||
export default {
|
||||
name: "Site Name",
|
||||
title: "Tagline",
|
||||
logo: "/images/logo.svg", // null to hide homepage logo
|
||||
intro: "Introduction text...",
|
||||
bio: "Bio text...",
|
||||
|
||||
// Blog page configuration
|
||||
blogPage: {
|
||||
enabled: true, // Enable /blog route
|
||||
showInNav: true, // Show in navigation
|
||||
title: "Blog", // Nav link and page title
|
||||
order: 0, // Nav order (lower = first)
|
||||
},
|
||||
|
||||
// Hardcoded navigation items for React routes
|
||||
hardcodedNavItems: [
|
||||
{
|
||||
slug: "stats",
|
||||
title: "Stats",
|
||||
order: 10,
|
||||
showInNav: true, // Set to false to hide from nav
|
||||
},
|
||||
{
|
||||
slug: "write",
|
||||
title: "Write",
|
||||
order: 20,
|
||||
showInNav: true,
|
||||
},
|
||||
],
|
||||
|
||||
// Inner page logo configuration
|
||||
innerPageLogo: {
|
||||
enabled: true, // Set to false to hide logo on inner pages
|
||||
size: 28, // Logo height in pixels (keeps aspect ratio)
|
||||
},
|
||||
|
||||
// Featured section
|
||||
featuredViewMode: "list", // 'list' or 'cards'
|
||||
showViewToggle: true,
|
||||
|
||||
// Logo gallery (static grid or scrolling marquee)
|
||||
logoGallery: {
|
||||
enabled: true, // false to hide
|
||||
images: [{ src: "/images/logos/logo.svg", href: "https://example.com" }],
|
||||
position: "above-footer",
|
||||
speed: 30,
|
||||
title: "Built with",
|
||||
scrolling: false, // false = static grid, true = scrolling marquee
|
||||
maxItems: 4, // Number of logos when scrolling is false
|
||||
},
|
||||
|
||||
links: {
|
||||
docs: "/docs",
|
||||
convex: "https://convex.dev",
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
**Logo configuration:**
|
||||
|
||||
- `logo`: Homepage logo path (set to `null` to hide). Uses `public/images/logo.svg` by default.
|
||||
- `innerPageLogo`: Logo shown on blog page, posts, and static pages. Desktop: top left. Mobile: top right. Set `enabled: false` to hide on inner pages while keeping homepage logo.
|
||||
|
||||
**Navigation structure:**
|
||||
|
||||
Navigation combines three sources sorted by `order`:
|
||||
|
||||
1. Blog link (if `blogPage.enabled` and `blogPage.showInNav` are true)
|
||||
2. Hardcoded nav items (React routes from `hardcodedNavItems`)
|
||||
3. Markdown pages (from `content/pages/` with `showInNav: true`)
|
||||
|
||||
All items sort by `order` (lower first), then alphabetically by title.
|
||||
|
||||
### Featured items
|
||||
|
||||
Posts and pages appear in the featured section when marked with `featured: true` in frontmatter.
|
||||
|
||||
**Add to featured section:**
|
||||
|
||||
```yaml
|
||||
# In any post or page frontmatter
|
||||
featured: true
|
||||
featuredOrder: 1
|
||||
excerpt: "Short description for card view."
|
||||
image: "/images/thumbnail.png"
|
||||
```
|
||||
|
||||
Then run `npm run sync` or `npm run sync:all`. No redeploy needed.
|
||||
|
||||
| Field | Description |
|
||||
| --------------- | -------------------------------------------- |
|
||||
| `featured` | Set `true` to show in featured section |
|
||||
| `featuredOrder` | Order in featured section (lower = first) |
|
||||
| `excerpt` | Short text shown on card view |
|
||||
| `image` | Thumbnail for card view (displays as square) |
|
||||
|
||||
**Thumbnail images:** In card view, the `image` field displays as a square thumbnail above the title. Non-square images are automatically cropped to center. Square thumbnails: 400x400px minimum (800x800px for retina).
|
||||
|
||||
**Posts without images:** Cards display without the image area. The card shows just the title and excerpt with adjusted padding.
|
||||
|
||||
**Ordering:** Items with `featuredOrder` appear first (lower numbers first). Items without `featuredOrder` appear after, sorted by creation time.
|
||||
|
||||
**Display options (in siteConfig):**
|
||||
|
||||
```typescript
|
||||
// In src/pages/Home.tsx
|
||||
const siteConfig = {
|
||||
featuredViewMode: "list", // 'list' or 'cards'
|
||||
showViewToggle: true, // Let users switch views
|
||||
};
|
||||
```
|
||||
|
||||
### GitHub contributions graph
|
||||
|
||||
Display your GitHub contribution activity on the homepage. Configure in `siteConfig`:
|
||||
|
||||
```typescript
|
||||
gitHubContributions: {
|
||||
enabled: true, // Set to false to hide
|
||||
username: "yourusername", // Your GitHub username
|
||||
showYearNavigation: true, // Show arrows to navigate between years
|
||||
linkToProfile: true, // Click graph to open GitHub profile
|
||||
title: "GitHub Activity", // Optional title above the graph
|
||||
},
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
| -------------------- | -------------------------------------- |
|
||||
| `enabled` | `true` to show, `false` to hide |
|
||||
| `username` | Your GitHub username |
|
||||
| `showYearNavigation` | Show prev/next year navigation |
|
||||
| `linkToProfile` | Click graph to visit GitHub profile |
|
||||
| `title` | Text above graph (`undefined` to hide) |
|
||||
|
||||
Theme-aware colors match each site theme. Uses public API (no GitHub token required).
|
||||
|
||||
### Visitor map
|
||||
|
||||
Display real-time visitor locations on a world map on the stats page. Uses Netlify's built-in geo detection (no third-party API needed). Privacy friendly: only stores city, country, and coordinates. No IP addresses stored.
|
||||
|
||||
```typescript
|
||||
visitorMap: {
|
||||
enabled: true, // Set to false to hide
|
||||
title: "Live Visitors", // Optional title above the map
|
||||
},
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
| --------- | ------------------------------------ |
|
||||
| `enabled` | `true` to show, `false` to hide |
|
||||
| `title` | Text above map (`undefined` to hide) |
|
||||
|
||||
The map displays with theme-aware colors. Visitor dots pulse to indicate live sessions. Location data comes from Netlify's automatic geo headers at the edge.
|
||||
|
||||
### Logo gallery
|
||||
|
||||
The homepage includes a logo gallery that can scroll infinitely or display as a static grid. Each logo can link to a URL.
|
||||
|
||||
```typescript
|
||||
// In src/config/siteConfig.ts
|
||||
logoGallery: {
|
||||
enabled: true, // false to hide
|
||||
images: [
|
||||
{ src: "/images/logos/logo1.svg", href: "https://example.com" },
|
||||
{ src: "/images/logos/logo2.svg", href: "https://another.com" },
|
||||
],
|
||||
position: "above-footer", // or 'below-featured'
|
||||
speed: 30, // Seconds for one scroll cycle
|
||||
title: "Built with", // undefined to hide
|
||||
scrolling: false, // false = static grid, true = scrolling marquee
|
||||
maxItems: 4, // Number of logos when scrolling is false
|
||||
},
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
| ----------- | ---------------------------------------------------------- |
|
||||
| `enabled` | `true` to show, `false` to hide |
|
||||
| `images` | Array of `{ src, href }` objects |
|
||||
| `position` | `'above-footer'` or `'below-featured'` |
|
||||
| `speed` | Seconds for one scroll cycle (lower = faster) |
|
||||
| `title` | Text above gallery (`undefined` to hide) |
|
||||
| `scrolling` | `true` for infinite scroll, `false` for static grid |
|
||||
| `maxItems` | Max logos to show when `scrolling` is `false` (default: 4) |
|
||||
|
||||
**Display modes:**
|
||||
|
||||
- `scrolling: true`: Infinite horizontal scroll with all logos
|
||||
- `scrolling: false`: Static centered grid showing first `maxItems` logos
|
||||
|
||||
**To add logos:**
|
||||
|
||||
1. Add SVG/PNG files to `public/images/logos/`
|
||||
2. Update the `images` array with `src` paths and `href` URLs
|
||||
3. Push to GitHub (requires rebuild)
|
||||
|
||||
**To disable:** Set `enabled: false`
|
||||
|
||||
**To remove samples:** Delete files from `public/images/logos/` or clear the images array.
|
||||
|
||||
### Blog page
|
||||
|
||||
The site supports a dedicated blog page at `/blog` with two view modes: list view (year-grouped posts) and card view (thumbnail grid). Configure in `src/config/siteConfig.ts`:
|
||||
|
||||
```typescript
|
||||
blogPage: {
|
||||
enabled: true, // Enable /blog route
|
||||
showInNav: true, // Show in navigation
|
||||
title: "Blog", // Nav link and page title
|
||||
order: 0, // Nav order (lower = first)
|
||||
viewMode: "list", // Default view: "list" or "cards"
|
||||
showViewToggle: true, // Show toggle button to switch views
|
||||
},
|
||||
displayOnHomepage: true, // Show posts on homepage
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
| ------------------- | -------------------------------------- |
|
||||
| `enabled` | Enable the `/blog` route |
|
||||
| `showInNav` | Show Blog link in navigation |
|
||||
| `title` | Text for nav link and page heading |
|
||||
| `order` | Position in navigation (lower = first) |
|
||||
| `viewMode` | Default view: `"list"` or `"cards"` |
|
||||
| `showViewToggle` | Show toggle button to switch views |
|
||||
| `displayOnHomepage` | Show post list on homepage |
|
||||
|
||||
**View modes:**
|
||||
|
||||
- **List view:** Year-grouped posts with titles, read time, and dates
|
||||
- **Card view:** Grid of cards showing thumbnails, titles, excerpts, and metadata
|
||||
|
||||
**Card view details:**
|
||||
|
||||
Cards display post thumbnails (from `image` frontmatter field), titles, excerpts (or descriptions), read time, and dates. Posts without images show cards without thumbnail areas. Grid is responsive: 3 columns on desktop, 2 on tablet, 1 on mobile.
|
||||
|
||||
**Display options:**
|
||||
|
||||
- Homepage only: `displayOnHomepage: true`, `blogPage.enabled: false`
|
||||
- Blog page only: `displayOnHomepage: false`, `blogPage.enabled: true`
|
||||
- Both: `displayOnHomepage: true`, `blogPage.enabled: true`
|
||||
|
||||
**Navigation order:** The Blog link merges with page links and sorts by order. Pages use the `order` field in frontmatter. Set `blogPage.order: 5` to position Blog after pages with order 0-4.
|
||||
|
||||
**View preference:** User's view mode choice is saved to localStorage and persists across page visits.
|
||||
|
||||
### Scroll-to-top button
|
||||
|
||||
A scroll-to-top button appears after scrolling down. Configure in `src/components/Layout.tsx`:
|
||||
|
||||
```typescript
|
||||
const scrollToTopConfig: Partial<ScrollToTopConfig> = {
|
||||
enabled: true, // Set to false to disable
|
||||
threshold: 300, // Show after scrolling 300px
|
||||
smooth: true, // Smooth scroll animation
|
||||
};
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
| ----------- | ------------------------------------------ |
|
||||
| `enabled` | `true` to show, `false` to hide |
|
||||
| `threshold` | Pixels scrolled before button appears |
|
||||
| `smooth` | `true` for smooth scroll, `false` for jump |
|
||||
|
||||
Uses Phosphor ArrowUp icon and works with all themes.
|
||||
|
||||
### Theme
|
||||
|
||||
Default: `tan`. Options: `dark`, `light`, `tan`, `cloud`.
|
||||
|
||||
Configure in `src/config/siteConfig.ts`:
|
||||
|
||||
```typescript
|
||||
export const siteConfig: SiteConfig = {
|
||||
// ... other config
|
||||
defaultTheme: "tan",
|
||||
};
|
||||
```
|
||||
|
||||
### Font
|
||||
|
||||
Configure the font in `src/config/siteConfig.ts`:
|
||||
|
||||
```typescript
|
||||
export const siteConfig: SiteConfig = {
|
||||
// ... other config
|
||||
fontFamily: "serif", // Options: "serif", "sans", or "monospace"
|
||||
};
|
||||
```
|
||||
|
||||
Or edit `src/styles/global.css` directly:
|
||||
|
||||
```css
|
||||
body {
|
||||
/* Sans-serif */
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
|
||||
/* Serif (default) */
|
||||
font-family: "New York", ui-serif, Georgia, serif;
|
||||
|
||||
/* Monospace */
|
||||
font-family: "IBM Plex Mono", "Liberation Mono", ui-monospace, monospace;
|
||||
}
|
||||
```
|
||||
|
||||
Available options: `serif` (default), `sans`, or `monospace`.
|
||||
|
||||
### Font sizes
|
||||
|
||||
All font sizes use CSS variables in `:root`. Customize by editing:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--font-size-base: 16px;
|
||||
--font-size-sm: 13px;
|
||||
--font-size-lg: 17px;
|
||||
--font-size-blog-content: 17px;
|
||||
--font-size-post-title: 32px;
|
||||
}
|
||||
```
|
||||
|
||||
Mobile sizes defined in `@media (max-width: 768px)` block.
|
||||
|
||||
### Images
|
||||
|
||||
| Image | Location | Size |
|
||||
| ---------------- | ------------------------------ | -------- |
|
||||
| Favicon | `public/favicon.svg` | 512x512 |
|
||||
| Site logo | `public/images/logo.svg` | 512x512 |
|
||||
| Default OG image | `public/images/og-default.svg` | 1200x630 |
|
||||
| Post images | `public/images/` | Any |
|
||||
|
||||
**Images require git deploy.** Images are served as static files from your repository, not synced to Convex. After adding images to `public/images/`:
|
||||
|
||||
1. Commit the image files to git
|
||||
2. Push to GitHub
|
||||
3. Wait for Netlify to rebuild
|
||||
|
||||
The `npm run sync` command only syncs markdown text content. Images are deployed when Netlify builds your site. Use `npm run sync:discovery` to update discovery files (AGENTS.md, llms.txt) when site configuration changes.
|
||||
|
||||
**Adding images to posts:** You can add images using markdown syntax `` or HTML `<img>` tags. The site uses `rehypeRaw` and `rehypeSanitize` to safely render HTML in markdown content. See [Using Images in Blog Posts](/using-images-in-posts) for complete examples and best practices.
|
||||
|
||||
**Logo options:**
|
||||
|
||||
- **Homepage logo:** Configured via `logo` in `siteConfig.ts`. Set to `null` to hide.
|
||||
- **Inner page logo:** Configured via `innerPageLogo` in `siteConfig.ts`. Shows on blog page, posts, and static pages. Desktop: top left corner. Mobile: top right corner (smaller). Set `enabled: false` to hide on inner pages while keeping homepage logo.
|
||||
299
public/raw/docs-content.md
Normal file
299
public/raw/docs-content.md
Normal file
@@ -0,0 +1,299 @@
|
||||
# Content
|
||||
|
||||
---
|
||||
Type: page
|
||||
Date: 2026-01-06
|
||||
---
|
||||
|
||||
## Content
|
||||
|
||||
**Markdown examples:** For complete markdown syntax examples including code blocks, tables, lists, links, images, collapsible sections, and all formatting options, see [Writing Markdown with Code Examples](/markdown-with-code-examples). That post includes copy-paste examples for every markdown feature.
|
||||
|
||||
### Blog posts
|
||||
|
||||
Create files in `content/blog/` with frontmatter:
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: "Post Title"
|
||||
description: "SEO description"
|
||||
date: "2025-01-15"
|
||||
slug: "url-path"
|
||||
published: true
|
||||
tags: ["tag1", "tag2"]
|
||||
readTime: "5 min read"
|
||||
image: "/images/og-image.png"
|
||||
---
|
||||
|
||||
Content here...
|
||||
```
|
||||
|
||||
See the [Frontmatter](/docs-frontmatter) page for all available fields.
|
||||
|
||||
### Static pages
|
||||
|
||||
Create files in `content/pages/` with frontmatter:
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: "Page Title"
|
||||
slug: "url-path"
|
||||
published: true
|
||||
order: 1
|
||||
---
|
||||
|
||||
Content here...
|
||||
```
|
||||
|
||||
See the [Frontmatter](/docs-frontmatter) page for all available fields.
|
||||
|
||||
### Home intro content
|
||||
|
||||
The homepage intro text can be synced from markdown via `content/pages/home.md` (slug: `home-intro`). This allows you to update homepage text without redeploying.
|
||||
|
||||
**Create home intro:**
|
||||
|
||||
1. Create `content/pages/home.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: "Home Intro"
|
||||
slug: "home-intro"
|
||||
published: true
|
||||
showInNav: false
|
||||
order: -1
|
||||
textAlign: "left"
|
||||
---
|
||||
|
||||
Your homepage intro text here.
|
||||
|
||||
## Features
|
||||
|
||||
**Feature one** - Description here.
|
||||
|
||||
**Feature two** - Description here.
|
||||
```
|
||||
|
||||
2. Run `npm run sync` to sync to Convex
|
||||
|
||||
3. Content appears on homepage instantly (no rebuild needed)
|
||||
|
||||
**Blog heading styles:** Headings (h1-h6) in home intro content use the same styling as blog posts (`blog-h1` through `blog-h6` classes). Each heading gets an automatic ID and a clickable anchor link (#) that appears on hover. Lists, blockquotes, horizontal rules, and links also use blog styling classes for consistent typography.
|
||||
|
||||
**Fallback:** If `home-intro` page is not found, the homepage falls back to `siteConfig.bio` text.
|
||||
|
||||
### Footer content
|
||||
|
||||
The footer content can be synced from markdown via `content/pages/footer.md` (slug: `footer`). This allows you to update footer text without touching code.
|
||||
|
||||
**Create footer content:**
|
||||
|
||||
1. Create `content/pages/footer.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: "Footer"
|
||||
slug: "footer"
|
||||
published: true
|
||||
showInNav: false
|
||||
order: -1
|
||||
---
|
||||
|
||||
Built with [Convex](https://convex.dev) for real-time sync and deployed on [Netlify](https://netlify.com).
|
||||
|
||||
Created by [Your Name](https://x.com/yourhandle). Follow on [Twitter/X](https://x.com/yourhandle) and [GitHub](https://github.com/yourusername).
|
||||
```
|
||||
|
||||
2. Run `npm run sync` to sync to Convex
|
||||
|
||||
3. Footer content appears on homepage, blog page, and all posts/pages instantly (no rebuild needed)
|
||||
|
||||
**Markdown support:** Footer content supports full markdown including links, paragraphs, line breaks, and images. External links automatically open in new tabs.
|
||||
|
||||
**Fallback:** If `footer` page is not found, the footer falls back to `siteConfig.footer.defaultContent`.
|
||||
|
||||
**Priority order:** Per-post/page frontmatter `footer:` field (custom override) > synced footer.md content > siteConfig.footer.defaultContent.
|
||||
|
||||
**Relationship with siteConfig:** The `content/pages/footer.md` page takes priority over `siteConfig.footer.defaultContent` when present. Use the markdown page for dynamic content that changes frequently, or keep using siteConfig for static footer content.
|
||||
|
||||
### Sidebar layout
|
||||
|
||||
Posts and pages can use a docs-style layout with a table of contents sidebar. Add `layout: "sidebar"` to the frontmatter:
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: "Documentation"
|
||||
slug: "docs"
|
||||
published: true
|
||||
layout: "sidebar"
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
## Section One
|
||||
|
||||
### Subsection
|
||||
|
||||
## Section Two
|
||||
```
|
||||
|
||||
**Features:**
|
||||
|
||||
- Left sidebar displays table of contents extracted from H1, H2, H3 headings
|
||||
- Two-column layout: 220px sidebar + flexible content area
|
||||
- Sidebar only appears if headings exist in the content
|
||||
- Active heading highlighting as you scroll
|
||||
- Smooth scroll navigation when clicking TOC links
|
||||
- Mobile responsive: stacks to single column below 1024px
|
||||
- Works for both blog posts and static pages
|
||||
|
||||
The sidebar extracts headings automatically from your markdown content. No manual TOC needed.
|
||||
|
||||
### 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
|
||||
---
|
||||
title: "My Tutorial"
|
||||
description: "A detailed guide"
|
||||
date: "2025-01-20"
|
||||
slug: "my-tutorial"
|
||||
published: true
|
||||
tags: ["tutorial"]
|
||||
layout: "sidebar"
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
## Advanced Topics
|
||||
```
|
||||
|
||||
### How frontmatter works
|
||||
|
||||
Frontmatter is the YAML metadata at the top of each markdown file between `---` markers. Here is how it flows through the system:
|
||||
|
||||
**Content directories:**
|
||||
|
||||
- `content/blog/*.md` contains blog posts with frontmatter
|
||||
- `content/pages/*.md` contains static pages with frontmatter
|
||||
|
||||
**Processing flow:**
|
||||
|
||||
1. Markdown files in `content/blog/` and `content/pages/` contain YAML frontmatter
|
||||
2. `scripts/sync-posts.ts` uses `gray-matter` to parse frontmatter and validate required fields
|
||||
3. Parsed data is sent to Convex mutations (`api.posts.syncPostsPublic`, `api.pages.syncPagesPublic`)
|
||||
4. `convex/schema.ts` defines the database structure for storing the data
|
||||
|
||||
**Adding a new frontmatter field:**
|
||||
|
||||
To add a custom frontmatter field, update these files:
|
||||
|
||||
1. The interface in `scripts/sync-posts.ts` (`PostFrontmatter` or `PageFrontmatter`)
|
||||
2. The parsing logic in `parseMarkdownFile()` or `parsePageFile()` functions
|
||||
3. The schema in `convex/schema.ts`
|
||||
4. The sync mutation in `convex/posts.ts` or `convex/pages.ts`
|
||||
|
||||
### Syncing content
|
||||
|
||||
**Development:**
|
||||
|
||||
```bash
|
||||
npm run sync # Sync markdown content
|
||||
npm run sync:discovery # Update discovery files (AGENTS.md, llms.txt)
|
||||
npm run sync:all # Sync content + discovery files together
|
||||
```
|
||||
|
||||
**Production:**
|
||||
|
||||
```bash
|
||||
npm run sync:prod # Sync markdown content
|
||||
npm run sync:discovery:prod # Update discovery files
|
||||
npm run sync:all:prod # Sync content + discovery files together
|
||||
```
|
||||
|
||||
**Sync everything together:**
|
||||
|
||||
```bash
|
||||
npm run sync:all # Development: content + discovery
|
||||
npm run sync:all:prod # Production: content + discovery
|
||||
```
|
||||
|
||||
### When to sync vs deploy
|
||||
|
||||
| What you're changing | Command | Timing |
|
||||
| -------------------------------- | -------------------------- | ----------------------- |
|
||||
| Blog posts in `content/blog/` | `npm run sync` | Instant (no rebuild) |
|
||||
| Pages in `content/pages/` | `npm run sync` | Instant (no rebuild) |
|
||||
| Featured items (via frontmatter) | `npm run sync` | Instant (no rebuild) |
|
||||
| Site config changes | `npm run sync:discovery` | Updates discovery files |
|
||||
| Import external URL | `npm run import` then sync | Instant (no rebuild) |
|
||||
| Images in `public/images/` | Git commit + push | Requires rebuild |
|
||||
| `siteConfig` in `Home.tsx` | Redeploy | Requires rebuild |
|
||||
| Logo gallery config | Redeploy | Requires rebuild |
|
||||
| React components/styles | Redeploy | Requires rebuild |
|
||||
|
||||
**Markdown content** syncs instantly to Convex. **Images and source code** require pushing to GitHub for Netlify to rebuild.
|
||||
|
||||
## Tag pages and related posts
|
||||
|
||||
Tag pages are available at `/tags/[tag]` for each tag used in your posts. They display all posts with that tag in a list or card view with localStorage persistence for view mode preference.
|
||||
|
||||
**Related posts:** Individual blog posts show up to 3 related posts in the footer based on shared tags. Posts are sorted by relevance (number of shared tags) then by date. Only appears on blog posts (not static pages).
|
||||
|
||||
**Tag links:** Tags in post footers link to their respective tag archive pages.
|
||||
|
||||
## Blog page featured layout
|
||||
|
||||
Posts can be marked as featured on the blog page using the `blogFeatured` frontmatter field:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: "My Featured Post"
|
||||
blogFeatured: true
|
||||
---
|
||||
```
|
||||
|
||||
The first `blogFeatured` post displays as a hero card with landscape image, tags, date, title, excerpt, author info, and read more link. Remaining `blogFeatured` posts display in a 2-column featured row with excerpts. Regular (non-featured) posts display in a 3-column grid without excerpts.
|
||||
392
public/raw/docs-dashboard.md
Normal file
392
public/raw/docs-dashboard.md
Normal file
@@ -0,0 +1,392 @@
|
||||
# Dashboard
|
||||
|
||||
---
|
||||
Type: page
|
||||
Date: 2026-01-06
|
||||
---
|
||||
|
||||
## Dashboard
|
||||
|
||||
The Dashboard at `/dashboard` provides a centralized UI for managing content, configuring the site, and performing sync operations. It's designed for developers who fork the repository to set up and manage their markdown blog.
|
||||
|
||||
**Access:** Navigate to `/dashboard` in your browser. The dashboard is not linked in the navigation by default (similar to Newsletter Admin pattern).
|
||||
|
||||
**Authentication:** WorkOS authentication is optional. Configure it in `siteConfig.ts`:
|
||||
|
||||
```typescript
|
||||
dashboard: {
|
||||
enabled: true,
|
||||
requireAuth: false, // Set to true to require WorkOS authentication
|
||||
},
|
||||
```
|
||||
|
||||
When `requireAuth` is `false`, the dashboard is open access. When `requireAuth` is `true` and WorkOS is configured, users must log in to access the dashboard. See [How to setup WorkOS](https://www.markdown.fast/how-to-setup-workos) for authentication setup.
|
||||
|
||||
### Content management
|
||||
|
||||
**Posts and Pages List Views:**
|
||||
|
||||
- View all posts and pages (published and unpublished)
|
||||
- Filter by status: All, Published, Drafts
|
||||
- Search by title or content
|
||||
- Pagination with "First" and "Next" buttons
|
||||
- Items per page selector (15, 25, 50, 100) - default: 15
|
||||
- Edit, view, and publish/unpublish options
|
||||
- WordPress-style UI with date, edit, view, and publish controls
|
||||
- **Source Badge:** Shows "Dashboard" or "Synced" to indicate content origin
|
||||
- **Delete Button:** Delete dashboard-created posts/pages directly (synced content protected)
|
||||
|
||||
**Post and Page Editor:**
|
||||
|
||||
- Markdown editor with live preview
|
||||
- Frontmatter sidebar on the right with all available fields
|
||||
- Draggable/resizable frontmatter sidebar (200px-600px width)
|
||||
- Independent scrolling for frontmatter sidebar
|
||||
- Preview mode shows content as it appears on the live site
|
||||
- Download markdown button to generate `.md` files
|
||||
- Copy markdown to clipboard
|
||||
- All frontmatter fields editable in sidebar
|
||||
- Preview uses ReactMarkdown with proper styling
|
||||
- **Save to Database:** Green "Save" button saves changes directly to the database
|
||||
|
||||
**Write Post and Write Page:**
|
||||
|
||||
- Full-screen writing interface
|
||||
- **Three Editor Modes:**
|
||||
- **Markdown:** Raw markdown editing (default)
|
||||
- **Rich Text:** WYSIWYG Quill editor with toolbar
|
||||
- **Preview:** Rendered markdown preview
|
||||
- Word/line/character counts
|
||||
- Frontmatter reference panel
|
||||
- Download markdown button for new content
|
||||
- **Save to DB:** Save directly to database without file sync
|
||||
- Content persists in localStorage
|
||||
- Separate storage for post and page content
|
||||
|
||||
### Cloud CMS features
|
||||
|
||||
The dashboard functions as a cloud-based CMS similar to WordPress, allowing you to create and edit content directly in the database without requiring the markdown file sync workflow.
|
||||
|
||||
**Dual Source Architecture:**
|
||||
|
||||
- **Dashboard Content:** Posts/pages created via "Save to DB" are marked with `source: "dashboard"`
|
||||
- **Synced Content:** Posts/pages from markdown files are marked with `source: "sync"`
|
||||
- Both coexist independently in the database
|
||||
- Sync operations only affect synced content (dashboard content is protected)
|
||||
|
||||
**Direct Database Operations:**
|
||||
|
||||
- Create new posts/pages directly in the database
|
||||
- Edit any post/page and save changes immediately
|
||||
- Delete dashboard-created content
|
||||
- Changes appear instantly (no sync required)
|
||||
|
||||
**Export to Markdown:**
|
||||
|
||||
- Any post/page can be exported as a `.md` file
|
||||
- Includes all frontmatter fields
|
||||
- Use for backup or converting to file-based workflow
|
||||
|
||||
**Bulk Export Script:**
|
||||
|
||||
```bash
|
||||
npm run export:db # Export dashboard posts to content/blog/
|
||||
npm run export:db:prod # Export from production database
|
||||
```
|
||||
|
||||
Exports all dashboard-created posts and pages to markdown files in the content folders.
|
||||
|
||||
### Rich Text Editor
|
||||
|
||||
The Write Post and Write Page sections include a Quill-based rich text editor with three editing modes.
|
||||
|
||||
**Editing Modes:**
|
||||
|
||||
- **Markdown:** Raw markdown text editing (default mode)
|
||||
- **Rich Text:** WYSIWYG editor with formatting toolbar
|
||||
- **Preview:** Rendered preview of the content
|
||||
|
||||
**Rich Text Toolbar:**
|
||||
|
||||
- Headers (H1, H2, H3)
|
||||
- Bold, italic, strikethrough
|
||||
- Blockquote, code block
|
||||
- Ordered and bullet lists
|
||||
- Links
|
||||
- Clear formatting
|
||||
|
||||
**Mode Switching:**
|
||||
|
||||
- Content automatically converts between HTML and Markdown when switching modes
|
||||
- Frontmatter is preserved when editing in Rich Text mode
|
||||
- Preview mode shows how content will appear on the live site
|
||||
|
||||
**Theme Integration:**
|
||||
|
||||
- Editor styling matches the current theme (dark, light, tan, cloud)
|
||||
- Toolbar uses CSS variables for consistent appearance
|
||||
|
||||
### AI Agent
|
||||
|
||||
The Dashboard includes a dedicated AI Agent section with tab-based UI for Chat and Image Generation.
|
||||
|
||||
**Chat Tab:**
|
||||
|
||||
- Multi-model selector: Claude Sonnet 4, GPT-4o, Gemini 2.0 Flash
|
||||
- Per-session chat history stored in Convex
|
||||
- Markdown rendering for AI responses
|
||||
- Copy functionality for AI responses
|
||||
- Lazy API key validation (errors only shown when user tries to use a specific model)
|
||||
|
||||
**Image Tab:**
|
||||
|
||||
- AI image generation with two models:
|
||||
- Nano Banana (gemini-2.0-flash-exp-image-generation) - Experimental model
|
||||
- Nano Banana Pro (imagen-3.0-generate-002) - Production model
|
||||
- Aspect ratio selection: 1:1, 16:9, 9:16, 4:3, 3:4
|
||||
- Images stored in Convex storage with session tracking
|
||||
- Gallery view of recent generated images
|
||||
|
||||
**Environment Variables (Convex):**
|
||||
|
||||
| Variable | Description |
|
||||
| ------------------- | -------------------------------------------------- |
|
||||
| `ANTHROPIC_API_KEY` | Required for Claude Sonnet 4 |
|
||||
| `OPENAI_API_KEY` | Required for GPT-4o |
|
||||
| `GOOGLE_AI_API_KEY` | Required for Gemini 2.0 Flash and image generation |
|
||||
|
||||
**Note:** Only configure the API keys for models you want to use. If a key is not set, users see a helpful setup message when they try to use that model.
|
||||
|
||||
### Newsletter management
|
||||
|
||||
All Newsletter Admin features integrated into the Dashboard:
|
||||
|
||||
- **Subscribers:** View, search, filter, and delete subscribers
|
||||
- **Send Newsletter:** Select a blog post to send as newsletter
|
||||
- **Write Email:** Compose custom emails with markdown support
|
||||
- **Recent Sends:** View last 10 newsletter sends (posts and custom emails)
|
||||
- **Email Stats:** Dashboard with total emails, newsletters sent, active subscribers, retention rate
|
||||
|
||||
All newsletter sections are full-width in the dashboard content area.
|
||||
|
||||
### Content import
|
||||
|
||||
**Direct Database Import:**
|
||||
|
||||
The Import URL section uses server-side Firecrawl to import articles directly to the database.
|
||||
|
||||
- Enter any article URL to import
|
||||
- Firecrawl scrapes and converts content to markdown
|
||||
- Post is saved directly to the database (no file sync needed)
|
||||
- Optional "Publish immediately" checkbox
|
||||
- Imported posts tagged with `imported` by default
|
||||
- Source attribution added automatically
|
||||
- Success message with link to view the imported post
|
||||
|
||||
**Setup:**
|
||||
|
||||
Add `FIRECRAWL_API_KEY` to your Convex environment variables:
|
||||
|
||||
```bash
|
||||
npx convex env set FIRECRAWL_API_KEY your-api-key-here
|
||||
```
|
||||
|
||||
Get your API key from [firecrawl.dev](https://firecrawl.dev).
|
||||
|
||||
**CLI Import (Alternative):**
|
||||
|
||||
You can also import via command line:
|
||||
|
||||
```bash
|
||||
npm run import <url> # Import URL as local markdown file
|
||||
```
|
||||
|
||||
This creates a file in `content/blog/` that requires syncing.
|
||||
|
||||
### Site configuration
|
||||
|
||||
**Config Generator:**
|
||||
|
||||
- UI to configure all settings in `src/config/siteConfig.ts`
|
||||
- Generates downloadable `siteConfig.ts` file
|
||||
- Hybrid approach: dashboard generates config, file-based config continues to work
|
||||
- Includes all site configuration options:
|
||||
- Site name, title, logo, bio, intro
|
||||
- Blog page settings
|
||||
- Featured section configuration
|
||||
- Logo gallery settings
|
||||
- GitHub contributions
|
||||
- Footer and social footer
|
||||
- Newsletter settings
|
||||
- Contact form settings
|
||||
- Stats page settings
|
||||
- And more
|
||||
|
||||
**Index HTML Editor:**
|
||||
|
||||
- View and edit `index.html` content
|
||||
- Meta tags, Open Graph, Twitter Cards, JSON-LD
|
||||
- Download updated HTML file
|
||||
|
||||
### Analytics
|
||||
|
||||
- Real-time stats dashboard (clone of `/stats` page)
|
||||
- Active visitors with per-page breakdown
|
||||
- Total page views and unique visitors
|
||||
- Views by page sorted by popularity
|
||||
- Does not follow `siteConfig.statsPage` settings (always accessible in dashboard)
|
||||
|
||||
### Sync commands
|
||||
|
||||
**Sync Content Section:**
|
||||
|
||||
- UI with buttons for all sync operations
|
||||
- Development sync commands:
|
||||
- `npm run sync` - Sync markdown content
|
||||
- `npm run sync:discovery` - Update discovery files (AGENTS.md, llms.txt)
|
||||
- `npm run sync:all` - Sync content + discovery files together
|
||||
- Production sync commands:
|
||||
- `npm run sync:prod` - Sync markdown content
|
||||
- `npm run sync:discovery:prod` - Update discovery files
|
||||
- `npm run sync:all:prod` - Sync content + discovery files together
|
||||
- Server status indicator shows if sync server is online
|
||||
- Copy and Execute buttons for each command
|
||||
- Real-time terminal output when sync server is running
|
||||
- Command modal shows full command output when sync server is offline
|
||||
- Toast notifications for success/error feedback
|
||||
|
||||
**Sync Server:**
|
||||
|
||||
- Local HTTP server for executing commands from dashboard
|
||||
- Start with `npm run sync-server` (runs on localhost:3001)
|
||||
- Execute commands directly from dashboard with real-time output streaming
|
||||
- Optional token authentication via `SYNC_TOKEN` environment variable
|
||||
- Whitelisted commands only for security
|
||||
- Health check endpoint for server availability detection
|
||||
- Copy icons for `npm run sync-server` command in dashboard
|
||||
|
||||
**Header Sync Buttons:**
|
||||
|
||||
- Quick sync buttons in dashboard header (right side)
|
||||
- `npm run sync:all` (dev) button
|
||||
- `npm run sync:all:prod` (prod) button
|
||||
- One-click sync for all content and discovery files
|
||||
- Automatically use sync server when available, fallback to command modal
|
||||
|
||||
### Dashboard features
|
||||
|
||||
**Search:**
|
||||
|
||||
- Search bar in header
|
||||
- Search dashboard features, page titles, and post content
|
||||
- Real-time results as you type
|
||||
|
||||
**Theme and Font:**
|
||||
|
||||
- Theme toggle (dark, light, tan, cloud)
|
||||
- Font switcher (serif, sans, monospace)
|
||||
- Preferences persist across sessions
|
||||
|
||||
**Mobile Responsive:**
|
||||
|
||||
- Fully responsive design
|
||||
- Mobile-optimized layout
|
||||
- Touch-friendly controls
|
||||
- Collapsible sidebar on mobile
|
||||
|
||||
**Toast Notifications:**
|
||||
|
||||
- Success, error, info, and warning notifications
|
||||
- Auto-dismiss after 4 seconds
|
||||
- Theme-aware styling
|
||||
- No browser default alerts
|
||||
|
||||
**Command Modal:**
|
||||
|
||||
- Shows sync command output
|
||||
- Copy command to clipboard
|
||||
- Close button to dismiss
|
||||
- Theme-aware styling
|
||||
|
||||
### Technical details
|
||||
|
||||
**Database Architecture:**
|
||||
|
||||
- Uses Convex queries for real-time data
|
||||
- All mutations follow Convex best practices (idempotent, indexed queries)
|
||||
- `source` field tracks content origin ("dashboard" or "sync")
|
||||
- `by_source` index for efficient filtering by source
|
||||
|
||||
**CMS Mutations (convex/cms.ts):**
|
||||
|
||||
- `createPost` / `createPage` - Create with `source: "dashboard"`
|
||||
- `updatePost` / `updatePage` - Update any post/page
|
||||
- `deletePost` / `deletePage` - Delete any post/page
|
||||
- `exportPostAsMarkdown` / `exportPageAsMarkdown` - Generate markdown with frontmatter
|
||||
|
||||
**Import Action (convex/importAction.ts):**
|
||||
|
||||
- Server-side Convex action using Firecrawl
|
||||
- Scrapes URL, converts to markdown, saves to database
|
||||
- Handles slug conflicts by appending timestamp
|
||||
|
||||
**UI State:**
|
||||
|
||||
- Frontmatter sidebar width persisted in localStorage
|
||||
- Editor content persisted in localStorage
|
||||
- Independent scrolling for editor and sidebar sections
|
||||
- Preview uses ReactMarkdown with remark-gfm, remark-breaks, rehype-raw, rehype-sanitize
|
||||
- Rich text editor uses Quill with Turndown/Showdown for conversion
|
||||
|
||||
### Sync commands reference
|
||||
|
||||
**Development:**
|
||||
|
||||
- `npm run sync` - Sync markdown content to development Convex
|
||||
- `npm run sync:discovery` - Update discovery files (AGENTS.md, llms.txt) with development data
|
||||
- `npm run sync:all` - Run both content sync and discovery sync (development)
|
||||
|
||||
**Production:**
|
||||
|
||||
- `npm run sync:prod` - Sync markdown content to production Convex
|
||||
- `npm run sync:discovery:prod` - Update discovery files with production data
|
||||
- `npm run sync:all:prod` - Run both content sync and discovery sync (production)
|
||||
|
||||
**Sync Server:**
|
||||
|
||||
- `npm run sync-server` - Start local HTTP server for executing sync commands from dashboard UI
|
||||
|
||||
**Content Import:**
|
||||
|
||||
- `npm run import <url>` - Import external URL as markdown post (requires FIRECRAWL_API_KEY in .env.local)
|
||||
|
||||
**Database Export:**
|
||||
|
||||
- `npm run export:db` - Export dashboard posts/pages to content folders (development)
|
||||
- `npm run export:db:prod` - Export dashboard posts/pages (production)
|
||||
|
||||
**Note:** The dashboard provides a UI for these commands. When the sync server is running (`npm run sync-server`), you can execute commands directly from the dashboard with real-time output. Otherwise, the dashboard shows commands in a modal for copying to your terminal.
|
||||
|
||||
### Environment variables
|
||||
|
||||
**Convex Environment Variables:**
|
||||
|
||||
| Variable | Description |
|
||||
| ------------------- | -------------------------------------------------- |
|
||||
| `ANTHROPIC_API_KEY` | Required for Claude Sonnet 4 (AI Agent) |
|
||||
| `OPENAI_API_KEY` | Required for GPT-4o (AI Agent) |
|
||||
| `GOOGLE_AI_API_KEY` | Required for Gemini 2.0 Flash and image generation |
|
||||
| `FIRECRAWL_API_KEY` | Required for direct URL import |
|
||||
|
||||
Set Convex environment variables with:
|
||||
|
||||
```bash
|
||||
npx convex env set VARIABLE_NAME value
|
||||
```
|
||||
|
||||
**Local Environment Variables (.env.local):**
|
||||
|
||||
| Variable | Description |
|
||||
| ------------------- | ------------------------------------------ |
|
||||
| `VITE_CONVEX_URL` | Your Convex deployment URL (auto-created) |
|
||||
| `FIRECRAWL_API_KEY` | For CLI import command only |
|
||||
98
public/raw/docs-deployment.md
Normal file
98
public/raw/docs-deployment.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# Deployment
|
||||
|
||||
---
|
||||
Type: page
|
||||
Date: 2026-01-06
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
### Netlify setup
|
||||
|
||||
1. Connect GitHub repo to Netlify
|
||||
2. Build command: `npm ci --include=dev && npx convex deploy --cmd 'npm run build'`
|
||||
3. Publish directory: `dist`
|
||||
4. Add env variables:
|
||||
- `CONVEX_DEPLOY_KEY` (from Convex Dashboard > Project Settings > Deploy Key)
|
||||
- `VITE_CONVEX_URL` (your production Convex URL, e.g., `https://your-deployment.convex.cloud`)
|
||||
|
||||
Both are required: deploy key for builds, URL for edge function runtime.
|
||||
|
||||
### Convex production
|
||||
|
||||
```bash
|
||||
npx convex deploy
|
||||
```
|
||||
|
||||
### Edge functions
|
||||
|
||||
RSS, sitemap, and API routes are handled by Netlify Edge Functions in `netlify/edge-functions/`. They dynamically read `VITE_CONVEX_URL` from the environment. No manual URL configuration needed.
|
||||
|
||||
## Convex schema
|
||||
|
||||
```typescript
|
||||
// convex/schema.ts
|
||||
export default defineSchema({
|
||||
posts: defineTable({
|
||||
slug: v.string(),
|
||||
title: v.string(),
|
||||
description: v.string(),
|
||||
content: v.string(),
|
||||
date: v.string(),
|
||||
published: v.boolean(),
|
||||
tags: v.array(v.string()),
|
||||
readTime: v.optional(v.string()),
|
||||
image: v.optional(v.string()),
|
||||
excerpt: v.optional(v.string()), // For card view
|
||||
featured: v.optional(v.boolean()), // Show in featured section
|
||||
featuredOrder: v.optional(v.number()), // Order in featured (lower = first)
|
||||
authorName: v.optional(v.string()), // Author display name
|
||||
authorImage: v.optional(v.string()), // Author avatar image URL
|
||||
lastSyncedAt: v.number(),
|
||||
})
|
||||
.index("by_slug", ["slug"])
|
||||
.index("by_published", ["published"])
|
||||
.index("by_featured", ["featured"]),
|
||||
|
||||
pages: defineTable({
|
||||
slug: v.string(),
|
||||
title: v.string(),
|
||||
content: v.string(),
|
||||
published: v.boolean(),
|
||||
order: v.optional(v.number()),
|
||||
excerpt: v.optional(v.string()), // For card view
|
||||
image: v.optional(v.string()), // Thumbnail for featured cards
|
||||
featured: v.optional(v.boolean()), // Show in featured section
|
||||
featuredOrder: v.optional(v.number()), // Order in featured (lower = first)
|
||||
authorName: v.optional(v.string()), // Author display name
|
||||
authorImage: v.optional(v.string()), // Author avatar image URL
|
||||
lastSyncedAt: v.number(),
|
||||
})
|
||||
.index("by_slug", ["slug"])
|
||||
.index("by_published", ["published"])
|
||||
.index("by_featured", ["featured"]),
|
||||
});
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Posts not appearing**
|
||||
|
||||
- Check `published: true` in frontmatter
|
||||
- Run `npm run sync` for development
|
||||
- Run `npm run sync:prod` for production
|
||||
- Use `npm run sync:all` or `npm run sync:all:prod` to sync content and update discovery files together
|
||||
- Verify in Convex dashboard
|
||||
|
||||
**RSS/Sitemap errors**
|
||||
|
||||
- Verify `VITE_CONVEX_URL` is set in Netlify
|
||||
- Test Convex HTTP URL: `https://your-deployment.convex.site/rss.xml`
|
||||
- Check edge functions in `netlify/edge-functions/`
|
||||
|
||||
**Build failures**
|
||||
|
||||
- Verify `CONVEX_DEPLOY_KEY` is set in Netlify
|
||||
- Ensure `@types/node` is in devDependencies
|
||||
- Build command must include `--include=dev`
|
||||
- Check Node.js version (18+)
|
||||
113
public/raw/docs-frontmatter.md
Normal file
113
public/raw/docs-frontmatter.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Frontmatter
|
||||
|
||||
---
|
||||
Type: page
|
||||
Date: 2026-01-06
|
||||
---
|
||||
|
||||
## Frontmatter
|
||||
|
||||
Frontmatter is the YAML metadata at the top of each markdown file between `---` markers. It controls how content is displayed, organized, and discovered.
|
||||
|
||||
## Blog post fields
|
||||
|
||||
| Field | Required | Description |
|
||||
| ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `title` | Yes | Post title |
|
||||
| `description` | Yes | SEO description |
|
||||
| `date` | Yes | YYYY-MM-DD format |
|
||||
| `slug` | Yes | URL path (unique) |
|
||||
| `published` | Yes | `true` to show |
|
||||
| `tags` | Yes | Array of strings |
|
||||
| `readTime` | No | Display time estimate |
|
||||
| `image` | No | OG image and featured card thumbnail. See [Using Images in Blog Posts](/using-images-in-posts) for markdown and HTML syntax |
|
||||
| `showImageAtTop` | No | Set `true` to display the image at the top of the post above the header (default: `false`) |
|
||||
| `excerpt` | No | Short text for card view |
|
||||
| `featured` | No | `true` to show in featured section |
|
||||
| `featuredOrder` | No | Order in featured (lower = first) |
|
||||
| `authorName` | No | Author display name shown next to date |
|
||||
| `authorImage` | No | Round author avatar image URL |
|
||||
| `layout` | No | Set to `"sidebar"` for docs-style layout with TOC |
|
||||
| `rightSidebar` | No | Enable right sidebar with CopyPageDropdown (opt-in, requires explicit `true`) |
|
||||
| `showFooter` | No | Show footer on this post (overrides siteConfig default) |
|
||||
| `footer` | No | Per-post footer markdown (overrides `footer.md` and siteConfig.defaultContent) |
|
||||
| `showSocialFooter` | No | Show social footer on this post (overrides siteConfig default) |
|
||||
| `aiChat` | No | Enable AI chat in right sidebar. Set `true` to enable (requires `rightSidebar: true` and `siteConfig.aiChat.enabledOnContent: true`). Set `false` to explicitly hide even if global config is enabled. |
|
||||
| `blogFeatured` | No | Show as featured on blog page (first becomes hero, rest in 2-column row) |
|
||||
| `newsletter` | No | Override newsletter signup display (`true` to show, `false` to hide) |
|
||||
| `contactForm` | No | Enable contact form on this post |
|
||||
| `unlisted` | No | Hide from listings but allow direct access via slug. Set `true` to hide from blog listings, featured sections, tag pages, search results, and related posts. Post remains accessible via direct link. |
|
||||
| `docsSection` | No | Include in docs sidebar. Set `true` to show in the docs section navigation. |
|
||||
| `docsSectionGroup` | No | Group name for docs sidebar. Posts with the same group name appear together. |
|
||||
| `docsSectionOrder` | No | Order within docs group. Lower numbers appear first within the group. |
|
||||
| `docsSectionGroupOrder` | No | Order of the group in docs sidebar. Lower numbers make the group appear first. Groups without this field sort alphabetically. |
|
||||
| `docsSectionGroupIcon` | No | Phosphor icon name for docs sidebar group (e.g., "Rocket", "Book", "PuzzlePiece"). Icon appears left of the group title. See [Phosphor Icons](https://phosphoricons.com) for available icons. |
|
||||
| `docsLanding` | No | Set `true` to use this post as the docs landing page (shown when navigating to `/docs`). |
|
||||
|
||||
## Page fields
|
||||
|
||||
| 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 |
|
||||
| `showImageAtTop` | No | Set `true` to display the image at the top of the page above the header (default: `false`) |
|
||||
| `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`) |
|
||||
| `showFooter` | No | Show footer on this page (overrides siteConfig default) |
|
||||
| `footer` | No | Per-page footer markdown (overrides `footer.md` and siteConfig.defaultContent) |
|
||||
| `showSocialFooter` | No | Show social footer on this page (overrides siteConfig default) |
|
||||
| `aiChat` | No | Enable AI chat in right sidebar. Set `true` to enable (requires `rightSidebar: true` and `siteConfig.aiChat.enabledOnContent: true`). Set `false` to explicitly hide even if global config is enabled. |
|
||||
| `newsletter` | No | Override newsletter signup display (`true` to show, `false` to hide) |
|
||||
| `contactForm` | No | Enable contact form on this page |
|
||||
| `textAlign` | No | Text alignment: "left" (default), "center", or "right". Used by `home.md` for home intro alignment |
|
||||
| `docsSection` | No | Include in docs sidebar. Set `true` to show in the docs section navigation. |
|
||||
| `docsSectionGroup` | No | Group name for docs sidebar. Pages with the same group name appear together. |
|
||||
| `docsSectionOrder` | No | Order within docs group. Lower numbers appear first within the group. |
|
||||
| `docsSectionGroupOrder` | No | Order of the group in docs sidebar. Lower numbers make the group appear first. Groups without this field sort alphabetically. |
|
||||
| `docsSectionGroupIcon` | No | Phosphor icon name for docs sidebar group (e.g., "Rocket", "Book", "PuzzlePiece"). Icon appears left of the group title. See [Phosphor Icons](https://phosphoricons.com) for available icons. |
|
||||
| `docsLanding` | No | Set `true` to use this page as the docs landing page (shown when navigating to `/docs`). |
|
||||
|
||||
## Common patterns
|
||||
|
||||
### 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.
|
||||
|
||||
### Unlisted posts
|
||||
|
||||
Set `unlisted: true` to hide a blog post from all listings while keeping it accessible via direct link. Unlisted posts are excluded from: blog listings (`/blog` page), featured sections (homepage), tag pages (`/tags/[tag]`), search results (Command+K), and related posts. The post remains accessible via direct URL (e.g., `/blog/post-slug`). Useful for draft posts, private content, or posts you want to share via direct link only. Note: `unlisted` only works for blog posts, not pages.
|
||||
|
||||
### Show image at top
|
||||
|
||||
Add `showImageAtTop: true` to display the `image` field at the top of the post/page above the header. Default behavior: if `showImageAtTop` is not set or `false`, image only used for Open Graph previews and featured card thumbnails.
|
||||
|
||||
### Image lightbox
|
||||
|
||||
Images in blog posts and pages automatically open in a full-screen lightbox when clicked (if enabled in `siteConfig.imageLightbox.enabled`). This allows readers to view images at full size. The lightbox can be closed by clicking outside the image, pressing Escape, or clicking the close button.
|
||||
|
||||
### Text alignment
|
||||
|
||||
Use `textAlign` field to control text alignment for page content. Options: `"left"` (default), `"center"`, or `"right"`. Used by `home.md` to control home intro alignment.
|
||||
|
||||
### Docs section
|
||||
|
||||
To add content to the docs sidebar:
|
||||
|
||||
1. Add `docsSection: true` to frontmatter
|
||||
2. Optionally set `docsSectionGroup` to group related content
|
||||
3. Use `docsSectionOrder` to control order within groups
|
||||
4. Use `docsSectionGroupOrder` to control group order
|
||||
5. Add `docsSectionGroupIcon` for visual icons (Phosphor icons)
|
||||
|
||||
### Docs landing page
|
||||
|
||||
Set `docsLanding: true` on one post or page to make it the docs landing page. This content displays when navigating to `/docs`.
|
||||
224
public/raw/docs-search.md
Normal file
224
public/raw/docs-search.md
Normal file
@@ -0,0 +1,224 @@
|
||||
# Search
|
||||
|
||||
---
|
||||
Type: page
|
||||
Date: 2026-01-06
|
||||
---
|
||||
|
||||
## Keyword Search
|
||||
|
||||
Keyword search matches exact words using Convex full-text search. Results update instantly as you type.
|
||||
|
||||
For meaning-based search that finds conceptually similar content, see [Semantic Search](/docs-semantic-search).
|
||||
|
||||
---
|
||||
|
||||
### Keyboard shortcuts
|
||||
|
||||
Press `Cmd+K` (Mac) or `Ctrl+K` (Windows/Linux) to open search. Click the magnifying glass icon works too.
|
||||
|
||||
| Key | Action |
|
||||
| --- | ------ |
|
||||
| `Cmd+K` / `Ctrl+K` | Open/close search |
|
||||
| `Tab` | Switch between Keyword and Semantic modes |
|
||||
| `↑` `↓` | Navigate results |
|
||||
| `Enter` | Select result |
|
||||
| `Esc` | Close modal |
|
||||
|
||||
### How keyword search works
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ KEYWORD SEARCH FLOW │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌──────────┐ ┌─────────────┐ ┌──────────────────┐
|
||||
│ Cmd+K │───▶│ SearchModal │───▶│ Convex Query │
|
||||
└──────────┘ └─────────────┘ └────────┬─────────┘
|
||||
│
|
||||
┌──────────────────────┴──────────────────────┐
|
||||
▼ ▼
|
||||
┌────────────────┐ ┌────────────────┐
|
||||
│ search_title │ │ search_content │
|
||||
│ (index) │ │ (index) │
|
||||
└───────┬────────┘ └───────┬────────┘
|
||||
│ │
|
||||
└──────────────────┬─────────────────────────┘
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ Dedupe + Rank │
|
||||
│ (title matches first)│
|
||||
└──────────┬──────────┘
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ Search Results │
|
||||
│ (max 15) │
|
||||
└──────────┬──────────┘
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ Navigate + ?q=term │
|
||||
└──────────┬──────────┘
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ Highlight matches │
|
||||
│ + scroll to first │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
1. User presses `Cmd+K` to open SearchModal
|
||||
2. SearchModal sends reactive query to Convex as user types
|
||||
3. Convex searches both title and content indexes in parallel
|
||||
4. Results are deduplicated (same post can match both indexes)
|
||||
5. Results ranked with title matches first, limited to 15
|
||||
6. User selects result, navigates with `?q=searchterm` param
|
||||
7. Destination page highlights all matches and scrolls to first
|
||||
|
||||
### Why keyword search
|
||||
|
||||
Keyword search is the default because it's:
|
||||
|
||||
- **Instant** - Results return in milliseconds, no API calls
|
||||
- **Free** - No external services, no per-query costs
|
||||
- **Reactive** - Updates in real-time as you type
|
||||
- **Highlightable** - Matches exact words, so results can be highlighted on the destination page
|
||||
|
||||
Use keyword search when you know the exact terms, code snippets, or commands you're looking for.
|
||||
|
||||
### How search indexes work
|
||||
|
||||
Search indexes are defined in `convex/schema.ts` on the posts and pages tables:
|
||||
|
||||
```typescript
|
||||
posts: defineTable({
|
||||
title: v.string(),
|
||||
content: v.string(),
|
||||
published: v.boolean(),
|
||||
// ... other fields
|
||||
})
|
||||
.searchIndex("search_content", {
|
||||
searchField: "content",
|
||||
filterFields: ["published"],
|
||||
})
|
||||
.searchIndex("search_title", {
|
||||
searchField: "title",
|
||||
filterFields: ["published"],
|
||||
}),
|
||||
```
|
||||
|
||||
Each table has two search indexes:
|
||||
|
||||
- `search_title` - Searches the title field
|
||||
- `search_content` - Searches the full markdown content
|
||||
|
||||
The `filterFields: ["published"]` allows filtering to only published content without a separate query.
|
||||
|
||||
### Search query implementation
|
||||
|
||||
The search query in `convex/search.ts` searches both titles and content, then deduplicates and ranks results:
|
||||
|
||||
```typescript
|
||||
// Search posts by title
|
||||
const postsByTitle = await ctx.db
|
||||
.query("posts")
|
||||
.withSearchIndex("search_title", (q) =>
|
||||
q.search("title", args.query).eq("published", true)
|
||||
)
|
||||
.take(10);
|
||||
|
||||
// Search posts by content
|
||||
const postsByContent = await ctx.db
|
||||
.query("posts")
|
||||
.withSearchIndex("search_content", (q) =>
|
||||
q.search("content", args.query).eq("published", true)
|
||||
)
|
||||
.take(10);
|
||||
```
|
||||
|
||||
Key features:
|
||||
|
||||
- **Dual search** - Searches both title and content indexes
|
||||
- **Filter by published** - Only returns published content
|
||||
- **Deduplication** - Removes duplicates when a post matches both title and content
|
||||
- **Ranking** - Title matches sort before content-only matches
|
||||
- **Snippets** - Generates context snippets around the search term
|
||||
- **Unlisted filtering** - Excludes posts with `unlisted: true`
|
||||
|
||||
### Frontend search modal
|
||||
|
||||
The `SearchModal` component (`src/components/SearchModal.tsx`) provides the UI:
|
||||
|
||||
```typescript
|
||||
// Reactive search query - updates as you type
|
||||
const results = useQuery(
|
||||
api.search.search,
|
||||
searchQuery.trim() ? { query: searchQuery } : "skip"
|
||||
);
|
||||
```
|
||||
|
||||
The `"skip"` parameter tells Convex to skip the query when the search field is empty, avoiding unnecessary database calls.
|
||||
|
||||
### Search result highlighting
|
||||
|
||||
When you click a search result, the app navigates to the page with a `?q=` parameter. The `useSearchHighlighting` hook (`src/hooks/useSearchHighlighting.ts`) then:
|
||||
|
||||
1. Waits for page content to load
|
||||
2. Finds all occurrences of the search term
|
||||
3. Wraps matches in `<mark>` tags with highlight styling
|
||||
4. Scrolls to the first match
|
||||
5. Highlights pulse, then fade after 4 seconds
|
||||
6. Press `Esc` to clear highlights manually
|
||||
|
||||
### Files involved
|
||||
|
||||
| File | Purpose |
|
||||
| ---- | ------- |
|
||||
| `convex/schema.ts` | Search index definitions |
|
||||
| `convex/search.ts` | Search query with deduplication and snippets |
|
||||
| `src/components/SearchModal.tsx` | Search UI with keyboard navigation |
|
||||
| `src/components/Layout.tsx` | Keyboard shortcut handler (Cmd+K) |
|
||||
| `src/hooks/useSearchHighlighting.ts` | Result highlighting and scroll-to-match |
|
||||
|
||||
### Adding search to new tables
|
||||
|
||||
To add search to a new table:
|
||||
|
||||
1. Add a search index in `convex/schema.ts`:
|
||||
|
||||
```typescript
|
||||
myTable: defineTable({
|
||||
title: v.string(),
|
||||
body: v.string(),
|
||||
status: v.string(),
|
||||
})
|
||||
.searchIndex("search_body", {
|
||||
searchField: "body",
|
||||
filterFields: ["status"],
|
||||
}),
|
||||
```
|
||||
|
||||
2. Create a search query in a Convex function:
|
||||
|
||||
```typescript
|
||||
const results = await ctx.db
|
||||
.query("myTable")
|
||||
.withSearchIndex("search_body", (q) =>
|
||||
q.search("body", searchTerm).eq("status", "published")
|
||||
)
|
||||
.take(10);
|
||||
```
|
||||
|
||||
3. Run `npx convex dev` to deploy the new index
|
||||
|
||||
### Limitations
|
||||
|
||||
- Search indexes only work on string fields
|
||||
- One `searchField` per index (create multiple indexes for multiple fields)
|
||||
- Filter fields support equality only (not ranges or inequalities)
|
||||
- Results are ranked by relevance, not by date or other fields
|
||||
- Maximum 10 results per `.take()` call (paginate for more)
|
||||
|
||||
### Resources
|
||||
|
||||
- [Convex Full-Text Search](https://docs.convex.dev/search/text-search)
|
||||
- [Search Index API](https://docs.convex.dev/api/classes/server.TableDefinition#searchindex)
|
||||
- [Semantic Search](/docs-semantic-search) - Vector-based search for finding similar content
|
||||
126
public/raw/docs-semantic-search.md
Normal file
126
public/raw/docs-semantic-search.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Semantic Search
|
||||
|
||||
---
|
||||
Type: page
|
||||
Date: 2026-01-06
|
||||
---
|
||||
|
||||
## Semantic Search
|
||||
|
||||
Semantic search finds content by meaning, not exact words. Ask questions naturally and find conceptually related content.
|
||||
|
||||
Press `Cmd+K` then `Tab` to switch to Semantic mode. For exact word matching, see [Keyword Search](/docs-search).
|
||||
|
||||
---
|
||||
|
||||
### When to use each mode
|
||||
|
||||
| Use case | Mode |
|
||||
|----------|------|
|
||||
| "authentication error" (exact term) | Keyword |
|
||||
| "login problems" (conceptual) | Semantic |
|
||||
| Find specific code or commands | Keyword |
|
||||
| "how do I deploy?" (question) | Semantic |
|
||||
| Need matches highlighted on page | Keyword |
|
||||
| Not sure of exact terminology | Semantic |
|
||||
|
||||
### How semantic search works
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ SEMANTIC SEARCH FLOW │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌──────────────┐ ┌─────────────────┐ ┌──────────────────┐
|
||||
│ User query: │───▶│ OpenAI API │───▶│ Query embedding │
|
||||
│ "how to │ │ text-embedding- │ │ [0.12, -0.45, │
|
||||
│ deploy" │ │ ada-002 │ │ 0.78, ...] │
|
||||
└──────────────┘ └─────────────────┘ └────────┬─────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ Convex vectorSearch │
|
||||
│ Compare to stored │
|
||||
│ post/page embeddings│
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ Results sorted by │
|
||||
│ similarity score │
|
||||
│ (0-100%) │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
1. Your query is converted to a vector (1536 numbers) using OpenAI's embedding model
|
||||
2. Convex compares this vector to stored embeddings for all posts and pages
|
||||
3. Results are ranked by similarity score (higher = more similar meaning)
|
||||
4. Top 15 results returned
|
||||
|
||||
### Technical comparison
|
||||
|
||||
| Aspect | Keyword | Semantic |
|
||||
|--------|---------|----------|
|
||||
| Speed | Instant | ~300ms |
|
||||
| Cost | Free | ~$0.0001/query |
|
||||
| Highlighting | Yes | No |
|
||||
| API required | No | OpenAI |
|
||||
|
||||
### Configuration
|
||||
|
||||
Semantic search requires an OpenAI API key:
|
||||
|
||||
```bash
|
||||
npx convex env set OPENAI_API_KEY sk-your-key-here
|
||||
```
|
||||
|
||||
If the key is not configured:
|
||||
- Semantic search returns empty results
|
||||
- Keyword search continues to work normally
|
||||
- Sync script skips embedding generation
|
||||
|
||||
### How embeddings are generated
|
||||
|
||||
When you run `npm run sync`:
|
||||
|
||||
1. Content syncs to Convex (posts and pages)
|
||||
2. Script checks for posts/pages without embeddings
|
||||
3. For each, combines title + content into text
|
||||
4. Calls OpenAI to generate 1536-dimension embedding
|
||||
5. Stores embedding in Convex database
|
||||
|
||||
Embeddings are generated once per post/page. If content changes, a new embedding is generated on the next sync.
|
||||
|
||||
### Files involved
|
||||
|
||||
| File | Purpose |
|
||||
| ---- | ------- |
|
||||
| `convex/schema.ts` | `embedding` field and `vectorIndex` on posts/pages |
|
||||
| `convex/embeddings.ts` | Embedding generation actions |
|
||||
| `convex/embeddingsQueries.ts` | Queries for posts/pages without embeddings |
|
||||
| `convex/semanticSearch.ts` | Vector search action |
|
||||
| `convex/semanticSearchQueries.ts` | Queries for hydrating search results |
|
||||
| `src/components/SearchModal.tsx` | Mode toggle (Tab to switch) |
|
||||
| `scripts/sync-posts.ts` | Triggers embedding generation after sync |
|
||||
|
||||
### Limitations
|
||||
|
||||
- **No highlighting**: Semantic search finds meaning, not exact words, so matches can't be highlighted
|
||||
- **API cost**: Each search query costs ~$0.0001 (embedding generation)
|
||||
- **Latency**: ~300ms vs instant for keyword search (API round-trip)
|
||||
- **Requires OpenAI key**: Won't work without `OPENAI_API_KEY` configured
|
||||
- **Token limit**: Content is truncated to ~8000 characters for embedding
|
||||
|
||||
### Similarity scores
|
||||
|
||||
Results show a percentage score (0-100%):
|
||||
- **90%+**: Very similar meaning
|
||||
- **70-90%**: Related content
|
||||
- **50-70%**: Loosely related
|
||||
- **<50%**: Weak match (may not be relevant)
|
||||
|
||||
### Resources
|
||||
|
||||
- [Convex Vector Search](https://docs.convex.dev/search/vector-search)
|
||||
- [OpenAI Embeddings](https://platform.openai.com/docs/guides/embeddings)
|
||||
- [Keyword Search](/docs-search) - Full-text search documentation
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
|
||||
---
|
||||
Type: page
|
||||
Date: 2026-01-04
|
||||
Date: 2026-01-06
|
||||
---
|
||||
|
||||
Built with [Convex](https://convex.dev) for real-time sync and deployed on [Netlify](https://netlify.com). Read the [project on GitHub](https://github.com/waynesutton/markdown-site) to fork and deploy your own. View [real-time site stats](/stats).
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
---
|
||||
Type: page
|
||||
Date: 2026-01-04
|
||||
Date: 2026-01-06
|
||||
---
|
||||
|
||||
An open-source publishing framework built for AI agents and developers to ship **[docs](/docs)**, or **[blogs](/blog)** or **[websites](/)**.
|
||||
@@ -27,4 +27,6 @@ agents. -->
|
||||
|
||||
**Real-time team sync** — Multiple developers run npm run sync from different machines.
|
||||
|
||||
**Sync Commands** - Sync discovery commands to update AGENTS.md, CLAUDE.md, and llms.txt
|
||||
**Sync Commands** - Sync discovery commands to update AGENTS.md, CLAUDE.md, and llms.txt
|
||||
|
||||
**Semantic search** - Find content by meaning, not just keywords, using vector embeddings.
|
||||
@@ -24,6 +24,8 @@ agents. -->
|
||||
|
||||
**Sync Commands** - Sync discovery commands to update AGENTS.md, CLAUDE.md, and llms.txt
|
||||
|
||||
**Semantic search** - Find content by meaning, not just keywords, using vector embeddings.
|
||||
|
||||
---
|
||||
|
||||
## Blog Posts (18)
|
||||
@@ -34,7 +36,7 @@ agents. -->
|
||||
- Date: 2025-12-29 | Reading time: 10 min read | Tags: workos, authentication, tutorial, dashboard
|
||||
- **[How to use the Markdown sync dashboard](/raw/how-to-use-the-markdown-sync-dashboard.md)** - Learn how to use the dashboard at /dashboard to manage content, configure your site, and sync markdown files without leaving your browser.
|
||||
- Date: 2025-12-29 | Reading time: 8 min read | Tags: dashboard, tutorial, content-management
|
||||
- **[Team Workflows with Git Version Control](/raw/team-workflows-git-version-control.md)** - How teams collaborate on markdown content using git, sync to shared Convex deployments, and automate production syncs with CI/CD.
|
||||
- **[Team Workflows](/raw/team-workflows-git-version-control.md)** - How teams collaborate on markdown content using git, sync to shared Convex deployments, and automate production syncs with CI/CD.
|
||||
- Date: 2025-12-29 | Reading time: 6 min read | Tags: git, convex, ci-cd, collaboration, workflow
|
||||
- **[How to Use the MCP Server with MarkDown Sync](/raw/how-to-use-mcp-server.md)** - Guide to using the HTTP-based Model Context Protocol(MCP) server at www.markdown.fast/mcp with Cursor and other AI tools
|
||||
- Date: 2025-12-28 | Reading time: 5 min read | Tags: mcp, cursor, ai, tutorial, netlify
|
||||
@@ -58,27 +60,34 @@ agents. -->
|
||||
- Date: 2025-12-14 | Reading time: 3 min read | Tags: tutorial, markdown, cursor, IDE, publishing
|
||||
- **[Writing Markdown with Code Examples](/raw/markdown-with-code-examples.md)** - A complete reference for writing markdown with links, code blocks, images, tables, and formatting. Copy examples directly into your posts.
|
||||
- Date: 2025-12-14 | Reading time: 5 min read | Tags: markdown, tutorial, code
|
||||
- **[Netlify edge functions blocking AI crawlers from static files](/raw/netlify-edge-excludedpath-ai-crawlers.md)** - Why excludedPath in netlify.toml isn't preventing edge functions from intercepting /raw/* requests, and how ChatGPT and Perplexity get blocked while Claude works.
|
||||
- Date: 2025-12-14 | Reading time: 5 min read | Tags: netlify, edge-functions, ai, troubleshooting, help
|
||||
- **[Setup Guide - Fork and Deploy Your Own Markdown Framework](/raw/setup-guide.md)** - Step-by-step guide to fork this markdown sync framework, set up Convex backend, and deploy to Netlify in under 10 minutes.
|
||||
- **[How we fixed AI crawlers blocked by Netlify edge functions](/raw/netlify-edge-excludedpath-ai-crawlers.md)** - ChatGPT and Perplexity couldn't fetch /raw/*.md files on Netlify. The fix: Content-Type headers. Here's what we tried and what actually worked.
|
||||
- Date: 2025-12-14 | Reading time: 5 min read | Tags: netlify, edge-functions, ai, troubleshooting
|
||||
- **[Setup Guide](/raw/setup-guide.md)** - Step-by-step guide to fork this markdown sync framework, set up Convex backend, and deploy to Netlify in under 10 minutes.
|
||||
- Date: 2025-12-14 | Reading time: 8 min read | Tags: convex, netlify, tutorial, deployment
|
||||
- **[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 (8)
|
||||
## Pages (15)
|
||||
|
||||
- **[Footer](/raw/footer.md)**
|
||||
- **[Home Intro](/raw/home-intro.md)**
|
||||
- **[Documentation](/raw/documentation.md)**
|
||||
- **[About](/raw/about.md)** - An open-source publishing framework built for AI agents and developers to ship websites, docs, or blogs.
|
||||
- **[Content](/raw/docs-content.md)**
|
||||
- **[Search](/raw/docs-search.md)**
|
||||
- **[Semantic Search](/raw/docs-semantic-search.md)**
|
||||
- **[Frontmatter](/raw/docs-frontmatter.md)**
|
||||
- **[Projects](/raw/projects.md)**
|
||||
- **[Contact](/raw/contact.md)**
|
||||
- **[Configuration](/raw/docs-configuration.md)**
|
||||
- **[Changelog](/raw/changelog.md)**
|
||||
- **[Dashboard](/raw/docs-dashboard.md)**
|
||||
- **[Deployment](/raw/docs-deployment.md)**
|
||||
- **[Newsletter](/raw/newsletter.md)**
|
||||
|
||||
---
|
||||
|
||||
**Total Content:** 18 posts, 8 pages
|
||||
**Total Content:** 18 posts, 15 pages
|
||||
|
||||
All content is available as raw markdown files at `/raw/{slug}.md`
|
||||
|
||||
|
||||
@@ -1,17 +1,32 @@
|
||||
# Netlify edge functions blocking AI crawlers from static files
|
||||
# How we fixed AI crawlers blocked by Netlify edge functions
|
||||
|
||||
> Why excludedPath in netlify.toml isn't preventing edge functions from intercepting /raw/* requests, and how ChatGPT and Perplexity get blocked while Claude works.
|
||||
> ChatGPT and Perplexity couldn't fetch /raw/*.md files on Netlify. The fix: Content-Type headers. Here's what we tried and what actually worked.
|
||||
|
||||
---
|
||||
Type: post
|
||||
Date: 2025-12-14
|
||||
Reading time: 5 min read
|
||||
Tags: netlify, edge-functions, ai, troubleshooting, help
|
||||
Tags: netlify, edge-functions, ai, troubleshooting
|
||||
---
|
||||
|
||||
## The fix
|
||||
|
||||
Add explicit `Content-Type` headers for your raw markdown files in `netlify.toml`:
|
||||
|
||||
```toml
|
||||
[[headers]]
|
||||
for = "/raw/*"
|
||||
[headers.values]
|
||||
Content-Type = "text/plain; charset=utf-8"
|
||||
Access-Control-Allow-Origin = "*"
|
||||
Cache-Control = "public, max-age=3600"
|
||||
```
|
||||
|
||||
Thanks to [KP](https://x.com/thisiskp_) for pointing us in the right direction.
|
||||
|
||||
## The problem
|
||||
|
||||
AI crawlers cannot access static markdown files at `/raw/*.md` on Netlify, even with `excludedPath` configured. ChatGPT and Perplexity return errors. Claude works.
|
||||
AI crawlers could not access static markdown files at `/raw/*.md` on Netlify, even with `excludedPath` configured. ChatGPT and Perplexity returned errors. Claude worked.
|
||||
|
||||
## What we're building
|
||||
|
||||
@@ -164,15 +179,19 @@ The core issue appears to be how ChatGPT and Perplexity fetch URLs. Their tools
|
||||
2. The edge function exclusions work for browsers but not for AI fetch tools
|
||||
3. There may be rate limiting or bot protection enabled by default
|
||||
|
||||
## Current workaround
|
||||
## Why Content-Type matters
|
||||
|
||||
Users can still share content with AI tools by:
|
||||
Without an explicit `Content-Type` header, Netlify serves files based on extension. The `.md` extension gets served as `text/markdown` or similar, which AI fetch tools may reject or misinterpret.
|
||||
|
||||
1. **Copy page** copies markdown to clipboard, then paste into any AI
|
||||
2. **View as Markdown** opens the raw `.md` file in a browser tab for manual copying
|
||||
3. **Download as SKILL.md** downloads in Anthropic Agent Skills format
|
||||
Setting `Content-Type = "text/plain; charset=utf-8"` tells the CDN and AI crawlers exactly what to expect. The `Access-Control-Allow-Origin = "*"` header ensures cross-origin requests work.
|
||||
|
||||
The direct "Open in ChatGPT/Claude/Perplexity" buttons have been disabled since the URLs don't work reliably.
|
||||
## What works now
|
||||
|
||||
Users can share content with AI tools via:
|
||||
|
||||
1. **Copy page** copies markdown to clipboard
|
||||
2. **View as Markdown** opens the raw `.md` file in browser
|
||||
3. **Open in ChatGPT/Claude/Perplexity** sends the URL directly (now working)
|
||||
|
||||
## Working features
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
---
|
||||
Type: page
|
||||
Date: 2026-01-04
|
||||
Date: 2026-01-06
|
||||
---
|
||||
|
||||
# Newsletter Demo Page
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
---
|
||||
Type: page
|
||||
Date: 2026-01-04
|
||||
Date: 2026-01-06
|
||||
---
|
||||
|
||||
This markdown framework is open source and built to be extended. Here is what ships out of the box.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Setup Guide - Fork and Deploy Your Own Markdown Framework
|
||||
# Setup Guide
|
||||
|
||||
> Step-by-step guide to fork this markdown sync framework, set up Convex backend, and deploy to Netlify in under 10 minutes.
|
||||
|
||||
@@ -337,29 +337,29 @@ Your markdown content here...
|
||||
|
||||
### Frontmatter Fields
|
||||
|
||||
| Field | Required | Description |
|
||||
| --------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `title` | Yes | Post title |
|
||||
| `description` | Yes | Short description for SEO |
|
||||
| `date` | Yes | Publication date (YYYY-MM-DD) |
|
||||
| `slug` | Yes | URL path (must be unique) |
|
||||
| `published` | Yes | Set to `true` to publish |
|
||||
| `tags` | Yes | Array of topic tags |
|
||||
| `readTime` | No | Estimated reading time |
|
||||
| `image` | No | Header/Open Graph image URL |
|
||||
| `excerpt` | No | Short excerpt for card view |
|
||||
| `featured` | No | Set `true` to show in featured section |
|
||||
| `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`) |
|
||||
| `unlisted` | No | Hide from listings but allow direct access via slug. Set `true` to hide from blog listings, featured sections, tag pages, search results, and related posts. Post remains accessible via direct link. |
|
||||
| `docsSection` | No | Include in docs sidebar. Set `true` to show in the docs section navigation. |
|
||||
| `docsSectionGroup` | No | Group name for docs sidebar. Posts with the same group name appear together. |
|
||||
| `docsSectionOrder` | No | Order within docs group. Lower numbers appear first within the group. |
|
||||
| `docsSectionGroupOrder` | No | Order of the group in docs sidebar. Lower numbers make the group appear first. Groups without this field sort alphabetically. |
|
||||
| `docsSectionGroupIcon` | No | Phosphor icon name for docs sidebar group (e.g., "Rocket", "Book", "PuzzlePiece"). Icon appears left of the group title. See [Phosphor Icons](https://phosphoricons.com) for available icons. |
|
||||
| `docsLanding` | No | Set `true` to use as the docs landing page (shown when navigating to `/docs`). |
|
||||
| Field | Required | Description |
|
||||
| ----------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `title` | Yes | Post title |
|
||||
| `description` | Yes | Short description for SEO |
|
||||
| `date` | Yes | Publication date (YYYY-MM-DD) |
|
||||
| `slug` | Yes | URL path (must be unique) |
|
||||
| `published` | Yes | Set to `true` to publish |
|
||||
| `tags` | Yes | Array of topic tags |
|
||||
| `readTime` | No | Estimated reading time |
|
||||
| `image` | No | Header/Open Graph image URL |
|
||||
| `excerpt` | No | Short excerpt for card view |
|
||||
| `featured` | No | Set `true` to show in featured section |
|
||||
| `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`) |
|
||||
| `unlisted` | No | Hide from listings but allow direct access via slug. Set `true` to hide from blog listings, featured sections, tag pages, search results, and related posts. Post remains accessible via direct link. |
|
||||
| `docsSection` | No | Include in docs sidebar. Set `true` to show in the docs section navigation. |
|
||||
| `docsSectionGroup` | No | Group name for docs sidebar. Posts with the same group name appear together. |
|
||||
| `docsSectionOrder` | No | Order within docs group. Lower numbers appear first within the group. |
|
||||
| `docsSectionGroupOrder` | No | Order of the group in docs sidebar. Lower numbers make the group appear first. Groups without this field sort alphabetically. |
|
||||
| `docsSectionGroupIcon` | No | Phosphor icon name for docs sidebar group (e.g., "Rocket", "Book", "PuzzlePiece"). Icon appears left of the group title. See [Phosphor Icons](https://phosphoricons.com) for available icons. |
|
||||
| `docsLanding` | No | Set `true` to use as the docs landing page (shown when navigating to `/docs`). |
|
||||
|
||||
### How Frontmatter Works
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Team Workflows with Git Version Control
|
||||
# Team Workflows
|
||||
|
||||
> How teams collaborate on markdown content using git, sync to shared Convex deployments, and automate production syncs with CI/CD.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user