diff --git a/AGENTS.md b/AGENTS.md index 23748a1..e41691c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,11 +1,13 @@ # AGENTS.md - Developer Guide for Insertr ## Build/Test Commands -- `just dev` - Full-stack development (recommended) +- `just dev` - Full-stack development with auto-enhanced demo sites (recommended) +- `just serve` - API server only with multi-site hosting - `just build` - Build entire project (Go binary + JS library) - `just build-lib` - Build JS library only +- `just enhance` - CLI enhancement of static sites -For running and testing our application read our justfile. +For complete command reference, see the justfile. ## Code Style Guidelines @@ -22,8 +24,34 @@ For running and testing our application read our justfile. - Use `const`/`let`, avoid `var` - Prefer template literals over string concatenation - Export classes/functions explicitly, avoid default exports when multiple exports exist +- NO COMMENTS unless explicitly asked - keep code clean and self-documenting +- HTML-first approach: preserve element attributes and styling perfectly ### Database - Use `sqlc` for Go database code generation -- SQL files in `db/queries/`, schemas in `db/{sqlite,postgresql}/schema.sql` +- SQL files in `db/queries/`, schemas in `db/{sqlite,postgresql}/setup.sql` +- Version history implemented with full rollback capabilities - Run `just sqlc` after schema changes +- Multi-database support: SQLite for development, PostgreSQL for production + +## Current Architecture + +### Frontend (Library) +- **StyleAware Editor**: Rich text editing with automatic style detection +- **HTML Preservation**: Perfect fidelity editing that maintains all element attributes +- **Style Detection Engine**: Automatically generates formatting options from detected CSS +- **Link Management**: Popup-based configuration for complex multi-property elements +- **Zero Dependencies**: No markdown libraries - HTML-first approach + +### Backend (Go Binary) +- **Multi-Site Server**: Hosts and auto-enhances multiple static sites +- **Real-Time Enhancement**: Content changes trigger immediate file updates +- **Full REST API**: Complete CRUD operations with version control +- **Authentication**: Mock (dev) + Authentik OIDC (production) +- **Database Layer**: SQLite/PostgreSQL with full version history + +### Key Principles +- **HTML-First**: No lossy markdown conversion - perfect attribute preservation +- **Zero Configuration**: Just add `class="insertr"` to any element +- **Style-Aware**: Automatically detect and offer CSS-based formatting options +- **Performance**: Regular visitors get pure static HTML with zero CMS overhead diff --git a/README.md b/README.md index 23ff3d6..2979c41 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,11 @@ Containers with `class="insertr"` automatically make their viable children edita - No runtime overhead ### 2. **Content Editors** (Authenticated users) -- Rich editing interface loads on demand -- Click-to-edit any marked element -- Smart input types: text, textarea, link editor, markdown -- Changes saved to database +- Style-aware editing interface loads on demand +- Click-to-edit any marked element +- Rich text editor with style preservation and formatting toolbar +- Link editor with popup configuration for complex elements +- Changes saved to database with version history ### 3. **Developers** (You) - Add `class="insertr"` to any element @@ -136,19 +137,23 @@ auth: ## πŸš€ Current Status **βœ… Complete Full-Stack CMS** -- **Professional Editor**: Modal forms, markdown support, authentication system -- **Enterprise Authentication**: Production-ready OIDC integration with Authentik, PKCE security -- **Content Persistence**: SQLite database with REST API, version control +- **Style-Aware Editor**: Rich text editor with automatic style detection and formatting toolbar +- **HTML Preservation**: Perfect fidelity editing that maintains all element attributes and styling +- **Enterprise Authentication**: Production-ready OIDC integration with Authentik, PKCE security +- **Content Persistence**: SQLite/PostgreSQL database with REST API, complete version control - **Version History**: Complete edit history with user attribution and one-click rollback - **Build Enhancement**: Parse HTML, inject database content, build-time optimization -- **Smart Detection**: Auto-detects content types (text/markdown/link) -- **Deterministic IDs**: Content-based ID generation for consistent developer experience +- **Smart Detection**: Auto-detects content types and generates style-based formatting options +- **Link Management**: Popup-based link configuration for complex multi-property elements - **Full Integration**: Seamless development workflow with hot reload **πŸš€ Production Ready** -- Deploy to cloud infrastructure -- Configure CDN for library assets -- Scale with PostgreSQL database +- βœ… Full HTTP API server with authentication and version control +- βœ… Build-time enhancement for static site generators +- βœ… PostgreSQL and SQLite database support +- βœ… Authentik OIDC integration for enterprise authentication +- Deploy enhanced static files to any CDN or host +- Run content API server on separate infrastructure ## πŸ› οΈ Quick Start @@ -166,8 +171,8 @@ just dev ``` This starts: -- **Demo site**: http://localhost:3000 (with live reload) -- **API server**: http://localhost:8080 (with content persistence) +- **Demo sites**: http://localhost:8080/sites/demo/ (auto-enhanced for testing) +- **API server**: http://localhost:8080/api/* (with content persistence and version control) ### **Using NPM** ```bash @@ -202,10 +207,11 @@ just clean # Clean build artifacts Running `just dev` gives you the **complete Insertr CMS**: -- βœ… **Professional Editor** - Modal forms, markdown support, authentication -- βœ… **Real-Time Persistence** - SQLite database with REST API +- βœ… **Style-Aware Editor** - Rich text editing with automatic style detection and formatting +- βœ… **HTML Preservation** - Perfect fidelity editing that maintains all styling and attributes +- βœ… **Real-Time Persistence** - SQLite database with REST API and authentication - βœ… **Version Control** - Complete edit history with user attribution and rollback -- βœ… **Content Management** - Create, read, update content via browser +- βœ… **Content Management** - Create, read, update content via browser with popup-based link editing - βœ… **Build Integration** - Binary enhances HTML with database content - βœ… **Hot Reload** - Changes reflected immediately @@ -309,18 +315,18 @@ just enhance input="src" output="public" # Custom paths ### **Runtime API Server** ```bash +# Development server with automatic site enhancement +./insertr serve --dev-mode --port 8080 + # Production server with PostgreSQL -./insertr serve --db "postgresql://user:pass@host:5432/dbname" +./insertr serve --db "postgresql://user:pass@host:5432/dbname" --config production.yaml -# Development server with SQLite -./insertr serve --dev-mode --port 8080 --db ./dev.db - -# Production server with custom config +# Production server with custom config and Authentik ./insertr --config ./production.yaml serve # Use justfile shortcuts -just serve # Development server on :8080 -just serve-prod port="443" # Production server +just serve # Development server on :8080 with auto-enhanced demo sites +just dev # Full development stack (recommended) ``` ### **Configuration Options** @@ -380,10 +386,10 @@ export INSERTR_SITE_ID="production" ``` ### **Content Type Detection** -- **Headlines** (`h1-h6`) β†’ Text input with character limits -- **Paragraphs** (`p`) β†’ Markdown textarea for rich content -- **Links/Buttons** (`a`, `button`) β†’ Text + URL fields -- **Containers** (`div`, `section`) β†’ Expand to viable children +- **Headlines** (`h1-h6`) β†’ Rich text editor with style-aware formatting +- **Paragraphs** (`p`) β†’ Rich text editor with detected style options from CSS +- **Links/Buttons** (`a`, `button`) β†’ Popup-based link editor with text + URL configuration +- **Containers** (`div`, `section`) β†’ Expand to viable children with automatic style detection ## πŸ”Œ API Reference @@ -512,81 +518,89 @@ mock_content: false # Use mock content instead of real data - Works with any framework or generator - Developer experience focused -## πŸ—οΈ **Server-Hosted Static Sites** (NEW) +## πŸš€ **Production Deployment Workflow** -**Real-time content updates for server-hosted static sites with immediate deployment.** +**Build-time enhancement + Runtime API for content management.** -### **Overview** -Insertr now supports hosting and enhancing static sites directly on the server. When content changes through the editor, static files are automatically updated in-place without requiring rebuilds or redeployment. +### **Production Architecture** +The production workflow separates content management from site hosting: -### **Key Benefits** -- βœ… **Immediate Updates**: Content changes are live instantly -- βœ… **Database Source of Truth**: Content stored in database, not files -- βœ… **No Rebuilds Required**: Only content updates, templates stay the same -- βœ… **Multi-Site Support**: Host multiple enhanced sites on one server -- βœ… **Automatic Backups**: Original files backed up before enhancement - -### **Workflow** ``` -1. Developer deploys static site β†’ Server directory (e.g., /var/www/mysite) -2. Insertr enhances files β†’ Adds editing capabilities + injects content -3. Editor makes changes β†’ API updates database + triggers file enhancement -4. Changes immediately live β†’ Visitors see updated content instantly +1. Static Site Build β†’ `insertr enhance` β†’ Enhanced Static Files β†’ Deploy to CDN/Host +2. Content Editing β†’ `insertr serve` API β†’ Database β†’ Triggers rebuild ``` -### **Configuration Example** -```yaml -# insertr.yaml -server: - port: 8080 - sites: - - site_id: "mysite" - path: "/var/www/mysite" - domain: "mysite.example.com" - auto_enhance: true - - - site_id: "blog" - path: "/var/www/blog" - domain: "blog.example.com" - auto_enhance: true -``` +### **Two-Server Architecture** +- **Content API Server**: Runs `insertr serve` for content management and authentication +- **Static Site Host**: Serves enhanced static files (CDN, GitHub Pages, Netlify, etc.) -### **Quick Start** +### **Production Workflow** ```bash -# 1. Configure sites in insertr.yaml -# 2. Start the server -./insertr serve --dev-mode +# 1. Build-time enhancement +./insertr enhance ./src --output ./dist --api https://cms.example.com -# 3. Your sites are automatically registered and enhanced -# 4. Content changes via editor immediately update static files +# 2. Deploy enhanced static files +rsync ./dist/ user@cdn:/var/www/site/ + +# 3. Run content API server (separate infrastructure) +./insertr serve --config production.yaml --db "postgresql://..." ``` +### **Benefits** +- βœ… **Performance**: Static files served from CDN with zero CMS overhead +- βœ… **Scalability**: Content API and static hosting scale independently +- βœ… **Reliability**: Static site works even if CMS server is down +- βœ… **Security**: Content editing isolated from public site hosting +- βœ… **Framework Agnostic**: Works with any static site generator or CDN + +### **Development Server** (Current Multi-Site Feature) +For development convenience, `insertr serve --dev-mode` can host multiple demo sites: + +```yaml +# insertr.yaml (development only) +server: + sites: + - site_id: "demo" + path: "./demos/demo_enhanced" + source_path: "./demos/demo" + auto_enhance: true +``` + +This serves sites at `http://localhost:8080/sites/demo/` for testing the editor interface. + ## βš™οΈ Configuration ### **YAML Configuration (insertr.yaml)** ```yaml +# Global settings +dev_mode: false # Development mode features + # Database configuration database: - path: "./insertr.db" # SQLite file path or PostgreSQL connection string + path: "./insertr.db" # SQLite file or PostgreSQL connection string -# Server configuration (with site hosting) +# Server configuration (multi-site hosting) server: port: 8080 # HTTP server port sites: # Server-hosted static sites - - site_id: "demo" - path: "./demo-site" - domain: "localhost:3000" - auto_enhance: true + - site_id: "mysite" + path: "/var/www/mysite_enhanced" # Enhanced site output directory + source_path: "/var/www/mysite" # Original static site files + auto_enhance: true # Auto-enhance on startup and content changes + discovery: + enabled: false # Use explicit class="insertr" markings (true = auto-discover) + aggressive: false # Aggressive element discovery -# API configuration (for remote content API) -api: - url: "" # Content API URL (leave empty to use local database) - key: "" # API authentication key - -# Build configuration (for CLI enhancement) -build: - input: "./src" # Default input directory for enhancement +# CLI enhancement configuration +cli: + site_id: "default" # Default site ID for CLI operations output: "./dist" # Default output directory for enhanced files + inject_demo_gate: true # Inject demo editing gate if none exist + +# API configuration (for CLI remote mode) +api: + url: "" # Content API URL (empty = use local database) + key: "" # API authentication key # Authentication configuration auth: @@ -594,13 +608,9 @@ auth: jwt_secret: "" # JWT signing secret (auto-generated in dev mode) # Authentik OIDC configuration (production) oidc: - endpoint: "https://auth.example.com/application/o/insertr/" # OIDC provider endpoint - client_id: "insertr-client" # OAuth2 client ID - client_secret: "your-secret" # Use AUTHENTIK_CLIENT_SECRET env var - -# Global settings -site_id: "demo" # Default site ID for content lookup -mock_content: false # Use mock content instead of real data + endpoint: "" # https://auth.example.com/application/o/insertr/ + client_id: "" # insertr-client (OAuth2 client ID) + client_secret: "" # OAuth2 client secret (or use AUTHENTIK_CLIENT_SECRET env var) ``` ### **Environment Variables** diff --git a/TODO.md b/TODO.md index ee18325..832fc62 100644 --- a/TODO.md +++ b/TODO.md @@ -1,497 +1,134 @@ -# Insertr Development TODO +# Insertr Development Roadmap -## πŸ” Architecture Analysis Complete (Dec 2024) +## 🎯 **Current Status** (September 2025) -**Key Discovery**: The architecture is already 90% complete and brilliantly designed! The missing piece is not LocalStorage persistence, but the **HTTP server application** that implements the API contract both clients expect. +### **βœ… Complete Full-Stack CMS** +- **Style-Aware Editor**: Rich text editing with automatic style detection and formatting toolbar +- **HTML Preservation**: Perfect fidelity editing that maintains all element attributes and styling +- **HTTP API Server**: Full REST API with authentication, version control, and rollback +- **Multi-Database Support**: SQLite (development) + PostgreSQL (production) +- **Authentication System**: Mock (development) + Authentik OIDC (production) +- **Build-Time Enhancement**: Content injection from database to static HTML +- **Development Workflow**: Hot reload, auto-enhanced demo sites, seamless testing -## βœ… What's Already Built & Working - -### **Complete Foundation** -- βœ… **Go Content Client** - Full REST API client with all CRUD operations (`internal/content/client.go`) -- βœ… **JavaScript API Client** - Browser client with same API endpoints (`lib/src/core/api-client.js`) -- βœ… **Content Types** - Well-defined data structures (`ContentItem`, `ContentResponse`) -- βœ… **Mock Backend** - Working development server with realistic test data -- βœ… **Build-Time Enhancement** - Content injection from database β†’ HTML during builds -- βœ… **Authentication System** - Complete auth flow ready for server integration -- βœ… **Professional Editor** - Modal forms, validation, smart field detection - -### **Architecture Philosophy Preserved** -- βœ… **"Tailwind of CMS"** - Zero configuration, build-time optimization -- βœ… **Framework Agnostic** - Works with any static site generator -- βœ… **Performance First** - Regular visitors get pure static HTML -- βœ… **Editor Progressive Enhancement** - Editing assets only load when needed - -## 🚨 **CRITICAL MISSING PIECE** - -### **HTTP Server Application** (90% of work remaining) -The CLI client and JavaScript client both expect a server at `/api/content/*`, but **no server exists**! - -**Required API Endpoints**: -``` -GET /api/content/{id}?site_id={site} # Get single content -GET /api/content?site_id={site} # Get all content for site -GET /api/content/bulk?site_id={site}&ids[]=... # Bulk get content -PUT /api/content/{id} # Update existing content -POST /api/content # Create new content -``` - -**Current State**: Both clients make HTTP calls to these endpoints, but they 404 because no server implements them. - -## 🎯 **Immediate Implementation Plan** - -### **πŸ”΄ Phase 1: HTTP Server (CRITICAL)** -**Goal**: Build the missing server application that implements the API contract - -#### 1.1 **Go HTTP Server** ⭐ **HIGHEST PRIORITY** -- [ ] **REST API Server** - Implement all 5 required endpoints -- [ ] **Database Layer** - SQLite for development, PostgreSQL for production -- [ ] **Authentication Middleware** - JWT/OAuth integration -- [ ] **CORS & Security** - Proper headers for browser integration -- [ ] **Content Validation** - Input sanitization and type checking - -#### 1.2 **Integration Testing** -- [ ] **CLI Client Integration** - Test all CLI commands work with real server -- [ ] **JavaScript Client Integration** - Test browser editor saves to real server -- [ ] **End-to-End Workflow** - Edit β†’ Save β†’ Build β†’ Deploy cycle - -### **🟑 Phase 2: Production Polish (IMPORTANT)** - -#### 2.1 **Client-Side Enhancements** -- [ ] **Editor-Server Integration** - Wire up `handleSave` to use `ApiClient` -- [ ] **Optimistic Updates** - Show immediate feedback, sync in background -- [ ] **Offline Support** - LocalStorage cache + sync when online -- [ ] **Loading States** - Professional feedback during saves - -#### 2.2 **Deployment Pipeline** -- [ ] **Build Triggers** - Auto-rebuild sites when content changes -- [ ] **Multi-Site Support** - Handle multiple domains/site IDs -- [ ] **CDN Integration** - Host insertr.js library on CDN -- [ ] **Database Migrations** - Schema versioning and updates - -### **🟒 Phase 3: Advanced Features (NICE-TO-HAVE)** - -#### 3.1 **Content Management Enhancements** -- [ ] **Content Versioning** - Track edit history and allow rollbacks -- [ ] **Content Validation** - Advanced validation rules per content type -- [ ] **Markdown Enhancements** - Live preview, toolbar, syntax highlighting -- [ ] **Media Management** - Image upload and asset management - -#### 3.2 **Developer Experience** -- [ ] **Development Tools** - Better debugging and development workflow -- [ ] **Configuration API** - Extensible field type system -- [ ] **Testing Suite** - Comprehensive test coverage -- [ ] **Documentation** - API reference and integration guides - -## πŸ’‘ **Key Architectural Insights** - -### **Why This Architecture is Brilliant** -1. **Performance First**: Regular visitors get pure static HTML with zero CMS overhead -2. **Separation of Concerns**: Content editing completely separate from site performance -3. **Build-Time Optimization**: Database content gets "baked into" static HTML during builds -4. **Progressive Enhancement**: Sites work without JavaScript, editing enhances with JavaScript -5. **Framework Agnostic**: Works with Hugo, Next.js, Jekyll, Gatsby, vanilla HTML, etc. - -### **Production Flow** -``` -Content Edits β†’ HTTP API Server β†’ Database - ↓ -Static Site Build ← CLI Enhancement ← Database Content - ↓ - Enhanced HTML β†’ CDN/Deploy -``` - -### **Current Working Flow** -``` -βœ… Browser Editor β†’ (404) Missing Server β†’ ❌ -βœ… CLI Enhancement ← Mock Data ← βœ… -βœ… Static HTML Generation ← βœ… -``` - -**Gap**: The HTTP server that connects editor saves to database storage. - -## πŸ—‚οΈ **Next Steps: Server Implementation** - -### **βœ… Implemented - Unified Binary Architecture** -``` -βœ… COMPLETED: All server functionality integrated into unified binary -cmd/ -β”œβ”€β”€ serve.go # Runtime API server command -└── enhance.go # Build-time enhancement command -internal/ -β”œβ”€β”€ api/ # HTTP handlers and middleware -β”œβ”€β”€ db/ # Multi-database layer with sqlc -└── content/ # Content management logic -``` - -### **Files to Modify** -- `lib/src/core/editor.js:91` - Wire up `ApiClient` to `handleSave` method -- `README.md` - Add server setup instructions -- `docker-compose.yml` - Add server for development stack - -## 🎯 **Success Criteria** - -### **Phase 1 Complete When**: -- βœ… HTTP server running on `localhost:8080` (or configurable port) -- βœ… All 5 API endpoints returning proper JSON responses -- βœ… JavaScript editor successfully saves edits to database -- βœ… CLI enhancement pulls latest content from database -- βœ… Full edit β†’ save β†’ build β†’ view cycle working end-to-end - -### **Production Ready When**: -- βœ… Multi-site support with proper site isolation -- βœ… Authentication and authorization working -- βœ… Database migrations strategy -- βœ… CDN hosting for insertr.js library -- βœ… Deployment documentation and examples - -## Current Architecture Status - -### βœ… **What's Working Well** -- **CLI Parser**: Detects 41+ elements across demo site, generates stable IDs -- **Authentication System**: Professional login/edit mode toggle with visual states -- **Form System**: Dynamic modal forms with smart field detection -- **Build Pipeline**: Automated library building and copying to demo site -- **Development Experience**: Hot reload with Air integration - -### πŸ” **Investigation Results** -- **File Analysis**: All legacy code removed, clean single implementation -- **Integration Testing**: CLI ↔ Library integration works seamlessly -- **Demo Site**: Both index.html and about.html use modern library correctly -- **Content Detection**: CLI successfully identifies text/markdown/link content types - -## Immediate Next Steps - -### 🎯 **Priority 1: Content Persistence** -**Goal**: Make edits survive page reload -- Create `lib/src/core/content-manager.js` for LocalStorage operations -- Integrate with existing form system for automatic save/restore -- Add change tracking and storage management - -### 🎯 **Priority 2: Server Application** -**Goal**: Backend API for real content storage -- Design REST API for content CRUD operations -- Add authentication integration (OAuth/JWT) -- Consider database choice (SQLite for simplicity vs PostgreSQL for production) +### **πŸ—οΈ Architecture Achievements** +- **Zero Configuration**: Just add `class="insertr"` to any element +- **Framework Agnostic**: Works with any static site generator +- **Performance First**: Regular visitors get pure static HTML with zero CMS overhead +- **HTML-First**: No lossy markdown conversion - perfect attribute preservation --- -## 🏁 **Ready to Build** +## πŸš€ **Priority Roadmap** -The analysis is complete. The architecture is sound and 90% implemented. +### **πŸ”΄ Phase 1: Editor Integration Polish** (High Priority) -**Next Action**: Create the HTTP server application that implements the API contract both clients expect. +#### **Frontend-Backend Integration** +- [ ] **Editor-API Connection**: Wire up StyleAware editor saves to actual HTTP API +- [ ] **Error Handling**: Proper error states, loading indicators, offline handling +- [ ] **Content Validation**: Client-side validation before API calls +- [ ] **Save Feedback**: Professional save/error feedback in editor interface -This will immediately unlock: -- βœ… Real content persistence (not just LocalStorage) -- βœ… Multi-user editing capabilities -- βœ… Production-ready content management -- βœ… Full integration between browser editor and CLI enhancement +#### **User Experience Enhancements** +- [ ] **Draft Auto-Save**: LocalStorage drafts during editing with recovery +- [ ] **Optimistic Updates**: Immediate UI feedback, background sync +- [ ] **Conflict Resolution**: Handle concurrent editing scenarios +- [ ] **Editor Performance**: Optimize style detection for large pages -*Let's build the missing server!* +### **🟑 Phase 2: Production Deployment** (Medium Priority) -## πŸ—οΈ **Database Schema Architecture Decision** (Sept 2025) +#### **Production Workflows** +- [ ] **CI/CD Integration**: GitHub Actions templates for static site generators +- [ ] **Deployment Examples**: Netlify, Vercel, CloudFlare Pages integration guides +- [ ] **CDN Configuration**: Library asset hosting and optimization +- [ ] **Database Migrations**: Schema versioning and update strategies -**Issue**: Inlined SQLite schema in `database.go` creates multiple sources of truth, same problem we just solved with model duplication. +#### **Enterprise Features** +- [ ] **Multi-Site API**: Single server managing multiple site content +- [ ] **User Management**: Role-based access control and permissions +- [ ] **Content Approval**: Editorial workflows and publishing controls +- [ ] **Performance Monitoring**: Analytics and optimization tools -### **Recommended Solutions** (in order of preference): +### **🟒 Phase 3: Advanced CMS Features** (Low Priority) -#### **🎯 Option 1: Schema-as-Query Pattern** ⭐ **RECOMMENDED** -``` -db/queries/ -β”œβ”€β”€ content.sql # CRUD queries -β”œβ”€β”€ versions.sql # Version control queries -β”œβ”€β”€ schema_setup.sql # Schema initialization as named query -└── indexes_setup.sql # Index creation as named query +#### **Content Management Enhancements** +- [ ] **Media Management**: Image upload, asset management, optimization +- [ ] **Content Templates**: Reusable content blocks and page templates +- [ ] **Search and Filtering**: Content discovery and organization tools +- [ ] **Import/Export**: Bulk content operations and migration tools + +#### **Developer Experience** +- [ ] **Plugin System**: Extensible content types and field configurations +- [ ] **Testing Framework**: Automated testing for content workflows +- [ ] **Documentation Site**: Interactive documentation with live examples +- [ ] **Performance Profiling**: Development tools and optimization guides + +--- + +## 🌐 **Future Features** (Planned) + +### **Production Static Site Hosting** +**Goal**: Extend current development multi-site server to production static site hosting + +**Current State**: Development server hosts enhanced demo sites at `/sites/{site_id}/` for testing convenience. + +**Future Enhancement**: Production-ready static site hosting with content management. + +#### **Proposed Production Static Site Server** +- **Use Case**: Small to medium sites that want unified hosting + content management +- **Alternative to**: Netlify CMS + hosting, Forestry + Vercel, etc. +- **Benefit**: Single server handles both static hosting AND content API + +#### **Architecture**: Static file serving WITHOUT enhancement +- **Static Serving**: Serve pre-enhanced files efficiently (like nginx/Apache) +- **Content API**: Separate `/api/*` endpoints for content management +- **Build Triggers**: Content changes trigger static site rebuilds +- **Multi-Tenant**: Multiple sites with custom domains + +#### **Configuration Example** +```yaml +# insertr.yaml (future production mode) +server: + mode: "production" + sites: + - site_id: "mysite" + domain: "mysite.com" + path: "/var/www/mysite" # Pre-enhanced static files + ssl_cert: "/etc/ssl/mysite.pem" + rebuild_command: "hugo && insertr enhance ./public --output /var/www/mysite" + + - site_id: "blog" + domain: "blog.example.com" + path: "/var/www/blog" + rebuild_command: "npm run build" ``` -**Benefits**: -- βœ… Single source of truth (schema files) -- βœ… sqlc generates type-safe setup functions -- βœ… Consistent with existing sqlc workflow -- βœ… Database-agnostic parameter syntax (`sqlc.arg()`) +#### **Implementation Plan** +- [ ] **Static File Server**: Efficient static file serving (no enhancement) +- [ ] **Domain Routing**: Route custom domains to appropriate site directories +- [ ] **SSL/TLS Support**: Automatic certificate management (Let's Encrypt) +- [ ] **Build Triggers**: Webhook system to trigger site rebuilds after content changes +- [ ] **Performance**: CDN integration, compression, caching headers +- [ ] **Monitoring**: Site uptime, performance metrics, error logging -**Implementation**: -```sql --- name: InitializeSchema :exec -CREATE TABLE IF NOT EXISTS content (...); -CREATE TABLE IF NOT EXISTS content_versions (...); +**Priority**: Low - implement after core content management features are stable --- name: CreateIndexes :exec -CREATE INDEX IF NOT EXISTS idx_content_site_id ON content(site_id); -``` +### **Advanced Style Preview System** -#### **πŸ”§ Option 2: Migration Tool Integration** -- Use `goose`, `golang-migrate`, or `dbmate` -- sqlc natively supports parsing migration directories -- Professional database management with up/down migrations -- **Trade-off**: Additional dependency and complexity +**Current State**: Basic style button previews using `getComputedStyle()` to show formatting effects. -#### **πŸ—‚οΈ Option 3: Embedded Schema Files** -- Use `go:embed` to read schema files at compile time -- Keep schema in separate `.sql` files -- **Trade-off**: Not processed by sqlc, less type safety +#### **Future Style Preview Enhancements** +- [ ] **Enhanced Style Support**: Background colors, borders, typography with safety constraints +- [ ] **Interactive Previews**: Hover effects, animations, responsive previews +- [ ] **Custom Style Creation**: Visual style picker with live preview +- [ ] **Style Inheritance Display**: Show which properties come from which CSS classes +- [ ] **Accessibility Validation**: Ensure previews meet contrast and readability standards -**βœ… COMPLETED**: Implemented Option 1 (Schema-as-Query) with important discovery: +### **Advanced Access Control** -**sqlc Limitations Discovered**: -- βœ… sqlc generates functions for `CREATE TABLE` and `ALTER TABLE` statements -- ❌ sqlc does **NOT** generate functions for `CREATE INDEX` or `CREATE TRIGGER` statements -- πŸ”§ **Solution**: Handle table creation via sqlc-generated functions, handle indexes/triggers manually in Go initialization code +**Current State**: Simple boolean authentication gate for page-level editing access. -**Final Implementation**: -- `db/*/setup.sql` - Contains table creation queries (sqlc generates type-safe functions) -- `internal/db/database.go` - Manual index/trigger creation using raw SQL -- **Best Practice**: Use sqlc for what it supports, manual SQL for what it doesn't - -## πŸ”„ **Editor Cache Architecture & Content State Management** (Dec 2024) - -### **Current Architecture Issues Identified** - -**Problem**: Conflict between preview system and content persistence after save operations. - -**Root Cause**: The unified Editor system was designed with preview-first architecture: -- `originalContent` stores DOM state when editing begins -- `clearPreview()` always restores to `originalContent` -- This creates race condition: `applyContent()` β†’ `clearPreview()` β†’ content reverted - -### **βœ… Implemented Solution: Post-Save Baseline Update** - -**Strategy**: After successful save, update the stored `originalContent` to match the new saved state. - -**Implementation** (`lib/src/ui/Editor.js`): -```js -// In save handler: -context.applyContent(content); // Apply new content to DOM -context.updateOriginalContent(); // Update baseline to match current DOM -this.previewer.clearPreview(); // Clear preview (won't revert since baseline matches) -``` - -**Benefits**: -- βœ… Content persists after save operations -- βœ… Future cancellations restore to saved state, not pre-edit state -- βœ… Maintains clean preview functionality -- βœ… No breaking changes to existing architecture - -### **Future Considerations: Draft System Architecture** - -**Current State Management**: -- **Browser Cache**: DOM elements store current content state -- **Server Cache**: Database stores persisted content -- **No Intermediate Cache**: Edits are either preview (temporary) or saved (permanent) - -**Potential Draft System Design**: - -#### **Option 1: LocalStorage Drafts** -```js -// Auto-save drafts locally during editing -const draftKey = `insertr_draft_${contentId}_${siteId}`; -localStorage.setItem(draftKey, JSON.stringify({ - content: currentContent, - timestamp: Date.now(), - originalContent: baseline -})); -``` - -**Benefits**: Offline support, immediate feedback, no server load -**Drawbacks**: Per-browser, no cross-device sync, storage limits - -#### **Option 2: Server-Side Drafts** -```js -// Auto-save drafts to server with special draft flag -PUT /api/content/{id}/draft -{ - "value": "Draft content...", - "type": "markdown", - "is_draft": true -} -``` - -**Benefits**: Cross-device sync, unlimited storage, collaborative editing potential -**Drawbacks**: Server complexity, authentication requirements, network dependency - -#### **Option 3: Hybrid Draft System** -- **LocalStorage**: Immediate draft saving during typing -- **Server Sync**: Periodic sync of drafts (every 30s or on significant changes) -- **Conflict Resolution**: Handle cases where server content changed while editing draft - -### **Cache Invalidation Strategy** - -**Current Behavior**: -- Content is cached in DOM until page reload -- No automatic refresh when content changes on server -- No awareness of multi-user editing scenarios - -**Recommended Enhancements**: - -#### **Option A: Manual Refresh Strategy** -- Add "Refresh Content" button to editor interface -- Check server content before editing (warn if changed) -- Simple conflict resolution (server wins vs local wins vs merge) - -#### **Option B: Polling Strategy** -- Poll server every N minutes for content changes -- Show notification if content was updated by others -- Allow user to choose: keep editing, reload, or merge - -#### **Option C: WebSocket Strategy** -- Real-time content change notifications -- Live collaborative editing indicators -- Automatic conflict resolution - -### **Implementation Priority** - -**Phase 1** (Immediate): βœ… **COMPLETED** -- Fix content persistence after save (baseline update approach) - -**Phase 2** (Short-term): -- Add LocalStorage draft auto-save during editing -- Implement draft recovery on page reload -- Basic conflict detection (server timestamp vs local timestamp) - -**Phase 3** (Long-term): -- Server-side draft support -- Real-time collaboration features -- Advanced conflict resolution - -### **Design Principles for Draft System** - -1. **Progressive Enhancement**: Site works without drafts, drafts enhance UX -2. **Data Safety**: Never lose user content, even in edge cases -3. **Performance First**: Drafts shouldn't impact site loading for regular visitors -4. **Conflict Transparency**: Always show user what's happening with their content -5. **Graceful Degradation**: Fallback to basic editing if draft system fails - -**Note**: Current architecture already supports foundation for all these enhancements through the unified Editor system and API client pattern. - -## πŸ—οΈ **Server-Hosted Static Sites Implementation** (Sept 2025) - -### **βœ… COMPLETED: Unified Binary HTTP Server** -The HTTP server has been successfully implemented and is production-ready: -- βœ… **Full REST API** - All content CRUD operations (`internal/api/handlers.go`) -- βœ… **Multi-Database Support** - SQLite + PostgreSQL with sqlc-generated queries -- βœ… **Authentication System** - JWT + mock tokens, ready for authentik integration -- βœ… **Bulk Operations** - Efficient bulk content retrieval (`injector.go:56-96`) -- βœ… **Content Enhancement** - Build-time content injection from database - -### **🎯 NEW PRIORITY: Server-Hosted File Enhancement** - -**Goal**: Enable real-time content updates for server-hosted static sites through database-driven file enhancement. - -**Architecture**: Database as source of truth β†’ Content changes β†’ File updates β†’ Immediate deployment - -### **Phase 1: Core File Enhancement System** -- [ ] **Extend enhancer.go** - Add `EnhanceInPlace()` method for in-place file modification -- [ ] **Site Manager** - Create registration system for static site paths -- [ ] **Handler Integration** - Hook file enhancement into `UpdateContent()` API calls -- [ ] **Configuration Extension** - Add sites configuration to `insertr.yaml` - -**Files to modify**: -- `internal/content/enhancer.go` - Add in-place enhancement methods -- `internal/api/handlers.go:225` - Integrate file enhancement trigger -- `insertr.yaml` - Add sites configuration section -- `cmd/serve.go` - Initialize site manager in server - -### **Phase 2: Multi-Site Server Management** -- [ ] **CLI Site Commands** - `insertr sites register/list/enhance` -- [ ] **File Backup System** - Backup original files before enhancement -- [ ] **Error Handling** - Graceful file permission and disk space handling -- [ ] **Performance Optimization** - Selective file updates, change detection - -### **Phase 3: Production Features** -- [ ] **Authentik Integration** - OIDC authentication for production deployment -- [ ] **Caddy Integration** - Reverse proxy configuration templates -- [ ] **Monitoring & Logging** - Track enhancement operations and errors -- [ ] **Multi-Environment** - Development vs production configuration - -### **Implementation Approach: Leverage Existing Architecture** - -**Reuse Existing Components**: -- βœ… **Bulk Injection** - `injector.go:56-96` already handles efficient content injection -- βœ… **Multi-Site Support** - `siteID` parameter used throughout API and database -- βœ… **Authentication** - `auth.go:47-67` supports JWT tokens ready for authentik -- βœ… **Content Client Interface** - `types.go:18-28` extensible for new features - -**New Components** (minimal additions): -- **Site Manager** - Register static site paths and manage file enhancement -- **In-Place Enhancer** - Extend existing enhancer for file modification -- **Configuration** - Extend YAML config for site registration - -### **Target Deployment Architecture** -``` -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ Caddy Reverse Proxy β”‚ -β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ -β”‚ Static Sites β”‚ Insertr Server β”‚ -β”‚ /site1/* ────→ β”‚ :8080/api/* β”‚ -β”‚ /site2/* ────→ β”‚ Authentication β”‚ -β”‚ /admin/* ────→ β”‚ File Enhancement β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - β”‚ β”‚ - β–Ό β–Ό - β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” - β”‚Static Files β”‚ β”‚ Database + β”‚ - β”‚/var/www/ │◄──── Enhancement β”‚ - β”‚ site1/ β”‚ β”‚ Engine β”‚ - β”‚ site2/ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ -``` - -**Content Update Workflow**: -1. Editor makes content change β†’ API call to `/api/content/{id}` -2. Content saved to database (source of truth) -3. File enhancement triggered β†’ Static files updated in-place -4. Changes immediately visible to visitors (served by Caddy) - -### **Future CI/CD Integration Features** (Post-Server Implementation) - -#### **External Build Trigger System** -- **Webhook Integration**: Trigger external CI/CD pipelines when content changes -- **GitHub Actions Templates**: Pre-built workflows for Hugo, Next.js, Jekyll + Insertr -- **Platform Integrations**: Native Netlify, Vercel, CloudFlare Pages integration -- **Build Status Tracking**: Monitor external build progress and deployment status - -#### **Hybrid Deployment Models** -- **File + Pipeline Mode**: Server-hosted sites with optional CI/CD rebuilds -- **Content API Mode**: Headless CMS for external static site generators -- **Multi-Environment**: Staging/production deployment coordination -- **Build Caching**: Intelligent rebuilds based on content vs template changes - -#### **Advanced Content Workflows** -- **Scheduled Publishing**: Time-based content activation -- **Content Approval**: Multi-user approval workflows before deployment -- **A/B Testing**: Content variations for different user segments -- **Rollback Integration**: Coordinate content rollbacks with deployment rollbacks - -### **Draft System Foundation** (Ready for Extension) - -**Current Architecture Supports**: -- βœ… **Bulk Operations** - `injector.go:56-96` efficient for batch content updates -- βœ… **Version Control** - Full version history with rollback capabilities -- βœ… **Content Types** - Extensible type system ready for draft fields - -**Future Draft Features**: -- **Server-Side Drafts** - Extend `ContentItem` with `is_draft` field -- **Batch Publishing** - Publish multiple draft items simultaneously -- **Preview Mode** - View sites with draft content before publishing -- **Auto-Save Drafts** - Background saving during editing sessions - -### **Benefits of Server-Hosted Approach** - -1. **βœ… Immediate Deployment** - Content changes are live instantly -2. **βœ… Database Source of Truth** - No git-based workflow complexity -3. **βœ… Content-Only Updates** - No template rebuilds required -4. **βœ… Existing Infrastructure** - Leverages Caddy + authentik setup -5. **βœ… Performance** - Static file serving with dynamic content injection -6. **βœ… Multi-Site** - Single server manages multiple enhanced sites -7. **βœ… Zero Downtime** - Updates don't require site rebuilds or deploys - -## 🎯 **Future Class Extensions: Advanced Access Control** (Planned) - -### **Granular Permission System for `.insertr-gate`** - -**Current Implementation**: Simple boolean authentication gate for page-level editing access. - -**Future Enhancement Concept**: Role-based access control and section-level permissions for enterprise applications. +**Future Enhancement**: Role-based access control and section-level permissions for enterprise applications. #### **Potential Extended Gate Classes** ```html @@ -506,10 +143,6 @@ The HTTP server has been successfully implemented and is production-ready:
Editor-level rich content
- -
-
Premium subscriber content
-
``` #### **Enterprise Use Cases** @@ -517,143 +150,40 @@ The HTTP server has been successfully implemented and is production-ready: - **Editorial Workflows**: Writers, editors, and admins with different capabilities - **Subscription Content**: Different content areas for different subscription tiers - **Department Permissions**: Marketing vs Engineering vs Sales content areas -- **Geographic Restrictions**: Region-specific editing permissions - -#### **Implementation Considerations** -- **Configuration Complexity**: How to maintain zero-config philosophy while supporting complex permissions -- **Role Definition**: Where and how to define roles and permissions -- **Authentication Integration**: Extending current auth system vs external RBAC systems -- **Fallback Behavior**: Graceful degradation for unauthorized users -- **Performance Impact**: Permission checks shouldn't slow down regular site visitors - -#### **Integration Points** -- **Authentik OIDC**: Leverage existing auth provider for role information -- **Database Schema**: Extend current content system with permission metadata -- **API Endpoints**: New endpoints for permission management and validation -- **Editor Interface**: Dynamic interface based on user permissions **Priority**: Low - implement after core functionality is stable and enterprise customers request advanced permissions. -**Design Principle**: Keep simple `.insertr-gate` as default, add optional complexity only when needed. +--- -## 🎨 **Style Preview Enhancement System** (Dec 2024) +## πŸ“Š **Success Metrics** -### **βœ… IMPLEMENTED: Basic Style Button Previews** +### **Phase 1 Complete When**: +- βœ… Editor saves successfully to HTTP API in all demo sites +- βœ… Error handling provides clear feedback for all failure scenarios +- βœ… Draft auto-save prevents content loss during editing +- βœ… Performance is acceptable on large pages with many editable elements -**Goal**: Make formatting buttons visually preview the styles they represent. +### **Phase 2 Complete When**: +- βœ… Production deployment guides for major platforms (Netlify, Vercel, etc.) +- βœ… Enterprise authentication working with real Authentik instances +- βœ… Multi-site content management for production use cases +- βœ… CDN hosting for insertr.js library with version management -**Current Implementation**: -- βœ… **Dynamic Style Copying** - JavaScript reads computed styles from original elements -- βœ… **Selective Property Application** - Copies color, font-weight, text-decoration, text-transform -- βœ… **Button Structure Preservation** - Maintains clickable button appearance with backgrounds/borders -- βœ… **Cross-Platform Compatibility** - Uses `window.getComputedStyle()` for universal support +### **Production Ready When**: +- βœ… Real-world sites using Insertr in production successfully +- βœ… Performance benchmarks meet or exceed existing CMS solutions +- βœ… Security audit completed for authentication and content handling +- βœ… Documentation and examples cover all major use cases -**Example Results**: -- "Emphasis" button displays with red bold text (from `.emph { color: #dc2626; font-weight: 700; }`) -- "Highlight" button shows preview styling while remaining clickable -- "Brand" button demonstrates text-transform and color changes +--- -### **πŸ”„ Future Style Preview Enhancements** +## πŸ”§ **Development Principles** -#### **🎯 Priority 1: Enhanced Style Support** -- [ ] **Background Colors** - Safe background preview with contrast protection -- [ ] **Border Styles** - Preview border styles while maintaining button structure -- [ ] **Typography Styles** - Font-family, font-size adjustments with readability limits -- [ ] **Advanced CSS Properties** - Text-shadow, letter-spacing, line-height previews +1. **Zero Configuration**: Markup-driven approach, no schema files +2. **HTML-First**: Perfect attribute preservation, no lossy conversions +3. **Performance**: Zero runtime cost for regular site visitors +4. **Framework Agnostic**: Works with any static site generator +5. **Developer Experience**: Minimal cognitive overhead, stays in markup +6. **Progressive Enhancement**: Sites work without JavaScript, editing enhances with JavaScript -#### **🎯 Priority 2: Interactive Previews** -- [ ] **Hover State Previews** - Show hover effects in button previews -- [ ] **Animation Previews** - Preview CSS transitions and animations safely -- [ ] **Responsive Previews** - Show how styles adapt across screen sizes -- [ ] **Nested Style Previews** - Handle complex nested styling scenarios - -#### **🎯 Priority 3: Advanced Preview Features** -- [ ] **Custom Style Creation** - Visual style picker with live preview -- [ ] **Style Inheritance Display** - Show which properties come from which classes -- [ ] **Accessibility Validation** - Ensure previews meet contrast and readability standards -- [ ] **Performance Optimization** - Cache computed styles, minimize recomputation - -### **πŸ”§ Technical Implementation Details** - -**Current Approach** (JavaScript-based style copying): -```javascript -const computedStyle = window.getComputedStyle(styleInfo.element); -if (computedStyle.color && computedStyle.color !== 'rgb(0, 0, 0)') { - button.style.setProperty('color', computedStyle.color, 'important'); -} -``` - -**Benefits**: -- βœ… Works with any CSS framework or custom styles -- βœ… No hardcoded style mappings required -- βœ… Automatically adapts to theme changes -- βœ… Handles complex CSS specificity scenarios - -**Limitations**: -- ⚠️ Limited to properties safe for button elements -- ⚠️ Background colors skipped to maintain readability -- ⚠️ Some complex styles may not preview accurately -- ⚠️ Performance impact on large style sets - -### **🎨 Design Considerations** - -**Readability vs Accuracy Trade-offs**: -- **Button Backgrounds**: Always use safe background to ensure clickability -- **Text Contrast**: Ensure sufficient contrast between text and button background -- **Property Selection**: Only preview properties that don't break button functionality -- **Fallback Handling**: Graceful degradation when style copying fails - -**User Experience Principles**: -- **Immediate Recognition**: Users should instantly understand what each button does -- **Consistent Interaction**: Buttons should always feel clickable regardless of style -- **Visual Hierarchy**: Style previews shouldn't overpower the editor interface -- **Accessibility**: Maintain WCAG compliance for all preview combinations - -### **πŸ§ͺ Testing Strategy for Style Previews** - -**Cross-Browser Compatibility**: -- Test `getComputedStyle()` behavior across major browsers -- Validate CSS custom property support and inheritance -- Check for inconsistencies in color space handling - -**Edge Case Handling**: -- Very long class names or deeply nested selectors -- Conflicting CSS properties from multiple stylesheets -- High contrast or accessibility mode compatibility -- Performance with large numbers of detected styles - -**Real-World Style Testing**: -- Test with popular CSS frameworks (Tailwind, Bootstrap, Bulma) -- Validate with complex design systems and component libraries -- Check compatibility with CSS-in-JS solutions -- Test with user-defined custom properties and themes - -### **πŸ“Š Future Metrics and Analytics** - -**Style Preview Usage Analytics**: -- Track which style previews are most commonly used -- Measure user engagement with preview vs non-preview buttons -- Identify styles that cause preview rendering issues -- Monitor performance impact of style computation - -**User Experience Improvements**: -- A/B test preview accuracy vs simplified representations -- Test user comprehension of style previews vs text labels -- Measure editing efficiency with vs without previews -- Gather feedback on preview helpfulness for different user types - -### **Integration with Future Features** - -**Style Management System**: -- Style preview integration with custom style creation tools -- Preview integration with theme switching and dark mode -- Connection to style documentation and design system integration -- Preview role in collaborative editing and style consistency - -**Performance Optimization**: -- Style preview caching for frequently used styles -- Lazy loading of preview computation for off-screen elements -- Optimization for mobile devices and slower browsers -- Integration with build-time style optimization - -**Note**: Current implementation provides solid foundation for all future enhancements while maintaining the zero-configuration philosophy. +**Built with ❀️ for developers who want powerful editing without the complexity** \ No newline at end of file diff --git a/demos/README.md b/demos/README.md index 80eebc4..c84d931 100644 --- a/demos/README.md +++ b/demos/README.md @@ -1,40 +1,72 @@ -# Test Sites Collection +# Demo Sites Collection -This directory contains a collection of real-world websites for testing insertr CMS functionality across different site types, CSS frameworks, and complexity levels. +This directory contains demo sites for testing and showcasing Insertr CMS functionality across different layouts, CSS frameworks, and content types. -## Directory Structure +## Available Demo Sites -- **`simple/`** - Simple sites with vanilla CSS and minimal layouts -- **`framework-based/`** - Sites using CSS frameworks (Bootstrap, Tailwind, etc.) -- **`complex/`** - Complex layouts with advanced interactions -- **`templates/`** - Template files for new test sites -- **`scripts/`** - Automation utilities for downloading and enhancing sites -- **`results/`** - Testing results, reports, and documentation +### **`default/`** - Basic Demo Site +- Simple HTML with explicit `class="insertr"` markings +- Demonstrates basic text and link editing +- Vanilla CSS styling -## Site Categories +### **`simple/`** - Minimal Example +- Single page with minimal styling +- Shows container expansion (`.insertr` on parent elements) +- Good starting point for new implementations -### Simple Sites -- **dan-eden-portfolio** - Clean personal portfolio with minimal styling -- **github-pages-simple** - Basic GitHub Pages site with standard layout +### **`dan-eden-portfolio/`** - Real-World Portfolio +- Clean personal portfolio with modern styling +- Uses automatic element discovery (no manual `.insertr` classes) +- Demonstrates style-aware editing with complex CSS -### Framework-Based Sites -- **bootstrap-docs** - Bootstrap documentation sections -- **tailwind-landing** - Tailwind CSS marketing pages +### **`devigo-web/`** - Multi-Page Site +- Multiple HTML files with navigation +- Various content types (headings, paragraphs, buttons, links) +- Tests cross-page content management -### Complex Sites -- **stripe-product** - Enterprise product pages with rich content -- **linear-features** - Modern SaaS feature pages +## Demo Server Usage -## Testing Process +### **Development Testing** +```bash +# Start demo server with all sites +just dev -1. **Download** - Use scripts to fetch HTML and assets -2. **Enhance** - Add insertr classes to content sections -3. **Test** - Verify functionality across different layouts -4. **Document** - Record results and compatibility notes +# Access sites at: +# http://localhost:8080/sites/default/ +# http://localhost:8080/sites/simple/ +# http://localhost:8080/sites/dan-eden-portfolio/ +# http://localhost:8080/sites/devigo-web/ +``` -## Each Site Includes +### **Configuration** +Each demo site is configured in `insertr.yaml`: +```yaml +server: + sites: + - site_id: "default" + source_path: "./demos/default" + path: "./demos/default_enhanced" + auto_enhance: true + discovery: + enabled: false # Uses explicit .insertr markings + + - site_id: "dan-eden-portfolio" + source_path: "./demos/dan-eden-portfolio" + path: "./demos/dan-eden-portfolio_enhanced" + auto_enhance: true + discovery: + enabled: true # Auto-discovers editable elements +``` -- Original HTML files -- `assets/` directory with CSS, JS, and images -- `README.md` with site-specific testing notes -- `insertr-config.json` with enhancement configuration \ No newline at end of file +### **Testing Features** +- **Style-Aware Editing**: Each site tests different CSS frameworks and styling approaches +- **Element Discovery**: Compare manual `.insertr` vs automatic discovery modes +- **Content Types**: Test text, rich content, links, and multi-property elements +- **Authentication**: Mock authentication for easy testing without setup + +## Adding New Demo Sites + +1. Create new directory in `demos/` +2. Add HTML files and assets +3. Configure in `insertr.yaml` under `server.sites` +4. Run `just dev` to test \ No newline at end of file diff --git a/demos/simple/index.html b/demos/simple/index.html index 25f214e..caf5791 100644 --- a/demos/simple/index.html +++ b/demos/simple/index.html @@ -43,10 +43,15 @@ } .fancy { - color: #7c3aed; + color: #059660; text-decoration: none; border-bottom: 2px solid #a855f7; } + + .fancy:visited { + color: #509; + border: 1px solid red + } .fancy:hover { background: #ede9fe; @@ -180,4 +185,4 @@

Need help? Contact our support team anytime.

- \ No newline at end of file + diff --git a/internal/content/assets/insertr.css b/internal/content/assets/insertr.css index e3e5f68..4feb49e 100644 --- a/internal/content/assets/insertr.css +++ b/internal/content/assets/insertr.css @@ -11,7 +11,7 @@ * - .insertr-gate: Minimal styling for user-defined gates * - .insertr-auth-*: Authentication controls and buttons * - .insertr-form-*: Modal forms and inputs - * - .insertr-version-*: Version history modal + * - .insertr-style-aware-*: Style-aware editor components */ /* ================================================================= @@ -546,133 +546,7 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after { color: var(--insertr-text-primary); } -/* ================================================================= - VERSION HISTORY MODAL - ================================================================= */ -.insertr-version-modal { - padding: var(--insertr-spacing-lg); - margin: 0; - font-family: var(--insertr-font-family); - font-size: var(--insertr-font-size-base); - line-height: var(--insertr-line-height); - color: var(--insertr-text-primary); - background: var(--insertr-bg-primary); -} - -.insertr-version-header { - margin: 0 0 var(--insertr-spacing-lg) 0; - padding: 0 0 var(--insertr-spacing-md) 0; - border-bottom: 1px solid var(--insertr-border-color); -} - -.insertr-version-title { - margin: 0 0 var(--insertr-spacing-sm) 0; - padding: 0; - font-size: var(--insertr-font-size-lg); - font-weight: 600; - color: var(--insertr-text-primary); -} - -.insertr-version-help { - margin: 0; - padding: 0; - font-size: var(--insertr-font-size-sm); - color: var(--insertr-text-muted); -} - -.insertr-version-list { - margin: 0 0 var(--insertr-spacing-lg) 0; - padding: 0; - max-height: 400px; - overflow-y: auto; -} - -.insertr-version-item { - padding: var(--insertr-spacing-sm); - margin: 0 0 var(--insertr-spacing-sm) 0; - border: 1px solid var(--insertr-border-color); - border-radius: var(--insertr-border-radius); - cursor: pointer; - transition: var(--insertr-transition); - background: var(--insertr-bg-primary); - color: var(--insertr-text-primary); -} - -.insertr-version-item:hover { - background: var(--insertr-bg-secondary); -} - -.insertr-version-item.selected { - border-color: var(--insertr-primary); - background: rgba(0, 123, 255, 0.1); -} - -.insertr-version-date { - font-weight: 500; - color: var(--insertr-text-primary); - margin: 0; - padding: 0; -} - -.insertr-version-preview { - margin: var(--insertr-spacing-xs) 0 0 0; - padding: 0; - font-size: var(--insertr-font-size-sm); - color: var(--insertr-text-secondary); - white-space: pre-wrap; - word-break: break-word; -} - -.insertr-version-actions { - display: flex; - gap: var(--insertr-spacing-sm); - justify-content: flex-end; - margin: 0; - padding: var(--insertr-spacing-md) 0 0 0; - border-top: 1px solid var(--insertr-border-color); -} - -.insertr-btn-restore, -.insertr-btn-close { - background: var(--insertr-primary); - color: var(--insertr-text-inverse); - border: none; - border-radius: var(--insertr-border-radius); - padding: var(--insertr-spacing-sm) var(--insertr-spacing-md); - margin: 0; - font-family: var(--insertr-font-family); - font-size: var(--insertr-font-size-base); - font-weight: 500; - cursor: pointer; - transition: var(--insertr-transition); - line-height: var(--insertr-line-height); - text-decoration: none; - display: inline-block; - text-align: center; - vertical-align: middle; -} - -.insertr-btn-close { - background: var(--insertr-text-secondary); - color: var(--insertr-text-inverse); -} - -.insertr-btn-close:hover { - background: var(--insertr-text-primary); - color: var(--insertr-text-inverse); -} - -.insertr-btn-restore:hover { - background: var(--insertr-primary-hover); - color: var(--insertr-text-inverse); -} - -.insertr-btn-restore:focus, -.insertr-btn-close:focus { - outline: 2px solid var(--insertr-primary); - outline-offset: 2px; -} /* ================================================================= STATUS AND FEEDBACK MESSAGES diff --git a/lib/package.json b/lib/package.json index b14a733..1aa5bd5 100644 --- a/lib/package.json +++ b/lib/package.json @@ -30,8 +30,5 @@ "@rollup/plugin-terser": "^0.4.0", "rollup": "^3.0.0" }, - "dependencies": { - "marked": "^16.2.1", - "turndown": "^7.2.1" - } + "dependencies": {} } diff --git a/lib/src/core/editor.js b/lib/src/core/editor.js index fda7bed..c48ce76 100644 --- a/lib/src/core/editor.js +++ b/lib/src/core/editor.js @@ -155,8 +155,8 @@ export class InsertrEditor { return 'link'; } - // ALL text elements use markdown for consistent editing experience - return 'markdown'; + // ALL text elements use text content type + return 'text'; } handleCancel(meta) { diff --git a/lib/src/core/insertr.js b/lib/src/core/insertr.js index 369adfa..067a69c 100644 --- a/lib/src/core/insertr.js +++ b/lib/src/core/insertr.js @@ -135,7 +135,7 @@ export class InsertrCore { const tag = element.tagName.toLowerCase(); if (element.classList.contains('insertr-group')) { - return 'markdown'; + return 'group'; } switch (tag) { @@ -146,9 +146,9 @@ export class InsertrCore { case 'a': case 'button': return 'link'; case 'div': case 'section': - return 'markdown'; + return 'text'; case 'span': - return 'markdown'; // Match backend: spans support inline markdown + return 'text'; default: return 'text'; } diff --git a/lib/src/styles/insertr.css b/lib/src/styles/insertr.css index e3e5f68..4feb49e 100644 --- a/lib/src/styles/insertr.css +++ b/lib/src/styles/insertr.css @@ -11,7 +11,7 @@ * - .insertr-gate: Minimal styling for user-defined gates * - .insertr-auth-*: Authentication controls and buttons * - .insertr-form-*: Modal forms and inputs - * - .insertr-version-*: Version history modal + * - .insertr-style-aware-*: Style-aware editor components */ /* ================================================================= @@ -546,133 +546,7 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after { color: var(--insertr-text-primary); } -/* ================================================================= - VERSION HISTORY MODAL - ================================================================= */ -.insertr-version-modal { - padding: var(--insertr-spacing-lg); - margin: 0; - font-family: var(--insertr-font-family); - font-size: var(--insertr-font-size-base); - line-height: var(--insertr-line-height); - color: var(--insertr-text-primary); - background: var(--insertr-bg-primary); -} - -.insertr-version-header { - margin: 0 0 var(--insertr-spacing-lg) 0; - padding: 0 0 var(--insertr-spacing-md) 0; - border-bottom: 1px solid var(--insertr-border-color); -} - -.insertr-version-title { - margin: 0 0 var(--insertr-spacing-sm) 0; - padding: 0; - font-size: var(--insertr-font-size-lg); - font-weight: 600; - color: var(--insertr-text-primary); -} - -.insertr-version-help { - margin: 0; - padding: 0; - font-size: var(--insertr-font-size-sm); - color: var(--insertr-text-muted); -} - -.insertr-version-list { - margin: 0 0 var(--insertr-spacing-lg) 0; - padding: 0; - max-height: 400px; - overflow-y: auto; -} - -.insertr-version-item { - padding: var(--insertr-spacing-sm); - margin: 0 0 var(--insertr-spacing-sm) 0; - border: 1px solid var(--insertr-border-color); - border-radius: var(--insertr-border-radius); - cursor: pointer; - transition: var(--insertr-transition); - background: var(--insertr-bg-primary); - color: var(--insertr-text-primary); -} - -.insertr-version-item:hover { - background: var(--insertr-bg-secondary); -} - -.insertr-version-item.selected { - border-color: var(--insertr-primary); - background: rgba(0, 123, 255, 0.1); -} - -.insertr-version-date { - font-weight: 500; - color: var(--insertr-text-primary); - margin: 0; - padding: 0; -} - -.insertr-version-preview { - margin: var(--insertr-spacing-xs) 0 0 0; - padding: 0; - font-size: var(--insertr-font-size-sm); - color: var(--insertr-text-secondary); - white-space: pre-wrap; - word-break: break-word; -} - -.insertr-version-actions { - display: flex; - gap: var(--insertr-spacing-sm); - justify-content: flex-end; - margin: 0; - padding: var(--insertr-spacing-md) 0 0 0; - border-top: 1px solid var(--insertr-border-color); -} - -.insertr-btn-restore, -.insertr-btn-close { - background: var(--insertr-primary); - color: var(--insertr-text-inverse); - border: none; - border-radius: var(--insertr-border-radius); - padding: var(--insertr-spacing-sm) var(--insertr-spacing-md); - margin: 0; - font-family: var(--insertr-font-family); - font-size: var(--insertr-font-size-base); - font-weight: 500; - cursor: pointer; - transition: var(--insertr-transition); - line-height: var(--insertr-line-height); - text-decoration: none; - display: inline-block; - text-align: center; - vertical-align: middle; -} - -.insertr-btn-close { - background: var(--insertr-text-secondary); - color: var(--insertr-text-inverse); -} - -.insertr-btn-close:hover { - background: var(--insertr-text-primary); - color: var(--insertr-text-inverse); -} - -.insertr-btn-restore:hover { - background: var(--insertr-primary-hover); - color: var(--insertr-text-inverse); -} - -.insertr-btn-restore:focus, -.insertr-btn-close:focus { - outline: 2px solid var(--insertr-primary); - outline-offset: 2px; -} /* ================================================================= STATUS AND FEEDBACK MESSAGES diff --git a/lib/src/ui/editor.js b/lib/src/ui/editor.js index c3aa72b..fe397d6 100644 --- a/lib/src/ui/editor.js +++ b/lib/src/ui/editor.js @@ -2,13 +2,11 @@ * Editor - Handles all content types with style-aware approach */ import { StyleAwareEditor } from './style-aware-editor.js'; -import { Previewer } from './previewer.js'; export class Editor { constructor() { this.currentOverlay = null; this.currentStyleEditor = null; - this.previewer = new Previewer(); } /** @@ -40,8 +38,7 @@ export class Editor { this.close(); }, onChange: (content) => { - // Optional: trigger live preview - this.handlePreviewChange(primaryElement, content); + // Optional: trigger change events if needed } }); @@ -125,237 +122,10 @@ export class Editor { } /** - * Handle preview changes from style-aware editor + * Handle content changes from style-aware editor */ - handlePreviewChange(element, content) { - // Implement live preview if needed - // For now, we'll skip this to avoid complexity - } - - /** - * Create editing form for any content type (legacy - keeping for compatibility) - */ - createForm(context, meta) { - const config = this.getFieldConfig(context); - const currentContent = context.extractContent(); - - const form = document.createElement('div'); - form.className = 'insertr-edit-form'; - - // Build form HTML - let formHTML = `
${config.label}
`; - - // Markdown textarea (always present) - formHTML += this.createMarkdownField(config, currentContent); - - // URL field (for links only) - if (config.includeUrl) { - formHTML += this.createUrlField(currentContent); - } - - // Form actions - formHTML += ` -
- - - -
- `; - - form.innerHTML = formHTML; - return form; - } - - /** - * Get field configuration for any element type (markdown-first) - */ - getFieldConfig(context) { - const elementCount = context.elements.length; - const primaryElement = context.primaryElement; - const isLink = primaryElement.tagName.toLowerCase() === 'a'; - - // Multi-element groups - if (elementCount > 1) { - return { - type: 'markdown', - includeUrl: false, - label: `Group Content (${elementCount} elements)`, - rows: Math.max(8, elementCount * 2), - placeholder: 'Edit all content together using markdown...' - }; - } - - // Single elements - all get markdown by default - const tag = primaryElement.tagName.toLowerCase(); - const baseConfig = { - type: 'markdown', - includeUrl: isLink, - placeholder: 'Enter content using markdown...' - }; - - // Customize by element type - switch (tag) { - case 'h1': - return { ...baseConfig, label: 'Main Headline', rows: 1, placeholder: 'Enter main headline...' }; - case 'h2': - return { ...baseConfig, label: 'Subheading', rows: 1, placeholder: 'Enter subheading...' }; - case 'h3': case 'h4': case 'h5': case 'h6': - return { ...baseConfig, label: 'Heading', rows: 2, placeholder: 'Enter heading (markdown supported)...' }; - case 'p': - return { ...baseConfig, label: 'Content', rows: 4, placeholder: 'Enter content using markdown...' }; - case 'span': - return { ...baseConfig, label: 'Text', rows: 2, placeholder: 'Enter text (markdown supported)...' }; - case 'button': - return { ...baseConfig, label: 'Button Text', rows: 1, placeholder: 'Enter button text...' }; - case 'a': - return { ...baseConfig, label: 'Link', rows: 2, placeholder: 'Enter link text (markdown supported)...' }; - default: - return { ...baseConfig, label: 'Content', rows: 3, placeholder: 'Enter content using markdown...' }; - } - } - - /** - * Create markdown textarea field - */ - createMarkdownField(config, content) { - const textContent = typeof content === 'object' ? content.text || '' : content; - - return ` -
- -
- Supports Markdown formatting (bold, italic, links, etc.) -
-
- `; - } - - /** - * Create URL field for links - */ - createUrlField(content) { - const url = typeof content === 'object' ? content.url || '' : ''; - - return ` -
- - -
- `; - } - - /** - * Setup event handlers - */ - setupEventHandlers(form, overlay, context, { onSave, onCancel }) { - const textarea = form.querySelector('textarea'); - const urlInput = form.querySelector('input[name="url"]'); - const saveBtn = form.querySelector('.insertr-btn-save'); - const cancelBtn = form.querySelector('.insertr-btn-cancel'); - const historyBtn = form.querySelector('.insertr-btn-history'); - - // Initialize previewer - this.previewer.setActiveContext(context); - - // Setup live preview for content changes - if (textarea) { - textarea.addEventListener('input', () => { - const content = this.extractFormData(form); - this.previewer.schedulePreview(context, content); - }); - } - - // Setup live preview for URL changes (links only) - if (urlInput) { - urlInput.addEventListener('input', () => { - const content = this.extractFormData(form); - this.previewer.schedulePreview(context, content); - }); - } - - // Save handler - if (saveBtn) { - saveBtn.addEventListener('click', () => { - const content = this.extractFormData(form); - - // Apply final content to elements - context.applyContent(content); - - // Update stored original content to match current state - // This makes the saved content the new baseline for future edits - context.updateOriginalContent(); - - // Clear preview styling (won't restore content since original matches current) - this.previewer.clearPreview(); - - // Callback with the content - onSave(content); - this.close(); - }); - } - - // Cancel handler - if (cancelBtn) { - cancelBtn.addEventListener('click', () => { - this.previewer.clearPreview(); - onCancel(); - this.close(); - }); - } - - // History handler - if (historyBtn) { - historyBtn.addEventListener('click', () => { - const contentId = historyBtn.getAttribute('data-content-id'); - console.log('Version history not implemented yet for:', contentId); - // TODO: Implement version history integration - }); - } - - // ESC key handler - const keyHandler = (e) => { - if (e.key === 'Escape') { - this.previewer.clearPreview(); - onCancel(); - this.close(); - document.removeEventListener('keydown', keyHandler); - } - }; - document.addEventListener('keydown', keyHandler); - - // Click outside handler - overlay.addEventListener('click', (e) => { - if (e.target === overlay) { - this.previewer.clearPreview(); - onCancel(); - this.close(); - } - }); - } - - /** - * Extract form data consistently - */ - extractFormData(form) { - const textarea = form.querySelector('textarea[name="content"]'); - const urlInput = form.querySelector('input[name="url"]'); - - const content = textarea ? textarea.value : ''; - - if (urlInput) { - // Link content - return { - text: content, - url: urlInput.value - }; - } - - // Regular content - return content; + handleContentChange(element, content) { + // Optional: implement change events if needed } /** @@ -445,10 +215,6 @@ export class Editor { * Close current editor */ close() { - if (this.previewer) { - this.previewer.clearPreview(); - } - if (this.currentStyleEditor) { this.currentStyleEditor.destroy(); this.currentStyleEditor = null; @@ -469,129 +235,4 @@ export class Editor { div.textContent = text; return div.innerHTML; } -} - -/** - * EditContext - Represents content elements for editing - */ -class EditContext { - constructor(elements, currentContent) { - this.elements = elements; - this.primaryElement = elements[0]; - this.originalContent = null; - this.currentContent = currentContent; - } - - /** - * Extract content from elements in markdown format - */ - extractContent() { - if (this.elements.length === 1) { - const element = this.elements[0]; - - // Handle links specially - if (element.tagName.toLowerCase() === 'a') { - return { - text: markdownConverter.htmlToMarkdown(element.innerHTML), - url: element.href - }; - } - - // Single element - convert to markdown - return markdownConverter.htmlToMarkdown(element.innerHTML); - } else { - // Multiple elements - use group extraction - return markdownConverter.extractGroupMarkdown(this.elements); - } - } - - /** - * Apply content to elements from markdown/object - */ - applyContent(content) { - if (this.elements.length === 1) { - const element = this.elements[0]; - - // Handle links specially - if (element.tagName.toLowerCase() === 'a' && typeof content === 'object') { - element.innerHTML = markdownConverter.markdownToHtml(content.text || ''); - if (content.url) { - element.href = content.url; - } - return; - } - - // Single element - convert markdown to HTML - const html = markdownConverter.markdownToHtml(content); - element.innerHTML = html; - } else { - // Multiple elements - use group update - markdownConverter.updateGroupElements(this.elements, content); - } - } - - /** - * Store original content for preview restoration - */ - storeOriginalContent() { - this.originalContent = this.elements.map(el => ({ - innerHTML: el.innerHTML, - href: el.href // Store href for links - })); - } - - /** - * Restore original content (for preview cancellation) - */ - restoreOriginalContent() { - if (this.originalContent) { - this.elements.forEach((el, index) => { - if (this.originalContent[index] !== undefined) { - el.innerHTML = this.originalContent[index].innerHTML; - if (this.originalContent[index].href) { - el.href = this.originalContent[index].href; - } - } - }); - } - } - - /** - * Update original content to match current element state (after save) - * This makes the current content the new baseline for future cancellations - */ - updateOriginalContent() { - this.originalContent = this.elements.map(el => ({ - innerHTML: el.innerHTML, - href: el.href // Store href for links - })); - } - - /** - * Apply preview styling to all elements - */ - applyPreviewStyling() { - this.elements.forEach(el => { - el.classList.add('insertr-preview-active'); - }); - - // Also apply to containers if they're groups - if (this.primaryElement.classList.contains('insertr-group')) { - this.primaryElement.classList.add('insertr-preview-active'); - } - } - - /** - * Remove preview styling from all elements - */ - removePreviewStyling() { - this.elements.forEach(el => { - el.classList.remove('insertr-preview-active'); - }); - - // Also remove from containers - if (this.primaryElement.classList.contains('insertr-group')) { - this.primaryElement.classList.remove('insertr-preview-active'); - } - } } \ No newline at end of file diff --git a/lib/src/ui/form-renderer.js b/lib/src/ui/form-renderer.js index d6a06be..b4fd965 100644 --- a/lib/src/ui/form-renderer.js +++ b/lib/src/ui/form-renderer.js @@ -1,5 +1,5 @@ /** - * InsertrFormRenderer - Form renderer using markdown-first approach + * InsertrFormRenderer - Form renderer for content editing * Thin wrapper around the Editor system */ import { Editor } from './editor.js'; @@ -52,190 +52,5 @@ export class InsertrFormRenderer { this.editor.close(); } - /** - * Show version history modal (placeholder for future implementation) - */ - async showVersionHistory(contentId, element, onRestore) { - try { - // Get version history from API - const apiClient = this.getApiClient(); - if (!apiClient) { - console.warn('No API client configured for version history'); - return; - } - - const versions = await apiClient.getContentVersions(contentId); - - // Create version history modal - const historyModal = this.createVersionHistoryModal(contentId, versions, onRestore); - document.body.appendChild(historyModal); - - // Setup handlers - this.setupVersionHistoryHandlers(historyModal, contentId); - - } catch (error) { - console.error('Failed to load version history:', error); - this.showVersionHistoryError('Failed to load version history. Please try again.'); - } - } - - /** - * Create version history modal (simplified placeholder) - */ - createVersionHistoryModal(contentId, versions, onRestore) { - const modal = document.createElement('div'); - modal.className = 'insertr-version-modal'; - - let versionsHTML = ''; - if (versions && versions.length > 0) { - versionsHTML = versions.map((version, index) => ` -
-
- ${index === 0 ? 'Previous Version' : `Version ${versions.length - index}`} - ${this.formatDate(version.created_at)} - ${version.created_by ? `by ${version.created_by}` : ''} -
-
${this.escapeHtml(this.truncateContent(version.value, 100))}
-
- - -
-
- `).join(''); - } else { - versionsHTML = '
No previous versions found
'; - } - - modal.innerHTML = ` -
-
-
-

Version History

- -
-
- ${versionsHTML} -
-
-
- `; - - return modal; - } - - /** - * Setup version history modal handlers - */ - setupVersionHistoryHandlers(modal, contentId) { - const closeBtn = modal.querySelector('.insertr-btn-close'); - const backdrop = modal.querySelector('.insertr-version-backdrop'); - - // Close handlers - if (closeBtn) { - closeBtn.addEventListener('click', () => modal.remove()); - } - - backdrop.addEventListener('click', (e) => { - if (e.target === backdrop) { - modal.remove(); - } - }); - - // Restore handlers - const restoreButtons = modal.querySelectorAll('.insertr-btn-restore'); - restoreButtons.forEach(btn => { - btn.addEventListener('click', async () => { - const versionId = btn.getAttribute('data-version-id'); - if (await this.confirmRestore()) { - await this.restoreVersion(contentId, versionId); - modal.remove(); - this.closeForm(); - } - }); - }); - - // View diff handlers - const viewButtons = modal.querySelectorAll('.insertr-btn-view-diff'); - viewButtons.forEach(btn => { - btn.addEventListener('click', () => { - const versionId = btn.getAttribute('data-version-id'); - this.showVersionDetails(versionId); - }); - }); - } - - /** - * Helper methods for version history - */ - formatDate(dateString) { - const date = new Date(dateString); - const now = new Date(); - const diff = now - date; - - // Less than 24 hours ago - if (diff < 24 * 60 * 60 * 1000) { - const hours = Math.floor(diff / (60 * 60 * 1000)); - if (hours < 1) { - const minutes = Math.floor(diff / (60 * 1000)); - return `${minutes}m ago`; - } - return `${hours}h ago`; - } - - // Less than 7 days ago - if (diff < 7 * 24 * 60 * 60 * 1000) { - const days = Math.floor(diff / (24 * 60 * 60 * 1000)); - return `${days}d ago`; - } - - // Older - show actual date - return date.toLocaleDateString(); - } - - truncateContent(content, maxLength) { - if (content.length <= maxLength) return content; - return content.substring(0, maxLength) + '...'; - } - - async confirmRestore() { - return confirm('Are you sure you want to restore this version? This will replace the current content.'); - } - - async restoreVersion(contentId, versionId) { - try { - const apiClient = this.getApiClient(); - await apiClient.rollbackContent(contentId, versionId); - return true; - } catch (error) { - console.error('Failed to restore version:', error); - alert('Failed to restore version. Please try again.'); - return false; - } - } - - showVersionDetails(versionId) { - // TODO: Implement detailed version view with diff - alert(`Version details not implemented yet (Version ID: ${versionId})`); - } - - showVersionHistoryError(message) { - alert(message); - } - - // Helper to get API client - getApiClient() { - return this.apiClient || window.insertrAPIClient || null; - } - - /** - * Escape HTML to prevent XSS - */ - escapeHtml(text) { - if (typeof text !== 'string') return ''; - const div = document.createElement('div'); - div.textContent = text; - return div.innerHTML; - } - } \ No newline at end of file diff --git a/lib/src/ui/previewer.js b/lib/src/ui/previewer.js deleted file mode 100644 index 7698b02..0000000 --- a/lib/src/ui/previewer.js +++ /dev/null @@ -1,157 +0,0 @@ -/** - * Previewer - Handles live preview for all content types - */ -import { markdownConverter } from '../utils/markdown.js'; - -export class Previewer { - constructor() { - this.previewTimeout = null; - this.activeContext = null; - this.resizeObserver = null; - this.onHeightChange = null; - } - - /** - * Set the active editing context for preview - */ - setActiveContext(context) { - this.clearPreview(); - this.activeContext = context; - this.startResizeObserver(); - } - - /** - * Schedule a preview update with debouncing - */ - schedulePreview(context, content) { - // Clear existing timeout - if (this.previewTimeout) { - clearTimeout(this.previewTimeout); - } - - // Schedule new preview with 500ms debounce - this.previewTimeout = setTimeout(() => { - this.updatePreview(context, content); - }, 500); - } - - /** - * Update preview with new content - */ - updatePreview(context, content) { - // Store original content if first preview - if (!context.originalContent) { - context.storeOriginalContent(); - } - - // Apply preview content to elements - this.applyPreviewContent(context, content); - context.applyPreviewStyling(); - } - - /** - * Apply preview content to context elements - */ - applyPreviewContent(context, content) { - if (context.elements.length === 1) { - const element = context.elements[0]; - - // Handle links specially - if (element.tagName.toLowerCase() === 'a') { - if (typeof content === 'object') { - // Update link text (markdown to HTML) - if (content.text !== undefined) { - const html = markdownConverter.markdownToHtml(content.text); - element.innerHTML = html; - } - // Update link URL - if (content.url !== undefined && content.url.trim()) { - element.href = content.url; - } - } else if (content && content.trim()) { - // Just markdown content for link text - const html = markdownConverter.markdownToHtml(content); - element.innerHTML = html; - } - return; - } - - // Regular single element - if (content && content.trim()) { - const html = markdownConverter.markdownToHtml(content); - element.innerHTML = html; - } - } else { - // Multiple elements - use group update - if (content && content.trim()) { - markdownConverter.updateGroupElements(context.elements, content); - } - } - } - - /** - * Clear all preview state and restore original content - */ - clearPreview() { - if (this.activeContext) { - this.activeContext.restoreOriginalContent(); - this.activeContext.removePreviewStyling(); - this.activeContext = null; - } - - if (this.previewTimeout) { - clearTimeout(this.previewTimeout); - this.previewTimeout = null; - } - - this.stopResizeObserver(); - } - - /** - * Start observing element size changes for modal repositioning - */ - startResizeObserver() { - this.stopResizeObserver(); - - if (this.activeContext) { - this.resizeObserver = new ResizeObserver(() => { - // Handle height changes for modal repositioning - if (this.onHeightChange) { - this.onHeightChange(this.activeContext.primaryElement); - } - }); - - // Observe all elements in the context - this.activeContext.elements.forEach(el => { - this.resizeObserver.observe(el); - }); - } - } - - /** - * Stop observing element size changes - */ - stopResizeObserver() { - if (this.resizeObserver) { - this.resizeObserver.disconnect(); - this.resizeObserver = null; - } - } - - /** - * Set callback for height changes (for modal repositioning) - */ - setHeightChangeCallback(callback) { - this.onHeightChange = callback; - } - - /** - * Get unique element ID for tracking - */ - getElementId(element) { - if (!element._insertrId) { - element._insertrId = 'insertr_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); - } - return element._insertrId; - } -} \ No newline at end of file diff --git a/lib/src/utils/markdown.js b/lib/src/utils/markdown.js deleted file mode 100644 index 1fa4538..0000000 --- a/lib/src/utils/markdown.js +++ /dev/null @@ -1,288 +0,0 @@ -/** - * Markdown conversion utilities using Marked and Turndown - */ -import { marked } from 'marked'; -import TurndownService from 'turndown'; - -/** - * MarkdownConverter - Handles bidirectional HTML ↔ Markdown conversion - */ -export class MarkdownConverter { - constructor() { - this.initializeMarked(); - this.initializeTurndown(); - } - - /** - * Configure marked for HTML output - MINIMAL MODE - * Only supports: **bold**, *italic*, and [links](url) - * Matches server-side goldmark configuration - */ - initializeMarked() { - marked.setOptions({ - gfm: false, // Disable GFM to match server minimal mode - breaks: true, // Convert \n to
(matches server) - pedantic: false, // Don't be overly strict - sanitize: false, // Allow HTML (we control the input) - smartLists: false, // Disable lists (not supported on server) - smartypants: false // Don't convert quotes/dashes - }); - - // Override renderers to restrict to minimal feature set - marked.use({ - renderer: { - // Disable headings - treat as plain text - heading(text, level) { - return text; - }, - // Disable lists - treat as plain text - list(body, ordered, start) { - return body.replace(/<\/?li>/g, ''); - }, - listitem(text) { - return text + '\n'; - }, - // Disable code blocks - treat as plain text - code(code, language) { - return code; - }, - blockquote(quote) { - return quote; // Disable blockquotes - treat as plain text - }, - // Disable horizontal rules - hr() { - return ''; - }, - // Disable tables - table(header, body) { - return header + body; - }, - tablecell(content, flags) { - return content; - }, - tablerow(content) { - return content; - } - } - }); - } - - /** - * Configure turndown for markdown output - MINIMAL MODE - * Only supports: **bold**, *italic*, and [links](url) - * Matches server-side goldmark configuration - */ - initializeTurndown() { - this.turndown = new TurndownService({ - // Minimal configuration - only basic formatting - headingStyle: 'atx', // # headers (but will be disabled) - hr: '---', // horizontal rule (but will be disabled) - bulletListMarker: '-', // bullet list (but will be disabled) - codeBlockStyle: 'fenced', // code blocks (but will be disabled) - fence: '```', // fence marker (but will be disabled) - emDelimiter: '*', // *italic* - matches server - strongDelimiter: '**', // **bold** - matches server - linkStyle: 'inlined', // [text](url) - matches server - linkReferenceStyle: 'full' // full reference links - }); - - // Add custom rules for better conversion - this.addTurndownRules(); - } - - /** - * Add custom turndown rules - MINIMAL MODE - * Only supports: **bold**, *italic*, and [links](url) - * Disables all other formatting to match server - */ - addTurndownRules() { - // Handle paragraph spacing properly - ensure double newlines between paragraphs - this.turndown.addRule('paragraph', { - filter: 'p', - replacement: function (content) { - if (!content.trim()) return ''; - return content.trim() + '\n\n'; - } - }); - - // Handle bold text in markdown - keep this (supported) - this.turndown.addRule('bold', { - filter: ['strong', 'b'], - replacement: function (content) { - if (!content.trim()) return ''; - return '**' + content + '**'; - } - }); - - // Handle italic text in markdown - keep this (supported) - this.turndown.addRule('italic', { - filter: ['em', 'i'], - replacement: function (content) { - if (!content.trim()) return ''; - return '*' + content + '*'; - } - }); - - // DISABLE unsupported features - convert to plain text - this.turndown.addRule('disableHeadings', { - filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], - replacement: function (content) { - return content; // Just return text content, no # markup - } - }); - - this.turndown.addRule('disableLists', { - filter: ['ul', 'ol', 'li'], - replacement: function (content) { - return content; // Just return text content, no list markup - } - }); - - this.turndown.addRule('disableCode', { - filter: ['pre', 'code'], - replacement: function (content) { - return content; // Just return text content, no code markup - } - }); - - this.turndown.addRule('disableBlockquotes', { - filter: 'blockquote', - replacement: function (content) { - return content; // Just return text content, no > markup - } - }); - - this.turndown.addRule('disableHR', { - filter: 'hr', - replacement: function () { - return ''; // Remove horizontal rules entirely - } - }); - } - - /** - * Convert HTML to Markdown - * @param {string} html - HTML string to convert - * @returns {string} - Markdown string - */ - htmlToMarkdown(html) { - if (!html || html.trim() === '') { - return ''; - } - - try { - const markdown = this.turndown.turndown(html); - // Clean up and normalize newlines for proper paragraph separation - return markdown - .replace(/\n{3,}/g, '\n\n') // Replace 3+ newlines with 2 - .replace(/^\n+|\n+$/g, '') // Remove leading/trailing newlines - .trim(); // Remove other whitespace - } catch (error) { - console.warn('HTML to Markdown conversion failed:', error); - // Fallback: extract text content - const tempDiv = document.createElement('div'); - tempDiv.innerHTML = html; - return tempDiv.textContent || tempDiv.innerText || ''; - } - } - - /** - * Convert Markdown to HTML - * @param {string} markdown - Markdown string to convert - * @returns {string} - HTML string - */ - markdownToHtml(markdown) { - if (!markdown || markdown.trim() === '') { - return ''; - } - - try { - const html = marked(markdown); - return html; - } catch (error) { - console.warn('Markdown to HTML conversion failed:', error); - // Fallback: convert line breaks to paragraphs - return markdown - .split(/\n\s*\n/) - .filter(p => p.trim()) - .map(p => `

${p.trim()}

`) - .join(''); - } - } - - /** - * Extract HTML content from a group of elements - * @param {HTMLElement[]} elements - Array of DOM elements - * @returns {string} - Combined HTML content - */ - extractGroupHTML(elements) { - const htmlParts = []; - - elements.forEach(element => { - // Wrap inner content in paragraph tags to preserve structure - const html = element.innerHTML.trim(); - if (html) { - // If element is already a paragraph, use its outer HTML - if (element.tagName.toLowerCase() === 'p') { - htmlParts.push(element.outerHTML); - } else { - // Wrap in paragraph tags - htmlParts.push(`

${html}

`); - } - } - }); - - return htmlParts.join('\n'); - } - - /** - * Convert HTML content from group elements to markdown - * @param {HTMLElement[]} elements - Array of DOM elements - * @returns {string} - Markdown representation - */ - extractGroupMarkdown(elements) { - const html = this.extractGroupHTML(elements); - const markdown = this.htmlToMarkdown(html); - return markdown; - } - - /** - * Update group elements with markdown content - * @param {HTMLElement[]} elements - Array of DOM elements to update - * @param {string} markdown - Markdown content to render - */ - updateGroupElements(elements, markdown) { - const html = this.markdownToHtml(markdown); - - // Split HTML into paragraphs - const tempDiv = document.createElement('div'); - tempDiv.innerHTML = html; - - const paragraphs = Array.from(tempDiv.querySelectorAll('p, div, h1, h2, h3, h4, h5, h6')); - - // Handle case where we have more/fewer paragraphs than elements - const maxCount = Math.max(elements.length, paragraphs.length); - - for (let i = 0; i < maxCount; i++) { - if (i < elements.length && i < paragraphs.length) { - // Update existing element with corresponding paragraph - elements[i].innerHTML = paragraphs[i].innerHTML; - } else if (i < elements.length) { - // More elements than paragraphs - clear extra elements - elements[i].innerHTML = ''; - } else if (i < paragraphs.length) { - // More paragraphs than elements - create new element - const newElement = document.createElement('p'); - newElement.innerHTML = paragraphs[i].innerHTML; - - // Insert after the last existing element - const lastElement = elements[elements.length - 1]; - lastElement.parentNode.insertBefore(newElement, lastElement.nextSibling); - elements.push(newElement); // Add to our elements array for future updates - } - } - } -} - -// Export singleton instance -export const markdownConverter = new MarkdownConverter(); \ No newline at end of file