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:
@@ -18,6 +18,43 @@ This document outlines the design and implementation plan for adding draft/publi
|
||||
- All-or-nothing manual enhancement process
|
||||
- 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
|
||||
|
||||
### 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
|
||||
- **FR4**: System supports rollback to previous published versions
|
||||
- **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
|
||||
- **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
|
||||
- **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
|
||||
CREATE TABLE published_versions (
|
||||
content_id TEXT NOT NULL,
|
||||
site_id TEXT NOT NULL,
|
||||
published_version_id INTEGER NOT NULL,
|
||||
published_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW()) NOT NULL,
|
||||
published_by TEXT NOT NULL,
|
||||
PRIMARY KEY (content_id, site_id)
|
||||
);
|
||||
-- Add state column to existing content_versions table
|
||||
ALTER TABLE content_versions ADD COLUMN state TEXT DEFAULT 'history' NOT NULL
|
||||
CHECK (state IN ('history', 'draft', 'live'));
|
||||
|
||||
-- Create index for efficient state-based queries
|
||||
CREATE INDEX idx_content_versions_state ON content_versions(content_id, site_id, state);
|
||||
|
||||
-- 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**:
|
||||
- Minimal database changes
|
||||
- Leverages existing version history
|
||||
- Backward compatible
|
||||
- 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)
|
||||
**Migration Strategy**:
|
||||
1. All existing `content_versions` entries become `state='history'`
|
||||
2. Current `content` table entries migrate to `content_versions` with `state='live'`
|
||||
3. Drop `content` table after migration (everything now in `content_versions`)
|
||||
|
||||
### Content States
|
||||
|
||||
| State | Draft Table | Published Table | Description |
|
||||
|-------|-------------|-----------------|-------------|
|
||||
| `draft_only` | ✅ Exists | ❌ None | New content, never published |
|
||||
| `published` | ✅ Exists | ✅ Exists, Same | Content published, no pending changes |
|
||||
| `modified` | ✅ Exists | ✅ Exists, Different | Published content with unpublished changes |
|
||||
| `scheduled` | ✅ Exists | ✅ Exists | Content queued for future publishing |
|
||||
| State | Description | Query Pattern |
|
||||
|-------|-------------|---------------|
|
||||
| `history` | Previous versions, for rollback | `WHERE state = 'history' ORDER BY created_at DESC` |
|
||||
| `draft` | Current working version, not published | `WHERE state = 'draft'` |
|
||||
| `live` | Currently published version | `WHERE state = 'live'` |
|
||||
|
||||
### 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
|
||||
|
||||
**New Endpoints**:
|
||||
```
|
||||
GET /api/content/{id}?mode=draft|published # Get content in specific state
|
||||
POST /api/content/{id}/save-draft # Save without publishing
|
||||
POST /api/content/{id}/publish # Publish draft content
|
||||
GET /api/content/{id}?state=draft|live|history # Get content in specific state
|
||||
POST /api/content/{id}/save-draft # Save as draft (auto-save)
|
||||
POST /api/content/{id}/publish # Publish draft to live
|
||||
POST /api/content/{id}/rollback/{version_id} # Rollback to specific version
|
||||
GET /api/content/{id}/diff # Compare draft vs live
|
||||
POST /api/enhancement/preview # Preview site with draft content
|
||||
GET /api/status/changes # List all unpublished changes
|
||||
POST /api/content/bulk-publish # Publish multiple items
|
||||
GET /api/content/diff/{id} # Show draft vs published diff
|
||||
POST /api/enhancement/preview # Preview with draft content
|
||||
POST /api/enhancement/publish # Enhance with published content
|
||||
GET /api/status/publishing # Get site publishing status
|
||||
```
|
||||
|
||||
**Enhanced Endpoints**:
|
||||
```
|
||||
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**:
|
||||
- Replace "🔄 Enhance" with publishing workflow buttons
|
||||
- Add "💾 Save Draft" (auto-saves every 30s)
|
||||
- Add "🚀 Publish Changes" with confirmation dialog
|
||||
- Add "👁️ Preview Changes" for draft enhancement
|
||||
- Add "📊 Publishing Status" indicator
|
||||
**Core Queries**:
|
||||
```go
|
||||
// Get current live content for enhancement
|
||||
func (r *Repository) GetLiveContent(siteID, contentID string) (*Content, error) {
|
||||
return r.queryContent(siteID, contentID, "live")
|
||||
}
|
||||
|
||||
**Visual States**:
|
||||
- 🟡 **Draft Pending**: Yellow indicator for unpublished changes
|
||||
- 🟢 **Published**: Green indicator when draft matches published
|
||||
- 🔵 **Scheduled**: Blue indicator for queued publishing
|
||||
- 🔴 **Error**: Red indicator for publishing failures
|
||||
// Get current draft for editing
|
||||
func (r *Repository) GetDraftContent(siteID, contentID string) (*Content, error) {
|
||||
return r.queryContent(siteID, contentID, "draft")
|
||||
}
|
||||
|
||||
**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
|
||||
- Publishing confirmation dialog with change summary
|
||||
- Bulk publishing interface for multiple content items
|
||||
- Publishing history and rollback interface
|
||||
- Version history with rollback capability
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Core Infrastructure (Week 1-2)
|
||||
- [ ] Create new database schema
|
||||
- [ ] Implement migration scripts
|
||||
- [ ] Update repository interfaces
|
||||
- [ ] Add basic draft/publish operations
|
||||
- [ ] Update content versioning system
|
||||
### Phase 1: Database Foundation (Week 1)
|
||||
- [ ] Add `state` column to `content_versions` table
|
||||
- [ ] Create state-based indexes and constraints
|
||||
- [ ] Write migration script for existing content
|
||||
- [ ] Test migration on demo sites
|
||||
|
||||
### Phase 2: API Development (Week 3-4)
|
||||
- [ ] Implement new API endpoints
|
||||
- [ ] Update existing endpoints for draft mode
|
||||
- [ ] Add enhancement mode switching
|
||||
- [ ] Implement publishing workflow APIs
|
||||
- [ ] Add content state management
|
||||
### Phase 2: Repository Layer (Week 2)
|
||||
- [ ] Update repository interfaces for state-based queries
|
||||
- [ ] Implement draft save/publish/rollback operations
|
||||
- [ ] Add state transition validation
|
||||
- [ ] Update existing content operations
|
||||
|
||||
### Phase 3: UI Integration (Week 5-6)
|
||||
- [ ] Update control panel with new buttons
|
||||
- [ ] Add visual state indicators
|
||||
- [ ] Implement draft auto-save
|
||||
- [ ] Add preview functionality
|
||||
- [ ] Create publishing confirmation dialogs
|
||||
### Phase 3: API Integration (Week 3)
|
||||
- [ ] Implement new draft/publish endpoints
|
||||
- [ ] Update existing endpoints for state handling
|
||||
- [ ] Add preview enhancement functionality
|
||||
- [ ] Implement bulk publishing API
|
||||
|
||||
### Phase 4: Advanced Features (Week 7-8)
|
||||
- [ ] Implement diff viewer
|
||||
- [ ] Add bulk publishing interface
|
||||
- [ ] Create publishing history view
|
||||
- [ ] Add rollback functionality
|
||||
- [ ] Implement scheduled publishing foundation
|
||||
### Phase 4: UI Implementation (Week 4)
|
||||
- [ ] Update control panel with new buttons and states
|
||||
- [ ] Implement auto-save functionality
|
||||
- [ ] Add diff viewer and publishing dialogs
|
||||
- [ ] Create publishing status dashboard
|
||||
|
||||
### Phase 5: Testing & Polish (Week 9-10)
|
||||
- [ ] Comprehensive testing across all demo sites
|
||||
- [ ] Performance optimization
|
||||
### Phase 5: Testing & Polish (Week 5)
|
||||
- [ ] Comprehensive testing across demo sites
|
||||
- [ ] Performance optimization and monitoring
|
||||
- [ ] Error handling and edge cases
|
||||
- [ ] Documentation updates
|
||||
- [ ] 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
|
||||
- [ ] Documentation and migration guides
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
- Repository layer draft/publish operations
|
||||
- Content state transition logic
|
||||
- API endpoint functionality
|
||||
- Migration script validation
|
||||
### Migration Testing
|
||||
- Test content migration with various demo site configurations
|
||||
- Validate data integrity before/after migration
|
||||
- Test rollback procedures if migration fails
|
||||
|
||||
### Integration Tests
|
||||
- End-to-end publishing workflow
|
||||
- Enhancement with different content modes
|
||||
### Workflow Testing
|
||||
- Draft save/publish cycles with various content types
|
||||
- Concurrent editing scenarios
|
||||
- Database migration processes
|
||||
- Auto-save reliability under different conditions
|
||||
- Enhancement preview vs live comparison
|
||||
|
||||
### User Acceptance Tests
|
||||
- Editorial workflow testing with demo sites
|
||||
- Performance testing under load
|
||||
- Cross-browser UI compatibility
|
||||
- Mobile device testing
|
||||
### Performance Testing
|
||||
- State-based query performance with large datasets
|
||||
- Auto-save frequency impact on database
|
||||
- Enhancement speed with draft vs live content
|
||||
|
||||
## Success Metrics
|
||||
|
||||
### Functional Metrics
|
||||
- ✅ All existing demo sites work without modification
|
||||
- ✅ Editors can save drafts without affecting live sites
|
||||
- ✅ Publishing workflow completes in <5 seconds
|
||||
### Functional Success
|
||||
- ✅ 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
|
||||
- ✅ Clear visual indication of content states
|
||||
- ✅ Intuitive publishing workflow
|
||||
- ✅ Auto-save prevents content loss
|
||||
- ✅ Preview functionality works accurately
|
||||
### User Experience Success
|
||||
- ✅ Clear visual distinction between draft and published states
|
||||
- ✅ Intuitive publishing workflow requiring minimal training
|
||||
- ✅ Preview functionality accurately reflects published output
|
||||
|
||||
### Technical Metrics
|
||||
- ✅ API response times remain under 200ms
|
||||
- ✅ Database migration completes in <30 minutes
|
||||
- ✅ Memory usage increase <20%
|
||||
- ✅ 100% test coverage for new functionality
|
||||
### Technical Success
|
||||
- ✅ State-based queries perform within 100ms
|
||||
- ✅ Database size increase <10% due to state optimization
|
||||
- ✅ 100% test coverage for new draft/publish functionality
|
||||
|
||||
## Future Considerations
|
||||
## Future Enhancements
|
||||
|
||||
### Potential Enhancements
|
||||
- **Scheduled Publishing**: Full calendar-based publishing system
|
||||
- **Approval Workflows**: Multi-stage content approval process
|
||||
- **Content Staging**: Multiple environment support (dev/staging/prod)
|
||||
- **Collaborative Editing**: Real-time collaborative editing features
|
||||
- **Content Templates**: Draft templates for consistent content structure
|
||||
### Near-term (Next 6 months)
|
||||
- **Scheduled Publishing**: Add `scheduled` state with `publish_at` timestamp
|
||||
- **Bulk Operations**: Enhanced multi-content publishing interface
|
||||
- **Content Conflicts**: Optimistic locking for concurrent editing
|
||||
|
||||
### Technical Debt
|
||||
- Consider eventual consolidation of version tables
|
||||
- Evaluate long-term storage strategies for large sites
|
||||
- Plan for horizontal scaling of publishing operations
|
||||
- Review and optimize database schema after real-world usage
|
||||
### Long-term (6+ months)
|
||||
- **Approval Workflows**: Multi-step editorial approval process
|
||||
- **Content Branching**: Multiple draft versions per content item
|
||||
- **Real-time Collaboration**: Live editing with conflict resolution
|
||||
|
||||
---
|
||||
|
||||
*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
133
demos/blog/README.md
Normal 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.*
|
||||
339
demos/blog/chanterelles.html
Normal file
339
demos/blog/chanterelles.html
Normal 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>© 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
159
demos/blog/index.html
Normal 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">© 2024 The Forager's Journal. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
79
demos/blog/insertr.yaml
Normal file
79
demos/blog/insertr.yaml
Normal 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
385
demos/blog/style.css
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,10 @@ database:
|
||||
server:
|
||||
port: 8080 # HTTP API server port
|
||||
sites: # Registered sites for file-based enhancement
|
||||
- site_id: "blog"
|
||||
path: "./demos/blog_enhanced"
|
||||
source_path: "./demos/blog/"
|
||||
auto_enhance: true
|
||||
- site_id: "default"
|
||||
path: "./demos/default_enhanced"
|
||||
source_path: "./demos/default"
|
||||
|
||||
@@ -421,11 +421,16 @@ func (h *ContentHandler) CreateCollectionItem(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
if req.SiteID == "" || req.CollectionID == "" {
|
||||
req.SiteID = r.URL.Query().Get("site_id")
|
||||
// Set collection ID from URL param if not provided in body
|
||||
if req.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 == "" {
|
||||
http.Error(w, "site_id is required", http.StatusBadRequest)
|
||||
return
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/insertr/insertr/internal/db"
|
||||
"golang.org/x/net/html"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
err = e.injectContent(processedElements, input.SiteID)
|
||||
if err != nil {
|
||||
@@ -170,7 +171,8 @@ func (e *ContentEngine) findEditableElements(doc *html.Node) ([]InsertrElement,
|
||||
Node: n,
|
||||
})
|
||||
}
|
||||
} else if e.hasInsertrAddClass(n) {
|
||||
}
|
||||
if e.hasInsertrAddClass(n) {
|
||||
// Collection element - add directly (no container transformation for collections)
|
||||
collectionElements = append(collectionElements, CollectionElement{
|
||||
Node: n,
|
||||
@@ -197,16 +199,6 @@ func (e *ContentEngine) findEditableElements(doc *html.Node) ([]InsertrElement,
|
||||
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
|
||||
func (e *ContentEngine) walkNodes(n *html.Node, fn func(*html.Node)) {
|
||||
fn(n)
|
||||
@@ -218,23 +210,13 @@ func (e *ContentEngine) walkNodes(n *html.Node, fn func(*html.Node)) {
|
||||
// hasInsertrClass checks if node has class="insertr"
|
||||
func (e *ContentEngine) hasInsertrClass(node *html.Node) bool {
|
||||
classes := GetClasses(node)
|
||||
for _, class := range classes {
|
||||
if class == "insertr" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(classes, "insertr")
|
||||
}
|
||||
|
||||
// hasInsertrAddClass checks if node has class="insertr-add" (collection)
|
||||
func (e *ContentEngine) hasInsertrAddClass(node *html.Node) bool {
|
||||
classes := GetClasses(node)
|
||||
for _, class := range classes {
|
||||
if class == "insertr-add" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(classes, "insertr-add")
|
||||
}
|
||||
|
||||
// addContentAttributes adds data-content-id attribute only
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"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 {
|
||||
htmlContent, err := os.ReadFile(inputPath)
|
||||
if err != nil {
|
||||
|
||||
20
justfile
20
justfile
@@ -15,11 +15,6 @@ dev: build-lib build
|
||||
#!/usr/bin/env bash
|
||||
echo "🚀 Starting Full-Stack Insertr Development..."
|
||||
echo "================================================"
|
||||
echo ""
|
||||
|
||||
# Note: Sites are auto-enhanced by the server on startup
|
||||
|
||||
echo ""
|
||||
echo "🔌 Starting Insertr server with all sites..."
|
||||
echo ""
|
||||
|
||||
@@ -77,10 +72,6 @@ build-lib:
|
||||
watch:
|
||||
cd lib && npm run dev
|
||||
|
||||
# Start Air hot-reload for unified binary development
|
||||
air:
|
||||
air
|
||||
|
||||
# Build unified binary only
|
||||
build-insertr:
|
||||
go build -o insertr .
|
||||
@@ -167,12 +158,6 @@ status:
|
||||
sqlc:
|
||||
sqlc generate
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Clean generated demo directories
|
||||
clean-demos:
|
||||
#!/usr/bin/env bash
|
||||
@@ -185,6 +170,11 @@ clean-demos:
|
||||
echo "🗑️ Removed: default_enhanced"
|
||||
fi
|
||||
|
||||
if [ -d "./demos/blog_enhanced/" ]; then
|
||||
rm -rf "./demos/blog_enhanced/"
|
||||
echo "🗑️ Removed: blog_enhanced"
|
||||
fi
|
||||
|
||||
if [ -d "./demos/simple_enhanced" ]; then
|
||||
rm -rf "./demos/simple_enhanced"
|
||||
echo "🗑️ Removed: simple_enhanced"
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
/**
|
||||
* 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:
|
||||
* - .insertr-gate: Minimal styling for user-defined gates
|
||||
* - .insertr-auth-*: Authentication controls and buttons
|
||||
@@ -142,8 +136,15 @@
|
||||
}
|
||||
|
||||
@keyframes insertr-pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.6; }
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
/* Action Buttons Section */
|
||||
@@ -237,34 +238,6 @@
|
||||
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 */
|
||||
body:not(.insertr-edit-mode) .insertr-editing-hover {
|
||||
@@ -376,7 +349,8 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
|
||||
background: var(--insertr-bg-primary) !important;
|
||||
border: 1px solid var(--insertr-border-color) !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);
|
||||
cursor: pointer;
|
||||
transition: var(--insertr-transition);
|
||||
|
||||
@@ -18,9 +18,6 @@ export class InsertrFormRenderer {
|
||||
* @param {Function} onCancel - Cancel callback
|
||||
*/
|
||||
showEditForm(meta, currentContent, onSave, onCancel) {
|
||||
const { element } = meta;
|
||||
|
||||
// All other elements use the editor directly
|
||||
return this.editor.edit(meta, currentContent, onSave, onCancel);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user