diff --git a/AGENTS.md b/AGENTS.md index 6b7c401..f7a8e66 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 diff --git a/README.md b/README.md index b573414..dba2eec 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/TASK.md b/TASK.md index e14c3dd..96863f7 100644 --- a/TASK.md +++ b/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 diff --git a/changelog.md b/changelog.md index ef211cc..2c8a54b 100644 --- a/changelog.md +++ b/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 diff --git a/content/blog/how-to-setup-workos.md b/content/blog/how-to-setup-workos.md index 8d1b9b0..2494d63 100644 --- a/content/blog/how-to-setup-workos.md +++ b/content/blog/how-to-setup-workos.md @@ -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`) diff --git a/content/blog/how-to-use-agentmail.md b/content/blog/how-to-use-agentmail.md index 6816dea..11b8c34 100644 --- a/content/blog/how-to-use-agentmail.md +++ b/content/blog/how-to-use-agentmail.md @@ -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 +

+ You received this email because you subscribed to ${escapeHtml(siteName)}.
+ Unsubscribe +

+``` + +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 +

+ You received this email because you subscribed to ${escapeHtml(siteName)}.
+ Unsubscribe +

+``` + +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 +

+ You received this email because you subscribed to ${escapeHtml(siteName)}.
+ Unsubscribe +

+``` + +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. diff --git a/content/blog/how-to-use-firecrawl.md b/content/blog/how-to-use-firecrawl.md index ada222a..1e96b59 100644 --- a/content/blog/how-to-use-firecrawl.md +++ b/content/blog/how-to-use-firecrawl.md @@ -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"] --- diff --git a/content/blog/how-to-use-mcp-server.md b/content/blog/how-to-use-mcp-server.md index 6ddea3d..5dbb162 100644 --- a/content/blog/how-to-use-mcp-server.md +++ b/content/blog/how-to-use-mcp-server.md @@ -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"] --- diff --git a/content/blog/how-to-use-the-markdown-sync-dashboard.md b/content/blog/how-to-use-the-markdown-sync-dashboard.md index 576dc79..12c5edd 100644 --- a/content/blog/how-to-use-the-markdown-sync-dashboard.md +++ b/content/blog/how-to-use-the-markdown-sync-dashboard.md @@ -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 +![Dashboard home](/images/dashboard1.png) + 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 +![editor](/images/dashboard3.png) + 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 +![AI Agent](/images/dashboard4.png) + 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 +![Newsletter management](/images/dashboard5.png) 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 +![site config](/images/dashboard6.png) + 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 +![Sync commands](/images/dashboard7.png) + Run sync operations from the dashboard without opening a terminal: **Development:** diff --git a/content/blog/setup-guide.md b/content/blog/setup-guide.md index e4270e3..aaeb193 100644 --- a/content/blog/setup-guide.md +++ b/content/blog/setup-guide.md @@ -396,6 +396,10 @@ This image appears when sharing on social media. Recommended: 1200x630 pixels. ![Alt text description](/images/screenshot.png) ``` +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. diff --git a/content/blog/team-workflows-git-version-control.md b/content/blog/team-workflows-git-version-control.md index d9e83c9..7209eec 100644 --- a/content/blog/team-workflows-git-version-control.md +++ b/content/blog/team-workflows-git-version-control.md @@ -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" diff --git a/content/pages/changelog-page.md b/content/pages/changelog-page.md index eaa776c..f796517 100644 --- a/content/pages/changelog-page.md +++ b/content/pages/changelog-page.md @@ -9,6 +9,41 @@ layout: "sidebar" All notable changes to this project. ![](https://img.shields.io/badge/License-MIT-yellow.svg) +## 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 diff --git a/content/pages/docs.md b/content/pages/docs.md index fc86154..0b2b394 100644 --- a/content/pages/docs.md +++ b/content/pages/docs.md @@ -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 ` - 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. diff --git a/files.md b/files.md index acb13cf..f12a31c 100644 --- a/files.md +++ b/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/`) diff --git a/public/images/dashboard.png b/public/images/dashboard.png new file mode 100644 index 0000000..f881a48 Binary files /dev/null and b/public/images/dashboard.png differ diff --git a/public/images/dashboard1.png b/public/images/dashboard1.png new file mode 100644 index 0000000..0b3bfc8 Binary files /dev/null and b/public/images/dashboard1.png differ diff --git a/public/images/dashboard3.png b/public/images/dashboard3.png new file mode 100644 index 0000000..09db4d6 Binary files /dev/null and b/public/images/dashboard3.png differ diff --git a/public/images/dashboard4.png b/public/images/dashboard4.png new file mode 100644 index 0000000..f066fed Binary files /dev/null and b/public/images/dashboard4.png differ diff --git a/public/images/dashboard5.png b/public/images/dashboard5.png new file mode 100644 index 0000000..ae60d99 Binary files /dev/null and b/public/images/dashboard5.png differ diff --git a/public/images/dashboard6.png b/public/images/dashboard6.png new file mode 100644 index 0000000..ec074b6 Binary files /dev/null and b/public/images/dashboard6.png differ diff --git a/public/images/dashboard7.png b/public/images/dashboard7.png new file mode 100644 index 0000000..168ebb4 Binary files /dev/null and b/public/images/dashboard7.png differ diff --git a/public/images/logos/workos.svg b/public/images/logos/workos.svg new file mode 100644 index 0000000..4159348 --- /dev/null +++ b/public/images/logos/workos.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/public/images/workos.png b/public/images/workos.png new file mode 100644 index 0000000..485f9e0 Binary files /dev/null and b/public/images/workos.png differ diff --git a/public/llms.txt b/public/llms.txt index 593889f..8c074fd 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -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 diff --git a/public/raw/changelog.md b/public/raw/changelog.md index 87e8152..04f3a18 100644 --- a/public/raw/changelog.md +++ b/public/raw/changelog.md @@ -8,6 +8,41 @@ Date: 2025-12-30 All notable changes to this project. ![](https://img.shields.io/badge/License-MIT-yellow.svg) +## 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 diff --git a/public/raw/docs.md b/public/raw/docs.md index 8c4891d..f33c7c2 100644 --- a/public/raw/docs.md +++ b/public/raw/docs.md @@ -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 ` - 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. diff --git a/public/raw/how-to-setup-workos.md b/public/raw/how-to-setup-workos.md index 8fefd45..d2690d1 100644 --- a/public/raw/how-to-setup-workos.md +++ b/public/raw/how-to-setup-workos.md @@ -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`) diff --git a/public/raw/how-to-use-agentmail.md b/public/raw/how-to-use-agentmail.md index 9e49c7d..f145857 100644 --- a/public/raw/how-to-use-agentmail.md +++ b/public/raw/how-to-use-agentmail.md @@ -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 +

+ You received this email because you subscribed to ${escapeHtml(siteName)}.
+ Unsubscribe +

+``` + +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 +

+ You received this email because you subscribed to ${escapeHtml(siteName)}.
+ Unsubscribe +

+``` + +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 +

+ You received this email because you subscribed to ${escapeHtml(siteName)}.
+ Unsubscribe +

+``` + +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. diff --git a/public/raw/how-to-use-the-markdown-sync-dashboard.md b/public/raw/how-to-use-the-markdown-sync-dashboard.md index 03f32f0..2daec7a 100644 --- a/public/raw/how-to-use-the-markdown-sync-dashboard.md +++ b/public/raw/how-to-use-the-markdown-sync-dashboard.md @@ -11,6 +11,8 @@ Tags: dashboard, tutorial, content-management # How to use the Markdown sync dashboard +![Dashboard home](/images/dashboard1.png) + 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 +![editor](/images/dashboard3.png) + 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 +![AI Agent](/images/dashboard4.png) + 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 +![Newsletter management](/images/dashboard5.png) 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 +![site config](/images/dashboard6.png) + 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 +![Sync commands](/images/dashboard7.png) + Run sync operations from the dashboard without opening a terminal: **Development:** diff --git a/public/raw/index.md b/public/raw/index.md index 2d098b3..dee6379 100644 --- a/public/raw/index.md +++ b/public/raw/index.md @@ -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` diff --git a/public/raw/setup-guide.md b/public/raw/setup-guide.md index a8eb95e..38b58f0 100644 --- a/public/raw/setup-guide.md +++ b/public/raw/setup-guide.md @@ -389,6 +389,10 @@ This image appears when sharing on social media. Recommended: 1200x630 pixels. ![Alt text description](/images/screenshot.png) ``` +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. diff --git a/public/raw/team-workflows-git-version-control.md b/public/raw/team-workflows-git-version-control.md deleted file mode 100644 index 206e05f..0000000 --- a/public/raw/team-workflows-git-version-control.md +++ /dev/null @@ -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. \ No newline at end of file diff --git a/src/components/BlogPost.tsx b/src/components/BlogPost.tsx index e0a2b39..0686c7b 100644 --- a/src/components/BlogPost.tsx +++ b/src/components/BlogPost.tsx @@ -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 ( +
+ +
+ {alt} + {alt &&
{alt}
} +
+
+ ); +} + // 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 ( {alt {alt && {alt}} @@ -611,41 +667,51 @@ export default function BlogPost({ content, slug, pageType = "post" }: BlogPostP // Render with inline embeds if placeholders exist if (hasInlineEmbeds) { return ( -
- {segments.map((segment, index) => { - if (segment.type === "newsletter") { - // Newsletter signup inline - return siteConfig.newsletter?.enabled ? ( - - ) : null; - } - if (segment.type === "contactform") { - // Contact form inline - return siteConfig.contactForm?.enabled ? ( - - ) : null; - } - // Markdown content segment - return renderMarkdown(segment.value, index); - })} -
+ <> +
+ {segments.map((segment, index) => { + if (segment.type === "newsletter") { + // Newsletter signup inline + return siteConfig.newsletter?.enabled ? ( + + ) : null; + } + if (segment.type === "contactform") { + // Contact form inline + return siteConfig.contactForm?.enabled ? ( + + ) : null; + } + // Markdown content segment + return renderMarkdown(segment.value, index); + })} +
+ {lightboxImage && ( + setLightboxImage(null)} + /> + )} + ); } // No inline embeds, render content normally return ( -
- +
+ { + if (isLightboxEnabled && src) { + setLightboxImage({ src, alt: alt || "" }); + } + }; return ( {alt {alt && {alt}} @@ -821,10 +894,18 @@ export default function BlogPost({ content, slug, pageType = "post" }: BlogPostP td({ children }) { return {children}; }, - }} - > - {cleanedContent} - -
+ }} + > + {cleanedContent} +
+
+ {lightboxImage && ( + setLightboxImage(null)} + /> + )} + ); } diff --git a/src/config/siteConfig.ts b/src/config/siteConfig.ts index 2d17fc6..21de733 100644 --- a/src/config/siteConfig.ts +++ b/src/config/siteConfig.ts @@ -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 }, }; diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index ecd72cc..237c652 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -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; + {/* Image Lightbox */} +
+

Image Lightbox

+
+ +
+
+ {/* Links */}

External Links

diff --git a/src/styles/global.css b/src/styles/global.css index bd29464..2a95512 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -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;