update: workos logo, blog post images and dashboard docs images upload
@@ -19,10 +19,10 @@ Your content is instantly available to browsers, LLMs, and AI agents.. Write mar
|
||||
- **Site Name**: markdown
|
||||
- **Site Title**: markdown sync framework
|
||||
- **Site URL**: https://markdown.fast
|
||||
- **Total Posts**: 15
|
||||
- **Total Posts**: 17
|
||||
- **Total Pages**: 5
|
||||
- **Latest Post**: 2025-12-29
|
||||
- **Last Updated**: 2025-12-29T21:11:54.407Z
|
||||
- **Last Updated**: 2025-12-30T06:25:18.540Z
|
||||
|
||||
## Tech stack
|
||||
|
||||
|
||||
@@ -533,6 +533,7 @@ A public markdown writing page at `/write` (not linked in navigation).
|
||||
Access directly at `yourdomain.com/write`. Content is stored in localStorage only (not synced to database). Use it to draft posts, then copy the content to a markdown file in `content/blog/` or `content/pages/` and run `npm run sync`.
|
||||
|
||||
**Features:**
|
||||
|
||||
- Three-column Cursor docs-style layout
|
||||
- Content type selector (Blog Post or Page) with dynamic frontmatter templates
|
||||
- Frontmatter field reference with individual copy buttons
|
||||
|
||||
14
TASK.md
@@ -7,10 +7,22 @@
|
||||
|
||||
## Current Status
|
||||
|
||||
v1.46.0 ready. Dashboard sync server feature complete. Local HTTP server allows executing sync commands directly from dashboard UI without opening terminal. Real-time output streaming, server status indicators, and copy/execute buttons implemented. Header sync buttons use sync server when available. Copy icons added for npm run sync-server command.
|
||||
v1.47.0 ready. Image lightbox feature complete. Images in blog posts and pages automatically open in full-screen lightbox when clicked (if enabled in siteConfig). Lightbox includes backdrop, close button, keyboard support (Escape), and caption display. Configurable via siteConfig.imageLightbox.enabled (default: true). Dashboard config generator includes image lightbox toggle. Documentation updated in docs.md and setup-guide.md.
|
||||
|
||||
## Completed
|
||||
|
||||
- [x] Image lightbox for blog posts and pages
|
||||
- [x] Added ImageLightboxConfig interface to siteConfig.ts with enabled option
|
||||
- [x] Created ImageLightbox component in BlogPost.tsx with backdrop, close button, keyboard support
|
||||
- [x] Updated img renderer to add click handler and clickable cursor when lightbox enabled
|
||||
- [x] Added CSS styles for lightbox backdrop, image, close button, and caption
|
||||
- [x] Added imageLightboxEnabled to Dashboard config generator
|
||||
- [x] Updated documentation: docs.md, setup-guide.md, files.md, changelog.md, changelog-page.md
|
||||
- [x] Images show pointer cursor and hover effect when lightbox is enabled
|
||||
- [x] Lightbox closes on backdrop click, Escape key, or close button
|
||||
- [x] Alt text displayed as caption in lightbox
|
||||
- [x] Default configuration: enabled: true (lightbox active by default)
|
||||
|
||||
- [x] Stats page configuration option for public/private access
|
||||
- [x] Added StatsPageConfig interface to siteConfig.ts with enabled and showInNav options
|
||||
- [x] Updated App.tsx to conditionally render /stats route based on config
|
||||
|
||||
25
changelog.md
@@ -4,6 +4,31 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [1.47.0] - 2025-12-29
|
||||
|
||||
### Added
|
||||
|
||||
- Image lightbox for blog posts and pages
|
||||
- Images automatically open in full-screen lightbox when clicked (if enabled)
|
||||
- Lightbox includes dark backdrop, close button (X icon), and caption display
|
||||
- Keyboard support: Press Escape to close lightbox
|
||||
- Click outside image (backdrop) to close
|
||||
- Alt text displayed as caption below image in lightbox
|
||||
- Images show pointer cursor (`zoom-in`) and subtle hover effect when lightbox is enabled
|
||||
- Configurable via `siteConfig.imageLightbox.enabled` (default: `true`)
|
||||
- Dashboard config generator includes image lightbox toggle
|
||||
- Responsive design: lightbox adapts to mobile screens
|
||||
|
||||
### Technical
|
||||
|
||||
- New component: `ImageLightbox` in `src/components/BlogPost.tsx`
|
||||
- New interface: `ImageLightboxConfig` in `src/config/siteConfig.ts`
|
||||
- Updated: `src/components/BlogPost.tsx` - Added lightbox state management and click handlers
|
||||
- Updated: `src/styles/global.css` - Added lightbox styles (`.image-lightbox-backdrop`, `.image-lightbox-img`, `.image-lightbox-close`, `.image-lightbox-caption`)
|
||||
- Updated: `src/pages/Dashboard.tsx` - Added image lightbox configuration option
|
||||
- Updated: `content/pages/docs.md` - Added image lightbox documentation
|
||||
- Updated: `content/blog/setup-guide.md` - Added image lightbox documentation
|
||||
|
||||
## [1.46.0] - 2025-12-29
|
||||
|
||||
### Added
|
||||
|
||||
@@ -7,7 +7,9 @@ published: true
|
||||
tags: ["workos", "authentication", "tutorial", "dashboard"]
|
||||
readTime: "10 min read"
|
||||
featured: true
|
||||
featuredOrder: 3
|
||||
featuredOrder: 4
|
||||
layout: "sidebar"
|
||||
image: /images/workos.png
|
||||
excerpt: "Complete guide to setting up WorkOS AuthKit authentication for your dashboard. WorkOS is optional and can be configured in siteConfig.ts."
|
||||
---
|
||||
|
||||
@@ -66,6 +68,7 @@ Before starting, make sure you have:
|
||||
During the AuthKit setup wizard, you'll reach step 4: **"Add default redirect endpoint URI"**
|
||||
|
||||
Enter this for local development:
|
||||
|
||||
```
|
||||
http://localhost:5173/callback
|
||||
```
|
||||
@@ -110,10 +113,10 @@ npm install @workos-inc/authkit-react @convex-dev/workos
|
||||
|
||||
**What these packages do:**
|
||||
|
||||
| Package | Purpose |
|
||||
|---------|---------|
|
||||
| Package | Purpose |
|
||||
| --------------------------- | ------------------------------------------ |
|
||||
| `@workos-inc/authkit-react` | WorkOS React SDK for handling login/logout |
|
||||
| `@convex-dev/workos` | Bridges WorkOS auth with Convex backend |
|
||||
| `@convex-dev/workos` | Bridges WorkOS auth with Convex backend |
|
||||
|
||||
## Step 4: Add environment variables
|
||||
|
||||
@@ -207,6 +210,7 @@ When `requireAuth` is `true` and WorkOS is configured, the dashboard requires lo
|
||||
## Step 7: Test locally
|
||||
|
||||
1. Start your development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
@@ -273,21 +277,25 @@ When WorkOS is not configured or `requireAuth: false`:
|
||||
## Troubleshooting
|
||||
|
||||
**Dashboard shows "Authentication Required" message:**
|
||||
|
||||
- Verify `VITE_WORKOS_CLIENT_ID` and `VITE_WORKOS_REDIRECT_URI` are set in `.env.local`
|
||||
- Check that `WORKOS_CLIENT_ID` is set in Convex environment variables
|
||||
- Ensure `requireAuth: true` in `siteConfig.ts`
|
||||
|
||||
**Login redirect not working:**
|
||||
|
||||
- Verify redirect URI matches exactly in WorkOS Dashboard
|
||||
- Check CORS settings include your domain
|
||||
- Ensure callback route is configured in `App.tsx`
|
||||
|
||||
**"WorkOS is not configured" message:**
|
||||
|
||||
- Check that both `VITE_WORKOS_CLIENT_ID` and `VITE_WORKOS_REDIRECT_URI` are set
|
||||
- Verify environment variables are loaded (check browser console)
|
||||
- Restart development server after adding environment variables
|
||||
|
||||
**Convex auth errors:**
|
||||
|
||||
- Verify `WORKOS_CLIENT_ID` is set in Convex environment variables
|
||||
- Check that `convex/auth.config.ts` exists and is correct
|
||||
- Ensure Convex functions are deployed (`npx convex deploy`)
|
||||
|
||||
@@ -5,7 +5,8 @@ date: "2025-12-27"
|
||||
slug: "how-to-use-agentmail"
|
||||
published: true
|
||||
featured: true
|
||||
featuredOrder: 3
|
||||
featuredOrder: 5
|
||||
layout: "sidebar"
|
||||
blogFeatured: true
|
||||
image: /images/agentmail-blog.png
|
||||
tags: ["agentmail", "newsletter", "email", "setup"]
|
||||
@@ -102,6 +103,72 @@ npm run newsletter:send:stats
|
||||
3. Choose a post or compose a custom email
|
||||
4. Click "Send Newsletter"
|
||||
|
||||
### Customizing email footers
|
||||
|
||||
All newsletter emails include a footer with unsubscribe information. Edit the footer text in `convex/newsletterActions.ts`.
|
||||
|
||||
The footer appears in three functions:
|
||||
|
||||
**1. Post newsletter (`sendPostNewsletter`)**
|
||||
|
||||
HTML footer (lines 138-141):
|
||||
|
||||
```typescript
|
||||
<p style="font-size: 12px; color: #888;">
|
||||
You received this email because you subscribed to ${escapeHtml(siteName)}.<br />
|
||||
<a href="${unsubscribeUrl}" style="color: #888;">Unsubscribe</a>
|
||||
</p>
|
||||
```
|
||||
|
||||
Plain text footer (line 146):
|
||||
|
||||
```typescript
|
||||
const text = `${post.title}\n\n${post.description}\n\nRead more: ${postUrl}\n\n---\nUnsubscribe: ${unsubscribeUrl}`;
|
||||
```
|
||||
|
||||
**2. Weekly digest (`sendWeeklyDigest`)**
|
||||
|
||||
HTML footer (lines 295-298):
|
||||
|
||||
```typescript
|
||||
<p style="font-size: 12px; color: #888;">
|
||||
You received this email because you subscribed to ${escapeHtml(siteName)}.<br />
|
||||
<a href="${unsubscribeUrl}" style="color: #888;">Unsubscribe</a>
|
||||
</p>
|
||||
```
|
||||
|
||||
Plain text footer (line 302):
|
||||
|
||||
```typescript
|
||||
const text = `Weekly Digest - ${recentPosts.length} new post${recentPosts.length > 1 ? "s" : ""}\n\n${postsText}\n\n---\nUnsubscribe: ${unsubscribeUrl}`;
|
||||
```
|
||||
|
||||
**3. Custom newsletter (`sendCustomNewsletter`)**
|
||||
|
||||
HTML footer (lines 512-515):
|
||||
|
||||
```typescript
|
||||
<p style="font-size: 12px; color: #888;">
|
||||
You received this email because you subscribed to ${escapeHtml(siteName)}.<br />
|
||||
<a href="${unsubscribeUrl}" style="color: #888;">Unsubscribe</a>
|
||||
</p>
|
||||
```
|
||||
|
||||
Plain text footer (line 519):
|
||||
|
||||
```typescript
|
||||
const text = `${contentText}\n\n---\nUnsubscribe: ${unsubscribeUrl}`;
|
||||
```
|
||||
|
||||
**Customization options:**
|
||||
|
||||
- Change the message text ("You received this email because...")
|
||||
- Modify the unsubscribe link text ("Unsubscribe")
|
||||
- Adjust styling (font size, color, spacing)
|
||||
- Add additional footer content
|
||||
|
||||
Edit all three locations to keep footers consistent across email types. The unsubscribe URL is automatically generated and must remain in the footer for compliance.
|
||||
|
||||
### Weekly digest
|
||||
|
||||
Automated weekly digest emails are sent every Sunday at 9:00 AM UTC. They include all posts published in the last 7 days.
|
||||
|
||||
@@ -4,6 +4,8 @@ description: "Import external articles as markdown posts using Firecrawl. Get yo
|
||||
date: "2025-12-26"
|
||||
slug: "how-to-use-firecrawl"
|
||||
published: true
|
||||
featured: true
|
||||
featuredOrder: 6
|
||||
image: /images/firecrwall-blog.png
|
||||
tags: ["tutorial", "firecrawl", "import"]
|
||||
---
|
||||
|
||||
@@ -6,6 +6,7 @@ slug: "how-to-use-mcp-server"
|
||||
image: /images/mcp-blog.png
|
||||
published: true
|
||||
blogFeatured: true
|
||||
layout: "sidebar"
|
||||
tags: ["mcp", "cursor", "ai", "tutorial", "netlify"]
|
||||
---
|
||||
|
||||
|
||||
@@ -7,12 +7,16 @@ published: true
|
||||
tags: ["dashboard", "tutorial", "content-management"]
|
||||
readTime: "8 min read"
|
||||
featured: true
|
||||
layout: "sidebar"
|
||||
featuredOrder: 2
|
||||
image: /images/dashboard.png
|
||||
excerpt: "A complete guide to using the dashboard for managing your markdown blog without leaving your browser."
|
||||
---
|
||||
|
||||
# How to use the Markdown sync dashboard
|
||||
|
||||

|
||||
|
||||
The dashboard at `/dashboard` gives you a centralized interface for managing your markdown blog. You can edit posts, sync content, configure settings, and more without switching between your editor and terminal.
|
||||
|
||||
## Accessing the dashboard
|
||||
@@ -50,6 +54,8 @@ Posts and pages display with their titles, publication status, and last modified
|
||||
|
||||
### Post and page editor
|
||||
|
||||

|
||||
|
||||
Edit markdown content with a live preview. The editor includes:
|
||||
|
||||
- Markdown editor on the left
|
||||
@@ -75,6 +81,8 @@ Write your content, fill in frontmatter fields, then download the markdown file.
|
||||
|
||||
## AI Agent
|
||||
|
||||

|
||||
|
||||
The dashboard includes a dedicated AI chat section separate from the Write page. Use it for:
|
||||
|
||||
- Writing assistance
|
||||
@@ -86,6 +94,7 @@ The AI Agent uses Anthropic Claude API and requires `ANTHROPIC_API_KEY` in your
|
||||
|
||||
## Newsletter management
|
||||
|
||||

|
||||
All Newsletter Admin features are integrated into the dashboard:
|
||||
|
||||
- Subscribers: View, search, filter, and delete subscribers
|
||||
@@ -111,6 +120,8 @@ Firecrawl import requires `FIRECRAWL_API_KEY` in your `.env.local` file.
|
||||
|
||||
## Site configuration
|
||||
|
||||

|
||||
|
||||
The Config Generator UI lets you configure all `siteConfig.ts` settings from the dashboard:
|
||||
|
||||
- Site name, title, logo, bio
|
||||
@@ -150,6 +161,8 @@ Stats update automatically via Convex subscriptions. No page refresh needed.
|
||||
|
||||
## Sync commands
|
||||
|
||||

|
||||
|
||||
Run sync operations from the dashboard without opening a terminal:
|
||||
|
||||
**Development:**
|
||||
|
||||
@@ -396,6 +396,10 @@ This image appears when sharing on social media. Recommended: 1200x630 pixels.
|
||||

|
||||
```
|
||||
|
||||
Inline images appear in the post content. Alt text is used as the caption below the image.
|
||||
|
||||
**Image lightbox:** By default, images in blog posts and pages open in a full-screen lightbox when clicked. 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. To disable this feature, set `imageLightbox.enabled: false` in `src/config/siteConfig.ts`.
|
||||
|
||||
**External Images:**
|
||||
|
||||
```markdown
|
||||
@@ -1086,6 +1090,8 @@ Pages appear automatically in the navigation when published.
|
||||
|
||||
**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. To disable this feature, set `imageLightbox.enabled: false` in `src/config/siteConfig.ts`.
|
||||
|
||||
**Footer:** Footer content can be set in frontmatter (`footer` field) or use `siteConfig.footer.defaultContent`. Control visibility globally via `siteConfig.footer.enabled` and per-page via `showFooter: true/false` frontmatter.
|
||||
|
||||
**Social footer:** Display social icons and copyright below the main footer. Configure via `siteConfig.socialFooter`. Control visibility per-page via `showSocialFooter: true/false` frontmatter.
|
||||
|
||||
@@ -8,6 +8,7 @@ tags: ["git", "convex", "ci-cd", "collaboration", "workflow"]
|
||||
readTime: "6 min read"
|
||||
image: /images/team-sync.png
|
||||
featured: false
|
||||
layout: "sidebar"
|
||||
newsletter: true
|
||||
|
||||
layout: "sidebar"
|
||||
|
||||
@@ -9,6 +9,41 @@ layout: "sidebar"
|
||||
All notable changes to this project.
|
||||

|
||||
|
||||
## v1.47.0
|
||||
|
||||
Released December 29, 2025
|
||||
|
||||
**Image lightbox for blog posts and pages**
|
||||
|
||||
- Images automatically open in full-screen lightbox when clicked (if enabled)
|
||||
- Lightbox includes dark backdrop, close button (X icon), and caption display
|
||||
- Keyboard support: Press Escape to close lightbox
|
||||
- Click outside image (backdrop) to close
|
||||
- Alt text displayed as caption below image in lightbox
|
||||
- Images show pointer cursor (`zoom-in`) and subtle hover effect when lightbox is enabled
|
||||
- Configurable via `siteConfig.imageLightbox.enabled` (default: `true`)
|
||||
- Dashboard config generator includes image lightbox toggle
|
||||
- Responsive design: lightbox adapts to mobile screens
|
||||
|
||||
**Configuration:**
|
||||
|
||||
```typescript
|
||||
// src/config/siteConfig.ts
|
||||
imageLightbox: {
|
||||
enabled: true, // Set to false to disable image lightbox
|
||||
},
|
||||
```
|
||||
|
||||
**Technical details:**
|
||||
|
||||
- New `ImageLightbox` component in `BlogPost.tsx` with backdrop, close button, and keyboard handlers
|
||||
- Lightbox state management using React hooks (`useState`, `useEffect`)
|
||||
- CSS styles for lightbox backdrop, image container, close button, and caption
|
||||
- Prevents body scrolling when lightbox is open
|
||||
- Smooth fade-in animation for lightbox appearance
|
||||
|
||||
Updated files: `src/components/BlogPost.tsx`, `src/config/siteConfig.ts`, `src/styles/global.css`, `src/pages/Dashboard.tsx`, `content/pages/docs.md`, `content/blog/setup-guide.md`
|
||||
|
||||
## v1.46.0
|
||||
|
||||
Released December 29, 2025
|
||||
|
||||
@@ -175,6 +175,8 @@ Content here...
|
||||
|
||||
**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.
|
||||
|
||||
### Home intro content
|
||||
@@ -1025,6 +1027,7 @@ When `requireAuth` is `false`, the dashboard is open access. When `requireAuth`
|
||||
### 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
|
||||
@@ -1034,6 +1037,7 @@ When `requireAuth` is `false`, the dashboard is open access. When `requireAuth`
|
||||
- WordPress-style UI with date, edit, view, and publish controls
|
||||
|
||||
**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)
|
||||
@@ -1045,6 +1049,7 @@ When `requireAuth` is `false`, the dashboard is open access. When `requireAuth`
|
||||
- Preview uses ReactMarkdown with proper styling
|
||||
|
||||
**Write Post and Write Page:**
|
||||
|
||||
- Full-screen writing interface
|
||||
- Markdown editor with word/line/character counts
|
||||
- Frontmatter reference panel
|
||||
@@ -1075,6 +1080,7 @@ All newsletter sections are full-width in the dashboard content area.
|
||||
### Content Import
|
||||
|
||||
**Firecrawl Import:**
|
||||
|
||||
- Import articles from external URLs using Firecrawl API
|
||||
- Requires `FIRECRAWL_API_KEY` in `.env.local`
|
||||
- Creates local markdown drafts in `content/blog/`
|
||||
@@ -1084,6 +1090,7 @@ All newsletter sections are full-width in the dashboard content area.
|
||||
### 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
|
||||
@@ -1100,6 +1107,7 @@ All newsletter sections are full-width in the dashboard content area.
|
||||
- And more
|
||||
|
||||
**Index HTML Editor:**
|
||||
|
||||
- View and edit `index.html` content
|
||||
- Meta tags, Open Graph, Twitter Cards, JSON-LD
|
||||
- Download updated HTML file
|
||||
@@ -1115,6 +1123,7 @@ All newsletter sections are full-width in the dashboard content area.
|
||||
### Sync Commands
|
||||
|
||||
**Sync Content Section:**
|
||||
|
||||
- UI with buttons for all sync operations
|
||||
- Development sync commands:
|
||||
- `npm run sync` - Sync markdown content
|
||||
@@ -1131,6 +1140,7 @@ All newsletter sections are full-width in the dashboard content area.
|
||||
- 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
|
||||
@@ -1140,6 +1150,7 @@ All newsletter sections are full-width in the dashboard content area.
|
||||
- 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
|
||||
@@ -1149,28 +1160,33 @@ All newsletter sections are full-width in the dashboard content area.
|
||||
### 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
|
||||
@@ -1188,19 +1204,23 @@ All newsletter sections are full-width in the dashboard content area.
|
||||
### 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)
|
||||
|
||||
**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.
|
||||
|
||||
6
files.md
@@ -34,7 +34,7 @@ A brief description of each file in the codebase.
|
||||
|
||||
| File | Description |
|
||||
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `siteConfig.ts` | Centralized site configuration (name, logo, blog page, posts display with homepage post limit and read more link, featured section with configurable title via featuredTitle, GitHub contributions, nav order, inner page logo settings, hardcoded navigation items for React routes, GitHub repository config for AI service raw URLs, font family configuration, right sidebar configuration, footer configuration with markdown support, social footer configuration, homepage configuration, AI chat configuration, newsletter configuration with admin and notifications, contact form configuration, weekly digest configuration, stats page configuration with public/private toggle, dashboard configuration with optional WorkOS authentication via requireAuth) |
|
||||
| `siteConfig.ts` | Centralized site configuration (name, logo, blog page, posts display with homepage post limit and read more link, featured section with configurable title via featuredTitle, GitHub contributions, nav order, inner page logo settings, hardcoded navigation items for React routes, GitHub repository config for AI service raw URLs, font family configuration, right sidebar configuration, footer configuration with markdown support, social footer configuration, homepage configuration, AI chat configuration, newsletter configuration with admin and notifications, contact form configuration, weekly digest configuration, stats page configuration with public/private toggle, dashboard configuration with optional WorkOS authentication via requireAuth, image lightbox configuration with enabled toggle) |
|
||||
|
||||
### Pages (`src/pages/`)
|
||||
|
||||
@@ -58,7 +58,7 @@ A brief description of each file in the codebase.
|
||||
| `ThemeToggle.tsx` | Theme switcher (dark/light/tan/cloud) |
|
||||
| `PostList.tsx` | Year-grouped blog post list or card grid (supports list/cards view modes, columns prop for 2/3 column grids, showExcerpts prop to control excerpt visibility) |
|
||||
| `BlogHeroCard.tsx` | Hero card component for the first blogFeatured post on blog page. Displays landscape image, tags, date, title, excerpt, author info, and read more link |
|
||||
| `BlogPost.tsx` | Markdown renderer with syntax highlighting, collapsible sections (details/summary), and text wrapping for plain text code blocks |
|
||||
| `BlogPost.tsx` | Markdown renderer with syntax highlighting, collapsible sections (details/summary), text wrapping for plain text code blocks, and image lightbox support (click images to magnify in full-screen overlay) |
|
||||
| `CopyPageDropdown.tsx` | Share dropdown with Copy page (markdown to clipboard), View as Markdown (opens raw .md file), Download as SKILL.md (Anthropic Agent Skills format), and Open in AI links (ChatGPT, Claude, Perplexity) using GitHub raw URLs with universal prompt |
|
||||
| `Footer.tsx` | Footer component that renders markdown content from frontmatter footer field or siteConfig.defaultContent. Can be enabled/disabled globally and per-page via frontmatter showFooter field. Renders inside article at bottom for posts/pages, and in current position on homepage. Supports images with size control via HTML attributes (width, height, style, class) |
|
||||
| `SearchModal.tsx` | Full text search modal with keyboard navigation |
|
||||
@@ -100,7 +100,7 @@ A brief description of each file in the codebase.
|
||||
|
||||
| File | Description |
|
||||
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `global.css` | Global CSS with theme variables, centralized font-size CSS variables for all themes, sidebar styling with alternate background colors, hidden scrollbar, and consistent borders using box-shadow for docs-style layout. Left sidebar (`.post-sidebar-wrapper`) and right sidebar (`.post-sidebar-right`) have separate, independent styles. Footer image styles (`.site-footer-image-wrapper`, `.site-footer-image`, `.site-footer-image-caption`) for responsive image display. Write page layout uses viewport height constraints (100vh) with overflow hidden to prevent page scroll, and AI chat uses flexbox with min-height: 0 for proper scrollable message area |
|
||||
| `global.css` | Global CSS with theme variables, centralized font-size CSS variables for all themes, sidebar styling with alternate background colors, hidden scrollbar, and consistent borders using box-shadow for docs-style layout. Left sidebar (`.post-sidebar-wrapper`) and right sidebar (`.post-sidebar-right`) have separate, independent styles. Footer image styles (`.site-footer-image-wrapper`, `.site-footer-image`, `.site-footer-image-caption`) for responsive image display. Write page layout uses viewport height constraints (100vh) with overflow hidden to prevent page scroll, and AI chat uses flexbox with min-height: 0 for proper scrollable message area. Image lightbox styles (`.image-lightbox-backdrop`, `.image-lightbox-img`, `.image-lightbox-close`, `.image-lightbox-caption`) for full-screen image magnification with backdrop, close button, and caption display |
|
||||
|
||||
## Convex Backend (`convex/`)
|
||||
|
||||
|
||||
BIN
public/images/dashboard.png
Normal file
|
After Width: | Height: | Size: 422 KiB |
BIN
public/images/dashboard1.png
Normal file
|
After Width: | Height: | Size: 265 KiB |
BIN
public/images/dashboard3.png
Normal file
|
After Width: | Height: | Size: 323 KiB |
BIN
public/images/dashboard4.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
public/images/dashboard5.png
Normal file
|
After Width: | Height: | Size: 136 KiB |
BIN
public/images/dashboard6.png
Normal file
|
After Width: | Height: | Size: 232 KiB |
BIN
public/images/dashboard7.png
Normal file
|
After Width: | Height: | Size: 224 KiB |
12
public/images/logos/workos.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg width="593" height="113" viewBox="0 0 593 113" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1155_23)">
|
||||
<path d="M171.855 16.9495H188.335L200.105 65.4453C202.224 74.6266 202.695 80.0411 202.695 80.0411H202.93C202.93 80.0411 203.637 74.6266 205.991 65.4453L217.291 16.9495H235.889L247.895 65.4453C250.249 74.862 250.72 80.0411 250.72 80.0411H250.955C250.955 80.0411 251.191 74.862 253.31 65.4453L264.61 16.9495H281.089L260.372 96.7557H242.245L229.768 48.4953C227.178 37.9016 226.943 33.4286 226.943 33.4286H226.708C226.708 33.4286 226.472 38.137 224.118 48.4953L212.347 96.7557H193.514C193.043 96.7557 171.855 16.9495 171.855 16.9495ZM278.264 67.7995C278.264 49.6724 290.035 37.6661 307.926 37.6661C325.583 37.6661 337.353 49.437 337.353 67.7995C337.353 86.162 325.583 97.9328 307.926 97.9328C290.035 97.9328 278.264 86.162 278.264 67.7995ZM322.051 67.7995C322.051 56.0286 316.401 49.437 307.926 49.437C298.745 49.437 293.566 56.9703 293.566 67.7995C293.566 79.8057 299.216 86.3974 307.926 86.3974C316.872 86.3974 322.051 79.0995 322.051 67.7995ZM344.651 38.6078H359.247V49.437H359.483C362.072 43.787 367.958 38.3724 378.316 38.3724C379.964 38.3724 381.141 38.6078 381.847 38.8432V53.4391H381.376C381.376 53.4391 379.964 52.9682 376.433 52.9682C365.133 52.9682 359.012 59.5599 359.012 72.037V96.9912H344.416V38.6078H344.651ZM388.674 16.9495H403.27V42.6099C403.27 57.6766 403.035 60.5016 403.035 60.5016H403.27L424.928 38.8432H443.055L417.63 64.0328L447.058 96.9912H429.872L408.685 72.9786L403.27 78.3932V96.9912H388.439L388.674 16.9495ZM447.058 57.2057C447.058 32.7224 462.36 16.2432 485.195 16.2432C508.03 16.2432 523.333 32.7224 523.333 57.2057C523.333 81.6891 508.03 98.1682 485.195 98.1682C462.36 98.1682 447.058 81.6891 447.058 57.2057ZM507.795 57.2057C507.795 40.4911 498.849 29.1911 485.43 29.1911C472.012 29.1911 463.066 40.4911 463.066 57.2057C463.066 73.9203 472.012 85.2203 485.43 85.2203C498.614 85.2203 507.795 73.9203 507.795 57.2057ZM528.983 70.1536H545.697C545.697 79.5703 552.053 84.7495 561.941 84.7495C570.18 84.7495 575.83 80.512 575.83 74.6266C575.83 68.0349 571.358 66.1516 557.703 63.562C544.991 60.9724 531.572 56.7349 531.572 40.2557C531.572 26.3661 543.343 16.0078 561.47 16.0078C580.303 16.0078 591.839 25.8953 591.839 40.7266H575.124C575.124 33.4286 569.474 28.9557 561.47 28.9557C553.23 28.9557 548.051 32.9578 548.051 38.8432C548.051 44.9641 551.583 47.5536 562.883 49.6724C579.362 53.2036 593.016 55.087 593.016 73.4495C593.016 88.0453 580.303 97.6974 561.705 97.6974C542.401 97.6974 528.983 86.8682 528.983 70.1536Z" fill="#666666"/>
|
||||
<path d="M0 56.5C0 59.0896 0.70625 61.4438 1.88333 63.5625L24.7188 103.113C27.0729 107.115 30.6042 110.41 35.0771 111.823C43.5521 114.648 52.7333 111.117 57.2063 103.583L62.6208 93.9313L40.9625 56.2646L64.0333 16.4792L69.4479 7.0625C71.0958 4.2375 73.2146 1.88333 75.8042 0H40.4917C34.3708 0 28.4854 3.29583 25.425 8.71042L1.88333 49.4375C0.70625 51.5563 0 53.9104 0 56.5Z" fill="#666666"/>
|
||||
<path d="M130.417 56.5003C130.417 53.9107 129.711 51.5565 128.534 49.4378L105.463 9.41695C100.99 1.6482 91.8091 -1.64763 83.3341 1.17737C78.8612 2.58987 75.3299 5.8857 72.9758 9.88778L67.5612 18.8336L89.4549 56.5003L66.3841 96.2857L60.9695 105.938C59.3216 108.763 57.2029 111.117 54.6133 113H90.1612C96.282 113 102.167 109.704 105.228 104.29L128.77 63.5628C129.711 61.444 130.417 59.0899 130.417 56.5003Z" fill="#666666"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1155_23">
|
||||
<rect width="592.779" height="113" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
BIN
public/images/workos.png
Normal file
|
After Width: | Height: | Size: 418 KiB |
@@ -1,6 +1,6 @@
|
||||
# llms.txt - Information for AI assistants and LLMs
|
||||
# Learn more: https://llmstxt.org/
|
||||
# Last updated: 2025-12-29T21:11:54.408Z
|
||||
# Last updated: 2025-12-30T06:25:18.541Z
|
||||
|
||||
> Your content is instantly available to browsers, LLMs, and AI agents.
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
- URL: https://markdown.fast
|
||||
- Description: Your content is instantly available to browsers, LLMs, and AI agents. Write markdown, sync from the terminal. Your content is instantly available to browsers, LLMs, and AI agents. Built on Convex and Netlify.
|
||||
- Topics: Markdown, Convex, React, TypeScript, Netlify, Open Source, AI, LLM, AEO, GEO
|
||||
- Total Posts: 15
|
||||
- Total Posts: 17
|
||||
- Latest Post: 2025-12-29
|
||||
- GitHub: https://github.com/waynesutton/markdown-site
|
||||
|
||||
|
||||
@@ -8,6 +8,41 @@ Date: 2025-12-30
|
||||
All notable changes to this project.
|
||||

|
||||
|
||||
## v1.47.0
|
||||
|
||||
Released December 29, 2025
|
||||
|
||||
**Image lightbox for blog posts and pages**
|
||||
|
||||
- Images automatically open in full-screen lightbox when clicked (if enabled)
|
||||
- Lightbox includes dark backdrop, close button (X icon), and caption display
|
||||
- Keyboard support: Press Escape to close lightbox
|
||||
- Click outside image (backdrop) to close
|
||||
- Alt text displayed as caption below image in lightbox
|
||||
- Images show pointer cursor (`zoom-in`) and subtle hover effect when lightbox is enabled
|
||||
- Configurable via `siteConfig.imageLightbox.enabled` (default: `true`)
|
||||
- Dashboard config generator includes image lightbox toggle
|
||||
- Responsive design: lightbox adapts to mobile screens
|
||||
|
||||
**Configuration:**
|
||||
|
||||
```typescript
|
||||
// src/config/siteConfig.ts
|
||||
imageLightbox: {
|
||||
enabled: true, // Set to false to disable image lightbox
|
||||
},
|
||||
```
|
||||
|
||||
**Technical details:**
|
||||
|
||||
- New `ImageLightbox` component in `BlogPost.tsx` with backdrop, close button, and keyboard handlers
|
||||
- Lightbox state management using React hooks (`useState`, `useEffect`)
|
||||
- CSS styles for lightbox backdrop, image container, close button, and caption
|
||||
- Prevents body scrolling when lightbox is open
|
||||
- Smooth fade-in animation for lightbox appearance
|
||||
|
||||
Updated files: `src/components/BlogPost.tsx`, `src/config/siteConfig.ts`, `src/styles/global.css`, `src/pages/Dashboard.tsx`, `content/pages/docs.md`, `content/blog/setup-guide.md`
|
||||
|
||||
## v1.46.0
|
||||
|
||||
Released December 29, 2025
|
||||
|
||||
@@ -171,6 +171,8 @@ Content here...
|
||||
|
||||
**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.
|
||||
|
||||
### Home intro content
|
||||
@@ -1021,6 +1023,7 @@ When `requireAuth` is `false`, the dashboard is open access. When `requireAuth`
|
||||
### 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
|
||||
@@ -1030,6 +1033,7 @@ When `requireAuth` is `false`, the dashboard is open access. When `requireAuth`
|
||||
- WordPress-style UI with date, edit, view, and publish controls
|
||||
|
||||
**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)
|
||||
@@ -1041,6 +1045,7 @@ When `requireAuth` is `false`, the dashboard is open access. When `requireAuth`
|
||||
- Preview uses ReactMarkdown with proper styling
|
||||
|
||||
**Write Post and Write Page:**
|
||||
|
||||
- Full-screen writing interface
|
||||
- Markdown editor with word/line/character counts
|
||||
- Frontmatter reference panel
|
||||
@@ -1071,6 +1076,7 @@ All newsletter sections are full-width in the dashboard content area.
|
||||
### Content Import
|
||||
|
||||
**Firecrawl Import:**
|
||||
|
||||
- Import articles from external URLs using Firecrawl API
|
||||
- Requires `FIRECRAWL_API_KEY` in `.env.local`
|
||||
- Creates local markdown drafts in `content/blog/`
|
||||
@@ -1080,6 +1086,7 @@ All newsletter sections are full-width in the dashboard content area.
|
||||
### 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
|
||||
@@ -1096,6 +1103,7 @@ All newsletter sections are full-width in the dashboard content area.
|
||||
- And more
|
||||
|
||||
**Index HTML Editor:**
|
||||
|
||||
- View and edit `index.html` content
|
||||
- Meta tags, Open Graph, Twitter Cards, JSON-LD
|
||||
- Download updated HTML file
|
||||
@@ -1111,6 +1119,7 @@ All newsletter sections are full-width in the dashboard content area.
|
||||
### Sync Commands
|
||||
|
||||
**Sync Content Section:**
|
||||
|
||||
- UI with buttons for all sync operations
|
||||
- Development sync commands:
|
||||
- `npm run sync` - Sync markdown content
|
||||
@@ -1127,6 +1136,7 @@ All newsletter sections are full-width in the dashboard content area.
|
||||
- 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
|
||||
@@ -1136,6 +1146,7 @@ All newsletter sections are full-width in the dashboard content area.
|
||||
- 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
|
||||
@@ -1145,28 +1156,33 @@ All newsletter sections are full-width in the dashboard content area.
|
||||
### 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
|
||||
@@ -1184,19 +1200,23 @@ All newsletter sections are full-width in the dashboard content area.
|
||||
### 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)
|
||||
|
||||
**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.
|
||||
|
||||
@@ -64,6 +64,7 @@ Before starting, make sure you have:
|
||||
During the AuthKit setup wizard, you'll reach step 4: **"Add default redirect endpoint URI"**
|
||||
|
||||
Enter this for local development:
|
||||
|
||||
```
|
||||
http://localhost:5173/callback
|
||||
```
|
||||
@@ -108,10 +109,10 @@ npm install @workos-inc/authkit-react @convex-dev/workos
|
||||
|
||||
**What these packages do:**
|
||||
|
||||
| Package | Purpose |
|
||||
|---------|---------|
|
||||
| Package | Purpose |
|
||||
| --------------------------- | ------------------------------------------ |
|
||||
| `@workos-inc/authkit-react` | WorkOS React SDK for handling login/logout |
|
||||
| `@convex-dev/workos` | Bridges WorkOS auth with Convex backend |
|
||||
| `@convex-dev/workos` | Bridges WorkOS auth with Convex backend |
|
||||
|
||||
## Step 4: Add environment variables
|
||||
|
||||
@@ -205,6 +206,7 @@ When `requireAuth` is `true` and WorkOS is configured, the dashboard requires lo
|
||||
## Step 7: Test locally
|
||||
|
||||
1. Start your development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
@@ -271,21 +273,25 @@ When WorkOS is not configured or `requireAuth: false`:
|
||||
## Troubleshooting
|
||||
|
||||
**Dashboard shows "Authentication Required" message:**
|
||||
|
||||
- Verify `VITE_WORKOS_CLIENT_ID` and `VITE_WORKOS_REDIRECT_URI` are set in `.env.local`
|
||||
- Check that `WORKOS_CLIENT_ID` is set in Convex environment variables
|
||||
- Ensure `requireAuth: true` in `siteConfig.ts`
|
||||
|
||||
**Login redirect not working:**
|
||||
|
||||
- Verify redirect URI matches exactly in WorkOS Dashboard
|
||||
- Check CORS settings include your domain
|
||||
- Ensure callback route is configured in `App.tsx`
|
||||
|
||||
**"WorkOS is not configured" message:**
|
||||
|
||||
- Check that both `VITE_WORKOS_CLIENT_ID` and `VITE_WORKOS_REDIRECT_URI` are set
|
||||
- Verify environment variables are loaded (check browser console)
|
||||
- Restart development server after adding environment variables
|
||||
|
||||
**Convex auth errors:**
|
||||
|
||||
- Verify `WORKOS_CLIENT_ID` is set in Convex environment variables
|
||||
- Check that `convex/auth.config.ts` exists and is correct
|
||||
- Ensure Convex functions are deployed (`npx convex deploy`)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
---
|
||||
Type: post
|
||||
Date: 2025-12-27
|
||||
Reading time: 5 min read
|
||||
Reading time: 6 min read
|
||||
Tags: agentmail, newsletter, email, setup
|
||||
---
|
||||
|
||||
@@ -100,6 +100,72 @@ npm run newsletter:send:stats
|
||||
3. Choose a post or compose a custom email
|
||||
4. Click "Send Newsletter"
|
||||
|
||||
### Customizing email footers
|
||||
|
||||
All newsletter emails include a footer with unsubscribe information. Edit the footer text in `convex/newsletterActions.ts`.
|
||||
|
||||
The footer appears in three functions:
|
||||
|
||||
**1. Post newsletter (`sendPostNewsletter`)**
|
||||
|
||||
HTML footer (lines 138-141):
|
||||
|
||||
```typescript
|
||||
<p style="font-size: 12px; color: #888;">
|
||||
You received this email because you subscribed to ${escapeHtml(siteName)}.<br />
|
||||
<a href="${unsubscribeUrl}" style="color: #888;">Unsubscribe</a>
|
||||
</p>
|
||||
```
|
||||
|
||||
Plain text footer (line 146):
|
||||
|
||||
```typescript
|
||||
const text = `${post.title}\n\n${post.description}\n\nRead more: ${postUrl}\n\n---\nUnsubscribe: ${unsubscribeUrl}`;
|
||||
```
|
||||
|
||||
**2. Weekly digest (`sendWeeklyDigest`)**
|
||||
|
||||
HTML footer (lines 295-298):
|
||||
|
||||
```typescript
|
||||
<p style="font-size: 12px; color: #888;">
|
||||
You received this email because you subscribed to ${escapeHtml(siteName)}.<br />
|
||||
<a href="${unsubscribeUrl}" style="color: #888;">Unsubscribe</a>
|
||||
</p>
|
||||
```
|
||||
|
||||
Plain text footer (line 302):
|
||||
|
||||
```typescript
|
||||
const text = `Weekly Digest - ${recentPosts.length} new post${recentPosts.length > 1 ? "s" : ""}\n\n${postsText}\n\n---\nUnsubscribe: ${unsubscribeUrl}`;
|
||||
```
|
||||
|
||||
**3. Custom newsletter (`sendCustomNewsletter`)**
|
||||
|
||||
HTML footer (lines 512-515):
|
||||
|
||||
```typescript
|
||||
<p style="font-size: 12px; color: #888;">
|
||||
You received this email because you subscribed to ${escapeHtml(siteName)}.<br />
|
||||
<a href="${unsubscribeUrl}" style="color: #888;">Unsubscribe</a>
|
||||
</p>
|
||||
```
|
||||
|
||||
Plain text footer (line 519):
|
||||
|
||||
```typescript
|
||||
const text = `${contentText}\n\n---\nUnsubscribe: ${unsubscribeUrl}`;
|
||||
```
|
||||
|
||||
**Customization options:**
|
||||
|
||||
- Change the message text ("You received this email because...")
|
||||
- Modify the unsubscribe link text ("Unsubscribe")
|
||||
- Adjust styling (font size, color, spacing)
|
||||
- Add additional footer content
|
||||
|
||||
Edit all three locations to keep footers consistent across email types. The unsubscribe URL is automatically generated and must remain in the footer for compliance.
|
||||
|
||||
### Weekly digest
|
||||
|
||||
Automated weekly digest emails are sent every Sunday at 9:00 AM UTC. They include all posts published in the last 7 days.
|
||||
|
||||
@@ -11,6 +11,8 @@ Tags: dashboard, tutorial, content-management
|
||||
|
||||
# How to use the Markdown sync dashboard
|
||||
|
||||

|
||||
|
||||
The dashboard at `/dashboard` gives you a centralized interface for managing your markdown blog. You can edit posts, sync content, configure settings, and more without switching between your editor and terminal.
|
||||
|
||||
## Accessing the dashboard
|
||||
@@ -48,6 +50,8 @@ Posts and pages display with their titles, publication status, and last modified
|
||||
|
||||
### Post and page editor
|
||||
|
||||

|
||||
|
||||
Edit markdown content with a live preview. The editor includes:
|
||||
|
||||
- Markdown editor on the left
|
||||
@@ -73,6 +77,8 @@ Write your content, fill in frontmatter fields, then download the markdown file.
|
||||
|
||||
## AI Agent
|
||||
|
||||

|
||||
|
||||
The dashboard includes a dedicated AI chat section separate from the Write page. Use it for:
|
||||
|
||||
- Writing assistance
|
||||
@@ -84,6 +90,7 @@ The AI Agent uses Anthropic Claude API and requires `ANTHROPIC_API_KEY` in your
|
||||
|
||||
## Newsletter management
|
||||
|
||||

|
||||
All Newsletter Admin features are integrated into the dashboard:
|
||||
|
||||
- Subscribers: View, search, filter, and delete subscribers
|
||||
@@ -109,6 +116,8 @@ Firecrawl import requires `FIRECRAWL_API_KEY` in your `.env.local` file.
|
||||
|
||||
## Site configuration
|
||||
|
||||

|
||||
|
||||
The Config Generator UI lets you configure all `siteConfig.ts` settings from the dashboard:
|
||||
|
||||
- Site name, title, logo, bio
|
||||
@@ -148,6 +157,8 @@ Stats update automatically via Convex subscriptions. No page refresh needed.
|
||||
|
||||
## Sync commands
|
||||
|
||||

|
||||
|
||||
Run sync operations from the dashboard without opening a terminal:
|
||||
|
||||
**Development:**
|
||||
|
||||
@@ -2,18 +2,16 @@
|
||||
|
||||
This is the homepage index of all published content.
|
||||
|
||||
## Blog Posts (17)
|
||||
## Blog Posts (16)
|
||||
|
||||
- **[How to setup WorkOS](/raw/how-to-setup-workos.md)** - Step-by-step guide to configure WorkOS AuthKit authentication for your markdown blog dashboard. WorkOS is optional and can be enabled in siteConfig.ts.
|
||||
- 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.
|
||||
- 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
|
||||
- **[How to use AgentMail with Markdown Sync](/raw/how-to-use-agentmail.md)** - Complete guide to setting up AgentMail for newsletters and contact forms in your markdown blog
|
||||
- Date: 2025-12-27 | Reading time: 5 min read | Tags: agentmail, newsletter, email, setup
|
||||
- Date: 2025-12-27 | Reading time: 6 min read | Tags: agentmail, newsletter, email, setup
|
||||
- **[How to use Firecrawl](/raw/how-to-use-firecrawl.md)** - Import external articles as markdown posts using Firecrawl. Get your API key and configure environment variables for local imports and AI chat.
|
||||
- Date: 2025-12-26 | Reading time: 2 min read | Tags: tutorial, firecrawl, import
|
||||
- **[Happy holidays and thank you](/raw/happy-holidays-2025.md)** - A quick note of thanks for stars, forks, and feedback. More AI-first publishing features coming in 2026.
|
||||
@@ -51,6 +49,6 @@ This is the homepage index of all published content.
|
||||
|
||||
---
|
||||
|
||||
**Total Content:** 17 posts, 7 pages
|
||||
**Total Content:** 16 posts, 7 pages
|
||||
|
||||
All content is available as raw markdown files at `/raw/{slug}.md`
|
||||
|
||||
@@ -389,6 +389,10 @@ This image appears when sharing on social media. Recommended: 1200x630 pixels.
|
||||

|
||||
```
|
||||
|
||||
Inline images appear in the post content. Alt text is used as the caption below the image.
|
||||
|
||||
**Image lightbox:** By default, images in blog posts and pages open in a full-screen lightbox when clicked. 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. To disable this feature, set `imageLightbox.enabled: false` in `src/config/siteConfig.ts`.
|
||||
|
||||
**External Images:**
|
||||
|
||||
```markdown
|
||||
@@ -1079,6 +1083,8 @@ Pages appear automatically in the navigation when published.
|
||||
|
||||
**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. To disable this feature, set `imageLightbox.enabled: false` in `src/config/siteConfig.ts`.
|
||||
|
||||
**Footer:** Footer content can be set in frontmatter (`footer` field) or use `siteConfig.footer.defaultContent`. Control visibility globally via `siteConfig.footer.enabled` and per-page via `showFooter: true/false` frontmatter.
|
||||
|
||||
**Social footer:** Display social icons and copyright below the main footer. Configure via `siteConfig.socialFooter`. Control visibility per-page via `showSocialFooter: true/false` frontmatter.
|
||||
|
||||
@@ -1,257 +0,0 @@
|
||||
# Team Workflows with Git Version Control
|
||||
|
||||
> How teams collaborate on markdown content using git, sync to shared Convex deployments, and automate production syncs with CI/CD.
|
||||
|
||||
---
|
||||
Type: post
|
||||
Date: 2025-12-29
|
||||
Reading time: 6 min read
|
||||
Tags: git, convex, ci-cd, collaboration, workflow
|
||||
---
|
||||
|
||||
# Team Workflows with Git Version Control
|
||||
|
||||
Teams use this markdown framework by treating markdown files as source code. Content lives in git, gets reviewed via pull requests, and syncs to Convex deployments for instant previews and production updates.
|
||||
|
||||
Here's how it works in practice.
|
||||
|
||||
## Initial Setup
|
||||
|
||||
Each team member clones the repo and sets up their own development environment:
|
||||
|
||||
```bash
|
||||
# Clone the repo
|
||||
git clone https://github.com/your-org/markdown-site.git
|
||||
cd markdown-site
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Initialize Convex (creates .env.local with dev deployment URL)
|
||||
npx convex dev
|
||||
|
||||
# Start dev server
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Each developer gets their own Convex dev deployment. The `.env.local` file contains a unique development URL and stays gitignored. This means everyone can preview changes locally without affecting others.
|
||||
|
||||
## Git Version Control
|
||||
|
||||
Markdown files are regular files in your git repository. Teams commit, push, and review content changes like any other code.
|
||||
|
||||
```bash
|
||||
# Team member writes a new post
|
||||
# Creates: content/blog/my-new-post.md
|
||||
|
||||
# Commit to git
|
||||
git add content/blog/my-new-post.md
|
||||
git commit -m "Add new blog post"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
The markdown files in `content/blog/` and `content/pages/` are the source of truth. They live in git, get reviewed via pull requests, and can be rolled back like any codebase.
|
||||
|
||||
This approach gives you:
|
||||
|
||||
- Full version history for all content
|
||||
- Pull request reviews before publishing
|
||||
- Easy rollbacks when needed
|
||||
- Branch-based workflows for drafts
|
||||
- Conflict resolution through git merge tools
|
||||
|
||||
## Syncing to Convex
|
||||
|
||||
The sync script reads markdown files from your local filesystem and uploads them to Convex. Development and production use separate deployments.
|
||||
|
||||
**Development (each developer):**
|
||||
|
||||
```bash
|
||||
# After pulling latest changes from git
|
||||
git pull origin main
|
||||
|
||||
# Sync markdown files to YOUR dev Convex
|
||||
npm run sync
|
||||
```
|
||||
|
||||
**Production (shared deployment):**
|
||||
|
||||
```bash
|
||||
# One person (or CI/CD) syncs to production
|
||||
npm run sync:prod
|
||||
```
|
||||
|
||||
The sync script:
|
||||
|
||||
1. Reads all `.md` files from `content/blog/` and `content/pages/`
|
||||
2. Parses frontmatter using `gray-matter`
|
||||
3. Uploads to Convex via `api.posts.syncPostsPublic` mutation
|
||||
4. Generates static files in `public/raw/` for AI access
|
||||
|
||||
Sync is idempotent. Running it multiple times is safe. The mutation updates posts by slug, so the last sync wins.
|
||||
|
||||
## Environment Files
|
||||
|
||||
Two environment files control which Convex deployment receives your content:
|
||||
|
||||
| File | Purpose | Git Status | Who Has It |
|
||||
| ----------------------- | --------------- | ---------- | ------------------------------- |
|
||||
| `.env.local` | Dev Convex URL | Gitignored | Each developer (different URLs) |
|
||||
| `.env.production.local` | Prod Convex URL | Gitignored | Team shares same URL |
|
||||
|
||||
**Team setup:**
|
||||
|
||||
1. Create production Convex deployment: `npx convex deploy`
|
||||
2. Share the production URL with team
|
||||
3. Each developer creates `.env.production.local`:
|
||||
```
|
||||
VITE_CONVEX_URL=https://your-prod-deployment.convex.cloud
|
||||
```
|
||||
|
||||
Each developer maintains their own dev environment while sharing the production deployment URL.
|
||||
|
||||
## Netlify Deployment
|
||||
|
||||
Netlify connects to your GitHub repo and auto-deploys on every push.
|
||||
|
||||
**Netlify Build Settings:**
|
||||
|
||||
- Build command: `npm ci --include=dev && npx convex deploy --cmd 'npm run build'`
|
||||
- Publish directory: `dist`
|
||||
|
||||
**Netlify Environment Variables:**
|
||||
|
||||
- `CONVEX_DEPLOY_KEY` - Deploys Convex functions at build time
|
||||
- `VITE_CONVEX_URL` - Production Convex URL (same as `.env.production.local`)
|
||||
|
||||
**Workflow:**
|
||||
|
||||
1. Team pushes markdown to GitHub
|
||||
2. Netlify auto-builds and deploys
|
||||
3. Site updates automatically (Convex functions deploy, frontend rebuilds)
|
||||
4. Content sync happens separately via `npm run sync:prod`
|
||||
|
||||
Netlify handles frontend deployment. Content sync is a separate step that updates the Convex database.
|
||||
|
||||
## Complete Team Workflow
|
||||
|
||||
Here's what happens when a team member adds a new blog post:
|
||||
|
||||
```bash
|
||||
# 1. Create markdown file locally
|
||||
# content/blog/team-update.md
|
||||
|
||||
# 2. Commit to git
|
||||
git add content/blog/team-update.md
|
||||
git commit -m "Add team update post"
|
||||
git push origin main
|
||||
|
||||
# 3. Sync to dev Convex (for local preview)
|
||||
npm run sync
|
||||
|
||||
# 4. Netlify auto-deploys from GitHub (frontend rebuilds)
|
||||
|
||||
# 5. Sync to production Convex (one person or CI/CD)
|
||||
npm run sync:prod
|
||||
```
|
||||
|
||||
**Result:**
|
||||
|
||||
- Markdown file is in git (version controlled)
|
||||
- Dev Convex has the post (local preview)
|
||||
- Production Convex has the post (live site)
|
||||
- Netlify site is rebuilt (frontend code)
|
||||
|
||||
## CI/CD Automation
|
||||
|
||||
You can automate production sync with GitHub Actions. This ensures content updates happen automatically when markdown files change.
|
||||
|
||||
Create `.github/workflows/sync-production.yml`:
|
||||
|
||||
```yaml
|
||||
name: Sync to Production
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "content/**"
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "18"
|
||||
- run: npm install
|
||||
- run: npm run sync:prod
|
||||
env:
|
||||
VITE_CONVEX_URL: ${{ secrets.PROD_CONVEX_URL }}
|
||||
```
|
||||
|
||||
**Setup:**
|
||||
|
||||
1. Add `PROD_CONVEX_URL` to GitHub Secrets (Settings > Secrets and variables > Actions)
|
||||
2. Push the workflow file to your repo
|
||||
3. Every push to `main` that changes files in `content/` triggers a production sync
|
||||
|
||||
**Benefits:**
|
||||
|
||||
- No manual sync step required
|
||||
- Content updates automatically after git push
|
||||
- Consistent production state
|
||||
- Works for all team members
|
||||
|
||||
**Optional: Sync discovery files too**
|
||||
|
||||
If you want to update discovery files (`AGENTS.md`, `llms.txt`) automatically:
|
||||
|
||||
```yaml
|
||||
- run: npm run sync:all:prod
|
||||
env:
|
||||
VITE_CONVEX_URL: ${{ secrets.PROD_CONVEX_URL }}
|
||||
```
|
||||
|
||||
This syncs both content and discovery files in one step.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
The system separates concerns across four layers:
|
||||
|
||||
**Git:** Source of truth for markdown files (version controlled)
|
||||
|
||||
**Convex Dev:** Each developer's local preview database
|
||||
|
||||
**Convex Prod:** Shared production database
|
||||
|
||||
**Netlify:** Frontend hosting (auto-deploys from git)
|
||||
|
||||
**Why this works:**
|
||||
|
||||
- Markdown files are plain text (great for git diffs)
|
||||
- Convex sync is instant (no rebuild needed for content changes)
|
||||
- Netlify handles frontend deployment (code changes trigger rebuilds)
|
||||
- Each developer can preview locally without affecting others
|
||||
|
||||
## Team Collaboration Best Practices
|
||||
|
||||
**Content changes:** Edit markdown → commit → push → sync to prod
|
||||
|
||||
**Code changes:** Edit React/TypeScript → commit → push → Netlify auto-deploys
|
||||
|
||||
**Config changes:** Edit `siteConfig.ts` → commit → push → Netlify rebuilds
|
||||
|
||||
**Convex schema changes:** Edit `convex/schema.ts` → commit → `npx convex deploy`
|
||||
|
||||
**Conflict resolution:**
|
||||
|
||||
- Git handles markdown file conflicts (merge/rebase)
|
||||
- Convex sync is idempotent (safe to run multiple times)
|
||||
- Last sync wins for content (Convex mutations update by slug)
|
||||
|
||||
## Summary
|
||||
|
||||
Teams collaborate on markdown content through git version control. Each developer maintains a local dev environment for previews, while production syncs happen automatically via CI/CD or manual commands. Netlify handles frontend deployment separately from content updates.
|
||||
|
||||
The workflow gives you version control, pull request reviews, and instant previews without sacrificing the real-time sync that makes content updates immediate.
|
||||
@@ -5,7 +5,7 @@ import remarkBreaks from "remark-breaks";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { Copy, Check } from "lucide-react";
|
||||
import { Copy, Check, X } from "lucide-react";
|
||||
import { useTheme } from "../context/ThemeContext";
|
||||
import NewsletterSignup from "./NewsletterSignup";
|
||||
import ContactForm from "./ContactForm";
|
||||
@@ -46,6 +46,53 @@ function CodeCopyButton({ code }: { code: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
// Image lightbox component
|
||||
function ImageLightbox({
|
||||
src,
|
||||
alt,
|
||||
onClose,
|
||||
}: {
|
||||
src: string;
|
||||
alt: string;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const handleBackdropClick = (e: React.MouseEvent) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", handleEscape);
|
||||
document.body.style.overflow = "hidden";
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleEscape);
|
||||
document.body.style.overflow = "";
|
||||
};
|
||||
}, [onClose]);
|
||||
|
||||
return (
|
||||
<div className="image-lightbox-backdrop" onClick={handleBackdropClick}>
|
||||
<button
|
||||
className="image-lightbox-close"
|
||||
onClick={onClose}
|
||||
aria-label="Close lightbox"
|
||||
>
|
||||
<X size={24} />
|
||||
</button>
|
||||
<div className="image-lightbox-content">
|
||||
<img src={src} alt={alt} className="image-lightbox-image" />
|
||||
{alt && <div className="image-lightbox-caption">{alt}</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Cursor Dark Theme colors for syntax highlighting
|
||||
const cursorDarkTheme: { [key: string]: React.CSSProperties } = {
|
||||
'code[class*="language-"]': {
|
||||
@@ -396,6 +443,8 @@ function HeadingAnchor({ id }: { id: string }) {
|
||||
|
||||
export default function BlogPost({ content, slug, pageType = "post" }: BlogPostProps) {
|
||||
const { theme } = useTheme();
|
||||
const [lightboxImage, setLightboxImage] = useState<{ src: string; alt: string } | null>(null);
|
||||
const isLightboxEnabled = siteConfig.imageLightbox?.enabled !== false;
|
||||
|
||||
const getCodeTheme = () => {
|
||||
switch (theme) {
|
||||
@@ -479,13 +528,20 @@ export default function BlogPost({ content, slug, pageType = "post" }: BlogPostP
|
||||
);
|
||||
},
|
||||
img({ src, alt }) {
|
||||
const handleImageClick = () => {
|
||||
if (isLightboxEnabled && src) {
|
||||
setLightboxImage({ src, alt: alt || "" });
|
||||
}
|
||||
};
|
||||
return (
|
||||
<span className="blog-image-wrapper">
|
||||
<img
|
||||
src={src}
|
||||
alt={alt || ""}
|
||||
className="blog-image"
|
||||
className={`blog-image ${isLightboxEnabled ? "blog-image-clickable" : ""}`}
|
||||
loading="lazy"
|
||||
onClick={isLightboxEnabled ? handleImageClick : undefined}
|
||||
style={isLightboxEnabled ? { cursor: "pointer" } : undefined}
|
||||
/>
|
||||
{alt && <span className="blog-image-caption">{alt}</span>}
|
||||
</span>
|
||||
@@ -611,41 +667,51 @@ export default function BlogPost({ content, slug, pageType = "post" }: BlogPostP
|
||||
// Render with inline embeds if placeholders exist
|
||||
if (hasInlineEmbeds) {
|
||||
return (
|
||||
<article className="blog-post-content">
|
||||
{segments.map((segment, index) => {
|
||||
if (segment.type === "newsletter") {
|
||||
// Newsletter signup inline
|
||||
return siteConfig.newsletter?.enabled ? (
|
||||
<NewsletterSignup
|
||||
key={`newsletter-${index}`}
|
||||
source={pageType === "page" ? "post" : "post"}
|
||||
postSlug={slug}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
if (segment.type === "contactform") {
|
||||
// Contact form inline
|
||||
return siteConfig.contactForm?.enabled ? (
|
||||
<ContactForm
|
||||
key={`contactform-${index}`}
|
||||
source={source}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
// Markdown content segment
|
||||
return renderMarkdown(segment.value, index);
|
||||
})}
|
||||
</article>
|
||||
<>
|
||||
<article className="blog-post-content">
|
||||
{segments.map((segment, index) => {
|
||||
if (segment.type === "newsletter") {
|
||||
// Newsletter signup inline
|
||||
return siteConfig.newsletter?.enabled ? (
|
||||
<NewsletterSignup
|
||||
key={`newsletter-${index}`}
|
||||
source={pageType === "page" ? "post" : "post"}
|
||||
postSlug={slug}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
if (segment.type === "contactform") {
|
||||
// Contact form inline
|
||||
return siteConfig.contactForm?.enabled ? (
|
||||
<ContactForm
|
||||
key={`contactform-${index}`}
|
||||
source={source}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
// Markdown content segment
|
||||
return renderMarkdown(segment.value, index);
|
||||
})}
|
||||
</article>
|
||||
{lightboxImage && (
|
||||
<ImageLightbox
|
||||
src={lightboxImage.src}
|
||||
alt={lightboxImage.alt}
|
||||
onClose={() => setLightboxImage(null)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// No inline embeds, render content normally
|
||||
return (
|
||||
<article className="blog-post-content">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm, remarkBreaks]}
|
||||
rehypePlugins={[rehypeRaw, [rehypeSanitize, sanitizeSchema]]}
|
||||
components={{
|
||||
<>
|
||||
<article className="blog-post-content">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm, remarkBreaks]}
|
||||
rehypePlugins={[rehypeRaw, [rehypeSanitize, sanitizeSchema]]}
|
||||
components={{
|
||||
code(codeProps) {
|
||||
const { className, children, node, style, ...restProps } = codeProps as {
|
||||
className?: string;
|
||||
@@ -702,13 +768,20 @@ export default function BlogPost({ content, slug, pageType = "post" }: BlogPostP
|
||||
);
|
||||
},
|
||||
img({ src, alt }) {
|
||||
const handleImageClick = () => {
|
||||
if (isLightboxEnabled && src) {
|
||||
setLightboxImage({ src, alt: alt || "" });
|
||||
}
|
||||
};
|
||||
return (
|
||||
<span className="blog-image-wrapper">
|
||||
<img
|
||||
src={src}
|
||||
alt={alt || ""}
|
||||
className="blog-image"
|
||||
className={`blog-image ${isLightboxEnabled ? "blog-image-clickable" : ""}`}
|
||||
loading="lazy"
|
||||
onClick={isLightboxEnabled ? handleImageClick : undefined}
|
||||
style={isLightboxEnabled ? { cursor: "pointer" } : undefined}
|
||||
/>
|
||||
{alt && <span className="blog-image-caption">{alt}</span>}
|
||||
</span>
|
||||
@@ -821,10 +894,18 @@ export default function BlogPost({ content, slug, pageType = "post" }: BlogPostP
|
||||
td({ children }) {
|
||||
return <td className="blog-td">{children}</td>;
|
||||
},
|
||||
}}
|
||||
>
|
||||
{cleanedContent}
|
||||
</ReactMarkdown>
|
||||
</article>
|
||||
}}
|
||||
>
|
||||
{cleanedContent}
|
||||
</ReactMarkdown>
|
||||
</article>
|
||||
{lightboxImage && (
|
||||
<ImageLightbox
|
||||
src={lightboxImage.src}
|
||||
alt={lightboxImage.alt}
|
||||
onClose={() => setLightboxImage(null)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -197,6 +197,12 @@ export interface DashboardConfig {
|
||||
requireAuth: boolean; // Require WorkOS authentication (only works if WorkOS is configured)
|
||||
}
|
||||
|
||||
// Image lightbox configuration
|
||||
// Enables click-to-magnify functionality for images in blog posts and pages
|
||||
export interface ImageLightboxConfig {
|
||||
enabled: boolean; // Global toggle for image lightbox feature
|
||||
}
|
||||
|
||||
// Social link configuration for social footer
|
||||
export interface SocialLink {
|
||||
platform:
|
||||
@@ -313,6 +319,9 @@ export interface SiteConfig {
|
||||
|
||||
// Dashboard configuration (optional)
|
||||
dashboard?: DashboardConfig;
|
||||
|
||||
// Image lightbox configuration (optional)
|
||||
imageLightbox?: ImageLightboxConfig;
|
||||
}
|
||||
|
||||
// Default site configuration
|
||||
@@ -373,6 +382,10 @@ export const siteConfig: SiteConfig = {
|
||||
src: "/images/logos/mcp.svg",
|
||||
href: "https://www.markdown.fast/how-to-use-mcp-server/",
|
||||
},
|
||||
{
|
||||
src: "/images/logos/workos.svg",
|
||||
href: "https://www.markdown.fast/how-to-setup-workos",
|
||||
},
|
||||
],
|
||||
position: "above-footer",
|
||||
speed: 30,
|
||||
@@ -620,10 +633,16 @@ Created by [Wayne](https://x.com/waynesutton) with Convex, Cursor, and Claude Op
|
||||
// WorkOS authentication is optional - if not configured, dashboard is open access
|
||||
// Set enabled: false to disable the dashboard entirely
|
||||
// WARNING: When requireAuth is false, anyone can access the dashboard
|
||||
// For production, set up WorkOS and change requireAuth to true
|
||||
dashboard: {
|
||||
enabled: true, // Global toggle for dashboard page
|
||||
requireAuth: true, // Set to true and configure WorkOS for secure authentication
|
||||
enabled: true,
|
||||
requireAuth: false,
|
||||
},
|
||||
|
||||
// Image lightbox configuration
|
||||
// Enables click-to-magnify functionality for images in blog posts and pages
|
||||
// Images open in a full-screen lightbox overlay when clicked
|
||||
imageLightbox: {
|
||||
enabled: true, // Set to false to disable image lightbox
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -3636,6 +3636,8 @@ function ConfigSection({
|
||||
mcpServerEnabled: siteConfig.mcpServer?.enabled || false,
|
||||
mcpServerEndpoint: siteConfig.mcpServer?.endpoint || "/mcp",
|
||||
mcpServerRequireAuth: siteConfig.mcpServer?.requireAuth || false,
|
||||
// Image lightbox
|
||||
imageLightboxEnabled: siteConfig.imageLightbox?.enabled !== false,
|
||||
});
|
||||
|
||||
const [copied, setCopied] = useState(false);
|
||||
@@ -3795,6 +3797,12 @@ export const siteConfig: SiteConfig = {
|
||||
newsletterNotifications: { enabled: true, newSubscriberAlert: true, weeklyStatsSummary: true },
|
||||
weeklyDigest: { enabled: true, dayOfWeek: 0, subject: "Weekly Digest" },
|
||||
mcpServer: { enabled: ${config.mcpServerEnabled}, endpoint: "${config.mcpServerEndpoint}", publicRateLimit: 50, authenticatedRateLimit: 1000, requireAuth: ${config.mcpServerRequireAuth} },
|
||||
|
||||
// Image lightbox configuration
|
||||
// Enables click-to-magnify functionality for images in blog posts and pages
|
||||
imageLightbox: {
|
||||
enabled: ${config.imageLightboxEnabled},
|
||||
},
|
||||
};
|
||||
|
||||
export default siteConfig;
|
||||
@@ -4584,6 +4592,23 @@ export default siteConfig;
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Image Lightbox */}
|
||||
<div className="dashboard-config-card">
|
||||
<h3>Image Lightbox</h3>
|
||||
<div className="config-field checkbox">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.imageLightboxEnabled}
|
||||
onChange={(e) =>
|
||||
handleChange("imageLightboxEnabled", e.target.checked)
|
||||
}
|
||||
/>
|
||||
<span>Enable image lightbox (click images to magnify)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Links */}
|
||||
<div className="dashboard-config-card">
|
||||
<h3>External Links</h3>
|
||||
|
||||
@@ -1686,6 +1686,15 @@ body {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.blog-image-clickable {
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.blog-image-clickable:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.blog-image-caption {
|
||||
display: block;
|
||||
font-size: var(--font-size-image-caption);
|
||||
@@ -1694,6 +1703,105 @@ body {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
/* Image lightbox styles */
|
||||
.image-lightbox-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
z-index: 10000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
animation: lightboxFadeIn 0.2s ease;
|
||||
}
|
||||
|
||||
@keyframes lightboxFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.image-lightbox-close {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 6px;
|
||||
color: #fff;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease, border-color 0.2s ease;
|
||||
z-index: 10001;
|
||||
}
|
||||
|
||||
.image-lightbox-close:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.image-lightbox-content {
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.image-lightbox-image {
|
||||
max-width: 100%;
|
||||
max-height: calc(90vh - 60px);
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.image-lightbox-caption {
|
||||
color: #fff;
|
||||
font-size: var(--font-size-base);
|
||||
text-align: center;
|
||||
max-width: 800px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.image-lightbox-backdrop {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.image-lightbox-close {
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.image-lightbox-content {
|
||||
max-width: 95vw;
|
||||
max-height: 95vh;
|
||||
}
|
||||
|
||||
.image-lightbox-image {
|
||||
max-height: calc(95vh - 60px);
|
||||
}
|
||||
|
||||
.image-lightbox-caption {
|
||||
font-size: var(--font-size-sm);
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Post footer styles */
|
||||
.post-footer {
|
||||
margin-top: 64px;
|
||||
|
||||