Add comprehensive blog demo showcasing advanced content management features

- Implement complete mushroom foraging blog with chanterelles article
- Add rich demonstration of .insertr and .insertr-add functionality
- Include comprehensive documentation for future .insertr-content vision
- Update project styling and configuration to support blog demo
- Enhance engine and API handlers for improved content management
This commit is contained in:
2025-10-23 21:47:51 +02:00
parent 74de64c66b
commit 4874849f80
13 changed files with 1411 additions and 340 deletions

View File

@@ -18,6 +18,43 @@ This document outlines the design and implementation plan for adding draft/publi
- All-or-nothing manual enhancement process - All-or-nothing manual enhancement process
- No rollback mechanism for published content - No rollback mechanism for published content
## Industry Research: How Other CMS Handle Drafts
### WordPress
**Approach**: Single table (`wp_posts`) with state field (`post_status`)
**States**: draft, pending, publish, future, private, trash, auto-draft
**Storage**: All content in one table, differentiated by status field
**Pros**: Simple schema, easy queries, unified storage
**Cons**: No separation between draft and live data, potential performance issues
### Drupal
**Approach**: Content moderation module with workflow states
**States**: Configurable (draft, needs_review, published, archived, etc.)
**Storage**: Moderation state entities linked to content revisions
**Pros**: Flexible workflows, proper revision tracking, role-based transitions
**Cons**: Complex architecture, steep learning curve
### Contentful (Headless)
**Approach**: Separate published/draft versions with sync API
**States**: draft, published, changed, archived
**Storage**: Maintains both draft and published versions simultaneously
**Pros**: Performance optimized, global CDN delivery, precise change tracking
**Cons**: Complex API, higher storage overhead, sync complexity
### Ghost
**Approach**: Single table with status field plus scheduled publishing
**States**: draft, published, scheduled, sent
**Storage**: Uses `status` field + `published_at` timestamp
**Pros**: Simple but effective, good scheduling support
**Cons**: Limited editorial workflow, no approval processes
### Strapi
**Approach**: Draft & Publish feature with timestamp-based differentiation
**States**: draft, published
**Storage**: Single table with `published_at` field (null = draft)
**Pros**: Clean API separation, optional feature, good performance
**Cons**: Limited workflow states, manual schema management
## Requirements ## Requirements
### Functional Requirements ### Functional Requirements
@@ -26,7 +63,7 @@ This document outlines the design and implementation plan for adding draft/publi
- **FR3**: Authorized users can publish draft content to live site - **FR3**: Authorized users can publish draft content to live site
- **FR4**: System supports rollback to previous published versions - **FR4**: System supports rollback to previous published versions
- **FR5**: Clear visual indication of draft vs published state - **FR5**: Clear visual indication of draft vs published state
- **FR6**: Batch publishing of multiple content changes - **FR6**: Auto-save functionality to prevent content loss
### Non-Functional Requirements ### Non-Functional Requirements
- **NFR1**: Backward compatibility with existing content - **NFR1**: Backward compatibility with existing content
@@ -34,289 +71,277 @@ This document outlines the design and implementation plan for adding draft/publi
- **NFR3**: Support for concurrent editing workflows - **NFR3**: Support for concurrent editing workflows
- **NFR4**: Audit trail for all publishing actions - **NFR4**: Audit trail for all publishing actions
## Architecture Approaches ## Recommended Solution: State-Based Approach
### Option A: Minimal Schema Impact (Published Version Pointer) Based on industry research and our existing architecture, we recommend following the **WordPress/Ghost pattern** with a state field approach. This provides the best balance of simplicity, performance, and functionality.
**Core Concept**: Use existing version system with a pointer to published versions. ### Schema Changes
**Core Change**: Add state tracking to existing `content_versions` table:
**Schema Changes**:
```sql ```sql
CREATE TABLE published_versions ( -- Add state column to existing content_versions table
content_id TEXT NOT NULL, ALTER TABLE content_versions ADD COLUMN state TEXT DEFAULT 'history' NOT NULL
site_id TEXT NOT NULL, CHECK (state IN ('history', 'draft', 'live'));
published_version_id INTEGER NOT NULL,
published_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) NOT NULL, -- Create index for efficient state-based queries
published_by TEXT NOT NULL, CREATE INDEX idx_content_versions_state ON content_versions(content_id, site_id, state);
PRIMARY KEY (content_id, site_id)
); -- Ensure only one draft and one live version per content item
CREATE UNIQUE INDEX idx_content_versions_unique_draft
ON content_versions(content_id, site_id) WHERE state = 'draft';
CREATE UNIQUE INDEX idx_content_versions_unique_live
ON content_versions(content_id, site_id) WHERE state = 'live';
``` ```
**Pros**: **Migration Strategy**:
- Minimal database changes 1. All existing `content_versions` entries become `state='history'`
- Leverages existing version history 2. Current `content` table entries migrate to `content_versions` with `state='live'`
- Backward compatible 3. Drop `content` table after migration (everything now in `content_versions`)
- Simple migration path
**Cons**:
- Less intuitive data model
- Enhancement modes add complexity
- Auto-save creates unnecessary versions
- Limited workflow capabilities
### Option B: Full Schema Redesign (Recommended)
**Core Concept**: Explicit draft and published content tables with rich workflow support.
**Schema Changes**:
```sql
-- Draft content (working state)
CREATE TABLE content_drafts (
id TEXT NOT NULL,
site_id TEXT NOT NULL,
html_content TEXT NOT NULL,
original_template TEXT,
created_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) NOT NULL,
updated_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) NOT NULL,
last_edited_by TEXT DEFAULT 'system' NOT NULL,
is_dirty BOOLEAN DEFAULT TRUE NOT NULL,
auto_save_at BIGINT,
PRIMARY KEY (id, site_id)
);
-- Published content (live state)
CREATE TABLE content_published (
id TEXT NOT NULL,
site_id TEXT NOT NULL,
html_content TEXT NOT NULL,
original_template TEXT,
published_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) NOT NULL,
published_by TEXT DEFAULT 'system' NOT NULL,
draft_version_at_publish BIGINT NOT NULL,
PRIMARY KEY (id, site_id)
);
-- Enhanced version tracking
CREATE TABLE content_versions (
version_id SERIAL PRIMARY KEY,
content_id TEXT NOT NULL,
site_id TEXT NOT NULL,
html_content TEXT NOT NULL,
original_template TEXT,
created_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) NOT NULL,
created_by TEXT DEFAULT 'system' NOT NULL,
version_type TEXT NOT NULL CHECK (version_type IN ('draft_save', 'publish', 'auto_save')),
is_published BOOLEAN DEFAULT FALSE NOT NULL
);
-- Future: Scheduled publishing support
CREATE TABLE publish_queue (
queue_id SERIAL PRIMARY KEY,
site_id TEXT NOT NULL,
content_ids TEXT[] NOT NULL,
scheduled_at BIGINT,
created_by TEXT NOT NULL,
status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'completed', 'failed'))
);
```
**Pros**:
- Clear semantic separation
- Rich workflow capabilities
- Optimized for different access patterns
- Supports advanced features (auto-save, batch publish, scheduling)
- Intuitive mental model
**Cons**:
- Significant schema changes
- Complex migration required
- More storage overhead
- Increased development complexity
## Recommended Solution: Option B (Full Schema Redesign)
### Content States ### Content States
| State | Draft Table | Published Table | Description | | State | Description | Query Pattern |
|-------|-------------|-----------------|-------------| |-------|-------------|---------------|
| `draft_only` | ✅ Exists | ❌ None | New content, never published | | `history` | Previous versions, for rollback | `WHERE state = 'history' ORDER BY created_at DESC` |
| `published` | ✅ Exists | ✅ Exists, Same | Content published, no pending changes | | `draft` | Current working version, not published | `WHERE state = 'draft'` |
| `modified` | ✅ Exists | ✅ Exists, Different | Published content with unpublished changes | | `live` | Currently published version | `WHERE state = 'live'` |
| `scheduled` | ✅ Exists | ✅ Exists | Content queued for future publishing |
### Workflow Logic
**Auto-save Process**:
1. User edits content → Auto-save creates/updates `state='draft'` version
2. Only one draft version exists per content item (upsert pattern)
3. Previous draft becomes `state='history'`
**Publishing Process**:
1. User clicks "Publish" → Current draft version updated to `state='live'`
2. Previous live version becomes `state='history'`
3. Enhancement triggered with all `state='live'` content
**Rollback Process**:
1. User selects historical version → Copy to new `state='live'` version
2. Previous live version becomes `state='history'`
3. Enhancement triggered
### API Design ### API Design
**New Endpoints**: **New Endpoints**:
``` ```
GET /api/content/{id}?mode=draft|published # Get content in specific state GET /api/content/{id}?state=draft|live|history # Get content in specific state
POST /api/content/{id}/save-draft # Save without publishing POST /api/content/{id}/save-draft # Save as draft (auto-save)
POST /api/content/{id}/publish # Publish draft content POST /api/content/{id}/publish # Publish draft to live
POST /api/content/bulk-publish # Publish multiple items POST /api/content/{id}/rollback/{version_id} # Rollback to specific version
GET /api/content/diff/{id} # Show draft vs published diff GET /api/content/{id}/diff # Compare draft vs live
POST /api/enhancement/preview # Preview with draft content POST /api/enhancement/preview # Preview site with draft content
POST /api/enhancement/publish # Enhance with published content GET /api/status/changes # List all unpublished changes
GET /api/status/publishing # Get site publishing status POST /api/content/bulk-publish # Publish multiple items
``` ```
**Enhanced Endpoints**: **Enhanced Endpoints**:
``` ```
PUT /api/content/{id} # Now saves as draft by default PUT /api/content/{id} # Now saves as draft by default
POST /api/enhancement # Only processes 'live' content
``` ```
### UI/UX Changes ### Repository Layer Changes
**Control Panel Updates**: **Core Queries**:
- Replace "🔄 Enhance" with publishing workflow buttons ```go
- Add "💾 Save Draft" (auto-saves every 30s) // Get current live content for enhancement
- Add "🚀 Publish Changes" with confirmation dialog func (r *Repository) GetLiveContent(siteID, contentID string) (*Content, error) {
- Add "👁️ Preview Changes" for draft enhancement return r.queryContent(siteID, contentID, "live")
- Add "📊 Publishing Status" indicator }
**Visual States**: // Get current draft for editing
- 🟡 **Draft Pending**: Yellow indicator for unpublished changes func (r *Repository) GetDraftContent(siteID, contentID string) (*Content, error) {
- 🟢 **Published**: Green indicator when draft matches published return r.queryContent(siteID, contentID, "draft")
- 🔵 **Scheduled**: Blue indicator for queued publishing }
- 🔴 **Error**: Red indicator for publishing failures
**New UI Components**: // Save as draft (upsert pattern)
func (r *Repository) SaveDraft(content *Content) error {
// Mark existing draft as history
r.updateState(content.ID, content.SiteID, "draft", "history")
// Insert new draft
return r.insertContentVersion(content, "draft")
}
// Publish draft to live
func (r *Repository) PublishDraft(siteID, contentID, publishedBy string) error {
// Mark existing live as history
r.updateState(contentID, siteID, "live", "history")
// Update draft to live
return r.updateState(contentID, siteID, "draft", "live")
}
```
## Strengths of This Approach
### 1. **Simplicity**
- Single table with state field (WordPress/Ghost pattern)
- Minimal schema changes to existing system
- Easy to understand and maintain
### 2. **Performance**
- Efficient state-based queries with proper indexing
- No complex joins between draft/live tables
- Leverages existing version history system
### 3. **Backward Compatibility**
- Existing content migrates cleanly to 'live' state
- Current APIs work with minimal changes
- Gradual rollout possible
### 4. **Storage Efficiency**
- No duplicate content storage (unlike Contentful approach)
- Reuses existing version infrastructure
- History naturally maintained
### 5. **Query Simplicity**
```sql
-- Get all draft content for a site
SELECT * FROM content_versions WHERE site_id = ? AND state = 'draft';
-- Get all live content for enhancement
SELECT * FROM content_versions WHERE site_id = ? AND state = 'live';
-- Check if content has unpublished changes
SELECT COUNT(*) FROM content_versions
WHERE content_id = ? AND site_id = ? AND state = 'draft';
```
## Weaknesses and Potential Roadblocks
### 1. **State Management Complexity**
**Risk**: Ensuring state transitions are atomic and consistent
**Mitigation**:
- Use database transactions for state changes
- Implement state validation triggers
- Add comprehensive error handling
### 2. **Concurrent Editing Conflicts**
**Risk**: Multiple editors creating conflicting draft versions
**Mitigation**:
- Unique constraints prevent multiple drafts
- Last-writer-wins with conflict detection
- Consider optimistic locking for future enhancement
### 3. **Auto-save Performance**
**Risk**: Frequent auto-save creating too many history versions
**Mitigation**:
- Implement debounced auto-save (30-second intervals)
- Consider version consolidation for excessive history
- Monitor database growth patterns
### 4. **Migration Risk**
**Risk**: Data loss or corruption during content table migration
**Mitigation**:
- Comprehensive backup before migration
- Gradual migration with validation steps
- Rollback plan if migration fails
### 5. **Limited Workflow States**
**Risk**: Only 3 states may be insufficient for complex editorial workflows
**Mitigation**:
- Start simple, extend states later if needed
- Most CMS start with basic draft/live model
- Consider "scheduled" state for future enhancement
## UI/UX Changes
### Control Panel Updates
- Replace "🔄 Enhance" with "💾 Save Draft" / "🚀 Publish"
- Add state indicators: 🟡 Draft Pending, 🟢 Published, 🔴 Error
- Add "👁️ Preview Changes" button for draft enhancement
- Show "📊 Publishing Status" with count of unpublished changes
### New UI Components
- Diff viewer showing draft vs published changes - Diff viewer showing draft vs published changes
- Publishing confirmation dialog with change summary
- Bulk publishing interface for multiple content items - Bulk publishing interface for multiple content items
- Publishing history and rollback interface - Version history with rollback capability
## Implementation Plan ## Implementation Plan
### Phase 1: Core Infrastructure (Week 1-2) ### Phase 1: Database Foundation (Week 1)
- [ ] Create new database schema - [ ] Add `state` column to `content_versions` table
- [ ] Implement migration scripts - [ ] Create state-based indexes and constraints
- [ ] Update repository interfaces - [ ] Write migration script for existing content
- [ ] Add basic draft/publish operations - [ ] Test migration on demo sites
- [ ] Update content versioning system
### Phase 2: API Development (Week 3-4) ### Phase 2: Repository Layer (Week 2)
- [ ] Implement new API endpoints - [ ] Update repository interfaces for state-based queries
- [ ] Update existing endpoints for draft mode - [ ] Implement draft save/publish/rollback operations
- [ ] Add enhancement mode switching - [ ] Add state transition validation
- [ ] Implement publishing workflow APIs - [ ] Update existing content operations
- [ ] Add content state management
### Phase 3: UI Integration (Week 5-6) ### Phase 3: API Integration (Week 3)
- [ ] Update control panel with new buttons - [ ] Implement new draft/publish endpoints
- [ ] Add visual state indicators - [ ] Update existing endpoints for state handling
- [ ] Implement draft auto-save - [ ] Add preview enhancement functionality
- [ ] Add preview functionality - [ ] Implement bulk publishing API
- [ ] Create publishing confirmation dialogs
### Phase 4: Advanced Features (Week 7-8) ### Phase 4: UI Implementation (Week 4)
- [ ] Implement diff viewer - [ ] Update control panel with new buttons and states
- [ ] Add bulk publishing interface - [ ] Implement auto-save functionality
- [ ] Create publishing history view - [ ] Add diff viewer and publishing dialogs
- [ ] Add rollback functionality - [ ] Create publishing status dashboard
- [ ] Implement scheduled publishing foundation
### Phase 5: Testing & Polish (Week 9-10) ### Phase 5: Testing & Polish (Week 5)
- [ ] Comprehensive testing across all demo sites - [ ] Comprehensive testing across demo sites
- [ ] Performance optimization - [ ] Performance optimization and monitoring
- [ ] Error handling and edge cases - [ ] Error handling and edge cases
- [ ] Documentation updates - [ ] Documentation and migration guides
- [ ] Migration testing
## Potential Challenges & Mitigation
### Challenge 1: Data Migration Complexity
**Risk**: Migrating existing content to new schema without data loss
**Mitigation**:
- Create comprehensive migration scripts with rollback capability
- Test migration on demo sites first
- Implement gradual migration with dual-write period
- Provide data validation and integrity checks
### Challenge 2: Concurrent Editing Conflicts
**Risk**: Multiple editors working on same content simultaneously
**Mitigation**:
- Implement optimistic locking with version checking
- Add conflict detection and resolution UI
- Consider edit session management
- Provide clear error messages for conflicts
### Challenge 3: Performance Impact
**Risk**: Additional database queries and storage overhead
**Mitigation**:
- Optimize database indexes for new access patterns
- Implement efficient content state queries
- Consider caching strategies for published content
- Monitor and profile database performance
### Challenge 4: Backward Compatibility
**Risk**: Breaking existing workflows and integrations
**Mitigation**:
- Maintain existing API compatibility during transition
- Provide clear migration path for existing users
- Implement feature flags for gradual rollout
- Extensive testing with current demo sites
### Challenge 5: Auto-save vs Version History Noise
**Risk**: Too many auto-save versions cluttering history
**Mitigation**:
- Separate auto-save from manual save operations
- Implement version consolidation strategies
- Use different version types (auto_save vs draft_save)
- Provide version cleanup mechanisms
## Testing Strategy ## Testing Strategy
### Unit Tests ### Migration Testing
- Repository layer draft/publish operations - Test content migration with various demo site configurations
- Content state transition logic - Validate data integrity before/after migration
- API endpoint functionality - Test rollback procedures if migration fails
- Migration script validation
### Integration Tests ### Workflow Testing
- End-to-end publishing workflow - Draft save/publish cycles with various content types
- Enhancement with different content modes
- Concurrent editing scenarios - Concurrent editing scenarios
- Database migration processes - Auto-save reliability under different conditions
- Enhancement preview vs live comparison
### User Acceptance Tests ### Performance Testing
- Editorial workflow testing with demo sites - State-based query performance with large datasets
- Performance testing under load - Auto-save frequency impact on database
- Cross-browser UI compatibility - Enhancement speed with draft vs live content
- Mobile device testing
## Success Metrics ## Success Metrics
### Functional Metrics ### Functional Success
- ✅ All existing demo sites work without modification
- ✅ Editors can save drafts without affecting live sites
- ✅ Publishing workflow completes in <5 seconds
- ✅ Zero data loss during migration - ✅ Zero data loss during migration
- ✅ All demo sites work without modification post-migration
- ✅ Draft/publish workflow completes in <5 seconds
- ✅ Auto-save prevents content loss in all scenarios
### User Experience Metrics ### User Experience Success
- ✅ Clear visual indication of content states - ✅ Clear visual distinction between draft and published states
- ✅ Intuitive publishing workflow - ✅ Intuitive publishing workflow requiring minimal training
-Auto-save prevents content loss -Preview functionality accurately reflects published output
- ✅ Preview functionality works accurately
### Technical Metrics ### Technical Success
-API response times remain under 200ms -State-based queries perform within 100ms
- ✅ Database migration completes in <30 minutes - ✅ Database size increase <10% due to state optimization
-Memory usage increase <20% -100% test coverage for new draft/publish functionality
- ✅ 100% test coverage for new functionality
## Future Considerations ## Future Enhancements
### Potential Enhancements ### Near-term (Next 6 months)
- **Scheduled Publishing**: Full calendar-based publishing system - **Scheduled Publishing**: Add `scheduled` state with `publish_at` timestamp
- **Approval Workflows**: Multi-stage content approval process - **Bulk Operations**: Enhanced multi-content publishing interface
- **Content Staging**: Multiple environment support (dev/staging/prod) - **Content Conflicts**: Optimistic locking for concurrent editing
- **Collaborative Editing**: Real-time collaborative editing features
- **Content Templates**: Draft templates for consistent content structure
### Technical Debt ### Long-term (6+ months)
- Consider eventual consolidation of version tables - **Approval Workflows**: Multi-step editorial approval process
- Evaluate long-term storage strategies for large sites - **Content Branching**: Multiple draft versions per content item
- Plan for horizontal scaling of publishing operations - **Real-time Collaboration**: Live editing with conflict resolution
- Review and optimize database schema after real-world usage
--- ---
*This document will be updated as the feature evolves through design reviews and implementation feedback.* *This approach follows industry best practices from WordPress and Ghost while leveraging Insertr's existing version infrastructure for maximum simplicity and reliability.*

133
demos/blog/README.md Normal file
View File

@@ -0,0 +1,133 @@
# 🍄 The Forager's Journal - Mushroom Blog Demo
A complete blog demonstration showcasing how `.insertr-content` could work for long-form content management. This demo features a mushroom foraging enthusiast's blog with rich content about wild mushroom identification, foraging techniques, and sustainable harvesting practices.
## What This Demo Showcases
### Current Insertr Features
- **Individual Element Editing** (`.insertr`): Page titles, navigation links, dates, categories, tags
- **Collection Management** (`.insertr-add`): Blog post previews, related links, knowledge cards
- **Real-time Persistence**: All content changes are saved to the database
- **Perfect Style Preservation**: All custom CSS styling is maintained during editing
### Future `.insertr-content` Vision
- **Rich Text Editing**: Long-form article content with sophisticated formatting
- **Content Structure Management**: Headings, paragraphs, lists, blockquotes, media
- **Blog-specific Features**: SEO metadata, reading time, author bios, content relationships
## Demo Content
### Main Pages
- **index.html** - Blog homepage with featured content and post previews
- **chanterelles.html** - Complete article about chanterelle mushroom foraging
- **insertr.yaml** - Configuration file with content types and settings
### Content Highlights
- **Expert Knowledge**: 20+ years of foraging experience from "Elena Rodriguez"
- **Safety Focus**: Emphasis on safe identification and sustainable practices
- **Rich Media**: High-quality nature photography and detailed descriptions
- **Engaging Writing**: Educational content that's accessible to beginners
## Accessing the Demo
1. **Start the development server**:
```bash
just dev
```
2. **Visit the blog**:
```
http://localhost:8080/sites/blog/
```
3. **Try editing content**:
- Click the "Edit" button in the top-right corner
- Click on any element with the `.insertr` class to edit
- Notice how changes persist across page reloads
## Technical Implementation
### Insertr Classes Used
#### `.insertr` - Individual Elements
- Page titles and headings
- Navigation links and buttons
- Metadata (dates, categories, tags)
- Author information
- Individual paragraphs in footer sections
#### `.insertr-add` - Collections
- Blog post previews grid
- Related article links
- Knowledge base cards
- Tag collections
#### `.insertr-content` - Long-form Content (Future)
- Article body content
- Author biographies
- Rich text sections with complex formatting
### Content Structure
```html
<!-- Current: Individual elements -->
<h1 class="insertr">Editable Title</h1>
<p class="insertr">Editable paragraph</p>
<!-- Current: Collections -->
<div class="insertr-add blog-posts">
<article>...</article>
<article>...</article>
</div>
<!-- Future: Rich content blocks -->
<div class="insertr-content">
<h2>Sophisticated Formatting</h2>
<p>With <strong>rich text</strong> editing...</p>
<blockquote>Author quotes and citations</blockquote>
<ul><li>Complex lists and structures</li></ul>
</div>
```
## Styling Approach
The demo uses a nature-inspired design system:
- **Color Palette**: Forest greens, warm creams, mushroom browns, golden accents
- **Typography**: Georgia serif for readability with nature-themed hierarchy
- **Layout**: Responsive grid system with clean, article-focused design
- **Components**: Reusable cards, buttons, and content blocks
## Content Strategy
### Target Audience
- **Beginner Foragers**: Safety-first approach with detailed guidance
- **Experienced Enthusiasts**: Advanced techniques and expert insights
- **Nature Lovers**: Environmental consciousness and ecosystem preservation
### Content Types
- **Field Guides**: Identification and habitat information
- **Safety Articles**: Risk mitigation and emergency procedures
- **Recipes**: From forest to table culinary content
- **Conservation**: Sustainable harvesting and ecological awareness
## Future Enhancements
When `.insertr-content` is implemented, this demo will showcase:
1. **Seamless Rich Text Editing**: Click anywhere in article content to edit
2. **Structure Management**: Drag-and-drop reordering of content blocks
3. **Media Integration**: Easy image insertion and gallery management
4. **SEO Optimization**: Built-in tools for metadata and content optimization
5. **Publishing Workflow**: Draft/review/publish states for content management
## Educational Value
This demo serves as a reference for:
- **Content Creators**: How rich CMS features can work with static sites
- **Developers**: Integration patterns for `.insertr-content` implementation
- **Designers**: Style preservation and responsive design with CMS functionality
- **Site Owners**: The power of combining static site performance with dynamic content management
---
*This demo represents the vision for how Insertr can evolve to handle complex content while maintaining its core principles of zero configuration and perfect style preservation.*

View File

@@ -0,0 +1,339 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title class="insertr">Golden Treasures: A Guide to Foraging Chanterelles - The Forager's Journal</title>
<meta name="description" class="insertr" content="Complete guide to finding, identifying, and sustainably harvesting chanterelle mushrooms. Learn the secrets of successful chanterelle foraging from an expert mycologist.">
<link href="style.css" rel="stylesheet">
</head>
<body>
<header class="site-header">
<div class="container">
<h1 class="site-title"><a href="index.html" style="color: inherit; text-decoration: none;">🍄 The Forager's Journal</a></h1>
<p class="site-tagline">Wild Mushroom Adventures & Foraging Wisdom</p>
<nav class="main-nav">
<a href="index.html" class="nav-link">Home</a>
<a href="#guides" class="nav-link">Field Guides</a>
<a href="#recipes" class="nav-link">Recipes</a>
<a href="#safety" class="nav-link">Safety</a>
<a href="#about" class="nav-link">About</a>
</nav>
</div>
</header>
<main class="main-content">
<article class="blog-post">
<div class="container">
<header class="post-header">
<div class="post-meta-info">
<span class="post-category insertr">Field Guide</span>
<time class="post-date insertr">March 15, 2024</time>
<span class="reading-time insertr">8 min read</span>
</div>
<h1 class="post-title insertr">Golden Treasures: A Guide to Foraging Chanterelles</h1>
<p class="post-subtitle insertr">Discover the secrets to finding these golden beauties, from perfect habitat identification to sustainable harvesting techniques</p>
</header>
<div class="post-featured-image">
<img src="https://images.unsplash.com/photo-1518264344460-b2b8c61dbeda?w=800&h=400&fit=crop" alt="Golden chanterelle mushrooms growing in forest moss" class="featured-image">
<p class="image-caption insertr">Chanterelles growing in their preferred habitat: mossy forest floor under deciduous trees</p>
</div>
<div class="post-content insertr-content">
<p class="lead">There's nothing quite like the thrill of spotting your first chanterelle of the season—that distinctive golden trumpet emerging from the forest floor like nature's own treasure. After two decades of foraging, chanterelles remain my absolute favorite wild mushroom, not just for their exquisite flavor, but for the adventure of finding them.</p>
<h2>Why Chanterelles Are Every Forager's Dream</h2>
<p>Chanterelles (<em>Cantharellus cibarius</em>) are considered the "gateway mushroom" for new foragers, and for good reason. They have few dangerous look-alikes, they're abundant when conditions are right, and their flavor is unmatched—earthy, fruity, with a subtle peppery finish that makes them prized in fine restaurants worldwide.</p>
<p>But perhaps most importantly, chanterelles are incredibly forgiving for beginners. Their distinctive features make misidentification unlikely once you know what to look for, and they're robust enough to withstand a bit of rough handling during your learning phase.</p>
<h2>Perfect Habitat: Where Golden Treasures Hide</h2>
<p>Understanding chanterelle habitat is crucial to successful foraging. These fungi form mycorrhizal relationships with specific trees, meaning they can't survive without their plant partners. Here's what to look for:</p>
<h3>Preferred Tree Associates</h3>
<ul>
<li><strong>Oaks</strong> - Particularly white and live oaks in mixed forests</li>
<li><strong>Douglas Fir</strong> - Especially in older, established forests</li>
<li><strong>Madrone</strong> - A reliable chanterelle partner in Pacific Coast regions</li>
<li><strong>Tanoak</strong> - Creates ideal conditions in California and Oregon</li>
</ul>
<h3>Environmental Conditions</h3>
<p>Chanterelles are particular about their growing conditions. They prefer:</p>
<ul>
<li>Well-drained slopes with good air circulation</li>
<li>Areas with dappled sunlight, not dense shade</li>
<li>Soils with good organic matter and leaf litter</li>
<li>Locations that stay moist but not waterlogged</li>
</ul>
<blockquote style="background: var(--cream); padding: 1.5rem; border-left: 4px solid var(--golden-yellow); margin: 2rem 0; font-style: italic;">
"I've learned that chanterelles are creatures of habit. Once you find a productive spot, return to it year after year. I have patches I've been visiting for fifteen years that still produce abundant flushes every season."
</blockquote>
<h2>Identification: Key Features That Don't Lie</h2>
<p>Proper identification is the foundation of safe foraging. Chanterelles have several distinctive characteristics that, when taken together, make them unmistakable:</p>
<h3>Cap and Shape</h3>
<p>Mature chanterelles have an inverted, trumpet-like shape with irregular, wavy edges. The cap color ranges from pale yellow to deep golden orange, sometimes with darker patches where insects have been feeding.</p>
<h3>The Gills That Aren't Gills</h3>
<p>This is the most important identification feature: chanterelles don't have true gills. Instead, they have forked ridges or "false gills" that run down the stem. These ridges are:</p>
<ul>
<li>Blunt-edged, not sharp like true gills</li>
<li>Forked and interconnected</li>
<li>The same color as the cap or slightly lighter</li>
<li>Difficult to separate from the cap flesh</li>
</ul>
<h3>Stem Characteristics</h3>
<p>Chanterelle stems are solid (not hollow), continuous with the cap, and often have an irregular, tapering shape. They're typically the same color as the cap or slightly paler.</p>
<h3>Spore Print</h3>
<p>While not always necessary for identification, chanterelles produce a white to pale yellow spore print—a useful confirmation if you're uncertain.</p>
<h2>Avoiding the Look-Alikes</h2>
<p>While chanterelles are relatively safe for beginners, there are a few species you should be aware of:</p>
<h3>Jack O'Lantern Mushrooms</h3>
<p>These orange mushrooms grow in clusters on wood and have true gills that glow faintly in the dark. They're not deadly but will cause significant gastrointestinal distress.</p>
<h3>False Chanterelles</h3>
<p>These have thinner, more blade-like gills and a darker orange color. While not severely toxic, they taste terrible and can cause stomach upset.</p>
<h2>Sustainable Harvesting Techniques</h2>
<p>Responsible foraging ensures that chanterelle patches remain productive for years to come. Here are the techniques I've refined over decades of harvesting:</p>
<h3>The Clean Cut Method</h3>
<p>Using a sharp knife, cut the mushroom at ground level, leaving the mycelium undisturbed. This allows the fungal network to continue growing and potentially produce more mushrooms in the same season.</p>
<h3>Selective Harvesting</h3>
<p>Take only what you can use, and leave smaller buttons to mature. I typically harvest chanterelles when they're 2-4 inches across—large enough to clean easily but not so old they're tough or buggy.</p>
<h3>Spore Dispersal</h3>
<p>Carry your harvest in an open basket or mesh bag, allowing spores to scatter as you walk. This natural distribution helps establish new growing areas.</p>
<h2>Cleaning and Preparation</h2>
<p>Chanterelles require careful cleaning to remove debris while preserving their delicate structure:</p>
<ol>
<li>Brush off loose dirt and debris with a soft brush or cloth</li>
<li>Trim the stem end where it was cut</li>
<li>Split larger mushrooms to check for insects</li>
<li>Rinse quickly in cold water only if absolutely necessary</li>
<li>Pat dry immediately with paper towels</li>
</ol>
<h2>Seasonal Timing: When to Hunt</h2>
<p>Chanterelle seasons vary by region, but generally follow these patterns:</p>
<ul>
<li><strong>Pacific Northwest:</strong> July through November, with peak season in September-October</li>
<li><strong>California:</strong> November through March, following winter rains</li>
<li><strong>Eastern United States:</strong> June through September, after summer rains</li>
</ul>
<p>The key is consistent moisture followed by warm temperatures. I typically wait for a week of steady rain followed by a few warm days before heading out to my best spots.</p>
<h2>Final Thoughts: Respect the Forest</h2>
<p>Chanterelle foraging has taught me patience, observation, and deep respect for forest ecosystems. These mushrooms are indicators of healthy forests, and their presence tells us that the intricate web of trees, soil, and fungi is functioning as it should.</p>
<p>Remember: when in doubt, don't harvest. Take time to learn, join local mycological societies, and consider taking guided forays before venturing out alone. The forest will always be there, and the chanterelles will fruit again next season.</p>
<p>Happy foraging, and may your baskets be full of golden treasures!</p>
</div>
<footer class="post-footer">
<div class="author-bio">
<h3 class="insertr">About the Author</h3>
<div class="insertr-content">
<p>Elena Rodriguez has been foraging for wild mushrooms for over 20 years and holds certifications in mycology from the Pacific Northwest Mycological Association. She leads foraging workshops throughout California and Oregon and has contributed to several field guides on western North American fungi.</p>
</div>
</div>
<div class="post-tags">
<h4 class="insertr">Tags</h4>
<span class="tag insertr">Chanterelles</span>
<span class="tag insertr">Foraging</span>
<span class="tag insertr">Identification</span>
<span class="tag insertr">Sustainable Harvesting</span>
<span class="tag insertr">Mushroom Safety</span>
</div>
<div class="related-posts">
<h4 class="insertr">Related Guides</h4>
<div class="insertr-add related-links">
<a href="spring-safety.html" class="related-link insertr">Spring Safety: Avoiding Dangerous Look-Alikes</a>
<a href="wild-mushroom-risotto.html" class="related-link insertr">Wild Mushroom Risotto Recipe</a>
<a href="#" class="related-link insertr">Beginner's Guide to Mushroom Identification</a>
</div>
</div>
</footer>
</div>
</article>
</main>
<footer class="site-footer">
<div class="container">
<div class="footer-content">
<div class="footer-section">
<h4>Safety First</h4>
<p>Never consume any wild mushroom without absolute certainty of identification. This blog is for educational purposes only. Always consult multiple expert sources and consider taking guided forays before attempting to forage on your own.</p>
</div>
<div class="footer-section">
<h4>Connect & Learn</h4>
<p>Join local mycological societies, attend guided forays, and continue learning from experienced foragers. The fungal kingdom has endless lessons to teach.</p>
</div>
</div>
<div class="footer-bottom">
<p>&copy; 2024 The Forager's Journal. All rights reserved.</p>
</div>
</div>
</footer>
<style>
/* Additional styles for blog post page */
.blog-post {
background: var(--warm-white);
padding: 2rem 0;
}
.post-header {
text-align: center;
margin-bottom: 3rem;
}
.post-meta-info {
display: flex;
justify-content: center;
gap: 1rem;
margin-bottom: 1rem;
flex-wrap: wrap;
}
.post-meta-info .post-category {
background: var(--sage-green);
color: white;
padding: 0.3rem 1rem;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 500;
}
.post-meta-info .post-date,
.post-meta-info .reading-time {
color: var(--mushroom-brown);
font-size: 0.9rem;
}
.post-title {
font-size: 2.5rem;
color: var(--forest-green);
margin-bottom: 1rem;
line-height: 1.2;
}
.post-subtitle {
font-size: 1.2rem;
color: var(--text-light);
font-style: italic;
max-width: 600px;
margin: 0 auto;
}
.post-featured-image {
margin-bottom: 3rem;
text-align: center;
}
.featured-image {
width: 100%;
max-width: 800px;
height: 400px;
object-fit: cover;
border-radius: 12px;
box-shadow: 0 8px 24px var(--soft-shadow);
}
.image-caption {
margin-top: 1rem;
font-size: 0.9rem;
color: var(--text-light);
font-style: italic;
}
.post-content {
max-width: 800px;
margin: 0 auto;
font-size: 1.1rem;
line-height: 1.8;
}
.post-footer {
max-width: 800px;
margin: 4rem auto 0;
border-top: 1px solid var(--sage-green);
padding-top: 2rem;
}
.author-bio,
.post-tags,
.related-posts {
margin-bottom: 2rem;
}
.post-tags .tag {
display: inline-block;
background: var(--cream);
color: var(--forest-green);
padding: 0.3rem 0.8rem;
margin: 0.2rem;
border-radius: 15px;
font-size: 0.9rem;
text-decoration: none;
border: 1px solid var(--sage-green);
}
.related-links {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.related-link {
color: var(--forest-green);
text-decoration: none;
padding: 0.5rem 0;
border-bottom: 1px solid var(--cream);
transition: color 0.3s ease;
}
.related-link:hover {
color: var(--golden-yellow);
}
@media (max-width: 768px) {
.post-title {
font-size: 2rem;
}
.post-content {
font-size: 1rem;
}
.featured-image {
height: 250px;
}
}
</style>
</body>
</html>

159
demos/blog/index.html Normal file
View File

@@ -0,0 +1,159 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title class="">The Forager's Journal - Wild Mushroom Adventures</title>
<meta name="description" content="Expert guides to mushroom foraging, identification, and sustainable harvesting practices from an experienced mycophile.">
<link href="style.css" rel="stylesheet">
</head>
<body>
<header class="site-header">
<div class="container">
<h1 class="site-title insertr">🍄 The Forager's Journal</h1>
<p class="site-tagline insertr">Wild Mushroom Adventures & Foraging Wisdom</p>
<nav class="main-nav">
<a href="#" class="nav-link insertr">Home</a>
<a href="#guides" class="nav-link insertr">Field Guides</a>
<a href="#recipes" class="nav-link insertr">Recipes</a>
<a href="#safety" class="nav-link insertr">Safety</a>
<a href="#" class="nav-link insertr-gate">Edit</a>
</nav>
</div>
</header>
<main class="main-content">
<section class="hero-section">
<div class="container">
<article class="featured-post">
<h2 class="insertr">Welcome to the Wild World of Mushroom Foraging</h2>
<div class="insertr insertr-add">
<p class="lead">After twenty years of wandering through forests with my basket and field guide, I've learned that mushroom foraging is much more than hunting for dinner—it's about connecting with nature, understanding ecosystems, and developing a deep respect for the fungal kingdom that quietly sustains our world.</p>
<p>Whether you're a complete beginner wondering where to start or an experienced forager looking to expand your knowledge, this journal chronicles my adventures, discoveries, and hard-learned lessons from the forest floor.</p>
</div>
</article>
</div>
</section>
<section class="blog-posts" id="recent-posts">
<div class="container">
<h2 class="section-title insertr">Recent Adventures</h2>
<div class="insertr-add post-grid">
<article class="blog-post-preview">
<img src="https://images.unsplash.com/photo-1699896706993-fe9226cfc027?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=1740" alt="Chanterelle mushrooms in forest setting" class="post-image">
<div class="post-content">
<h3 class="post-title insertr">Golden Treasures: A Guide to Foraging Chanterelles</h3>
<p class="post-excerpt insertr">Discover the secrets to finding these golden beauties, from perfect habitat identification to sustainable harvesting techniques that ensure future seasons of abundance.</p>
<div class="post-meta">
<span class="post-date insertr">March 15, 2024</span>
<span class="post-category insertr">Field Guide</span>
</div>
<a href="chanterelles.html" class="read-more insertr">Read Full Guide →</a>
</div>
</article>
<article class="blog-post-preview">
<img src="https://images.unsplash.com/photo-1570197788417-0e82375c9371?w=400&h=250&fit=crop" alt="Shiitake mushrooms growing on log" class="post-image">
<div class="post-content">
<h3 class="post-title insertr">Spring Safety: Avoiding Dangerous Look-Alikes</h3>
<p class="post-excerpt insertr">Spring brings excitement for new growth, but also increased risk. Learn to distinguish between safe edibles and their potentially deadly twins with confidence.</p>
<div class="post-meta">
<span class="post-date insertr">March 8, 2024</span>
<span class="post-category insertr">Safety</span>
</div>
<a href="spring-safety.html" class="read-more insertr">Essential Reading →</a>
</div>
</article>
<article class="blog-post-preview">
<img src="https://images.unsplash.com/photo-1601574422784-7756746094fc?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=928" alt="Various wild mushrooms in wicker basket" class="post-image">
<div class="post-content">
<h3 class="post-title insertr">From Forest to Table: Wild Mushroom Risotto</h3>
<p class="post-excerpt insertr">Transform your morning's harvest into an elegant dinner with this technique-focused risotto recipe that celebrates the unique flavors of wild fungi.</p>
<div class="post-meta">
<span class="post-date insertr">February 28, 2024</span>
<span class="post-category insertr">Recipes</span>
</div>
<a href="wild-mushroom-risotto.html" class="read-more insertr">Get Recipe →</a>
</div>
</article>
</div>
</div>
</section>
<section class="knowledge-base" id="guides">
<div class="container">
<h2 class="section-title insertr">Essential Knowledge</h2>
<div class="knowledge-grid">
<div class="knowledge-card">
<h3 class="insertr">🔍 Identification Guide</h3>
<div class="insertr-content">
<p>Master the art of proper mushroom identification with detailed guides covering:</p>
<ul>
<li>Spore print techniques</li>
<li>Key identifying features</li>
<li>Seasonal availability charts</li>
<li>Habitat preferences</li>
</ul>
</div>
</div>
<div class="knowledge-card">
<h3 class="insertr">⚠️ Safety First</h3>
<div class="insertr-content">
<p>Foraging safely requires knowledge and caution. Essential safety topics include:</p>
<ul>
<li>The cardinal rule: when in doubt, don't</li>
<li>Common toxic look-alikes</li>
<li>Proper preparation methods</li>
<li>Emergency procedures</li>
</ul>
</div>
</div>
<div class="knowledge-card">
<h3 class="insertr">🌱 Sustainable Practices</h3>
<div class="insertr-content">
<p>Responsible foraging ensures mushrooms for future generations:</p>
<ul>
<li>Leave no trace principles</li>
<li>Proper harvesting techniques</li>
<li>Spore dispersal considerations</li>
<li>Ecosystem preservation</li>
</ul>
</div>
</div>
</div>
</div>
</section>
</main>
<footer class="site-footer">
<div class="container">
<div class="footer-content">
<div class="footer-section">
<h4 class="insertr">About the Author</h4>
<div class="insertr-content">
<p>Elena Rodriguez is a certified mycologist and foraging instructor with over two decades of experience. She leads workshops across the Pacific Northwest and has discovered three new species of forest fungi.</p>
</div>
</div>
<div class="footer-section">
<h4 class="insertr">Safety Disclaimer</h4>
<div class="insertr-content">
<p>Never consume any wild mushroom without absolute certainty of identification. This blog is for educational purposes only. Always consult multiple expert sources and consider taking guided forays before attempting to forage on your own.</p>
</div>
</div>
<div class="footer-section">
<h4 class="insertr">Connect</h4>
<p class="insertr">Follow along on social media for daily finds and seasonal updates.</p>
</div>
</div>
<div class="footer-bottom">
<p class="insertr">&copy; 2024 The Forager's Journal. All rights reserved.</p>
</div>
</div>
</footer>
</body>
</html>

79
demos/blog/insertr.yaml Normal file
View File

@@ -0,0 +1,79 @@
# The Forager's Journal - Blog Demo Configuration
site_id: "mushroom-blog"
# Discovery settings for this demo
discovery:
enabled: true
aggressive: false
containers: true
individual: true
# Content types for future blog functionality
content_types:
blog_posts:
template: "templates/blog-post.html"
output_pattern: "posts/{slug}.html"
fields:
title: text
subtitle: text
content: insertr-content
excerpt: text
published_date: date
author: text
category: text
tags: list
featured_image: image
featured_image_alt: text
pages:
template: "templates/page.html"
output_pattern: "{slug}.html"
fields:
title: text
content: insertr-content
meta_description: text
# Example content structure for blog posts
sample_content:
chanterelles_guide:
type: blog_posts
title: "Golden Treasures: A Guide to Foraging Chanterelles"
subtitle: "Discover the secrets to finding these golden beauties"
category: "Field Guide"
published_date: "2024-03-15"
author: "Elena Rodriguez"
tags: ["Chanterelles", "Foraging", "Identification", "Sustainable Harvesting"]
featured_image: "https://images.unsplash.com/photo-1518264344460-b2b8c61dbeda"
featured_image_alt: "Golden chanterelle mushrooms growing in forest moss"
spring_safety:
type: blog_posts
title: "Spring Safety: Avoiding Dangerous Look-Alikes"
subtitle: "Essential knowledge for safe spring foraging"
category: "Safety"
published_date: "2024-03-08"
author: "Elena Rodriguez"
tags: ["Safety", "Identification", "Spring Foraging", "Look-alikes"]
mushroom_risotto:
type: blog_posts
title: "From Forest to Table: Wild Mushroom Risotto"
subtitle: "Transform your harvest into an elegant dinner"
category: "Recipes"
published_date: "2024-02-28"
author: "Elena Rodriguez"
tags: ["Recipes", "Cooking", "Chanterelles", "Wild Mushrooms"]
# SEO and metadata settings
seo:
site_title: "The Forager's Journal"
site_description: "Expert guides to mushroom foraging, identification, and sustainable harvesting practices from an experienced mycophile."
author: "Elena Rodriguez"
language: "en"
# Social media settings
social:
twitter_handle: "@foragers_journal"
facebook_page: "TheForagersJournal"
instagram: "@foragers.journal"

385
demos/blog/style.css Normal file
View File

@@ -0,0 +1,385 @@
/* The Forager's Journal - Mushroom Blog Styles */
:root {
--forest-green: #2d5016;
--moss-green: #4a6741;
--sage-green: #87a96b;
--cream: #f8f6f0;
--warm-white: #fefdfb;
--mushroom-brown: #8b7355;
--earth-brown: #6b4423;
--golden-yellow: #d4af37;
--soft-shadow: rgba(45, 80, 22, 0.1);
--text-dark: #333;
--text-light: #666;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Georgia', 'Times New Roman', serif;
line-height: 1.6;
color: var(--text-dark);
background-color: var(--warm-white);
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
/* Header Styles */
.site-header {
background: linear-gradient(135deg, var(--forest-green), var(--moss-green));
color: var(--warm-white);
padding: 2rem 0;
box-shadow: 0 4px 12px var(--soft-shadow);
}
.site-title {
font-size: 2.5rem;
font-weight: bold;
margin-bottom: 0.5rem;
text-align: center;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.site-tagline {
font-size: 1.1rem;
text-align: center;
margin-bottom: 2rem;
opacity: 0.9;
font-style: italic;
}
.main-nav {
display: flex;
justify-content: center;
gap: 2rem;
flex-wrap: wrap;
}
.nav-link {
color: var(--warm-white);
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 20px;
transition: all 0.3s ease;
font-weight: 500;
}
.nav-link:hover {
background-color: rgba(255, 255, 255, 0.2);
transform: translateY(-2px);
}
/* Hero Section */
.hero-section {
background: linear-gradient(rgba(248, 246, 240, 0.9), rgba(248, 246, 240, 0.9)),
url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="20" cy="20" r="2" fill="%23d4af37" opacity="0.3"/><circle cx="80" cy="30" r="1.5" fill="%234a6741" opacity="0.4"/><circle cx="60" cy="70" r="1" fill="%236b4423" opacity="0.3"/></svg>');
padding: 4rem 0;
}
.featured-post {
background: var(--warm-white);
padding: 3rem;
border-radius: 12px;
box-shadow: 0 8px 24px var(--soft-shadow);
border-left: 5px solid var(--golden-yellow);
max-width: 800px;
margin: 0 auto;
}
.featured-post h2 {
font-size: 2.2rem;
color: var(--forest-green);
margin-bottom: 1.5rem;
line-height: 1.3;
}
.lead {
font-size: 1.2rem;
color: var(--moss-green);
margin-bottom: 1.5rem;
font-weight: 500;
line-height: 1.5;
}
/* Blog Posts Section */
.blog-posts {
padding: 4rem 0;
background-color: var(--cream);
}
.section-title {
font-size: 2rem;
color: var(--forest-green);
text-align: center;
margin-bottom: 3rem;
position: relative;
}
.section-title::after {
content: '🍄';
display: block;
font-size: 1.5rem;
margin-top: 0.5rem;
}
.post-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 2rem;
margin-top: 2rem;
}
.blog-post-preview {
background: var(--warm-white);
border-radius: 12px;
overflow: hidden;
box-shadow: 0 6px 20px var(--soft-shadow);
transition: all 0.3s ease;
}
.blog-post-preview:hover {
transform: translateY(-5px);
box-shadow: 0 12px 32px rgba(45, 80, 22, 0.15);
}
.post-image {
width: 100%;
height: 250px;
object-fit: cover;
border-bottom: 3px solid var(--sage-green);
}
.post-content {
padding: 1.5rem;
}
.post-title {
font-size: 1.3rem;
color: var(--forest-green);
margin-bottom: 1rem;
line-height: 1.4;
}
.post-excerpt {
color: var(--text-light);
margin-bottom: 1rem;
line-height: 1.6;
}
.post-meta {
display: flex;
gap: 1rem;
margin-bottom: 1rem;
font-size: 0.9rem;
}
.post-date {
color: var(--mushroom-brown);
}
.post-category {
background: var(--sage-green);
color: white;
padding: 0.2rem 0.8rem;
border-radius: 12px;
font-size: 0.8rem;
font-weight: 500;
}
.read-more {
color: var(--forest-green);
text-decoration: none;
font-weight: 600;
display: inline-flex;
align-items: center;
transition: color 0.3s ease;
}
.read-more:hover {
color: var(--golden-yellow);
}
/* Knowledge Base Section */
.knowledge-base {
padding: 4rem 0;
background: var(--warm-white);
}
.knowledge-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin-top: 2rem;
}
.knowledge-card {
background: linear-gradient(135deg, var(--cream), var(--warm-white));
padding: 2rem;
border-radius: 12px;
border: 1px solid rgba(135, 169, 107, 0.2);
box-shadow: 0 4px 16px var(--soft-shadow);
}
.knowledge-card h3 {
font-size: 1.4rem;
color: var(--forest-green);
margin-bottom: 1rem;
}
.knowledge-card ul {
list-style: none;
padding-left: 0;
}
.knowledge-card li {
padding: 0.3rem 0;
padding-left: 1.5rem;
position: relative;
color: var(--text-light);
}
.knowledge-card li::before {
content: '🌿';
position: absolute;
left: 0;
font-size: 0.8rem;
}
/* Footer */
.site-footer {
background: var(--forest-green);
color: var(--warm-white);
padding: 3rem 0 1rem;
}
.footer-content {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
margin-bottom: 2rem;
}
.footer-section h4 {
color: var(--golden-yellow);
margin-bottom: 1rem;
font-size: 1.1rem;
}
.footer-section p {
line-height: 1.6;
opacity: 0.9;
}
.footer-bottom {
border-top: 1px solid rgba(255, 255, 255, 0.2);
padding-top: 1rem;
text-align: center;
opacity: 0.8;
}
/* Insertr Content Specific Styles */
.insertr-content {
font-size: 1rem;
line-height: 1.7;
}
.insertr-content p {
margin-bottom: 1.2rem;
}
.insertr-content ul, .insertr-content ol {
margin-bottom: 1.2rem;
padding-left: 2rem;
}
.insertr-content li {
margin-bottom: 0.5rem;
}
.insertr-content h2, .insertr-content h3 {
color: var(--forest-green);
margin-top: 2rem;
margin-bottom: 1rem;
}
.insertr-content h2 {
font-size: 1.6rem;
}
.insertr-content h3 {
font-size: 1.3rem;
}
/* Responsive Design */
@media (max-width: 768px) {
.site-title {
font-size: 2rem;
}
.main-nav {
gap: 1rem;
}
.nav-link {
padding: 0.4rem 0.8rem;
font-size: 0.9rem;
}
.featured-post {
padding: 2rem;
margin: 0 1rem;
}
.featured-post h2 {
font-size: 1.8rem;
}
.post-grid {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.knowledge-grid {
grid-template-columns: 1fr;
}
.footer-content {
grid-template-columns: 1fr;
gap: 1.5rem;
}
}
@media (max-width: 480px) {
.container {
padding: 0 15px;
}
.site-title {
font-size: 1.6rem;
}
.site-tagline {
font-size: 1rem;
}
.featured-post {
padding: 1.5rem;
}
.post-content {
padding: 1.2rem;
}
.knowledge-card {
padding: 1.5rem;
}
}

View File

@@ -12,6 +12,10 @@ database:
server: server:
port: 8080 # HTTP API server port port: 8080 # HTTP API server port
sites: # Registered sites for file-based enhancement sites: # Registered sites for file-based enhancement
- site_id: "blog"
path: "./demos/blog_enhanced"
source_path: "./demos/blog/"
auto_enhance: true
- site_id: "default" - site_id: "default"
path: "./demos/default_enhanced" path: "./demos/default_enhanced"
source_path: "./demos/default" source_path: "./demos/default"

View File

@@ -421,11 +421,16 @@ func (h *ContentHandler) CreateCollectionItem(w http.ResponseWriter, r *http.Req
return return
} }
if req.SiteID == "" || req.CollectionID == "" { // Set collection ID from URL param if not provided in body
req.SiteID = r.URL.Query().Get("site_id") if req.CollectionID == "" {
req.CollectionID = collectionID req.CollectionID = collectionID
} }
// Set site ID from query param if not provided in body
if req.SiteID == "" {
req.SiteID = r.URL.Query().Get("site_id")
}
if req.SiteID == "" { if req.SiteID == "" {
http.Error(w, "site_id is required", http.StatusBadRequest) http.Error(w, "site_id is required", http.StatusBadRequest)
return return

View File

@@ -7,6 +7,7 @@ import (
"github.com/insertr/insertr/internal/db" "github.com/insertr/insertr/internal/db"
"golang.org/x/net/html" "golang.org/x/net/html"
"slices"
) )
// AuthProvider represents authentication provider information // AuthProvider represents authentication provider information
@@ -118,7 +119,7 @@ func (e *ContentEngine) ProcessContent(input ContentInput) (*ContentResult, erro
} }
} }
// 5. Inject content if required by mode // 6. Inject content if required by mode
if input.Mode == Enhancement || input.Mode == ContentInjection { if input.Mode == Enhancement || input.Mode == ContentInjection {
err = e.injectContent(processedElements, input.SiteID) err = e.injectContent(processedElements, input.SiteID)
if err != nil { if err != nil {
@@ -170,7 +171,8 @@ func (e *ContentEngine) findEditableElements(doc *html.Node) ([]InsertrElement,
Node: n, Node: n,
}) })
} }
} else if e.hasInsertrAddClass(n) { }
if e.hasInsertrAddClass(n) {
// Collection element - add directly (no container transformation for collections) // Collection element - add directly (no container transformation for collections)
collectionElements = append(collectionElements, CollectionElement{ collectionElements = append(collectionElements, CollectionElement{
Node: n, Node: n,
@@ -197,16 +199,6 @@ func (e *ContentEngine) findEditableElements(doc *html.Node) ([]InsertrElement,
return insertrElements, collectionElements return insertrElements, collectionElements
} }
// findInsertrElements finds all elements with class="insertr" and applies container transformation
// This implements the "syntactic sugar transformation" from CLASSES.md:
// - Containers with .insertr get their .insertr class removed
// - Viable children of those containers get .insertr class added
// - Regular elements with .insertr are kept as-is
func (e *ContentEngine) findInsertrElements(doc *html.Node) []InsertrElement {
insertrElements, _ := e.findEditableElements(doc)
return insertrElements
}
// walkNodes walks through all nodes in the document // walkNodes walks through all nodes in the document
func (e *ContentEngine) walkNodes(n *html.Node, fn func(*html.Node)) { func (e *ContentEngine) walkNodes(n *html.Node, fn func(*html.Node)) {
fn(n) fn(n)
@@ -218,23 +210,13 @@ func (e *ContentEngine) walkNodes(n *html.Node, fn func(*html.Node)) {
// hasInsertrClass checks if node has class="insertr" // hasInsertrClass checks if node has class="insertr"
func (e *ContentEngine) hasInsertrClass(node *html.Node) bool { func (e *ContentEngine) hasInsertrClass(node *html.Node) bool {
classes := GetClasses(node) classes := GetClasses(node)
for _, class := range classes { return slices.Contains(classes, "insertr")
if class == "insertr" {
return true
}
}
return false
} }
// hasInsertrAddClass checks if node has class="insertr-add" (collection) // hasInsertrAddClass checks if node has class="insertr-add" (collection)
func (e *ContentEngine) hasInsertrAddClass(node *html.Node) bool { func (e *ContentEngine) hasInsertrAddClass(node *html.Node) bool {
classes := GetClasses(node) classes := GetClasses(node)
for _, class := range classes { return slices.Contains(classes, "insertr-add")
if class == "insertr-add" {
return true
}
}
return false
} }
// addContentAttributes adds data-content-id attribute only // addContentAttributes adds data-content-id attribute only

View File

@@ -9,7 +9,6 @@ import (
"golang.org/x/net/html" "golang.org/x/net/html"
) )
// ProcessFile processes a single HTML file (following Go stdlib pattern like os.ReadFile/WriteFile)
func (e *ContentEngine) ProcessFile(inputPath, outputPath, siteID string, mode ProcessMode) error { func (e *ContentEngine) ProcessFile(inputPath, outputPath, siteID string, mode ProcessMode) error {
htmlContent, err := os.ReadFile(inputPath) htmlContent, err := os.ReadFile(inputPath)
if err != nil { if err != nil {

View File

@@ -15,11 +15,6 @@ dev: build-lib build
#!/usr/bin/env bash #!/usr/bin/env bash
echo "🚀 Starting Full-Stack Insertr Development..." echo "🚀 Starting Full-Stack Insertr Development..."
echo "================================================" echo "================================================"
echo ""
# Note: Sites are auto-enhanced by the server on startup
echo ""
echo "🔌 Starting Insertr server with all sites..." echo "🔌 Starting Insertr server with all sites..."
echo "" echo ""
@@ -77,10 +72,6 @@ build-lib:
watch: watch:
cd lib && npm run dev cd lib && npm run dev
# Start Air hot-reload for unified binary development
air:
air
# Build unified binary only # Build unified binary only
build-insertr: build-insertr:
go build -o insertr . go build -o insertr .
@@ -167,12 +158,6 @@ status:
sqlc: sqlc:
sqlc generate sqlc generate
# Clean generated demo directories # Clean generated demo directories
clean-demos: clean-demos:
#!/usr/bin/env bash #!/usr/bin/env bash
@@ -185,6 +170,11 @@ clean-demos:
echo "🗑️ Removed: default_enhanced" echo "🗑️ Removed: default_enhanced"
fi fi
if [ -d "./demos/blog_enhanced/" ]; then
rm -rf "./demos/blog_enhanced/"
echo "🗑️ Removed: blog_enhanced"
fi
if [ -d "./demos/simple_enhanced" ]; then if [ -d "./demos/simple_enhanced" ]; then
rm -rf "./demos/simple_enhanced" rm -rf "./demos/simple_enhanced"
echo "🗑️ Removed: simple_enhanced" echo "🗑️ Removed: simple_enhanced"

View File

@@ -1,12 +1,6 @@
/** /**
* INSERTR CSS - Centralized Styles for Content Management Interface * INSERTR CSS - Centralized Styles for Content Management Interface
* *
* Architecture: Simple class-based CSS with proper specificity
* - Class selectors (0,0,1,0) automatically beat universal selectors (0,0,0,0)
* - No cascade layers needed - works in all browsers
* - No !important needed - specificity handles conflicts naturally
* - Explicit colors prevent inheritance issues
*
* Components: * Components:
* - .insertr-gate: Minimal styling for user-defined gates * - .insertr-gate: Minimal styling for user-defined gates
* - .insertr-auth-*: Authentication controls and buttons * - .insertr-auth-*: Authentication controls and buttons
@@ -26,18 +20,18 @@
--insertr-danger: #dc3545; --insertr-danger: #dc3545;
--insertr-warning: #ffc107; --insertr-warning: #ffc107;
--insertr-info: #17a2b8; --insertr-info: #17a2b8;
/* Text colors */ /* Text colors */
--insertr-text-primary: #333333; --insertr-text-primary: #333333;
--insertr-text-secondary: #666666; --insertr-text-secondary: #666666;
--insertr-text-muted: #999999; --insertr-text-muted: #999999;
--insertr-text-inverse: #ffffff; --insertr-text-inverse: #ffffff;
/* Background colors */ /* Background colors */
--insertr-bg-primary: #ffffff; --insertr-bg-primary: #ffffff;
--insertr-bg-secondary: #f8f9fa; --insertr-bg-secondary: #f8f9fa;
--insertr-bg-overlay: rgba(0, 0, 0, 0.5); --insertr-bg-overlay: rgba(0, 0, 0, 0.5);
/* Border and spacing */ /* Border and spacing */
--insertr-border-color: #dee2e6; --insertr-border-color: #dee2e6;
--insertr-border-radius: 4px; --insertr-border-radius: 4px;
@@ -45,21 +39,21 @@
--insertr-spacing-sm: 8px; --insertr-spacing-sm: 8px;
--insertr-spacing-md: 16px; --insertr-spacing-md: 16px;
--insertr-spacing-lg: 24px; --insertr-spacing-lg: 24px;
/* Z-index management */ /* Z-index management */
--insertr-z-modal-backdrop: 1040; --insertr-z-modal-backdrop: 1040;
--insertr-z-modal: 1050; --insertr-z-modal: 1050;
--insertr-z-tooltip: 1070; --insertr-z-tooltip: 1070;
--insertr-z-overlay: 999999; --insertr-z-overlay: 999999;
/* Typography */ /* Typography */
--insertr-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; --insertr-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
--insertr-font-size-sm: 12px; --insertr-font-size-sm: 12px;
--insertr-font-size-base: 14px; --insertr-font-size-base: 14px;
--insertr-line-height: 1.4; --insertr-line-height: 1.4;
/* Form elements - using existing variables */ /* Form elements - using existing variables */
/* Animation */ /* Animation */
--insertr-transition: all 0.2s ease-in-out; --insertr-transition: all 0.2s ease-in-out;
} }
@@ -142,8 +136,15 @@
} }
@keyframes insertr-pulse { @keyframes insertr-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; } 0%,
100% {
opacity: 1;
}
50% {
opacity: 0.6;
}
} }
/* Action Buttons Section */ /* Action Buttons Section */
@@ -237,34 +238,6 @@
transition: var(--insertr-transition); transition: var(--insertr-transition);
} }
.insertr-editing-hover::after {
content: '✏️ Click to edit';
position: absolute;
top: -30px;
left: 50%;
transform: translateX(-50%);
background: var(--insertr-text-primary);
color: var(--insertr-text-inverse);
padding: var(--insertr-spacing-xs) var(--insertr-spacing-sm);
border-radius: var(--insertr-border-radius);
font-size: var(--insertr-font-size-sm);
font-family: var(--insertr-font-family);
white-space: nowrap;
z-index: var(--insertr-z-tooltip);
opacity: 0;
animation: insertr-tooltip-show 0.2s ease-in-out forwards;
}
@keyframes insertr-tooltip-show {
from {
opacity: 0;
transform: translateX(-50%) translateY(-5px);
}
to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
/* Hide editing indicators when not in edit mode */ /* Hide editing indicators when not in edit mode */
body:not(.insertr-edit-mode) .insertr-editing-hover { body:not(.insertr-edit-mode) .insertr-editing-hover {
@@ -376,17 +349,18 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
background: var(--insertr-bg-primary) !important; background: var(--insertr-bg-primary) !important;
border: 1px solid var(--insertr-border-color) !important; border: 1px solid var(--insertr-border-color) !important;
border-radius: var(--insertr-border-radius) !important; border-radius: var(--insertr-border-radius) !important;
padding: 6px 10px !important; /* Move padding to button level */ padding: 6px 10px !important;
/* Move padding to button level */
font-size: var(--insertr-font-size-sm); font-size: var(--insertr-font-size-sm);
cursor: pointer; cursor: pointer;
transition: var(--insertr-transition); transition: var(--insertr-transition);
/* Fixed button dimensions and layout */ /* Fixed button dimensions and layout */
min-height: 32px; min-height: 32px;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
/* Reset button-level text properties to prevent inheritance */ /* Reset button-level text properties to prevent inheritance */
font-family: var(--insertr-font-family); font-family: var(--insertr-font-family);
text-decoration: none; text-decoration: none;
@@ -400,7 +374,7 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
/* Create style containment boundary */ /* Create style containment boundary */
contain: layout style; contain: layout style;
overflow: hidden; overflow: hidden;
@@ -410,13 +384,13 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
.insertr-style-sample { .insertr-style-sample {
/* Authentic style preview - let all styling come through naturally */ /* Authentic style preview - let all styling come through naturally */
display: inline-block; display: inline-block;
/* Size constraints only */ /* Size constraints only */
max-width: 100px; max-width: 100px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
/* All other styling (color, weight, transform, decoration, etc.) /* All other styling (color, weight, transform, decoration, etc.)
comes from applied classes - no interference */ comes from applied classes - no interference */
} }
@@ -534,7 +508,7 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
} }
/* Form inputs and selects */ /* Form inputs and selects */
.insertr-form-input, .insertr-form-input,
.insertr-form-select { .insertr-form-select {
width: 100%; width: 100%;
padding: var(--insertr-spacing-sm) var(--insertr-spacing-md); padding: var(--insertr-spacing-sm) var(--insertr-spacing-md);
@@ -550,7 +524,7 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
} }
/* Focus states */ /* Focus states */
.insertr-form-input:focus, .insertr-form-input:focus,
.insertr-form-select:focus { .insertr-form-select:focus {
outline: none; outline: none;
border-color: var(--insertr-primary); border-color: var(--insertr-primary);
@@ -563,7 +537,7 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
} }
/* Disabled states */ /* Disabled states */
.insertr-form-input:disabled, .insertr-form-input:disabled,
.insertr-form-select:disabled { .insertr-form-select:disabled {
background: var(--insertr-bg-secondary); background: var(--insertr-bg-secondary);
color: var(--insertr-text-muted); color: var(--insertr-text-muted);
@@ -572,14 +546,14 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
} }
/* Error states */ /* Error states */
.insertr-form-input.insertr-error, .insertr-form-input.insertr-error,
.insertr-form-select.insertr-error { .insertr-form-select.insertr-error {
border-color: var(--insertr-danger); border-color: var(--insertr-danger);
box-shadow: 0 0 0 2px rgba(220, 53, 69, 0.25); box-shadow: 0 0 0 2px rgba(220, 53, 69, 0.25);
} }
/* Success states */ /* Success states */
.insertr-form-input.insertr-success, .insertr-form-input.insertr-success,
.insertr-form-select.insertr-success { .insertr-form-select.insertr-success {
border-color: var(--insertr-success); border-color: var(--insertr-success);
box-shadow: 0 0 0 2px rgba(40, 167, 69, 0.25); box-shadow: 0 0 0 2px rgba(40, 167, 69, 0.25);
@@ -917,7 +891,7 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
} }
/* Collection items hover state */ /* Collection items hover state */
.insertr-collection-active > *:hover { .insertr-collection-active>*:hover {
background: rgba(0, 123, 255, 0.03); background: rgba(0, 123, 255, 0.03);
outline: 1px solid rgba(var(--insertr-primary), 0.2); outline: 1px solid rgba(var(--insertr-primary), 0.2);
outline-offset: 2px; outline-offset: 2px;
@@ -925,7 +899,7 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
} }
/* Show item controls on hover */ /* Show item controls on hover */
.insertr-collection-active > *:hover .insertr-item-controls { .insertr-collection-active>*:hover .insertr-item-controls {
opacity: 1; opacity: 1;
} }
@@ -938,7 +912,7 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
width: 100%; width: 100%;
max-width: 200px; max-width: 200px;
} }
.insertr-item-controls { .insertr-item-controls {
position: relative; position: relative;
opacity: 1; opacity: 1;
@@ -947,10 +921,10 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
justify-content: center; justify-content: center;
margin-top: var(--insertr-spacing-xs); margin-top: var(--insertr-spacing-xs);
} }
.insertr-control-btn { .insertr-control-btn {
width: 32px; width: 32px;
height: 32px; height: 32px;
font-size: 14px; font-size: 14px;
} }
} }

View File

@@ -18,9 +18,6 @@ export class InsertrFormRenderer {
* @param {Function} onCancel - Cancel callback * @param {Function} onCancel - Cancel callback
*/ */
showEditForm(meta, currentContent, onSave, onCancel) { showEditForm(meta, currentContent, onSave, onCancel) {
const { element } = meta;
// All other elements use the editor directly
return this.editor.edit(meta, currentContent, onSave, onCancel); return this.editor.edit(meta, currentContent, onSave, onCancel);
} }